#include "UiWidget.hpp" #include #include #include "UiDebugBounds.hpp" namespace jdi { UiEventResult UiEventResult::Handled() { UiEventResult result; result.consumed = true; result.redraw = true; return result; } UiEventResult UiEventResult::Swallowed() { UiEventResult result; result.consumed = true; result.redraw = false; return result; } UiPaintContext UiPaintContext::WithClip(const gd::RectF& rect) const { UiPaintContext child = *this; gd::RectF intersection; if (UiRectIntersect(clip, rect, intersection)) { child.clip = intersection; } else { child.clip = gd::RectF(); } return child; } UiWidget::UiWidget() = default; UiWidget::~UiWidget() { // 子节点析构前先把"指向自己的根级状态"清掉:否则根的 m_capture / m_modal / m_hover // 会留下悬空指针(子节点被移除后又被派发事件 = 访问违例)。 UiWidget* root = Root(); if (root != nullptr && root != this) { if (root->m_capture == this) { root->m_capture = nullptr; } if (root->m_modal == this) { root->m_modal = nullptr; } if (root->m_hover == this) { root->m_hover = nullptr; } } } // ------------------------------------------------------------------ // 树结构 // ------------------------------------------------------------------ UiWidget& UiWidget::AddChild(std::unique_ptr child) { if (child == nullptr) { return *this; } UiWidget* raw = child.get(); raw->m_parent = this; m_children.push_back(std::move(child)); // 子节点的期望尺寸会影响自己 ⇒ 向上失效。 InvalidateMeasure(); return *this; } void UiWidget::RemoveChild(UiWidget* child) { if (child == nullptr) { return; } for (auto iterator = m_children.begin(); iterator != m_children.end(); ++iterator) { if (iterator->get() == child) { (*iterator)->m_parent = nullptr; m_children.erase(iterator); InvalidateMeasure(); return; } } } UiWidget* UiWidget::Root() { UiWidget* node = this; while (node->m_parent != nullptr) { node = node->m_parent; } return node; } const UiWidget* UiWidget::Root() const { const UiWidget* node = this; while (node->m_parent != nullptr) { node = node->m_parent; } return node; } // ------------------------------------------------------------------ // 布局 // ------------------------------------------------------------------ void UiWidget::InvalidateMeasure() { if (!m_measureValid) { // 已经失效 ⇒ 不必继续上溯(祖先此前也一定被失效过)。 return; } m_measureValid = false; if (m_parent != nullptr) { m_parent->InvalidateMeasure(); } } gd::SizeF UiWidget::Measure(const UiMeasureContext& ctx) { const gd::SizeF size = m_measureValid ? m_desiredSize : OnMeasure(ctx); m_desiredSize = size; m_measureValid = true; return m_desiredSize; } void UiWidget::Arrange(const gd::RectF& bounds) { m_bounds = bounds; OnArrange(); } void UiWidget::MeasureChildren(const UiMeasureContext& ctx) { for (const std::unique_ptr& child : m_children) { if (child == nullptr || !child->m_visible) { continue; } child->Measure(ctx); } } gd::SizeF UiWidget::ChildDesiredSize(std::size_t index) const { if (index >= m_children.size() || m_children[index] == nullptr) { return gd::SizeF(0.0f, 0.0f); } return m_children[index]->m_desiredSize; } // ------------------------------------------------------------------ // 绘制 // ------------------------------------------------------------------ bool UiWidget::DirtyIntersects(const gd::RectF& dirty) const { gd::RectF intersection; return UiRectIntersect(dirty, m_bounds, intersection); } void UiWidget::Paint(UiPaintContext& ctx) { if (!m_visible || ctx.g == nullptr || !UiRectHasArea(m_bounds)) { return; } // (1) 与脏区求交:**脏区为空表示"未知 ⇒ 全画"**。 gd::RectF effectiveDirty = ctx.dirty; if (UiRectHasArea(effectiveDirty) && !DirtyIntersects(effectiveDirty)) { // 本节点及其整棵子树都不在脏区内 ⇒ 直接跳过(这是本框架唯一真正省时间的机制)。 return; } // (2) 裁剪 = 祖先裁剪切 ∩ (自身矩形外扩 1px);脏区非空时再与脏区求交。 // ⚠️ 外扩 1px 是刻意设计的:边框/描边是**以矩形边界为中心**画的(`UiDrawRoundRect` // 的 1px 笔宽有一半在矩形外),严格按矩形裁剪会把外半侧切掉 ⇒ 控件看起来变细。 // 这是与旧绘制代码保持“逐像素一致”的必要保证。 UiPaintContext local = ctx; gd::RectF clip; if (!UiRectIntersect(ctx.clip, UiRectInflate(m_bounds, kUiClipBleed), clip)) { return; // 完全被祖先裁掉 } if (UiRectHasArea(effectiveDirty)) { gd::RectF clipWithDirty; if (!UiRectIntersect(clip, effectiveDirty, clipWithDirty)) { return; } clip = clipWithDirty; } local.clip = clip; // (3) 收紧 GDI+ 裁剪。⚠️ 这里**只**约束 GDI+ 矢量绘制;GDI 绘制(`UiText` / // `UiFillRect` 的快路径 / `UiDrawSolidLines`)**不受**该裁剪约束 ⇒ 派生类必须把 // `local.clip` 传给 `UiTextClipped` 一类带 `ETO_CLIPPED` 的辅助函数, // 否则文字会画到本节点之外(本项目"滚动时文字串到标题栏/工具栏"就是这类 BUG)。 const int saveState = ctx.g->Save(); ctx.g->SetClip(local.clip, gd::CombineModeIntersect); local.dirty = UiRectHasArea(effectiveDirty) ? clip : gd::RectF(); OnPaint(local); // (4) 子节点:顺序 = z 序(末尾最上层),脏区向下取交集。 for (const std::unique_ptr& child : m_children) { if (child == nullptr) { continue; } UiPaintContext childContext = local; gd::RectF childClip; if (!UiRectIntersect(local.clip, UiRectInflate(child->m_bounds, kUiClipBleed), childClip)) { continue; } childContext.clip = childClip; childContext.dirty = local.dirty; child->Paint(childContext); } ctx.g->Restore(saveState); } bool UiWidget::DispatchRedraw(UiEvent& event) { Dispatch(event); return UiRectHasArea(TakeDirty()); } void UiWidget::CollectDebugBoxes(UiDebugBoxList& boxes, int depth) const { if (!m_visible || !UiRectHasArea(m_bounds)) { // 不可见 / 未摆放的节点没有边界可画,其子树也不会被 `Paint` 画出来 ⇒ 一并跳过。 return; } boxes.Add(m_bounds, depth); // 子节点按 z 序递归(末尾最上层)—— 与 `Paint` 的顺序一致,重叠时上层压住下层。 for (const std::unique_ptr& child : m_children) { if (child != nullptr) { child->CollectDebugBoxes(boxes, depth + 1); } } } void UiWidget::Invalidate(const gd::RectF& rect) { if (!UiRectHasArea(rect)) { return; } m_dirty = UiRectUnion(m_dirty, rect); if (m_parent != nullptr) { // 向上冒泡的是"客户区坐标"的同一块矩形;父层只并集,**不清空**子节点的脏区。 m_parent->Invalidate(rect); } } void UiWidget::InvalidateAll() { m_dirty = m_bounds; if (m_parent != nullptr) { m_parent->Invalidate(m_bounds); } for (const std::unique_ptr& child : m_children) { if (child != nullptr) { child->InvalidateAll(); } } } gd::RectF UiWidget::TakeDirty() { const gd::RectF dirty = m_dirty; m_dirty = gd::RectF(); return dirty; } // ------------------------------------------------------------------ // 事件 // ------------------------------------------------------------------ bool UiWidget::Contains(int x, int y) const { if (!m_visible || m_hitTransparent || !UiRectHasArea(m_bounds)) { return false; } const float fx = static_cast(x); const float fy = static_cast(y); return fx >= m_bounds.X && fx < m_bounds.X + m_bounds.Width && fy >= m_bounds.Y && fy < m_bounds.Y + m_bounds.Height; } UiWidget* UiWidget::HitTestDeep(int x, int y) { if (!Contains(x, y)) { return nullptr; } // 反 z 序:末尾(最上层)优先。 for (auto iterator = m_children.rbegin(); iterator != m_children.rend(); ++iterator) { if (*iterator == nullptr) { continue; } if (UiWidget* hit = (*iterator)->HitTestDeep(x, y)) { return hit; } } return this; } UiEventResult UiWidget::Dispatch(UiEvent& event) { UiWidget* root = Root(); if (root == nullptr) { return UiEventResult(); } const bool mouseEvent = (event.kind == UiEventKind::MOUSE_MOVE || event.kind == UiEventKind::MOUSE_DOWN || event.kind == UiEventKind::MOUSE_UP || event.kind == UiEventKind::MOUSE_DOUBLE_CLICK || event.kind == UiEventKind::MOUSE_WHEEL || event.kind == UiEventKind::QUERY_CURSOR); // (1) 捕获优先:拖拽期间所有鼠标事件直接投给捕获者(与 Windows `SetCapture` 同义)。 if (mouseEvent && root->m_capture != nullptr) { return root->m_capture->OnEvent(event); } // (2) 模态:只向模态子树派发(菜单/拾色器等浮层)。 if (root->m_modal != nullptr) { return root->m_modal->DispatchAlongHitPath(event); } // (3) 悬停链(只在根节点维护:鼠标移动会改变悬停者)。 if (event.kind == UiEventKind::MOUSE_MOVE || event.kind == UiEventKind::MOUSE_LEAVE) { root->UpdateHover(event.x, event.y); } // (4) 键盘/字符:沿焦点链派发(无焦点时交给根,保证宿主级快捷键仍可用)。 if (event.kind == UiEventKind::KEY_DOWN || event.kind == UiEventKind::CHAR) { UiWidget* node = (root->m_focus != nullptr) ? root->m_focus : root; for (; node != nullptr; node = node->m_parent) { UiEventResult result = node->OnEvent(event); if (result.consumed) { return result; } } return UiEventResult(); } return DispatchAlongHitPath(event); } void UiWidget::CollectHitChain(int x, int y, std::vector& chain) { if (!Contains(x, y)) { return; } chain.push_back(this); // 反 z 序:末尾(最上层)优先;只进入**真正命中**的那一个子节点。 for (auto iterator = m_children.rbegin(); iterator != m_children.rend(); ++iterator) { if (*iterator == nullptr) { continue; } if ((*iterator)->Contains(x, y)) { (*iterator)->CollectHitChain(x, y, chain); return; } } } UiEventResult UiWidget::DispatchAlongHitPath(UiEvent& event) { if (!m_visible) { return UiEventResult(); } const bool positional = (event.kind == UiEventKind::MOUSE_MOVE || event.kind == UiEventKind::MOUSE_DOWN || event.kind == UiEventKind::MOUSE_UP || event.kind == UiEventKind::MOUSE_DOUBLE_CLICK || event.kind == UiEventKind::MOUSE_WHEEL || event.kind == UiEventKind::QUERY_CURSOR); if (!positional) { // 非位置事件(理论上已被 `Dispatch` 分流):自身优先,再逐个子节点。 UiEventResult result = OnEvent(event); if (result.consumed) { return result; } for (const std::unique_ptr& child : m_children) { if (child == nullptr || !child->m_visible) { continue; } result = child->DispatchAlongHitPath(event); if (result.consumed) { return result; } } return UiEventResult(); } std::vector chain; CollectHitChain(event.x, event.y, chain); if (chain.empty()) { return UiEventResult(); } // 自最深的命中节点向上冒泡:命中者优先,未消费才交给祖先(合法的"兜底"语义)。 for (UiWidget* node : chain) { if (event.kind == UiEventKind::QUERY_CURSOR) { if (HCURSOR cursor = node->OnQueryCursor(event.x, event.y)) { event.cursor = cursor; return UiEventResult::Swallowed(); } continue; } UiEventResult result = node->OnEvent(event); if (result.consumed) { return result; } } if (event.kind == UiEventKind::QUERY_CURSOR && event.cursor != nullptr) { return UiEventResult::Swallowed(); } return UiEventResult(); } void UiWidget::UpdateHover(int x, int y) { UiWidget* target = (m_visible) ? HitTestDeep(x, y) : nullptr; if (target == m_hover) { return; } UiWidget* previous = m_hover; m_hover = target; if (previous != nullptr) { previous->m_hovered = false; previous->OnHoverChanged(false); previous->Invalidate(previous->m_bounds); } if (target != nullptr) { target->m_hovered = true; target->m_hoverX = x; target->m_hoverY = y; target->OnHoverChanged(true); target->Invalidate(target->m_bounds); } } // ------------------------------------------------------------------ // 交互仲裁 // ------------------------------------------------------------------ void UiWidget::CaptureMouse() { UiWidget* root = Root(); if (root != nullptr) { root->m_capture = this; } } void UiWidget::ReleaseMouse() { UiWidget* root = Root(); if (root != nullptr && root->m_capture == this) { root->m_capture = nullptr; } } UiWidget* UiWidget::MouseCaptureWidget() const { const UiWidget* root = Root(); return (root != nullptr) ? root->m_capture : nullptr; } void UiWidget::SetModal(bool modal) { UiWidget* root = Root(); if (root == nullptr) { return; } if (modal) { root->m_modal = this; } else if (root->m_modal == this) { root->m_modal = nullptr; } } UiWidget* UiWidget::ModalWidget() const { const UiWidget* root = Root(); return (root != nullptr) ? root->m_modal : nullptr; } UiWidget* UiWidget::HoverWidget() const { const UiWidget* root = Root(); return (root != nullptr) ? root->m_hover : nullptr; } bool UiWidget::HoverPoint(int& x, int& y) const { if (!m_hovered) { return false; } x = m_hoverX; y = m_hoverY; return true; } void UiWidget::RequestFocus() { UiWidget* root = Root(); if (root == nullptr || root->m_focus == this) { return; } if (root->m_focus != nullptr) { root->m_focus->Invalidate(root->m_focus->m_bounds); } root->m_focus = this; Invalidate(m_bounds); } void UiWidget::ReleaseFocus() { UiWidget* root = Root(); if (root != nullptr && root->m_focus == this) { root->m_focus = nullptr; Invalidate(m_bounds); } } UiWidget* UiWidget::FocusWidget() const { const UiWidget* root = Root(); return (root != nullptr) ? root->m_focus : nullptr; } // ------------------------------------------------------------------ // 动画 // ------------------------------------------------------------------ bool UiWidget::AdvanceAnimation(std::int64_t nowMs) { bool active = AdvanceAnimationSelf(nowMs); for (const std::unique_ptr& child : m_children) { if (child != nullptr && child->AdvanceAnimation(nowMs)) { active = true; } } return active; } bool UiWidget::AnimationActive() const { if (AnimationActiveSelf()) { return true; } for (const std::unique_ptr& child : m_children) { if (child != nullptr && child->AnimationActive()) { return true; } } return false; } // ------------------------------------------------------------------ // 缓存键 // ------------------------------------------------------------------ std::uint64_t UiWidget::BaseContentKey() const { std::uint64_t key = 0xCBF29CE484222325ull; key = UiMixKeyFloat(key, m_bounds.X); key = UiMixKeyFloat(key, m_bounds.Y); key = UiMixKeyFloat(key, m_bounds.Width); key = UiMixKeyFloat(key, m_bounds.Height); key = UiMixKey(key, m_visible ? 1u : 0u); return key; } } // namespace jdi