diff --git a/Button.cpp b/Button.cpp index a45e383..4e6a50e 100644 --- a/Button.cpp +++ b/Button.cpp @@ -432,6 +432,8 @@ bool Button::handleEvent(const ExMessage& msg) // 事件吞噬规则: // - 鼠标移动:只有“当前命中按钮”时才吞掉,避免前一个按钮在清 hover 时截断消息, // 导致后一个真正命中的按钮收不到 WM_MOUSEMOVE。 + // 这里的前提是:父容器会保证“没有拿到真实消息的后续兄弟”还能走一遍 + // clearTransientMouseState(),从而清掉旧 hover / tooltip。 // - 鼠标按下/抬起:命中按钮区域时吞掉,避免点击穿透到底层控件。 if (msg.message == WM_MOUSEMOVE) { @@ -451,6 +453,79 @@ bool Button::handleEvent(const ExMessage& msg) return consume; } +bool Button::clearTransientMouseState() +{ + if (!show) + return false; + + bool hoverChanged = false; + bool normalClickChanged = false; + bool bodyVisualChanged = false; + bool tooltipChanged = false; + + if (hover) + { + hover = false; + hoverChanged = true; + bodyVisualChanged = true; + } + + // NORMAL 模式下的按下态只属于一次鼠标交互过程; + // 如果本次 WM_MOUSEMOVE 已经被同层更上层的兄弟控件消费, + // 当前按钮就不应继续保留这笔临时按下视觉。 + if (mode == StellarX::ButtonMode::NORMAL && click) + { + click = false; + normalClickChanged = true; + bodyVisualChanged = true; + } + + if (tipVisible) + { + hideTooltip(); + tooltipChanged = true; + } + + const bool stateChanged = bodyVisualChanged || tooltipChanged; + if (!stateChanged) + return false; + + SX_LOG_TRACE("Event") + << SX_T("清理按钮鼠标临时状态:id=", "clear button transient mouse state: id=") + << id + << SX_T(" hover=", " hover=") << (hoverChanged ? 1 : 0) + << SX_T(" tooltip=", " tooltip=") << (tooltipChanged ? 1 : 0) + << SX_T(" normalClick=", " normalClick=") << (normalClickChanged ? 1 : 0); + + markEventVisualChanged(true); + + // 只有按钮本体视觉真正变化时,才请求按钮自身区域重绘; + // 若只是 Tooltip 消失,hideTooltip() 已经通过回贴快照清掉了悬浮层区域, + // 不再额外重绘按钮本体,避免高频鼠标移动时的无意义重画。 + if (bodyVisualChanged) + { + dirty = true; + requestRepaint(parent); + } + + return true; +} + +RECT Button::getManagedRepaintCoverageRect() const +{ + RECT coverage = getBoundsRect(); + + if (!tipEnabled || !tipVisible) + return coverage; + + const RECT tipRect = tipLabel.getManagedRepaintCoverageRect(); + coverage.left = (std::min)(coverage.left, tipRect.left); + coverage.top = (std::min)(coverage.top, tipRect.top); + coverage.right = (std::max)(coverage.right, tipRect.right); + coverage.bottom = (std::max)(coverage.bottom, tipRect.bottom); + return coverage; +} + void Button::setOnClickListener(std::function callback) { this->onClickCallback = std::move(callback); @@ -558,7 +633,14 @@ void Button::setButtonShape(StellarX::ControlShape shape) //允许通过外部函数修改按钮的点击状态,并执行相应的回调函数 void Button::setButtonClick(BOOL click) { - this->click = click; + const bool targetClick = (click != FALSE); + // TOGGLE 状态若没有发生变化,就不应重复触发 onToggleOn/OffCallback。 + // 否则像 TabControl 这种“外部再次激活已激活页签”的场景, + // 会把页面显示/隐藏、快照失效、重绘链整条重复执行一遍,最终破坏可见页的背景恢复语义。 + if (mode == StellarX::ButtonMode::TOGGLE && this->click == targetClick) + return; + + this->click = targetClick; if (mode == StellarX::ButtonMode::NORMAL && click) { diff --git a/Button.h b/Button.h index 323ef0e..f220ef4 100644 --- a/Button.h +++ b/Button.h @@ -101,87 +101,94 @@ public: void draw() override; //按钮事件处理 bool handleEvent(const ExMessage& msg) override; + // 清理按钮的鼠标瞬时状态;用于父容器在 WM_MOUSEMOVE 短路后补做 hover/tooltip 收口。 + bool clearTransientMouseState() override; + // Tooltip 可见时,按钮实际写像素范围不再等于按钮本体,需要把 Tooltip 矩形并入 coverage。 + RECT getManagedRepaintCoverageRect() const override; - //设置回调函数 + // 设置 NORMAL 模式下的点击回调 void setOnClickListener(std::function callback); - //设置TOGGLE模式下被点击的回调函数 + // 设置 TOGGLE 模式下切换到打开状态时的回调 void setOnToggleOnListener(std::function callback); - //设置TOGGLE模式下取消点击的回调函数 + // 设置 TOGGLE 模式下切换到关闭状态时的回调 void setOnToggleOffListener(std::function callback); - //设置按钮模式 + // 设置按钮模式(NORMAL / TOGGLE / DISABLED) void setbuttonMode(StellarX::ButtonMode mode); - //设置圆角矩形椭圆宽度 + // 设置圆角矩形的圆角宽度 void setROUND_RECTANGLEwidth(int width); - //设置圆角矩形椭圆高度 + // 设置圆角矩形的圆角高度 void setROUND_RECTANGLEheight(int height); - //设置按钮填充模式 + // 设置按钮填充模式 void setFillMode(StellarX::FillMode mode); - //设置按钮填充图案 + // 设置按钮图案填充样式 void setFillIma(StellarX::FillStyle ima); - //设置按钮填充图像 + // 设置按钮图像填充资源 void setFillIma(std::string imaName); - //设置按钮边框颜色 + // 设置按钮边框颜色 void setButtonBorder(COLORREF Border); - //设置按钮未被点击颜色 + // 设置按钮默认态颜色 void setButtonFalseColor(COLORREF color); - //设置按钮文本 + // 设置按钮文本(char* 重载) void setButtonText(const char* text); + // 设置按钮文本(std::string 重载) void setButtonText(std::string text); - //设置按钮形状 + // 设置按钮几何形状 void setButtonShape(StellarX::ControlShape shape); - //设置按钮点击状态 + // 直接设置按钮点击状态;TOGGLE 模式下会按状态变化触发相应回调 void setButtonClick(BOOL click); - //判断按钮是否被点击 + // 查询按钮当前是否处于点击/选中状态 bool isClicked() const; - //获取按钮文字 + // 获取按钮文本 std::string getButtonText() const; + // 获取按钮文本的 C 字符串视图 const char* getButtonText_c() const; - //获取按钮模式 + // 获取按钮模式 StellarX::ButtonMode getButtonMode() const; - //获取按钮形状 + // 获取按钮形状 StellarX::ControlShape getButtonShape() const; - //获取按钮填充模式 + // 获取按钮填充模式 StellarX::FillMode getFillMode() const; - //获取按钮填充图案 + // 获取按钮图案填充样式 StellarX::FillStyle getFillIma() const; - //获取按钮填充图像 + // 获取按钮图像填充资源 IMAGE* getFillImaImage() const; - //获取按钮边框颜色 + // 获取按钮边框颜色 COLORREF getButtonBorder() const; - //获取按钮文字颜色 + // 获取按钮文字颜色 COLORREF getButtonTextColor() const; - //获取按钮文字样式 + // 获取按钮文字样式 StellarX::ControlText getButtonTextStyle() const; public: // === Tooltip API=== - //设置是否启用提示框 + // 开关 Tooltip;关闭时只修改内部启用/显示状态,不负责智能选位 void enableTooltip(bool on) { tipEnabled = on; if (!on) tipVisible = false; } - //设置提示框延时 + // 设置 Tooltip 延时(毫秒) void setTooltipDelay(int ms) { tipDelayMs = (ms < 0 ? 0 : ms); } - //设置提示框是否跟随鼠标 + // 设置 Tooltip 是否跟随鼠标 void setTooltipFollowCursor(bool on) { tipFollowCursor = on; } - //设置提示框位置偏移 + // 设置 Tooltip 相对鼠标/按钮的偏移量 void setTooltipOffset(int dx, int dy) { tipOffsetX = dx; tipOffsetY = dy; } - //设置提示框样式 + // 设置 Tooltip 的文字、背景和透明样式 void setTooltipStyle(COLORREF text, COLORREF bk, bool transparent); - //设置提示框文本 + // 设置 NORMAL 模式下 Tooltip 文本 void setTooltipText(const std::string& s) { tipTextClick = s; tipUserOverride = true; } + // 设置 TOGGLE 模式开/关两种状态下的 Tooltip 文本 void setTooltipTextsForToggle(const std::string& onText, const std::string& offText); private: - //初始化按钮 + // 初始化按钮内部状态、颜色和 Tooltip 默认样式 void initButton(const std::string text, StellarX::ButtonMode mode, StellarX::ControlShape shape, COLORREF ct, COLORREF cf, COLORREF ch); - //判断鼠标是否在圆形按钮内 + // 判断鼠标是否在圆形按钮内 bool isMouseInCircle(int mouseX, int mouseY, int x, int y, int radius); - //判断鼠标是否在椭圆按钮内 + // 判断鼠标是否在椭圆按钮内 bool isMouseInEllipse(int mouseX, int mouseY, int x, int y, int width, int height); //获取对话框类型 bool model() const override { return false; } - //文本截断 + // 按当前按钮宽度和语言特征裁剪文本 void cutButtonText(); - // 统一隐藏&恢复背景 + // 统一隐藏 Tooltip 并恢复其背景快照 void hideTooltip(); - // 根据当前 click 状态选择文案 + // 根据当前 click 状态选择 Tooltip 文案 void refreshTooltipTextForState(); }; diff --git a/Canvas.cpp b/Canvas.cpp index 20260b8..6485be1 100644 --- a/Canvas.cpp +++ b/Canvas.cpp @@ -20,16 +20,45 @@ static const char* SxCanvasMsgName(UINT m) } } +namespace +{ + bool SxCanvasRectValid(const RECT& rc) + { + return rc.right > rc.left && rc.bottom > rc.top; + } + + bool SxCanvasRectsIntersect(const RECT& a, const RECT& b) + { + return a.left < b.right && a.right > b.left && + a.top < b.bottom && a.bottom > b.top; + } + + RECT SxCanvasUnionRect(const RECT& a, const RECT& b) + { + RECT out{}; + out.left = (std::min)(a.left, b.left); + out.top = (std::min)(a.top, b.top); + out.right = (std::max)(a.right, b.right); + out.bottom = (std::max)(a.bottom, b.bottom); + return out; + } +} + Canvas::Canvas() :Control(0, 0, 100, 100) { this->id = "Canvas"; + // Canvas 是通用容器,当前阶段显式允许双轴 Stretch。 + this->layoutCapability.allowStretchX = true; + this->layoutCapability.allowStretchY = true; } Canvas::Canvas(int x, int y, int width, int height) :Control(x, y, width, height) { this->id = "Canvas"; + this->layoutCapability.allowStretchX = true; + this->layoutCapability.allowStretchY = true; } void Canvas::relayoutManagedChildren() @@ -162,19 +191,46 @@ bool Canvas::handleEvent(const ExMessage& msg) bool anyVisualChanged = false; Control* firstConsumer = nullptr; - for (auto it = controls.rbegin(); it != controls.rend(); ++it) + if (msg.message == WM_MOUSEMOVE) { - Control* c = it->get(); - bool cConsumed = c->handleEvent(msg); - - if (c->isDirty()) anyDirty = true; - if (c->didEventAffectVisual()) anyVisualChanged = true; - - if (cConsumed) + // WM_MOUSEMOVE 需要特殊处理: + // - 第一个命中的兄弟分支收到真实消息; + // - 后续兄弟不再重新命中,只清理旧 hover / tooltip 等临时状态。 + for (auto it = controls.rbegin(); it != controls.rend(); ++it) { - firstConsumer = c; - consumed = true; - break; + Control* c = it->get(); + if (!consumed) + { + bool cConsumed = c->handleEvent(msg); + if (c->didEventAffectVisual()) anyVisualChanged = true; + if (cConsumed) + { + firstConsumer = c; + consumed = true; + } + } + else if (c->clearTransientMouseState()) + { + anyVisualChanged = true; + } + } + } + else + { + for (auto it = controls.rbegin(); it != controls.rend(); ++it) + { + Control* c = it->get(); + bool cConsumed = c->handleEvent(msg); + + if (c->isDirty()) anyDirty = true; + if (c->didEventAffectVisual()) anyVisualChanged = true; + + if (cConsumed) + { + firstConsumer = c; + consumed = true; + break; + } } } @@ -198,6 +254,20 @@ bool Canvas::handleEvent(const ExMessage& msg) return consumed; } +bool Canvas::clearTransientMouseState() +{ + bool changed = false; + for (auto it = controls.rbegin(); it != controls.rend(); ++it) + { + Control* child = it->get(); + if (!child->IsVisible()) + continue; + if (child->clearTransientMouseState()) + changed = true; + } + return changed; +} + void Canvas::addControl(std::unique_ptr control) { @@ -333,9 +403,68 @@ void Canvas::requestRepaint(Control* parent) SX_LOG_TRACE("Dirty") << SX_T("Canvas 请求局部重绘:id=", "Canvas::requestRepaint(partial): id=") << id; + RECT coverage{}; + bool hasCoverage = false; + auto commitManagedChild = [&](Control* child, bool forceOverlayRedraw) + { + if (!child || !child->IsVisible()) + return; + + const bool directDirty = child->isDirty(); + const bool subtreeDirty = child->hasManagedDirtySubtree(); + + if (forceOverlayRedraw) + { + // overlay 补画必须先作废旧快照: + // 下层兄弟刚刚已经写过像素,若继续沿用旧快照,会把旧背景再贴回来。 + child->invalidateBackgroundSnapshot(); + child->setDirty(true); + child->draw(); + } + else if (directDirty) + { + child->draw(); + } + else if (subtreeDirty) + { + // 这次真正脏的是“child 下面的子树”,而不是 child 自身。 + // 例如嵌套 Canvas 中,第二层/第三层按钮脏了,但第一层 Canvas 自己并不 dirty。 + // 这里必须把这条直接子分支提交下去,否则深层按钮状态永远没有机会被真正画出来。 + child->commitManagedRepaint(); + } + else + { + return; + } + + const RECT childRect = child->getManagedRepaintCoverageRect(); + if (!hasCoverage) + { + coverage = childRect; + hasCoverage = true; + } + else + { + coverage = SxCanvasUnionRect(coverage, childRect); + } + }; + for (auto& control : controls) - if (control->isDirty() && control->IsVisible()) - control->draw(); + { + Control* child = control.get(); + if (!child->IsVisible()) + continue; + + if (child->hasManagedDirtySubtree()) + { + commitManagedChild(child, false); + } + else if (hasCoverage && SxCanvasRectsIntersect(child->getManagedRepaintCoverageRect(), coverage)) + { + // 位于本次累计 coverage 上方、且发生相交的兄弟控件,需要补画回最上层。 + commitManagedChild(child, true); + } + } return; } @@ -344,6 +473,35 @@ void Canvas::requestRepaint(Control* parent) onRequestRepaintAsRoot(); } +bool Canvas::hasManagedDirtySubtree() const +{ + if (dirty) + return true; + + for (const auto& child : controls) + { + if (!child->IsVisible()) + continue; + if (child->hasManagedDirtySubtree()) + return true; + } + return false; +} + +RECT Canvas::getManagedRepaintCoverageRect() const +{ + // Canvas::draw() 会先写自身背景,再强制绘制全部可见子控件。 + // 因此它的实际 coverage 不能只看本体矩形,还要把可见子控件 coverage 递归并入。 + RECT coverage = getBoundsRect(); + for (const auto& child : controls) + { + if (!child->IsVisible()) + continue; + coverage = SxCanvasUnionRect(coverage, child->getManagedRepaintCoverageRect()); + } + return coverage; +} + bool Canvas::canCommitManagedPartialRepaint() const { // Canvas 只有在“自己本体不脏 + 仍持有有效背景快照”时, diff --git a/Canvas.h b/Canvas.h index e867175..2623284 100644 --- a/Canvas.h +++ b/Canvas.h @@ -37,19 +37,27 @@ protected: // 清除所有子控件 void clearAllControls(); public: + // 默认构造:创建一个基础矩形容器 Canvas(); + // 指定初始位置和尺寸构造容器 Canvas(int x, int y, int width, int height); ~Canvas() {} + // 修改容器运行态 X,并同步重算全部受管理子控件的世界坐标 void setX(int x)override; + // 修改容器运行态 Y,并同步重算全部受管理子控件的世界坐标 void setY(int y)override; + // 修改容器运行态宽度,并同步重算全部受管理子控件的世界坐标 void setWidth(int width) override; + // 修改容器运行态高度,并同步重算全部受管理子控件的世界坐标 void setHeight(int height) override; //绘制容器及其子控件 void draw() override; bool handleEvent(const ExMessage& msg) override; - //添加控件 + // 递归清理子树中的 hover / tooltip / 临时按下态,不把 Canvas 自己升级为整块重绘源。 + bool clearTransientMouseState() override; + // 添加一个由 Canvas 托管的子控件 void addControl(std::unique_ptr control); //设置容器样式 void setShape(StellarX::ControlShape shape); @@ -69,11 +77,13 @@ public: void onWindowResize() override; // 托管模式下登记为 root;非托管模式下走局部或根级重绘 void requestRepaint(Control* parent)override; + bool hasManagedDirtySubtree() const override; + RECT getManagedRepaintCoverageRect() const override; // 判断当前 Canvas 是否可安全做局部提交 bool canCommitManagedPartialRepaint() const override; // 托管收口阶段执行 Canvas 的真正重绘 void commitManagedRepaint() override; - //获取子控件列表 + // 获取直接子控件列表 std::vector>& getControls() { return controls; } protected: // 统一解算后,按当前运行态矩形把所有受管理子控件重新映射到新的世界坐标。 diff --git a/Control.cpp b/Control.cpp index 50e6357..a0267f3 100644 --- a/Control.cpp +++ b/Control.cpp @@ -133,6 +133,35 @@ namespace result.pos = childDesignPos; return result; } + + const char* SxAxisName(bool horizontal) + { + return horizontal ? "X" : "Y"; + } + + void LogAxisDowngradeIfNeeded(const std::string& id, bool horizontal, + const StellarX::AxisLayoutSpec& spec, bool allowStretch) + { + if (spec.sizePolicy != StellarX::AxisSizePolicy::Stretch) + return; + + if (!spec.anchorStart || !spec.anchorEnd) + { + SX_LOGD("Layout") + << SX_T("布局降级:id=", "layout downgrade: id=") << id + << SX_T(" axis=", " axis=") << SxAxisName(horizontal) + << SX_T(" 原因=Stretch 但未同时锚定起止边", " reason=stretch without dual anchors"); + return; + } + + if (!allowStretch) + { + SX_LOGD("Layout") + << SX_T("能力边界拦截:id=", "layout capability intercept: id=") << id + << SX_T(" axis=", " axis=") << SxAxisName(horizontal) + << SX_T(" 原因=当前控件禁止该轴 Stretch", " reason=stretch disabled by capability"); + } + } } StellarX::ControlText& StellarX::ControlText::operator=(const ControlText& text) @@ -270,15 +299,31 @@ void Control::setHeight(int height) dirty = true; } +bool Control::clearTransientMouseState() +{ + // 基类默认没有 hover / tooltip / 临时按下态等鼠标瞬时状态。 + // 具体控件若需要在“未收到本次 WM_MOUSEMOVE”时做清理,重写此接口即可。 + return false; +} + void Control::commitCurrentGeometryAsDesignRect() { // 该接口是“显式提交新的设计基线”的唯一入口之一。 // 普通布局解算、父容器重排、窗口 resize 均不得自动回写 local*, // 否则会导致设计基线漂移,后续解算越来越不稳定。 + SX_LOGD("Layout") + << SX_T("提交设计基线:id=", "commit design rect: id=") << id + << SX_T(" world=(", " world=(") << x << "," << y << "," << width << "," << height << ")" + << SX_T(" parentLocalBefore=(", " parentLocalBefore=(") + << localx << "," << localy << "," << localWidth << "," << localHeight << ")"; localx = parent ? (x - parent->getX()) : x; localy = parent ? (y - parent->getY()) : y; localWidth = width; localHeight = height; + SX_LOGD("Layout") + << SX_T("提交设计基线完成:id=", "commit design rect done: id=") << id + << SX_T(" parentLocalAfter=(", " parentLocalAfter=(") + << localx << "," << localy << "," << localWidth << "," << localHeight << ")"; } StellarX::ResolvedLayoutRect Control::resolveLayoutRect(int parentDesignW, int parentDesignH, @@ -299,6 +344,8 @@ StellarX::ResolvedLayoutRect Control::resolveLayoutRect(int parentDesignW, int p } // 第 1 层:先在父局部坐标系内分别解算水平轴和垂直轴。 + LogAxisDowngradeIfNeeded(id, true, layoutSpec.horizontal, layoutCapability.allowStretchX); + LogAxisDowngradeIfNeeded(id, false, layoutSpec.vertical, layoutCapability.allowStretchY); const LayoutAxisResult horizontal = ResolveAxis(parentDesignW, parentCurrentW, localx, localWidth, layoutSpec.horizontal, layoutCapability.allowStretchX); const LayoutAxisResult vertical = ResolveAxis(parentDesignH, parentCurrentH, localy, localHeight, @@ -436,6 +483,17 @@ Control* Control::getManagedRepaintRoot() return root; } +Control* Control::getManagedRepaintDirectBranch(Control* root) +{ + if (!root) + return nullptr; + + Control* branch = this; + while (branch->parent && branch->parent != root) + branch = branch->parent; + return branch; +} + RECT Control::getBoundsRect() const { RECT rc{}; @@ -446,6 +504,13 @@ RECT Control::getBoundsRect() const return rc; } +RECT Control::getManagedRepaintCoverageRect() const +{ + // 基类默认认为“实际绘制 coverage == 控件本体矩形”。 + // 复合控件或带浮层的控件会 override,把附加绘制区域一并并入。 + return getBoundsRect(); +} + bool Control::canCommitManagedPartialRepaint() const { // 基类默认不承诺自己能安全做局部提交; @@ -453,6 +518,13 @@ bool Control::canCommitManagedPartialRepaint() const return false; } +bool Control::hasManagedDirtySubtree() const +{ + // 基类只感知自身是否为脏; + // 容器类会 override 为“自身或其受管理子树是否有脏内容”。 + return dirty; +} + void Control::commitManagedRepaint() { if (!show) diff --git a/Control.h b/Control.h index 6d59cdd..764dfff 100644 --- a/Control.h +++ b/Control.h @@ -124,6 +124,12 @@ public: int getLocalRight() const { return localx + localWidth; } int getLocalBottom() const { return localy + localHeight; } + // 公开几何 setter: + // - 面向用户使用 + // - 默认只修改运行态矩形 + // - 不允许自动回写设计基线 local* + // 若某个复合控件需要在 setter 后维护自己拥有的运行态子树一致性, + // 由该控件在 override 中自行处理,但仍不得越权承担统一布局器职责。 virtual void setX(int x); virtual void setY(int y); virtual void setWidth(int width); @@ -132,6 +138,10 @@ public: virtual void draw() = 0; virtual bool handleEvent(const ExMessage& msg) = 0;//返回true代表事件已消费 + // 轻量清理接口:仅用于“没有机会收到本次 WM_MOUSEMOVE”的兄弟分支, + // 清掉 hover / tooltip / 临时按下态等鼠标瞬时状态。 + // 该接口不是事件分发入口,不参与新的命中判断,也不允许回写设计基线。 + virtual bool clearTransientMouseState(); //设置是否显示 virtual void setIsVisible(bool show); //设置父容器指针 @@ -140,8 +150,14 @@ public: virtual void setHostWindow(Window* host) { this->hostWindow = host; } Window* getHostWindow() const; // 获取宿主 Window;子控件可沿 parent 向上回溯 RECT getBoundsRect() const; // 获取当前控件外接矩形,用于覆盖/相交判断 + // 获取本控件“本轮实际可能写到屏幕上的覆盖范围”。 + // 默认等于控件本体矩形;像 Button 这类会额外绘制 Tooltip 的控件,可 override 后扩大范围。 + // 托管重绘 coverage、overlay 相交判断统一走这个接口,而不再默认使用控件本体 bounds。 + virtual RECT getManagedRepaintCoverageRect() const; Control* getManagedRepaintRoot(); // 找到本控件对应的托管重绘 root + Control* getManagedRepaintDirectBranch(Control* root); // 找到“root 下面承接本次脏变化的直接子分支” bool hasValidBackgroundSnapshot() const { return hasSnap && saveBkImage != nullptr; } // 当前是否持有可用于局部恢复的快照 + virtual bool hasManagedDirtySubtree() const; // 当前控件或其受管理子树中是否存在待提交的脏内容 virtual bool canCommitManagedPartialRepaint() const; // 当前 root 是否可安全做“局部提交”而非整 root 重画 virtual void commitManagedRepaint(); // 托管收口阶段真正执行绘制的入口 //设置是否重绘 @@ -157,16 +173,25 @@ public: //用来检查对话框是否模态,其他控件不用实现 virtual bool model()const = 0; //布局 + // 设置旧版布局模式(兼容入口) void setLayoutMode(StellarX::LayoutMode layoutMode_); + // 设置旧版双锚点输入,并映射到内部统一 LayoutSpec void setAnchor(StellarX::Anchor anchor_1, StellarX::Anchor anchor_2); + // 获取旧版锚点 1(兼容读取入口) StellarX::Anchor getAnchor_1() const; + // 获取旧版锚点 2(兼容读取入口) StellarX::Anchor getAnchor_2() const; + // 获取旧版布局模式 StellarX::LayoutMode getLayoutMode() const; // 获取内部统一布局规格;供 Window / Canvas 等统一解算入口使用。 const StellarX::LayoutSpec& getLayoutSpec() const; // 获取控件能力边界;用于判断某个轴是否允许 Stretch。 const StellarX::LayoutCapability& getLayoutCapability() const; - // 显式将当前运行态矩形提交为新的设计基线。普通 resize / 重排过程中不得自动调用。 + // 显式将当前运行态矩形提交为新的设计基线。 + // 这是“运行态 -> 设计态”的受控入口之一: + // - 普通 resize / 重排过程中不得自动调用 + // - 公开 setter 也不得隐式调用 + // - 仅用于少数需要把当前结果固化为新设计基线的场景 void commitCurrentGeometryAsDesignRect(); protected: // 第 1 层:根据父设计尺寸、父当前尺寸和本控件设计矩形,解算出当前运行态局部矩形。 diff --git a/Dialog.cpp b/Dialog.cpp index 52fda94..2f947ef 100644 --- a/Dialog.cpp +++ b/Dialog.cpp @@ -5,6 +5,10 @@ Dialog::Dialog(Window& h, std::string text, std::string message, StellarX::Messa : Canvas(), message(message), type(type), modal(modal), hWnd(h), titleText(text) { this->id = "Dialog"; + // Dialog 当前阶段只做“居中 + 自管内部 chrome”, + // 不参与外部 Stretch;宿主窗口变化时只重新居中。 + this->layoutCapability.allowStretchX = false; + this->layoutCapability.allowStretchY = false; setHostWindow(&hWnd); show = false; } diff --git a/Dialog.h b/Dialog.h index a57344a..98a4832 100644 --- a/Dialog.h +++ b/Dialog.h @@ -63,18 +63,19 @@ class Dialog : public Canvas bool pendingCleanup = false; //延迟清理 public: StellarX::ControlText textStyle; // 字体样式 - // 清理方法声明 + // 在事件安全点执行延迟清理;用于非模态关闭后统一回收 void performDelayedCleanup(); - //工厂模式下调用非模态对话框时用来返回结果 + // 非模态工厂调用时用于回传结果 std::function resultCallback; - //设置非模态获取结果的回调函数 + // 设置非模态结果回调 void SetResultCallback(std::function cb); - //获取对话框消息,用以去重 + // 获取标题文本,用于非模态去重 std::string GetCaption() const; - //获取对话框消息,用以去重 + // 获取正文文本,用于非模态去重 std::string GetText() const; public: + // 构造对话框;modal=false 时按非模态浮层工作 Dialog(Window& hWnd, std::string text, std::string message = "对话框", StellarX::MessageBoxType type = StellarX::MessageBoxType::OK, bool modal = true); ~Dialog(); //绘制对话框 @@ -94,7 +95,7 @@ public: // 获取对话框结果 StellarX::MessageBoxResult GetResult() const; - //获取对话框类型 + // 返回当前对话框是否为模态 bool model() const override; // 显示对话框 @@ -119,13 +120,14 @@ private: void initDialogSize(); // 依据当前 Dialog 的 x/y/width/height 重新创建标题和按钮 void rebuildChrome(); + // 禁止走普通 Canvas 添加路径;Dialog 内部控件由自身 chrome 重建逻辑统一维护 void addControl(std::unique_ptr control); bool canCommitManagedPartialRepaint() const override; // 判断当前 Dialog 是否可安全做局部提交 void commitManagedRepaint() override; // 托管收口阶段执行 Dialog 的真正重绘 // 清除所有控件 void clearControls(); - //创建对话框按钮 + // 创建标准对话框功能按钮 std::unique_ptr