diff --git a/ui/README.md b/ui/README.md index 6ff8ee0a..27fc2204 100644 --- a/ui/README.md +++ b/ui/README.md @@ -40,6 +40,7 @@ - 修复了 `TabControl` / `TreeView` / `Accordion` 这类会改变内容结构的控件在切换后未及时重新布局而导致的稳定性问题 - 修复了单行 `TextBox`(如 demo 中的 Search Box)输入光标和文本区域未垂直居中的问题 - 修复了单行 `TextBox` 在垂直居中后输入法候选框未同步跟随文本区域下移的问题 +- 新增一版场景级 invalidation 机制:控件现可通过 `InvalidateMeasure` / `InvalidateArrange` / `InvalidateVisual` 统一声明布局或重绘失效,`Scene` / `Window` 会在下一次绘制前自动补做布局,减少到处手动 `Relayout()` 的耦合 ## 构建 @@ -105,6 +106,7 @@ build/bin/Debug/msimeui-demo.exe ## 后续工作 +- 继续把场景级 invalidation 细化为局部布局和局部重绘,减少全树重排成本 - 继续完善 `TextBox` 的输入、样式和布局能力 -- 增加滚动容器、列表、树、菜单和弹出层 +- 增加菜单、弹出层、overlay 和更完整的命中/焦点管理 - 引入资源字典、主题和声明式界面描述 diff --git a/ui/include/msimeui/Layout.h b/ui/include/msimeui/Layout.h index b07d94fb..dc884765 100644 --- a/ui/include/msimeui/Layout.h +++ b/ui/include/msimeui/Layout.h @@ -66,6 +66,9 @@ class Visual virtual bool OnChar(wchar_t ch, LPARAM lParam); virtual bool OnTimer(UINT_PTR timerId); virtual HCURSOR GetCursor() const; + void InvalidateMeasure(); + void InvalidateArrange(); + void InvalidateVisual(); void SetMargin(float uniform); void SetMargin(Thickness margin); diff --git a/ui/include/msimeui/Scene.h b/ui/include/msimeui/Scene.h index 4d476583..b70a9da5 100644 --- a/ui/include/msimeui/Scene.h +++ b/ui/include/msimeui/Scene.h @@ -11,7 +11,11 @@ class Scene public: void SetRoot(std::shared_ptr root); void Attach(Window *window); + void EnsureLayout(const SizeF &size); void Layout(const SizeF &size); + void InvalidateMeasure(); + void InvalidateArrange(); + void InvalidateVisual(); void Render(DeviceResources &deviceResources); Visual *FindVisualAt(const PointF &point); Visual *FindFocusableAt(const PointF &point); @@ -19,5 +23,10 @@ class Scene private: std::shared_ptr root_; + Window *window_ = nullptr; + SizeF lastLayoutSize_ = {}; + bool hasLastLayoutSize_ = false; + bool measureDirty_ = true; + bool arrangeDirty_ = true; }; } // namespace msimeui diff --git a/ui/include/msimeui/Window.h b/ui/include/msimeui/Window.h index b7603fd3..a2115367 100644 --- a/ui/include/msimeui/Window.h +++ b/ui/include/msimeui/Window.h @@ -25,6 +25,8 @@ class Window SizeF ClientPixelsToDips(const SIZE &size) const; RECT DipsToClientPixels(const RectF &rect) const; void Invalidate(); + void InvalidateMeasure(); + void InvalidateArrange(); void Relayout(); void SetScene(std::unique_ptr scene); diff --git a/ui/src/Controls.cpp b/ui/src/Controls.cpp index d94c8f2f..8634ab89 100644 --- a/ui/src/Controls.cpp +++ b/ui/src/Controls.cpp @@ -323,10 +323,7 @@ void CheckBox::SetChecked(bool checked) } checked_ = checked; - if (window_) - { - window_->Invalidate(); - } + InvalidateVisual(); } SizeF CheckBox::Measure(const SizeF &availableSize) @@ -436,10 +433,7 @@ ProgressBar::ProgressBar(float height) : preferredHeight_(height) void ProgressBar::SetValue(float value) { value_ = std::clamp(value, 0.0f, 1.0f); - if (window_) - { - window_->Invalidate(); - } + InvalidateVisual(); } float ProgressBar::GetValue() const @@ -608,10 +602,7 @@ void Slider::SetValueInternal(float value, bool notify) { const float oldValue = value_; value_ = std::clamp(value, minValue_, maxValue_); - if (window_) - { - window_->Invalidate(); - } + InvalidateVisual(); if (notify && oldValue != value_ && onChanged_) { @@ -668,10 +659,7 @@ ListView::ListView(float itemHeight) : itemHeight_(itemHeight) void ListView::AddItem(Item item) { items_.push_back(std::move(item)); - if (window_) - { - window_->Invalidate(); - } + InvalidateMeasure(); } void ListView::ClearItems() @@ -679,10 +667,7 @@ void ListView::ClearItems() items_.clear(); selectedIndex_ = 0; pressedIndex_ = static_cast(-1); - if (window_) - { - window_->Invalidate(); - } + InvalidateMeasure(); } void ListView::SetOnSelectionChanged(SelectionChangedHandler handler) @@ -861,10 +846,7 @@ void TreeView::AddRoot(Node node) { selectedNode_ = &roots_.front(); } - if (window_) - { - window_->Invalidate(); - } + InvalidateMeasure(); } void TreeView::Clear() @@ -873,10 +855,7 @@ void TreeView::Clear() visibleNodes_.clear(); pressedNode_ = nullptr; selectedNode_ = nullptr; - if (window_) - { - window_->Invalidate(); - } + InvalidateMeasure(); } void TreeView::SetOnSelectionChanged(SelectionChangedHandler handler) @@ -1017,10 +996,7 @@ bool TreeView::OnMouseUp(const POINT &point, WPARAM keyState) { hit->node->expanded = !hit->node->expanded; BuildVisibleNodes(); - if (window_) - { - window_->Relayout(); - } + InvalidateMeasure(); } else { @@ -1111,10 +1087,7 @@ void TabControl::AddTab(std::wstring title, std::shared_ptr content) { tabs_.back().content->Attach(window_); } - if (window_) - { - window_->Invalidate(); - } + InvalidateMeasure(); } void TabControl::ClearTabs() @@ -1124,10 +1097,7 @@ void TabControl::ClearTabs() selectedIndex_ = 0; pressedIndex_ = static_cast(-1); pressed_ = false; - if (window_) - { - window_->Invalidate(); - } + InvalidateMeasure(); } void TabControl::SetSelectedIndex(size_t index) @@ -1149,10 +1119,7 @@ void TabControl::SetSelectedIndex(size_t index) { onSelectionChanged_(selectedIndex_); } - if (window_) - { - window_->Relayout(); - } + InvalidateMeasure(); } size_t TabControl::GetSelectedIndex() const @@ -1370,10 +1337,7 @@ void Accordion::AddSection(std::wstring title, std::shared_ptr content, { sections_.back().content->Attach(window_); } - if (window_) - { - window_->Invalidate(); - } + InvalidateMeasure(); } void Accordion::ClearSections() @@ -1383,10 +1347,7 @@ void Accordion::ClearSections() contentRects_.clear(); pressed_ = false; pressedIndex_ = static_cast(-1); - if (window_) - { - window_->Invalidate(); - } + InvalidateMeasure(); } void Accordion::SetAllowMultipleExpanded(bool allowMultipleExpanded) @@ -1408,6 +1369,7 @@ void Accordion::SetAllowMultipleExpanded(bool allowMultipleExpanded) CollapseOtherSections(firstExpanded); } } + InvalidateMeasure(); } SizeF Accordion::Measure(const SizeF &availableSize) @@ -1617,7 +1579,7 @@ bool Accordion::OnMouseUp(const POINT &point, WPARAM keyState) { CollapseOtherSections(hit); } - window_->Relayout(); + InvalidateMeasure(); } return true; diff --git a/ui/src/Layout.cpp b/ui/src/Layout.cpp index 1d142718..984b55c2 100644 --- a/ui/src/Layout.cpp +++ b/ui/src/Layout.cpp @@ -181,6 +181,30 @@ HCURSOR Visual::GetCursor() const return LoadCursor(nullptr, IDC_ARROW); } +void Visual::InvalidateMeasure() +{ + if (window_) + { + window_->InvalidateMeasure(); + } +} + +void Visual::InvalidateArrange() +{ + if (window_) + { + window_->InvalidateArrange(); + } +} + +void Visual::InvalidateVisual() +{ + if (window_) + { + window_->Invalidate(); + } +} + void Visual::SetMargin(float uniform) { SetMargin({uniform, uniform, uniform, uniform}); @@ -189,6 +213,7 @@ void Visual::SetMargin(float uniform) void Visual::SetMargin(Thickness margin) { margin_ = margin; + InvalidateMeasure(); } const Thickness &Visual::GetMargin() const @@ -204,6 +229,7 @@ void Visual::SetPadding(float uniform) void Visual::SetPadding(Thickness padding) { padding_ = padding; + InvalidateMeasure(); } const Thickness &Visual::GetPadding() const @@ -214,71 +240,85 @@ const Thickness &Visual::GetPadding() const void Visual::SetWidth(float width) { explicitWidth_ = std::max(width, 0.0f); + InvalidateMeasure(); } void Visual::SetHeight(float height) { explicitHeight_ = std::max(height, 0.0f); + InvalidateMeasure(); } void Visual::SetMinWidth(float width) { minWidth_ = std::max(width, 0.0f); + InvalidateMeasure(); } void Visual::SetMinHeight(float height) { minHeight_ = std::max(height, 0.0f); + InvalidateMeasure(); } void Visual::SetMaxWidth(float width) { maxWidth_ = width >= 0.0f ? width : -1.0f; + InvalidateMeasure(); } void Visual::SetMaxHeight(float height) { maxHeight_ = height >= 0.0f ? height : -1.0f; + InvalidateMeasure(); } void Visual::ClearWidth() { explicitWidth_ = -1.0f; + InvalidateMeasure(); } void Visual::ClearHeight() { explicitHeight_ = -1.0f; + InvalidateMeasure(); } void Visual::ClearMinWidth() { minWidth_ = 0.0f; + InvalidateMeasure(); } void Visual::ClearMinHeight() { minHeight_ = 0.0f; + InvalidateMeasure(); } void Visual::ClearMaxWidth() { maxWidth_ = -1.0f; + InvalidateMeasure(); } void Visual::ClearMaxHeight() { maxHeight_ = -1.0f; + InvalidateMeasure(); } void Visual::SetHorizontalAlignment(HorizontalAlignment alignment) { horizontalAlignment_ = alignment; + InvalidateArrange(); } void Visual::SetVerticalAlignment(VerticalAlignment alignment) { verticalAlignment_ = alignment; + InvalidateArrange(); } SizeF Visual::MeasureInLayout(const SizeF &availableSize) @@ -378,7 +418,12 @@ bool Visual::HasExplicitHeight() const void Panel::AddChild(std::shared_ptr child) { + if (child && window_) + { + child->Attach(window_); + } children_.push_back(std::move(child)); + InvalidateMeasure(); } void Panel::Attach(Window *window) @@ -907,26 +952,35 @@ void ScrollViewer::UpdateContentLayout() void Grid::AddRow(GridLength length) { rowDefinitions_.push_back(length); + InvalidateMeasure(); } void Grid::AddColumn(GridLength length) { columnDefinitions_.push_back(length); + InvalidateMeasure(); } void Grid::AddChild(std::shared_ptr child, size_t row, size_t column, size_t rowSpan, size_t columnSpan) { + if (child && window_) + { + child->Attach(window_); + } children_.push_back({std::move(child), {row, column, std::max(rowSpan, 1), std::max(columnSpan, 1)}, {}}); + InvalidateMeasure(); } void Grid::SetRowSpacing(float spacing) { rowSpacing_ = std::max(spacing, 0.0f); + InvalidateMeasure(); } void Grid::SetColumnSpacing(float spacing) { columnSpacing_ = std::max(spacing, 0.0f); + InvalidateMeasure(); } void Grid::Attach(Window *window) @@ -1152,6 +1206,7 @@ void Container::SetChild(std::shared_ptr child) { child_->Attach(window_); } + InvalidateMeasure(); } void Container::Attach(Window *window) @@ -1350,10 +1405,7 @@ TextBlock::TextBlock(std::wstring text, float fontSize, D2D1_COLOR_F color, bool void TextBlock::SetText(std::wstring text) { text_ = std::move(text); - if (window_) - { - window_->Invalidate(); - } + InvalidateMeasure(); } SizeF TextBlock::Measure(const SizeF &availableSize) diff --git a/ui/src/Scene.cpp b/ui/src/Scene.cpp index 55af70f1..f2417291 100644 --- a/ui/src/Scene.cpp +++ b/ui/src/Scene.cpp @@ -15,20 +15,40 @@ constexpr float kScenePaddingDips = 5.0f; void Scene::SetRoot(std::shared_ptr root) { root_ = std::move(root); + if (root_ && window_) + { + root_->Attach(window_); + } + measureDirty_ = true; + arrangeDirty_ = true; } void Scene::Attach(Window *window) { + window_ = window; if (root_) { root_->Attach(window); } } +void Scene::EnsureLayout(const SizeF &size) +{ + const bool sizeChanged = !hasLastLayoutSize_ || lastLayoutSize_.width != size.width || lastLayoutSize_.height != size.height; + if (sizeChanged || measureDirty_ || arrangeDirty_) + { + Layout(size); + } +} + void Scene::Layout(const SizeF &size) { if (!root_) { + lastLayoutSize_ = size; + hasLastLayoutSize_ = true; + measureDirty_ = false; + arrangeDirty_ = false; return; } @@ -36,6 +56,31 @@ void Scene::Layout(const SizeF &size) std::max(size.height - kScenePaddingDips * 2.0f, 0.0f)}; root_->MeasureInLayout(innerSize); root_->ArrangeInLayout({kScenePaddingDips, kScenePaddingDips, innerSize.width, innerSize.height}); + lastLayoutSize_ = size; + hasLastLayoutSize_ = true; + measureDirty_ = false; + arrangeDirty_ = false; +} + +void Scene::InvalidateMeasure() +{ + measureDirty_ = true; + arrangeDirty_ = true; + InvalidateVisual(); +} + +void Scene::InvalidateArrange() +{ + arrangeDirty_ = true; + InvalidateVisual(); +} + +void Scene::InvalidateVisual() +{ + if (window_) + { + window_->Invalidate(); + } } void Scene::Render(DeviceResources &deviceResources) diff --git a/ui/src/Window.cpp b/ui/src/Window.cpp index 07f6405b..c9453418 100644 --- a/ui/src/Window.cpp +++ b/ui/src/Window.cpp @@ -107,6 +107,28 @@ void Window::Invalidate() InvalidateRect(hwnd_, nullptr, FALSE); } +void Window::InvalidateMeasure() +{ + if (scene_) + { + scene_->InvalidateMeasure(); + return; + } + + Invalidate(); +} + +void Window::InvalidateArrange() +{ + if (scene_) + { + scene_->InvalidateArrange(); + return; + } + + Invalidate(); +} + void Window::Relayout() { if (!hwnd_ || !scene_) @@ -117,7 +139,8 @@ void Window::Relayout() RECT rc = {}; GetClientRect(hwnd_, &rc); const SIZE pixelSize = {rc.right, rc.bottom}; - scene_->Layout(ClientPixelsToDips(pixelSize)); + scene_->InvalidateMeasure(); + scene_->EnsureLayout(ClientPixelsToDips(pixelSize)); InvalidateRect(hwnd_, nullptr, FALSE); } @@ -306,6 +329,10 @@ void Window::OnPaint() target->Clear(D2D1::ColorF(0xF3F5F8)); if (scene_) { + RECT rc = {}; + GetClientRect(hwnd_, &rc); + const SIZE pixelSize = {rc.right, rc.bottom}; + scene_->EnsureLayout(ClientPixelsToDips(pixelSize)); scene_->Render(deviceResources_); } @@ -328,7 +355,7 @@ void Window::OnSize() if (scene_) { const SIZE pixelSize = {rc.right, rc.bottom}; - scene_->Layout(ClientPixelsToDips(pixelSize)); + scene_->EnsureLayout(ClientPixelsToDips(pixelSize)); } InvalidateRect(hwnd_, nullptr, FALSE);