From ff58d90d34e8752ccd84e24ab9692cb813178f96 Mon Sep 17 00:00:00 2001 From: fanlusky <1730976608@qq.com> Date: Fri, 12 Jun 2026 22:53:09 +0800 Subject: [PATCH] Change the demo page to a scrollable layout and fix the issue of buttons being too wide in the initial state. --- ui/demos/msimeui-demo/DemoScene.cpp | 2 +- ui/include/msimeui/Layout.h | 23 +++++ ui/include/msimeui/Scene.h | 1 + ui/include/msimeui/Window.h | 1 + ui/src/Controls.cpp | 18 +++- ui/src/Layout.cpp | 134 ++++++++++++++++++++++++++++ ui/src/Scene.cpp | 5 ++ ui/src/Window.cpp | 19 ++++ 8 files changed, 198 insertions(+), 5 deletions(-) diff --git a/ui/demos/msimeui-demo/DemoScene.cpp b/ui/demos/msimeui-demo/DemoScene.cpp index 2ac7235f..2e863c73 100644 --- a/ui/demos/msimeui-demo/DemoScene.cpp +++ b/ui/demos/msimeui-demo/DemoScene.cpp @@ -137,7 +137,7 @@ std::unique_ptr CreateDemoScene() root->AddChild(searchCard); auto scene = std::make_unique(); - scene->SetRoot(root); + scene->SetRoot(std::make_shared(root)); return scene; } } // namespace msimeui diff --git a/ui/include/msimeui/Layout.h b/ui/include/msimeui/Layout.h index afd6b6cc..8d067f53 100644 --- a/ui/include/msimeui/Layout.h +++ b/ui/include/msimeui/Layout.h @@ -32,6 +32,7 @@ class Visual virtual bool OnMouseDown(const POINT &point, WPARAM keyState); virtual bool OnMouseUp(const POINT &point, WPARAM keyState); virtual bool OnMouseMove(const POINT &point, WPARAM keyState); + virtual bool OnMouseWheel(const POINT &point, short delta, WPARAM keyState); virtual bool OnKeyDown(WPARAM key, LPARAM lParam); virtual bool OnChar(wchar_t ch, LPARAM lParam); virtual bool OnTimer(UINT_PTR timerId); @@ -110,6 +111,28 @@ class WrapPanel : public Panel std::vector rowItems_; }; +class ScrollViewer : public Visual +{ + public: + explicit ScrollViewer(std::shared_ptr content); + + SizeF Measure(const SizeF &availableSize) override; + void Arrange(const RectF &finalRect) override; + void Render(DeviceResources &deviceResources) override; + void Attach(Window *window) override; + Visual *FindVisualAt(const PointF &point) override; + Visual *FindFocusableAt(const PointF &point) override; + Visual *FindFirstFocusableDescendant() override; + bool OnMouseWheel(const POINT &point, short delta, WPARAM keyState) override; + + private: + void ClampScrollOffset(); + + std::shared_ptr content_; + SizeF measuredContent_ = {}; + float scrollOffsetY_ = 0.0f; +}; + class Card : public Panel { public: diff --git a/ui/include/msimeui/Scene.h b/ui/include/msimeui/Scene.h index 2e0760e6..4d476583 100644 --- a/ui/include/msimeui/Scene.h +++ b/ui/include/msimeui/Scene.h @@ -15,6 +15,7 @@ class Scene void Render(DeviceResources &deviceResources); Visual *FindVisualAt(const PointF &point); Visual *FindFocusableAt(const PointF &point); + bool OnMouseWheel(const POINT &point, short delta, WPARAM keyState); private: std::shared_ptr root_; diff --git a/ui/include/msimeui/Window.h b/ui/include/msimeui/Window.h index 8442cd6c..f7c72043 100644 --- a/ui/include/msimeui/Window.h +++ b/ui/include/msimeui/Window.h @@ -33,6 +33,7 @@ class Window LRESULT HandleMessage(UINT message, WPARAM wParam, LPARAM lParam); void OnPaint(); void OnSize(); + bool OnMouseWheel(WPARAM wParam, LPARAM lParam); void SetFocusedVisual(Visual *visual); bool UpdateCursorForClientPoint(const POINT &point); diff --git a/ui/src/Controls.cpp b/ui/src/Controls.cpp index 33e20a56..2a6b6ad7 100644 --- a/ui/src/Controls.cpp +++ b/ui/src/Controls.cpp @@ -15,6 +15,18 @@ using Microsoft::WRL::ComPtr; namespace { +IDWriteFactory *GetSharedDWriteFactory() +{ + static ComPtr factory; + if (!factory) + { + DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), + reinterpret_cast(factory.GetAddressOf())); + } + + return factory.Get(); +} + constexpr float kControlCornerRadius = 12.0f; constexpr float kControlPaddingX = 16.0f; constexpr float kControlPaddingY = 10.0f; @@ -155,8 +167,7 @@ void Button::SetOnClick(ClickHandler handler) SizeF Button::Measure(const SizeF &availableSize) { const float textWidth = std::max(availableSize.width - kControlPaddingX * 2.0f, 1.0f); - const SizeF measuredText = MeasureText(window_ ? window_->GetDeviceResources().GetDWriteFactory() : nullptr, text_, 16.0f, true, - textWidth); + const SizeF measuredText = MeasureText(GetSharedDWriteFactory(), text_, 16.0f, true, textWidth); return {std::min(availableSize.width, measuredText.width + kControlPaddingX * 2.0f), std::max(preferredHeight_, measuredText.height + kControlPaddingY * 2.0f)}; } @@ -287,8 +298,7 @@ void CheckBox::SetChecked(bool checked) SizeF CheckBox::Measure(const SizeF &availableSize) { const float textWidth = std::max(availableSize.width - kCheckBoxIndicatorSize - 12.0f, 1.0f); - const SizeF measuredText = MeasureText(window_ ? window_->GetDeviceResources().GetDWriteFactory() : nullptr, text_, 15.0f, false, - textWidth); + const SizeF measuredText = MeasureText(GetSharedDWriteFactory(), text_, 15.0f, false, textWidth); return {std::min(availableSize.width, kCheckBoxIndicatorSize + 12.0f + measuredText.width), std::max(28.0f, measuredText.height)}; } diff --git a/ui/src/Layout.cpp b/ui/src/Layout.cpp index 2ee27400..4338ada0 100644 --- a/ui/src/Layout.cpp +++ b/ui/src/Layout.cpp @@ -89,6 +89,14 @@ bool Visual::OnMouseMove(const POINT &point, WPARAM keyState) return false; } +bool Visual::OnMouseWheel(const POINT &point, short delta, WPARAM keyState) +{ + (void)point; + (void)delta; + (void)keyState; + return false; +} + bool Visual::OnKeyDown(WPARAM key, LPARAM lParam) { (void)key; @@ -344,6 +352,132 @@ void WrapPanel::Render(DeviceResources &deviceResources) } } +ScrollViewer::ScrollViewer(std::shared_ptr content) : content_(std::move(content)) +{ +} + +SizeF ScrollViewer::Measure(const SizeF &availableSize) +{ + if (!content_) + { + return availableSize; + } + + measuredContent_ = content_->Measure({availableSize.width, 1000000.0f}); + return availableSize; +} + +void ScrollViewer::Arrange(const RectF &finalRect) +{ + bounds_ = finalRect; + ClampScrollOffset(); + + if (content_) + { + content_->Arrange({finalRect.x, finalRect.y - scrollOffsetY_, finalRect.width, measuredContent_.height}); + } +} + +void ScrollViewer::Render(DeviceResources &deviceResources) +{ + if (!content_) + { + return; + } + + ID2D1HwndRenderTarget *target = deviceResources.GetRenderTarget(); + if (!target) + { + return; + } + + target->PushAxisAlignedClip( + D2D1::RectF(bounds_.x, bounds_.y, bounds_.x + bounds_.width, bounds_.y + bounds_.height), + D2D1_ANTIALIAS_MODE_PER_PRIMITIVE); + content_->Render(deviceResources); + target->PopAxisAlignedClip(); +} + +void ScrollViewer::Attach(Window *window) +{ + Visual::Attach(window); + if (content_) + { + content_->Attach(window); + } +} + +Visual *ScrollViewer::FindVisualAt(const PointF &point) +{ + if (!HitTest(point)) + { + return nullptr; + } + + if (content_) + { + if (Visual *hit = content_->FindVisualAt(point)) + { + return hit; + } + } + + return this; +} + +Visual *ScrollViewer::FindFocusableAt(const PointF &point) +{ + if (!HitTest(point)) + { + return nullptr; + } + + if (content_) + { + return content_->FindFocusableAt(point); + } + + return nullptr; +} + +Visual *ScrollViewer::FindFirstFocusableDescendant() +{ + return content_ ? content_->FindFirstFocusableDescendant() : nullptr; +} + +bool ScrollViewer::OnMouseWheel(const POINT &point, short delta, WPARAM keyState) +{ + (void)keyState; + + if (!window_ || !HitTest(window_->ClientPixelsToDips(point))) + { + return false; + } + + const float maxOffset = std::max(measuredContent_.height - bounds_.height, 0.0f); + if (maxOffset <= 0.0f) + { + return false; + } + + scrollOffsetY_ = std::clamp(scrollOffsetY_ - (static_cast(delta) / static_cast(WHEEL_DELTA)) * 72.0f, 0.0f, + maxOffset); + + if (content_) + { + content_->Arrange({bounds_.x, bounds_.y - scrollOffsetY_, bounds_.width, measuredContent_.height}); + } + + window_->Invalidate(); + return true; +} + +void ScrollViewer::ClampScrollOffset() +{ + const float maxOffset = std::max(measuredContent_.height - bounds_.height, 0.0f); + scrollOffsetY_ = std::clamp(scrollOffsetY_, 0.0f, maxOffset); +} + Card::Card(Brush brush, float padding) : brush_(brush), padding_(padding) { } diff --git a/ui/src/Scene.cpp b/ui/src/Scene.cpp index a42c949f..1bdd2589 100644 --- a/ui/src/Scene.cpp +++ b/ui/src/Scene.cpp @@ -55,4 +55,9 @@ Visual *Scene::FindFocusableAt(const PointF &point) { return root_ ? root_->FindFocusableAt(point) : nullptr; } + +bool Scene::OnMouseWheel(const POINT &point, short delta, WPARAM keyState) +{ + return root_ ? root_->OnMouseWheel(point, delta, keyState) : false; +} } // namespace msimeui diff --git a/ui/src/Window.cpp b/ui/src/Window.cpp index 9801faca..33cc9393 100644 --- a/ui/src/Window.cpp +++ b/ui/src/Window.cpp @@ -187,6 +187,13 @@ LRESULT Window::HandleMessage(UINT message, WPARAM wParam, LPARAM lParam) return 0; } + case WM_MOUSEWHEEL: + if (OnMouseWheel(wParam, lParam)) + { + return 0; + } + break; + case WM_KEYDOWN: if (wParam == VK_ESCAPE && focusedVisual_) { @@ -298,6 +305,18 @@ void Window::OnSize() InvalidateRect(hwnd_, nullptr, FALSE); } +bool Window::OnMouseWheel(WPARAM wParam, LPARAM lParam) +{ + if (!scene_) + { + return false; + } + + POINT point = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)}; + ScreenToClient(hwnd_, &point); + return scene_->OnMouseWheel(point, GET_WHEEL_DELTA_WPARAM(wParam), GET_KEYSTATE_WPARAM(wParam)); +} + bool Window::UpdateCursorForClientPoint(const POINT &point) { if (!scene_)