From 5729a5aa1b443e3e25f3e14dee29636d3b31a6f8 Mon Sep 17 00:00:00 2001 From: crupest Date: Sun, 18 Oct 2020 21:09:21 +0800 Subject: ... --- include/cru/ui/render/WindowRenderObject.hpp | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'include/cru/ui/render/WindowRenderObject.hpp') diff --git a/include/cru/ui/render/WindowRenderObject.hpp b/include/cru/ui/render/WindowRenderObject.hpp index 4c254f42..d2ca5526 100644 --- a/include/cru/ui/render/WindowRenderObject.hpp +++ b/include/cru/ui/render/WindowRenderObject.hpp @@ -1,6 +1,8 @@ #pragma once #include "RenderObject.hpp" +#include + namespace cru::ui::render { class WindowRenderObject : public RenderObject { public: @@ -13,6 +15,9 @@ class WindowRenderObject : public RenderObject { RenderObject* HitTest(const Point& point) override; + public: + std::u16string_view GetName() const override; + protected: Size OnMeasureContent(const MeasureRequirement& requirement, const MeasureSize& preferred_size) override; -- cgit v1.2.3 From 6ce70ed4b08c7db20b6b384be26c7fc4e11c98de Mon Sep 17 00:00:00 2001 From: crupest Date: Wed, 28 Oct 2020 20:51:55 +0800 Subject: ... --- demos/main/main.cpp | 4 +- include/cru/ui/Base.hpp | 2 +- include/cru/ui/Control.hpp | 12 +- include/cru/ui/UiHost.hpp | 185 ------------ include/cru/ui/Window.hpp | 6 +- include/cru/ui/WindowHost.hpp | 185 ++++++++++++ include/cru/ui/render/RenderObject.hpp | 6 +- include/cru/ui/render/WindowRenderObject.hpp | 2 +- src/ui/CMakeLists.txt | 4 +- src/ui/ContentControl.cpp | 6 +- src/ui/Control.cpp | 20 +- src/ui/LayoutControl.cpp | 4 +- src/ui/UiHost.cpp | 416 -------------------------- src/ui/Window.cpp | 4 +- src/ui/WindowHost.cpp | 418 +++++++++++++++++++++++++++ src/ui/controls/TextControlService.hpp | 6 +- src/ui/render/RenderObject.cpp | 6 +- src/ui/render/WindowRenderObject.cpp | 4 +- 18 files changed, 646 insertions(+), 644 deletions(-) delete mode 100644 include/cru/ui/UiHost.hpp create mode 100644 include/cru/ui/WindowHost.hpp delete mode 100644 src/ui/UiHost.cpp create mode 100644 src/ui/WindowHost.cpp (limited to 'include/cru/ui/render/WindowRenderObject.hpp') diff --git a/demos/main/main.cpp b/demos/main/main.cpp index 10b43b0f..f7635231 100644 --- a/demos/main/main.cpp +++ b/demos/main/main.cpp @@ -2,7 +2,7 @@ #include "cru/platform/native/UiApplication.hpp" #include "cru/platform/native/Window.hpp" #include "cru/ui/Base.hpp" -#include "cru/ui/UiHost.hpp" +#include "cru/ui/WindowHost.hpp" #include "cru/ui/Window.hpp" #include "cru/ui/controls/Button.hpp" #include "cru/ui/controls/FlexLayout.hpp" @@ -45,7 +45,7 @@ int main() { const auto text_box = TextBox::Create(); flex_layout->AddChild(text_box, 2); - window->GetUiHost()->GetNativeWindowResolver()->Resolve()->SetVisible(true); + window->GetWindowHost()->GetNativeWindowResolver()->Resolve()->SetVisible(true); return application->Run(); } diff --git a/include/cru/ui/Base.hpp b/include/cru/ui/Base.hpp index 6be359ab..dd93f187 100644 --- a/include/cru/ui/Base.hpp +++ b/include/cru/ui/Base.hpp @@ -29,7 +29,7 @@ namespace colors = cru::platform::colors; class Window; class Control; class ClickDetector; -class UiHost; +class WindowHost; namespace render { class RenderObject; diff --git a/include/cru/ui/Control.hpp b/include/cru/ui/Control.hpp index bd86bc2f..692dcc9b 100644 --- a/include/cru/ui/Control.hpp +++ b/include/cru/ui/Control.hpp @@ -9,7 +9,7 @@ namespace cru::ui { class Control : public Object { - friend UiHost; + friend WindowHost; protected: Control(); @@ -27,7 +27,7 @@ class Control : public Object { //*************** region: tree *************** public: // Get the ui host if attached, otherwise, return nullptr. - UiHost* GetUiHost() const { return ui_host_; } + WindowHost* GetWindowHost() const { return ui_host_; } Control* GetParent() const { return parent_; } @@ -37,7 +37,7 @@ class Control : public Object { void TraverseDescendants(const std::function& predicate); void _SetParent(Control* parent); - void _SetDescendantUiHost(UiHost* host); + void _SetDescendantWindowHost(WindowHost* host); private: static void _TraverseDescendants( @@ -135,13 +135,13 @@ class Control : public Object { //*************** region: tree *************** protected: virtual void OnParentChanged(Control* old_parent, Control* new_parent); - virtual void OnAttachToHost(UiHost* host); - virtual void OnDetachFromHost(UiHost* host); + virtual void OnAttachToHost(WindowHost* host); + virtual void OnDetachFromHost(WindowHost* host); virtual void OnMouseHoverChange(bool newHover) { CRU_UNUSED(newHover) } private: - UiHost* ui_host_ = nullptr; + WindowHost* ui_host_ = nullptr; Control* parent_ = nullptr; private: diff --git a/include/cru/ui/UiHost.hpp b/include/cru/ui/UiHost.hpp deleted file mode 100644 index 2437b967..00000000 --- a/include/cru/ui/UiHost.hpp +++ /dev/null @@ -1,185 +0,0 @@ -#pragma once -#include -#include "Base.hpp" - -#include "cru/common/Event.hpp" -#include "cru/common/SelfResolvable.hpp" -#include "render/Base.hpp" - -namespace cru::ui { -struct AfterLayoutEventArgs {}; - -// The host of all controls and render objects. -// -// 3 situations on destroy: -// 1. Native window destroyed, IsRetainAfterDestroy: false: -// OnNativeDestroy(set native_window_destroyed_ to true, call ~Window due to -// deleting_ is false and IsRetainAfterDestroy is false) -> ~Window -> -// ~UiHost(not destroy native window repeatedly due to native_window_destroyed_ -// is true) -// 2. Native window destroyed, IsRetainAfterDestroy: true: -// OnNativeDestroy(set native_window_destroyed_ to true, not call ~Window -// because deleting_ is false and IsRetainAfterDestroy is true) -// then, ~Window -> ~UiHost(not destroy native window repeatedly due to -// native_window_destroyed_ is true) -// 3. Native window not destroyed, ~Window is called: -// ~Window -> ~UiHost(set deleting_ to true, destroy native window -// due to native_window_destroyed is false) -> OnNativeDestroy(not call ~Window -// due to deleting_ is true and IsRetainAfterDestroy is whatever) -// In conclusion: -// 1. Set native_window_destroyed_ to true at the beginning of OnNativeDestroy. -// 2. Set deleting_ to true at the beginning of ~UiHost. -// 3. Destroy native window when native_window_destroy_ is false in ~Window. -// 4. Delete Window when deleting_ is false and IsRetainAfterDestroy is false in -// OnNativeDestroy. -class UiHost : public Object, public SelfResolvable { - CRU_DEFINE_CLASS_LOG_TAG(u"cru::ui::UiHost") - - public: - // This will create root window render object and attach it to window. - // It will also create and manage a native window. - UiHost(Window* window); - - CRU_DELETE_COPY(UiHost) - CRU_DELETE_MOVE(UiHost) - - ~UiHost() override; - - public: - // Mark the layout as invalid, and arrange a re-layout later. - // This method could be called more than one times in a message cycle. But - // layout only takes place once. - void InvalidateLayout(); - - // Mark the paint as invalid, and arrange a re-paint later. - // This method could be called more than one times in a message cycle. But - // paint only takes place once. - void InvalidatePaint(); - - IEvent* AfterLayoutEvent() { - return &after_layout_event_; - } - - // If true, preferred size of root render object is set to window size when - // measure. Default is true. - bool IsLayoutPreferToFillWindow() const; - void SetLayoutPreferToFillWindow(bool value); - - void Relayout(); - - // Get current control that mouse hovers on. This ignores the mouse-capture - // control. Even when mouse is captured by another control, this function - // return the control under cursor. You can use `GetMouseCaptureControl` to - // get more info. - Control* GetMouseHoverControl() const { return mouse_hover_control_; } - - //*************** region: focus *************** - - // Request focus for specified control. - bool RequestFocusFor(Control* control); - - // Get the control that has focus. - Control* GetFocusControl(); - - //*************** region: focus *************** - - // Pass nullptr to release capture. If mouse is already capture by a control, - // this capture will fail and return false. If control is identical to the - // capturing control, capture is not changed and this function will return - // true. - // - // When capturing control changes, - // appropriate event will be sent. If mouse is not on the capturing control - // and capture is released, mouse enter event will be sent to the mouse-hover - // control. If mouse is not on the capturing control and capture is set, mouse - // leave event will be sent to the mouse-hover control. - bool CaptureMouseFor(Control* control); - - // Return null if not captured. - Control* GetMouseCaptureControl(); - - Control* HitTest(const Point& point); - - void UpdateCursor(); - - std::shared_ptr - GetNativeWindowResolver() { - return native_window_resolver_; - } - - bool IsRetainAfterDestroy() { return retain_after_destroy_; } - - void SetRetainAfterDestroy(bool destroy) { retain_after_destroy_ = destroy; } - - // Is layout is invalid, wait for relayout and then run the action. Otherwist - // run it right now. - void RunAfterLayoutStable(std::function action); - - private: - //*************** region: native messages *************** - void OnNativeDestroy(platform::native::INativeWindow* window, std::nullptr_t); - void OnNativePaint(platform::native::INativeWindow* window, std::nullptr_t); - void OnNativeResize(platform::native::INativeWindow* window, - const Size& size); - - void OnNativeFocus(platform::native::INativeWindow* window, - cru::platform::native::FocusChangeType focus); - - void OnNativeMouseEnterLeave( - platform::native::INativeWindow* window, - cru::platform::native::MouseEnterLeaveType enter); - void OnNativeMouseMove(platform::native::INativeWindow* window, - const Point& point); - void OnNativeMouseDown( - platform::native::INativeWindow* window, - const platform::native::NativeMouseButtonEventArgs& args); - void OnNativeMouseUp( - platform::native::INativeWindow* window, - const platform::native::NativeMouseButtonEventArgs& args); - - void OnNativeKeyDown(platform::native::INativeWindow* window, - const platform::native::NativeKeyEventArgs& args); - void OnNativeKeyUp(platform::native::INativeWindow* window, - const platform::native::NativeKeyEventArgs& args); - - //*************** region: event dispatcher helper *************** - - void DispatchMouseHoverControlChangeEvent(Control* old_control, - Control* new_control, - const Point& point, bool no_leave, - bool no_enter); - - private: - bool need_layout_ = false; - - Event after_layout_event_; - std::vector > after_layout_stable_action_; - - std::shared_ptr - native_window_resolver_; - - // See remarks of UiHost. - bool retain_after_destroy_ = false; - // See remarks of UiHost. - bool deleting_ = false; - - // We need this because calling Resolve on resolver in handler of destroy - // event is bad and will always get the dying window. But we need to label the - // window as destroyed so the destructor will not destroy native window - // repeatedly. See remarks of UiHost. - bool native_window_destroyed_ = false; - - std::vector event_revoker_guards_; - - Window* window_control_; - std::unique_ptr root_render_object_; - - Control* mouse_hover_control_; - - Control* focus_control_; // "focus_control_" can't be nullptr - - Control* mouse_captured_control_; - - bool layout_prefer_to_fill_window_ = true; -}; -} // namespace cru::ui diff --git a/include/cru/ui/Window.hpp b/include/cru/ui/Window.hpp index 450ea97b..5ea24855 100644 --- a/include/cru/ui/Window.hpp +++ b/include/cru/ui/Window.hpp @@ -3,7 +3,7 @@ namespace cru::ui { class Window final : public ContentControl { - friend UiHost; + friend WindowHost; public: static constexpr std::u16string_view control_type = u"Window"; @@ -32,9 +32,9 @@ class Window final : public ContentControl { void OnChildChanged(Control* old_child, Control* new_child) override; private: - std::unique_ptr managed_ui_host_; + std::unique_ptr managed_ui_host_; - // UiHost is responsible to take care of lifetime of this. + // WindowHost is responsible to take care of lifetime of this. render::WindowRenderObject* render_object_; }; } // namespace cru::ui diff --git a/include/cru/ui/WindowHost.hpp b/include/cru/ui/WindowHost.hpp new file mode 100644 index 00000000..64116590 --- /dev/null +++ b/include/cru/ui/WindowHost.hpp @@ -0,0 +1,185 @@ +#pragma once +#include +#include "Base.hpp" + +#include "cru/common/Event.hpp" +#include "cru/common/SelfResolvable.hpp" +#include "render/Base.hpp" + +namespace cru::ui { +struct AfterLayoutEventArgs {}; + +// The host of all controls and render objects. +// +// 3 situations on destroy: +// 1. Native window destroyed, IsRetainAfterDestroy: false: +// OnNativeDestroy(set native_window_destroyed_ to true, call ~Window due to +// deleting_ is false and IsRetainAfterDestroy is false) -> ~Window -> +// ~WindowHost(not destroy native window repeatedly due to native_window_destroyed_ +// is true) +// 2. Native window destroyed, IsRetainAfterDestroy: true: +// OnNativeDestroy(set native_window_destroyed_ to true, not call ~Window +// because deleting_ is false and IsRetainAfterDestroy is true) +// then, ~Window -> ~WindowHost(not destroy native window repeatedly due to +// native_window_destroyed_ is true) +// 3. Native window not destroyed, ~Window is called: +// ~Window -> ~WindowHost(set deleting_ to true, destroy native window +// due to native_window_destroyed is false) -> OnNativeDestroy(not call ~Window +// due to deleting_ is true and IsRetainAfterDestroy is whatever) +// In conclusion: +// 1. Set native_window_destroyed_ to true at the beginning of OnNativeDestroy. +// 2. Set deleting_ to true at the beginning of ~WindowHost. +// 3. Destroy native window when native_window_destroy_ is false in ~Window. +// 4. Delete Window when deleting_ is false and IsRetainAfterDestroy is false in +// OnNativeDestroy. +class WindowHost : public Object, public SelfResolvable { + CRU_DEFINE_CLASS_LOG_TAG(u"cru::ui::WindowHost") + + public: + // This will create root window render object and attach it to window. + // It will also create and manage a native window. + WindowHost(Window* window); + + CRU_DELETE_COPY(WindowHost) + CRU_DELETE_MOVE(WindowHost) + + ~WindowHost() override; + + public: + // Mark the layout as invalid, and arrange a re-layout later. + // This method could be called more than one times in a message cycle. But + // layout only takes place once. + void InvalidateLayout(); + + // Mark the paint as invalid, and arrange a re-paint later. + // This method could be called more than one times in a message cycle. But + // paint only takes place once. + void InvalidatePaint(); + + IEvent* AfterLayoutEvent() { + return &after_layout_event_; + } + + // If true, preferred size of root render object is set to window size when + // measure. Default is true. + bool IsLayoutPreferToFillWindow() const; + void SetLayoutPreferToFillWindow(bool value); + + void Relayout(); + + // Get current control that mouse hovers on. This ignores the mouse-capture + // control. Even when mouse is captured by another control, this function + // return the control under cursor. You can use `GetMouseCaptureControl` to + // get more info. + Control* GetMouseHoverControl() const { return mouse_hover_control_; } + + //*************** region: focus *************** + + // Request focus for specified control. + bool RequestFocusFor(Control* control); + + // Get the control that has focus. + Control* GetFocusControl(); + + //*************** region: focus *************** + + // Pass nullptr to release capture. If mouse is already capture by a control, + // this capture will fail and return false. If control is identical to the + // capturing control, capture is not changed and this function will return + // true. + // + // When capturing control changes, + // appropriate event will be sent. If mouse is not on the capturing control + // and capture is released, mouse enter event will be sent to the mouse-hover + // control. If mouse is not on the capturing control and capture is set, mouse + // leave event will be sent to the mouse-hover control. + bool CaptureMouseFor(Control* control); + + // Return null if not captured. + Control* GetMouseCaptureControl(); + + Control* HitTest(const Point& point); + + void UpdateCursor(); + + std::shared_ptr + GetNativeWindowResolver() { + return native_window_resolver_; + } + + bool IsRetainAfterDestroy() { return retain_after_destroy_; } + + void SetRetainAfterDestroy(bool destroy) { retain_after_destroy_ = destroy; } + + // Is layout is invalid, wait for relayout and then run the action. Otherwist + // run it right now. + void RunAfterLayoutStable(std::function action); + + private: + //*************** region: native messages *************** + void OnNativeDestroy(platform::native::INativeWindow* window, std::nullptr_t); + void OnNativePaint(platform::native::INativeWindow* window, std::nullptr_t); + void OnNativeResize(platform::native::INativeWindow* window, + const Size& size); + + void OnNativeFocus(platform::native::INativeWindow* window, + cru::platform::native::FocusChangeType focus); + + void OnNativeMouseEnterLeave( + platform::native::INativeWindow* window, + cru::platform::native::MouseEnterLeaveType enter); + void OnNativeMouseMove(platform::native::INativeWindow* window, + const Point& point); + void OnNativeMouseDown( + platform::native::INativeWindow* window, + const platform::native::NativeMouseButtonEventArgs& args); + void OnNativeMouseUp( + platform::native::INativeWindow* window, + const platform::native::NativeMouseButtonEventArgs& args); + + void OnNativeKeyDown(platform::native::INativeWindow* window, + const platform::native::NativeKeyEventArgs& args); + void OnNativeKeyUp(platform::native::INativeWindow* window, + const platform::native::NativeKeyEventArgs& args); + + //*************** region: event dispatcher helper *************** + + void DispatchMouseHoverControlChangeEvent(Control* old_control, + Control* new_control, + const Point& point, bool no_leave, + bool no_enter); + + private: + bool need_layout_ = false; + + Event after_layout_event_; + std::vector > after_layout_stable_action_; + + std::shared_ptr + native_window_resolver_; + + // See remarks of WindowHost. + bool retain_after_destroy_ = false; + // See remarks of WindowHost. + bool deleting_ = false; + + // We need this because calling Resolve on resolver in handler of destroy + // event is bad and will always get the dying window. But we need to label the + // window as destroyed so the destructor will not destroy native window + // repeatedly. See remarks of WindowHost. + bool native_window_destroyed_ = false; + + std::vector event_revoker_guards_; + + Window* window_control_; + std::unique_ptr root_render_object_; + + Control* mouse_hover_control_; + + Control* focus_control_; // "focus_control_" can't be nullptr + + Control* mouse_captured_control_; + + bool layout_prefer_to_fill_window_ = true; +}; +} // namespace cru::ui diff --git a/include/cru/ui/render/RenderObject.hpp b/include/cru/ui/render/RenderObject.hpp index 57251e3a..f052221e 100644 --- a/include/cru/ui/render/RenderObject.hpp +++ b/include/cru/ui/render/RenderObject.hpp @@ -64,7 +64,7 @@ class RenderObject : public Object { Control* GetAttachedControl() const { return control_; } void SetAttachedControl(Control* new_control) { control_ = new_control; } - UiHost* GetUiHost() const { return ui_host_; } + WindowHost* GetWindowHost() const { return ui_host_; } RenderObject* GetParent() const { return parent_; } @@ -198,11 +198,11 @@ class RenderObject : public Object { private: void SetParent(RenderObject* new_parent); - void SetRenderHostRecursive(UiHost* host); + void SetRenderHostRecursive(WindowHost* host); private: Control* control_ = nullptr; - UiHost* ui_host_ = nullptr; + WindowHost* ui_host_ = nullptr; RenderObject* parent_ = nullptr; std::vector children_{}; diff --git a/include/cru/ui/render/WindowRenderObject.hpp b/include/cru/ui/render/WindowRenderObject.hpp index d2ca5526..23fd8748 100644 --- a/include/cru/ui/render/WindowRenderObject.hpp +++ b/include/cru/ui/render/WindowRenderObject.hpp @@ -6,7 +6,7 @@ namespace cru::ui::render { class WindowRenderObject : public RenderObject { public: - WindowRenderObject(UiHost* host); + WindowRenderObject(WindowHost* host); WindowRenderObject(const WindowRenderObject& other) = delete; WindowRenderObject(WindowRenderObject&& other) = delete; WindowRenderObject& operator=(const WindowRenderObject& other) = delete; diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt index a83ab1d7..d59fd7da 100644 --- a/src/ui/CMakeLists.txt +++ b/src/ui/CMakeLists.txt @@ -12,9 +12,9 @@ add_library(cru_ui STATIC NoChildControl.cpp ShortcutHub.cpp UiEvent.cpp - UiHost.cpp UiManager.cpp Window.cpp + WindowHost.cpp controls/Button.cpp controls/Container.cpp controls/FlexLayout.cpp @@ -42,9 +42,9 @@ target_sources(cru_ui PUBLIC ${CRU_UI_INCLUDE_DIR}/NoChildControl.hpp ${CRU_UI_INCLUDE_DIR}/ShortcutHub.hpp ${CRU_UI_INCLUDE_DIR}/UiEvent.hpp - ${CRU_UI_INCLUDE_DIR}/UiHost.hpp ${CRU_UI_INCLUDE_DIR}/UiManager.hpp ${CRU_UI_INCLUDE_DIR}/Window.hpp + ${CRU_UI_INCLUDE_DIR}/WindowHost.hpp ${CRU_UI_INCLUDE_DIR}/controls/Base.hpp ${CRU_UI_INCLUDE_DIR}/controls/Button.hpp ${CRU_UI_INCLUDE_DIR}/controls/Container.hpp diff --git a/src/ui/ContentControl.cpp b/src/ui/ContentControl.cpp index 8d1a17d2..60d944d6 100644 --- a/src/ui/ContentControl.cpp +++ b/src/ui/ContentControl.cpp @@ -12,16 +12,16 @@ void ContentControl::SetChild(Control* child) { Expects(!dynamic_cast(child)); // Can't add a window as child. if (child == child_) return; - const auto host = GetUiHost(); + const auto host = GetWindowHost(); const auto old_child = child_; child_ = child; if (old_child) { old_child->_SetParent(nullptr); - old_child->_SetDescendantUiHost(nullptr); + old_child->_SetDescendantWindowHost(nullptr); } if (child) { child->_SetParent(this); - child->_SetDescendantUiHost(host); + child->_SetDescendantWindowHost(host); } OnChildChanged(old_child, child); } diff --git a/src/ui/Control.cpp b/src/ui/Control.cpp index cd1367fe..cd7ed0dc 100644 --- a/src/ui/Control.cpp +++ b/src/ui/Control.cpp @@ -3,7 +3,7 @@ #include "cru/platform/native/Cursor.hpp" #include "cru/platform/native/UiApplication.hpp" #include "cru/ui/Base.hpp" -#include "cru/ui/UiHost.hpp" +#include "cru/ui/WindowHost.hpp" #include "RoutedEventDispatch.hpp" #include @@ -32,7 +32,7 @@ void Control::_SetParent(Control* parent) { if (old_parent != new_parent) OnParentChanged(old_parent, new_parent); } -void Control::_SetDescendantUiHost(UiHost* host) { +void Control::_SetDescendantWindowHost(WindowHost* host) { if (host == nullptr && ui_host_ == nullptr) return; // You can only attach or detach window. @@ -64,35 +64,35 @@ void Control::_TraverseDescendants( } bool Control::RequestFocus() { - auto host = GetUiHost(); + auto host = GetWindowHost(); if (host == nullptr) return false; return host->RequestFocusFor(this); } bool Control::HasFocus() { - auto host = GetUiHost(); + auto host = GetWindowHost(); if (host == nullptr) return false; return host->GetFocusControl() == this; } bool Control::CaptureMouse() { - auto host = GetUiHost(); + auto host = GetWindowHost(); if (host == nullptr) return false; return host->CaptureMouseFor(this); } bool Control::ReleaseMouse() { - auto host = GetUiHost(); + auto host = GetWindowHost(); if (host == nullptr) return false; return host->CaptureMouseFor(nullptr); } bool Control::IsMouseCaptured() { - auto host = GetUiHost(); + auto host = GetWindowHost(); if (host == nullptr) return false; return host->GetMouseCaptureControl() == this; @@ -113,7 +113,7 @@ std::shared_ptr Control::GetInheritedCursor() { void Control::SetCursor(std::shared_ptr cursor) { cursor_ = std::move(cursor); - const auto host = GetUiHost(); + const auto host = GetWindowHost(); if (host != nullptr) { host->UpdateCursor(); } @@ -124,7 +124,7 @@ void Control::OnParentChanged(Control* old_parent, Control* new_parent) { CRU_UNUSED(new_parent) } -void Control::OnAttachToHost(UiHost* host) { CRU_UNUSED(host) } +void Control::OnAttachToHost(WindowHost* host) { CRU_UNUSED(host) } -void Control::OnDetachFromHost(UiHost* host) { CRU_UNUSED(host) } +void Control::OnDetachFromHost(WindowHost* host) { CRU_UNUSED(host) } } // namespace cru::ui diff --git a/src/ui/LayoutControl.cpp b/src/ui/LayoutControl.cpp index 4813566b..05fcdc4a 100644 --- a/src/ui/LayoutControl.cpp +++ b/src/ui/LayoutControl.cpp @@ -19,7 +19,7 @@ void LayoutControl::AddChild(Control* control, const Index position) { children_.insert(this->children_.cbegin() + position, control); control->_SetParent(this); - control->_SetDescendantUiHost(GetUiHost()); + control->_SetDescendantWindowHost(GetWindowHost()); OnAddChild(control, position); } @@ -36,7 +36,7 @@ void LayoutControl::RemoveChild(const Index position) { children_.erase(i); child->_SetParent(nullptr); - child->_SetDescendantUiHost(nullptr); + child->_SetDescendantWindowHost(nullptr); OnRemoveChild(child, position); } diff --git a/src/ui/UiHost.cpp b/src/ui/UiHost.cpp deleted file mode 100644 index a881c16d..00000000 --- a/src/ui/UiHost.cpp +++ /dev/null @@ -1,416 +0,0 @@ -#include "cru/ui/UiHost.hpp" - -#include "RoutedEventDispatch.hpp" -#include "cru/common/Logger.hpp" -#include "cru/platform/graph/Painter.hpp" -#include "cru/platform/native/InputMethod.hpp" -#include "cru/platform/native/UiApplication.hpp" -#include "cru/platform/native/Window.hpp" -#include "cru/ui/DebugFlags.hpp" -#include "cru/ui/Window.hpp" -#include "cru/ui/render/MeasureRequirement.hpp" -#include "cru/ui/render/WindowRenderObject.hpp" - -namespace cru::ui { -using platform::native::INativeWindow; -using platform::native::IUiApplication; - -namespace event_names { -#ifdef CRU_DEBUG -// clang-format off -#define CRU_DEFINE_EVENT_NAME(name) constexpr const char16_t* name = CRU_MAKE_UNICODE_LITERAL(name); -// clang-format on -#else -#define CRU_DEFINE_EVENT_NAME(name) constexpr const char16_t* name = u""; -#endif - -CRU_DEFINE_EVENT_NAME(LoseFocus) -CRU_DEFINE_EVENT_NAME(GainFocus) -CRU_DEFINE_EVENT_NAME(MouseEnter) -CRU_DEFINE_EVENT_NAME(MouseLeave) -CRU_DEFINE_EVENT_NAME(MouseMove) -CRU_DEFINE_EVENT_NAME(MouseDown) -CRU_DEFINE_EVENT_NAME(MouseUp) -CRU_DEFINE_EVENT_NAME(KeyDown) -CRU_DEFINE_EVENT_NAME(KeyUp) - -#undef CRU_DEFINE_EVENT_NAME -} // namespace event_names - -namespace { -bool IsAncestor(Control* control, Control* ancestor) { - while (control != nullptr) { - if (control == ancestor) return true; - control = control->GetParent(); - } - return false; -} - -// Ancestor at last. -std::vector GetAncestorList(Control* control) { - std::vector l; - while (control != nullptr) { - l.push_back(control); - control = control->GetParent(); - } - return l; -} - -Control* FindLowestCommonAncestor(Control* left, Control* right) { - if (left == nullptr || right == nullptr) return nullptr; - - auto&& left_list = GetAncestorList(left); - auto&& right_list = GetAncestorList(right); - - // the root is different - if (left_list.back() != right_list.back()) return nullptr; - - // find the last same control or the last control (one is ancestor of the - // other) - auto left_iter = left_list.crbegin(); - auto right_iter = right_list.crbegin(); - - while (true) { - if (left_iter == left_list.crend()) { - return left_list.front(); - } - if (right_iter == right_list.crend()) { - return right_list.front(); - } - if (*left_iter != *right_iter) { - return *(--left_iter); - } - ++left_iter; - ++right_iter; - } -} -} // namespace - -namespace { -template -inline void BindNativeEvent( - UiHost* host, INativeWindow* native_window, IEvent* event, - void (UiHost::*handler)(INativeWindow*, typename IEvent::EventArgs), - std::vector& guard_pool) { - guard_pool.push_back(EventRevokerGuard(event->AddHandler( - std::bind(handler, host, native_window, std::placeholders::_1)))); -} -} // namespace - -UiHost::UiHost(Window* window) - : window_control_(window), - mouse_hover_control_(nullptr), - focus_control_(window), - mouse_captured_control_(nullptr) { - const auto ui_application = IUiApplication::GetInstance(); - native_window_resolver_ = ui_application->CreateWindow(nullptr); - - const auto native_window = native_window_resolver_->Resolve(); - - auto input_method_context = - ui_application->GetInputMethodManager()->GetContext(native_window); - input_method_context->DisableIME(); - - window->ui_host_ = this; - - root_render_object_ = std::make_unique(this); - root_render_object_->SetAttachedControl(window); - window->render_object_ = root_render_object_.get(); - - BindNativeEvent(this, native_window, native_window->DestroyEvent(), - &UiHost::OnNativeDestroy, event_revoker_guards_); - BindNativeEvent(this, native_window, native_window->PaintEvent(), - &UiHost::OnNativePaint, event_revoker_guards_); - BindNativeEvent(this, native_window, native_window->ResizeEvent(), - &UiHost::OnNativeResize, event_revoker_guards_); - BindNativeEvent(this, native_window, native_window->FocusEvent(), - &UiHost::OnNativeFocus, event_revoker_guards_); - BindNativeEvent(this, native_window, native_window->MouseEnterLeaveEvent(), - &UiHost::OnNativeMouseEnterLeave, event_revoker_guards_); - BindNativeEvent(this, native_window, native_window->MouseMoveEvent(), - &UiHost::OnNativeMouseMove, event_revoker_guards_); - BindNativeEvent(this, native_window, native_window->MouseDownEvent(), - &UiHost::OnNativeMouseDown, event_revoker_guards_); - BindNativeEvent(this, native_window, native_window->MouseUpEvent(), - &UiHost::OnNativeMouseUp, event_revoker_guards_); - BindNativeEvent(this, native_window, native_window->KeyDownEvent(), - &UiHost::OnNativeKeyDown, event_revoker_guards_); - BindNativeEvent(this, native_window, native_window->KeyUpEvent(), - &UiHost::OnNativeKeyUp, event_revoker_guards_); -} - -UiHost::~UiHost() { - deleting_ = true; - window_control_->TraverseDescendants( - [this](Control* control) { control->OnDetachFromHost(this); }); - if (!native_window_destroyed_) { - const auto native_window = native_window_resolver_->Resolve(); - if (native_window) { - native_window->Close(); - } - } -} - -void UiHost::InvalidatePaint() { - if (const auto native_window = native_window_resolver_->Resolve()) - native_window->RequestRepaint(); -} - -void UiHost::InvalidateLayout() { - if constexpr (debug_flags::layout) - log::TagDebug(log_tag, u"A relayout is requested."); - if (!need_layout_) { - platform::native::IUiApplication::GetInstance()->SetImmediate( - [resolver = this->CreateResolver()] { - if (const auto host = resolver.Resolve()) { - host->Relayout(); - host->need_layout_ = false; - host->InvalidatePaint(); - } - }); - need_layout_ = true; - } -} - -bool UiHost::IsLayoutPreferToFillWindow() const { - return layout_prefer_to_fill_window_; -} - -void UiHost::SetLayoutPreferToFillWindow(bool value) { - if (value == layout_prefer_to_fill_window_) return; - layout_prefer_to_fill_window_ = value; - InvalidateLayout(); -} - -void UiHost::Relayout() { - const auto native_window = native_window_resolver_->Resolve(); - const auto client_size = native_window - ? native_window->GetClientSize() - : Size{100, 100}; // a reasonable assumed size - - root_render_object_->Measure( - render::MeasureRequirement{client_size, - IsLayoutPreferToFillWindow() - ? render::MeasureSize(client_size) - : render::MeasureSize::NotSpecified()}, - render::MeasureSize::NotSpecified()); - root_render_object_->Layout(Point{}); - for (auto& action : after_layout_stable_action_) action(); - after_layout_stable_action_.clear(); - after_layout_event_.Raise(AfterLayoutEventArgs{}); - if constexpr (debug_flags::layout) - log::TagDebug(log_tag, u"A relayout is finished."); -} - -bool UiHost::RequestFocusFor(Control* control) { - Expects(control != nullptr); // The control to request focus can't be null. - // You can set it as the window. - - if (focus_control_ == control) return true; - - const auto old_focus_control = focus_control_; - - focus_control_ = control; - - DispatchEvent(event_names::LoseFocus, old_focus_control, - &Control::LoseFocusEvent, nullptr, false); - - DispatchEvent(event_names::GainFocus, control, &Control::GainFocusEvent, - nullptr, false); - - return true; -} - -Control* UiHost::GetFocusControl() { return focus_control_; } - -bool UiHost::CaptureMouseFor(Control* control) { - const auto native_window = native_window_resolver_->Resolve(); - if (!native_window) return false; - - if (control == mouse_captured_control_) return true; - - if (control == nullptr) { - const auto old_capture_control = mouse_captured_control_; - mouse_captured_control_ = - nullptr; // update this in case this is used in event handlers - if (old_capture_control != mouse_hover_control_) { - DispatchMouseHoverControlChangeEvent( - old_capture_control, mouse_hover_control_, - native_window->GetMousePosition(), true, false); - } - UpdateCursor(); - return true; - } - - if (mouse_captured_control_) return false; - - mouse_captured_control_ = control; - DispatchMouseHoverControlChangeEvent( - mouse_hover_control_, mouse_captured_control_, - native_window->GetMousePosition(), false, true); - UpdateCursor(); - return true; -} - -Control* UiHost::GetMouseCaptureControl() { return mouse_captured_control_; } - -void UiHost::RunAfterLayoutStable(std::function action) { - if (need_layout_) { - after_layout_stable_action_.push_back(std::move(action)); - } else { - action(); - } -} - -void UiHost::OnNativeDestroy(INativeWindow* window, std::nullptr_t) { - CRU_UNUSED(window) - native_window_destroyed_ = true; - if (!deleting_ && !retain_after_destroy_) delete window_control_; -} - -void UiHost::OnNativePaint(INativeWindow* window, std::nullptr_t) { - auto painter = window->BeginPaint(); - painter->Clear(colors::white); - root_render_object_->Draw(painter.get()); - painter->EndDraw(); -} - -void UiHost::OnNativeResize(INativeWindow* window, const Size& size) { - CRU_UNUSED(window) - CRU_UNUSED(size) - - InvalidateLayout(); -} - -void UiHost::OnNativeFocus(INativeWindow* window, - platform::native::FocusChangeType focus) { - CRU_UNUSED(window) - - focus == platform::native::FocusChangeType::Gain - ? DispatchEvent(event_names::GainFocus, focus_control_, - &Control::GainFocusEvent, nullptr, true) - : DispatchEvent(event_names::LoseFocus, focus_control_, - &Control::LoseFocusEvent, nullptr, true); -} - -void UiHost::OnNativeMouseEnterLeave( - INativeWindow* window, platform::native::MouseEnterLeaveType type) { - CRU_UNUSED(window) - - if (type == platform::native::MouseEnterLeaveType::Leave) { - DispatchEvent(event_names::MouseLeave, mouse_hover_control_, - &Control::MouseLeaveEvent, nullptr); - mouse_hover_control_ = nullptr; - } -} - -void UiHost::OnNativeMouseMove(INativeWindow* window, const Point& point) { - CRU_UNUSED(window) - - // Find the first control that hit test succeed. - const auto new_mouse_hover_control = HitTest(point); - const auto old_mouse_hover_control = mouse_hover_control_; - mouse_hover_control_ = new_mouse_hover_control; - - if (mouse_captured_control_) { - const auto n = FindLowestCommonAncestor(new_mouse_hover_control, - mouse_captured_control_); - const auto o = FindLowestCommonAncestor(old_mouse_hover_control, - mouse_captured_control_); - bool a = IsAncestor(o, n); - if (a) { - DispatchEvent(event_names::MouseLeave, o, &Control::MouseLeaveEvent, n); - } else { - DispatchEvent(event_names::MouseEnter, n, &Control::MouseEnterEvent, o, - point); - } - DispatchEvent(event_names::MouseMove, mouse_captured_control_, - &Control::MouseMoveEvent, nullptr, point); - UpdateCursor(); - return; - } - - DispatchMouseHoverControlChangeEvent( - old_mouse_hover_control, new_mouse_hover_control, point, false, false); - DispatchEvent(event_names::MouseMove, new_mouse_hover_control, - &Control::MouseMoveEvent, nullptr, point); - UpdateCursor(); -} - -void UiHost::OnNativeMouseDown( - INativeWindow* window, - const platform::native::NativeMouseButtonEventArgs& args) { - CRU_UNUSED(window) - - Control* control = - mouse_captured_control_ ? mouse_captured_control_ : HitTest(args.point); - DispatchEvent(event_names::MouseDown, control, &Control::MouseDownEvent, - nullptr, args.point, args.button, args.modifier); -} - -void UiHost::OnNativeMouseUp( - INativeWindow* window, - const platform::native::NativeMouseButtonEventArgs& args) { - CRU_UNUSED(window) - - Control* control = - mouse_captured_control_ ? mouse_captured_control_ : HitTest(args.point); - DispatchEvent(event_names::MouseUp, control, &Control::MouseUpEvent, nullptr, - args.point, args.button, args.modifier); -} - -void UiHost::OnNativeKeyDown(INativeWindow* window, - const platform::native::NativeKeyEventArgs& args) { - CRU_UNUSED(window) - - DispatchEvent(event_names::KeyDown, focus_control_, &Control::KeyDownEvent, - nullptr, args.key, args.modifier); -} - -void UiHost::OnNativeKeyUp(INativeWindow* window, - const platform::native::NativeKeyEventArgs& args) { - CRU_UNUSED(window) - - DispatchEvent(event_names::KeyUp, focus_control_, &Control::KeyUpEvent, - nullptr, args.key, args.modifier); -} - -void UiHost::DispatchMouseHoverControlChangeEvent(Control* old_control, - Control* new_control, - const Point& point, - bool no_leave, - bool no_enter) { - if (new_control != old_control) // if the mouse-hover-on control changed - { - const auto lowest_common_ancestor = - FindLowestCommonAncestor(old_control, new_control); - if (!no_leave && old_control != nullptr) - DispatchEvent(event_names::MouseLeave, old_control, - &Control::MouseLeaveEvent, - lowest_common_ancestor); // dispatch mouse leave event. - if (!no_enter && new_control != nullptr) { - DispatchEvent(event_names::MouseEnter, new_control, - &Control::MouseEnterEvent, lowest_common_ancestor, - point); // dispatch mouse enter event. - } - } -} - -void UiHost::UpdateCursor() { - if (const auto native_window = native_window_resolver_->Resolve()) { - const auto capture = GetMouseCaptureControl(); - native_window->SetCursor( - (capture ? capture : GetMouseHoverControl())->GetInheritedCursor()); - } -} - -Control* UiHost::HitTest(const Point& point) { - const auto render_object = root_render_object_->HitTest(point); - if (render_object) { - const auto control = render_object->GetAttachedControl(); - Ensures(control); - return control; - } - return window_control_; -} -} // namespace cru::ui diff --git a/src/ui/Window.cpp b/src/ui/Window.cpp index dca95ebb..a8cb315b 100644 --- a/src/ui/Window.cpp +++ b/src/ui/Window.cpp @@ -1,7 +1,7 @@ #include "cru/ui/Window.hpp" #include "cru/ui/render/WindowRenderObject.hpp" -#include "cru/ui/UiHost.hpp" +#include "cru/ui/WindowHost.hpp" namespace cru::ui { Window* Window::CreateOverlapped() { @@ -9,7 +9,7 @@ Window* Window::CreateOverlapped() { } Window::Window(tag_overlapped_constructor) { - managed_ui_host_ = std::make_unique(this); + managed_ui_host_ = std::make_unique(this); } Window::~Window() { diff --git a/src/ui/WindowHost.cpp b/src/ui/WindowHost.cpp new file mode 100644 index 00000000..d3dec422 --- /dev/null +++ b/src/ui/WindowHost.cpp @@ -0,0 +1,418 @@ +#include "cru/ui/WindowHost.hpp" + +#include "RoutedEventDispatch.hpp" +#include "cru/common/Logger.hpp" +#include "cru/platform/graph/Painter.hpp" +#include "cru/platform/native/InputMethod.hpp" +#include "cru/platform/native/UiApplication.hpp" +#include "cru/platform/native/Window.hpp" +#include "cru/ui/DebugFlags.hpp" +#include "cru/ui/Window.hpp" +#include "cru/ui/render/MeasureRequirement.hpp" +#include "cru/ui/render/WindowRenderObject.hpp" + +namespace cru::ui { +using platform::native::INativeWindow; +using platform::native::IUiApplication; + +namespace event_names { +#ifdef CRU_DEBUG +// clang-format off +#define CRU_DEFINE_EVENT_NAME(name) constexpr const char16_t* name = CRU_MAKE_UNICODE_LITERAL(name); +// clang-format on +#else +#define CRU_DEFINE_EVENT_NAME(name) constexpr const char16_t* name = u""; +#endif + +CRU_DEFINE_EVENT_NAME(LoseFocus) +CRU_DEFINE_EVENT_NAME(GainFocus) +CRU_DEFINE_EVENT_NAME(MouseEnter) +CRU_DEFINE_EVENT_NAME(MouseLeave) +CRU_DEFINE_EVENT_NAME(MouseMove) +CRU_DEFINE_EVENT_NAME(MouseDown) +CRU_DEFINE_EVENT_NAME(MouseUp) +CRU_DEFINE_EVENT_NAME(KeyDown) +CRU_DEFINE_EVENT_NAME(KeyUp) + +#undef CRU_DEFINE_EVENT_NAME +} // namespace event_names + +namespace { +bool IsAncestor(Control* control, Control* ancestor) { + while (control != nullptr) { + if (control == ancestor) return true; + control = control->GetParent(); + } + return false; +} + +// Ancestor at last. +std::vector GetAncestorList(Control* control) { + std::vector l; + while (control != nullptr) { + l.push_back(control); + control = control->GetParent(); + } + return l; +} + +Control* FindLowestCommonAncestor(Control* left, Control* right) { + if (left == nullptr || right == nullptr) return nullptr; + + auto&& left_list = GetAncestorList(left); + auto&& right_list = GetAncestorList(right); + + // the root is different + if (left_list.back() != right_list.back()) return nullptr; + + // find the last same control or the last control (one is ancestor of the + // other) + auto left_iter = left_list.crbegin(); + auto right_iter = right_list.crbegin(); + + while (true) { + if (left_iter == left_list.crend()) { + return left_list.front(); + } + if (right_iter == right_list.crend()) { + return right_list.front(); + } + if (*left_iter != *right_iter) { + return *(--left_iter); + } + ++left_iter; + ++right_iter; + } +} +} // namespace + +namespace { +template +inline void BindNativeEvent( + WindowHost* host, INativeWindow* native_window, IEvent* event, + void (WindowHost::*handler)(INativeWindow*, typename IEvent::EventArgs), + std::vector& guard_pool) { + guard_pool.push_back(EventRevokerGuard(event->AddHandler( + std::bind(handler, host, native_window, std::placeholders::_1)))); +} +} // namespace + +WindowHost::WindowHost(Window* window) + : window_control_(window), + mouse_hover_control_(nullptr), + focus_control_(window), + mouse_captured_control_(nullptr) { + const auto ui_application = IUiApplication::GetInstance(); + native_window_resolver_ = ui_application->CreateWindow(nullptr); + + const auto native_window = native_window_resolver_->Resolve(); + + auto input_method_context = + ui_application->GetInputMethodManager()->GetContext(native_window); + input_method_context->DisableIME(); + + window->ui_host_ = this; + + root_render_object_ = std::make_unique(this); + root_render_object_->SetAttachedControl(window); + window->render_object_ = root_render_object_.get(); + + BindNativeEvent(this, native_window, native_window->DestroyEvent(), + &WindowHost::OnNativeDestroy, event_revoker_guards_); + BindNativeEvent(this, native_window, native_window->PaintEvent(), + &WindowHost::OnNativePaint, event_revoker_guards_); + BindNativeEvent(this, native_window, native_window->ResizeEvent(), + &WindowHost::OnNativeResize, event_revoker_guards_); + BindNativeEvent(this, native_window, native_window->FocusEvent(), + &WindowHost::OnNativeFocus, event_revoker_guards_); + BindNativeEvent(this, native_window, native_window->MouseEnterLeaveEvent(), + &WindowHost::OnNativeMouseEnterLeave, event_revoker_guards_); + BindNativeEvent(this, native_window, native_window->MouseMoveEvent(), + &WindowHost::OnNativeMouseMove, event_revoker_guards_); + BindNativeEvent(this, native_window, native_window->MouseDownEvent(), + &WindowHost::OnNativeMouseDown, event_revoker_guards_); + BindNativeEvent(this, native_window, native_window->MouseUpEvent(), + &WindowHost::OnNativeMouseUp, event_revoker_guards_); + BindNativeEvent(this, native_window, native_window->KeyDownEvent(), + &WindowHost::OnNativeKeyDown, event_revoker_guards_); + BindNativeEvent(this, native_window, native_window->KeyUpEvent(), + &WindowHost::OnNativeKeyUp, event_revoker_guards_); +} + +WindowHost::~WindowHost() { + deleting_ = true; + window_control_->TraverseDescendants( + [this](Control* control) { control->OnDetachFromHost(this); }); + if (!native_window_destroyed_) { + const auto native_window = native_window_resolver_->Resolve(); + if (native_window) { + native_window->Close(); + } + } +} + +void WindowHost::InvalidatePaint() { + if (const auto native_window = native_window_resolver_->Resolve()) + native_window->RequestRepaint(); +} + +void WindowHost::InvalidateLayout() { + if constexpr (debug_flags::layout) + log::TagDebug(log_tag, u"A relayout is requested."); + if (!need_layout_) { + platform::native::IUiApplication::GetInstance()->SetImmediate( + [resolver = this->CreateResolver()] { + if (const auto host = resolver.Resolve()) { + host->Relayout(); + host->need_layout_ = false; + host->InvalidatePaint(); + } + }); + need_layout_ = true; + } +} + +bool WindowHost::IsLayoutPreferToFillWindow() const { + return layout_prefer_to_fill_window_; +} + +void WindowHost::SetLayoutPreferToFillWindow(bool value) { + if (value == layout_prefer_to_fill_window_) return; + layout_prefer_to_fill_window_ = value; + InvalidateLayout(); +} + +void WindowHost::Relayout() { + const auto native_window = native_window_resolver_->Resolve(); + const auto client_size = native_window + ? native_window->GetClientSize() + : Size{100, 100}; // a reasonable assumed size + + root_render_object_->Measure( + render::MeasureRequirement{client_size, + IsLayoutPreferToFillWindow() + ? render::MeasureSize(client_size) + : render::MeasureSize::NotSpecified()}, + render::MeasureSize::NotSpecified()); + root_render_object_->Layout(Point{}); + for (auto& action : after_layout_stable_action_) action(); + after_layout_stable_action_.clear(); + after_layout_event_.Raise(AfterLayoutEventArgs{}); + if constexpr (debug_flags::layout) + log::TagDebug(log_tag, u"A relayout is finished."); +} + +bool WindowHost::RequestFocusFor(Control* control) { + Expects(control != nullptr); // The control to request focus can't be null. + // You can set it as the window. + + if (focus_control_ == control) return true; + + const auto old_focus_control = focus_control_; + + focus_control_ = control; + + DispatchEvent(event_names::LoseFocus, old_focus_control, + &Control::LoseFocusEvent, nullptr, false); + + DispatchEvent(event_names::GainFocus, control, &Control::GainFocusEvent, + nullptr, false); + + return true; +} + +Control* WindowHost::GetFocusControl() { return focus_control_; } + +bool WindowHost::CaptureMouseFor(Control* control) { + const auto native_window = native_window_resolver_->Resolve(); + if (!native_window) return false; + + if (control == mouse_captured_control_) return true; + + if (control == nullptr) { + const auto old_capture_control = mouse_captured_control_; + mouse_captured_control_ = + nullptr; // update this in case this is used in event handlers + if (old_capture_control != mouse_hover_control_) { + DispatchMouseHoverControlChangeEvent( + old_capture_control, mouse_hover_control_, + native_window->GetMousePosition(), true, false); + } + UpdateCursor(); + return true; + } + + if (mouse_captured_control_) return false; + + mouse_captured_control_ = control; + DispatchMouseHoverControlChangeEvent( + mouse_hover_control_, mouse_captured_control_, + native_window->GetMousePosition(), false, true); + UpdateCursor(); + return true; +} + +Control* WindowHost::GetMouseCaptureControl() { + return mouse_captured_control_; +} + +void WindowHost::RunAfterLayoutStable(std::function action) { + if (need_layout_) { + after_layout_stable_action_.push_back(std::move(action)); + } else { + action(); + } +} + +void WindowHost::OnNativeDestroy(INativeWindow* window, std::nullptr_t) { + CRU_UNUSED(window) + native_window_destroyed_ = true; + if (!deleting_ && !retain_after_destroy_) delete window_control_; +} + +void WindowHost::OnNativePaint(INativeWindow* window, std::nullptr_t) { + auto painter = window->BeginPaint(); + painter->Clear(colors::white); + root_render_object_->Draw(painter.get()); + painter->EndDraw(); +} + +void WindowHost::OnNativeResize(INativeWindow* window, const Size& size) { + CRU_UNUSED(window) + CRU_UNUSED(size) + + InvalidateLayout(); +} + +void WindowHost::OnNativeFocus(INativeWindow* window, + platform::native::FocusChangeType focus) { + CRU_UNUSED(window) + + focus == platform::native::FocusChangeType::Gain + ? DispatchEvent(event_names::GainFocus, focus_control_, + &Control::GainFocusEvent, nullptr, true) + : DispatchEvent(event_names::LoseFocus, focus_control_, + &Control::LoseFocusEvent, nullptr, true); +} + +void WindowHost::OnNativeMouseEnterLeave( + INativeWindow* window, platform::native::MouseEnterLeaveType type) { + CRU_UNUSED(window) + + if (type == platform::native::MouseEnterLeaveType::Leave) { + DispatchEvent(event_names::MouseLeave, mouse_hover_control_, + &Control::MouseLeaveEvent, nullptr); + mouse_hover_control_ = nullptr; + } +} + +void WindowHost::OnNativeMouseMove(INativeWindow* window, const Point& point) { + CRU_UNUSED(window) + + // Find the first control that hit test succeed. + const auto new_mouse_hover_control = HitTest(point); + const auto old_mouse_hover_control = mouse_hover_control_; + mouse_hover_control_ = new_mouse_hover_control; + + if (mouse_captured_control_) { + const auto n = FindLowestCommonAncestor(new_mouse_hover_control, + mouse_captured_control_); + const auto o = FindLowestCommonAncestor(old_mouse_hover_control, + mouse_captured_control_); + bool a = IsAncestor(o, n); + if (a) { + DispatchEvent(event_names::MouseLeave, o, &Control::MouseLeaveEvent, n); + } else { + DispatchEvent(event_names::MouseEnter, n, &Control::MouseEnterEvent, o, + point); + } + DispatchEvent(event_names::MouseMove, mouse_captured_control_, + &Control::MouseMoveEvent, nullptr, point); + UpdateCursor(); + return; + } + + DispatchMouseHoverControlChangeEvent( + old_mouse_hover_control, new_mouse_hover_control, point, false, false); + DispatchEvent(event_names::MouseMove, new_mouse_hover_control, + &Control::MouseMoveEvent, nullptr, point); + UpdateCursor(); +} + +void WindowHost::OnNativeMouseDown( + INativeWindow* window, + const platform::native::NativeMouseButtonEventArgs& args) { + CRU_UNUSED(window) + + Control* control = + mouse_captured_control_ ? mouse_captured_control_ : HitTest(args.point); + DispatchEvent(event_names::MouseDown, control, &Control::MouseDownEvent, + nullptr, args.point, args.button, args.modifier); +} + +void WindowHost::OnNativeMouseUp( + INativeWindow* window, + const platform::native::NativeMouseButtonEventArgs& args) { + CRU_UNUSED(window) + + Control* control = + mouse_captured_control_ ? mouse_captured_control_ : HitTest(args.point); + DispatchEvent(event_names::MouseUp, control, &Control::MouseUpEvent, nullptr, + args.point, args.button, args.modifier); +} + +void WindowHost::OnNativeKeyDown( + INativeWindow* window, const platform::native::NativeKeyEventArgs& args) { + CRU_UNUSED(window) + + DispatchEvent(event_names::KeyDown, focus_control_, &Control::KeyDownEvent, + nullptr, args.key, args.modifier); +} + +void WindowHost::OnNativeKeyUp( + INativeWindow* window, const platform::native::NativeKeyEventArgs& args) { + CRU_UNUSED(window) + + DispatchEvent(event_names::KeyUp, focus_control_, &Control::KeyUpEvent, + nullptr, args.key, args.modifier); +} + +void WindowHost::DispatchMouseHoverControlChangeEvent(Control* old_control, + Control* new_control, + const Point& point, + bool no_leave, + bool no_enter) { + if (new_control != old_control) // if the mouse-hover-on control changed + { + const auto lowest_common_ancestor = + FindLowestCommonAncestor(old_control, new_control); + if (!no_leave && old_control != nullptr) + DispatchEvent(event_names::MouseLeave, old_control, + &Control::MouseLeaveEvent, + lowest_common_ancestor); // dispatch mouse leave event. + if (!no_enter && new_control != nullptr) { + DispatchEvent(event_names::MouseEnter, new_control, + &Control::MouseEnterEvent, lowest_common_ancestor, + point); // dispatch mouse enter event. + } + } +} + +void WindowHost::UpdateCursor() { + if (const auto native_window = native_window_resolver_->Resolve()) { + const auto capture = GetMouseCaptureControl(); + native_window->SetCursor( + (capture ? capture : GetMouseHoverControl())->GetInheritedCursor()); + } +} + +Control* WindowHost::HitTest(const Point& point) { + const auto render_object = root_render_object_->HitTest(point); + if (render_object) { + const auto control = render_object->GetAttachedControl(); + Ensures(control); + return control; + } + return window_control_; +} +} // namespace cru::ui diff --git a/src/ui/controls/TextControlService.hpp b/src/ui/controls/TextControlService.hpp index 8419f35c..8c1bd32f 100644 --- a/src/ui/controls/TextControlService.hpp +++ b/src/ui/controls/TextControlService.hpp @@ -12,7 +12,7 @@ #include "cru/ui/DebugFlags.hpp" #include "cru/ui/ShortcutHub.hpp" #include "cru/ui/UiEvent.hpp" -#include "cru/ui/UiHost.hpp" +#include "cru/ui/WindowHost.hpp" #include "cru/ui/render/CanvasRenderObject.hpp" #include "cru/ui/render/ScrollRenderObject.hpp" #include "cru/ui/render/TextRenderObject.hpp" @@ -208,7 +208,7 @@ class TextControlService : public Object { } void ScrollToCaret() { - this->control_->GetUiHost()->RunAfterLayoutStable([this]() { + this->control_->GetWindowHost()->RunAfterLayoutStable([this]() { const auto caret_rect = this->GetTextRenderObject()->GetCaretRect(); this->GetScrollRenderObject()->ScrollToContain(caret_rect, Thickness{5.f}); @@ -408,7 +408,7 @@ class TextControlService : public Object { void GainFocusHandler(event::FocusChangeEventArgs& args) { CRU_UNUSED(args); if (editable_) { - UiHost* ui_host = this->control_->GetUiHost(); + WindowHost* ui_host = this->control_->GetWindowHost(); auto window = ui_host->GetNativeWindowResolver()->Resolve(); if (window == nullptr) return; input_method_context_ = diff --git a/src/ui/render/RenderObject.cpp b/src/ui/render/RenderObject.cpp index c85f8080..5266daaf 100644 --- a/src/ui/render/RenderObject.cpp +++ b/src/ui/render/RenderObject.cpp @@ -3,7 +3,7 @@ #include "cru/common/Logger.hpp" #include "cru/platform/graph/util/Painter.hpp" #include "cru/ui/DebugFlags.hpp" -#include "cru/ui/UiHost.hpp" +#include "cru/ui/WindowHost.hpp" #include #include @@ -24,7 +24,7 @@ void RenderObject::AddChild(RenderObject* render_object, const Index position) { children_.insert(children_.cbegin() + position, render_object); render_object->SetParent(this); - render_object->SetRenderHostRecursive(GetUiHost()); + render_object->SetRenderHostRecursive(GetWindowHost()); OnAddChild(render_object, position); } @@ -304,7 +304,7 @@ void RenderObject::NotifyAfterLayoutRecursive(RenderObject* render_object) { } } -void RenderObject::SetRenderHostRecursive(UiHost* host) { +void RenderObject::SetRenderHostRecursive(WindowHost* host) { ui_host_ = host; for (const auto child : GetChildren()) { child->SetRenderHostRecursive(host); diff --git a/src/ui/render/WindowRenderObject.cpp b/src/ui/render/WindowRenderObject.cpp index a136c1e9..23cca12e 100644 --- a/src/ui/render/WindowRenderObject.cpp +++ b/src/ui/render/WindowRenderObject.cpp @@ -2,10 +2,10 @@ #include "../Helper.hpp" #include "cru/platform/graph/util/Painter.hpp" -#include "cru/ui/UiHost.hpp" +#include "cru/ui/WindowHost.hpp" namespace cru::ui::render { -WindowRenderObject::WindowRenderObject(UiHost* host) { +WindowRenderObject::WindowRenderObject(WindowHost* host) { SetChildMode(ChildMode::Single); ui_host_ = host; after_layout_event_guard_.Reset(host->AfterLayoutEvent()->AddHandler( -- cgit v1.2.3 From 52594324b302f6e9da10ac01fe803196371bb2d9 Mon Sep 17 00:00:00 2001 From: crupest Date: Thu, 29 Oct 2020 00:01:26 +0800 Subject: ... --- demos/input_method/main.cpp | 7 +- demos/main/main.cpp | 6 +- include/cru/ui/ContentControl.hpp | 13 ++-- include/cru/ui/Control.hpp | 29 ++++---- include/cru/ui/LayoutControl.hpp | 18 +---- include/cru/ui/NoChildControl.hpp | 10 +-- include/cru/ui/Window.hpp | 18 ++--- include/cru/ui/WindowHost.hpp | 72 +++++++----------- include/cru/ui/render/Base.hpp | 1 - include/cru/ui/render/RenderObject.hpp | 20 +++-- include/cru/ui/render/WindowRenderObject.hpp | 34 --------- src/ui/CMakeLists.txt | 2 - src/ui/ContentControl.cpp | 24 +++--- src/ui/Control.cpp | 106 +++++++++++++++++---------- src/ui/LayoutControl.cpp | 47 ------------ src/ui/NoChildControl.cpp | 4 +- src/ui/Window.cpp | 31 ++++---- src/ui/WindowHost.cpp | 98 ++++++++++--------------- src/ui/controls/TextControlService.hpp | 77 +++++++++++-------- src/ui/render/RenderObject.cpp | 25 +++---- src/ui/render/WindowRenderObject.cpp | 44 ----------- src/win/native/UiApplication.cpp | 5 -- src/win/native/Window.cpp | 1 + 23 files changed, 271 insertions(+), 421 deletions(-) delete mode 100644 include/cru/ui/render/WindowRenderObject.hpp delete mode 100644 src/ui/render/WindowRenderObject.cpp (limited to 'include/cru/ui/render/WindowRenderObject.hpp') diff --git a/demos/input_method/main.cpp b/demos/input_method/main.cpp index bff0e1d3..06098253 100644 --- a/demos/input_method/main.cpp +++ b/demos/input_method/main.cpp @@ -14,12 +14,9 @@ int main() { auto graph_factory = application->GetGraphFactory(); - auto window_resolver = application->CreateWindow(nullptr); + auto window = application->CreateWindow(nullptr); - auto window = window_resolver->Resolve(); - - auto input_method_context = - application->GetInputMethodManager()->GetContext(window); + auto input_method_context = window->GetInputMethodContext(); auto brush = graph_factory->CreateSolidColorBrush(); brush->SetColor(colors::black); diff --git a/demos/main/main.cpp b/demos/main/main.cpp index f7635231..e973682e 100644 --- a/demos/main/main.cpp +++ b/demos/main/main.cpp @@ -2,8 +2,8 @@ #include "cru/platform/native/UiApplication.hpp" #include "cru/platform/native/Window.hpp" #include "cru/ui/Base.hpp" -#include "cru/ui/WindowHost.hpp" #include "cru/ui/Window.hpp" +#include "cru/ui/WindowHost.hpp" #include "cru/ui/controls/Button.hpp" #include "cru/ui/controls/FlexLayout.hpp" #include "cru/ui/controls/TextBlock.hpp" @@ -30,7 +30,7 @@ int main() { flex_layout->SetContentMainAlign(cru::ui::FlexCrossAlignment::Center); flex_layout->SetItemCrossAlign(cru::ui::FlexCrossAlignment::Center); - window->SetChild(flex_layout); + window->AddChild(flex_layout, 0); const auto text_block = TextBlock::Create(); text_block->SetText(u"Hello World from CruUI!"); @@ -45,7 +45,7 @@ int main() { const auto text_box = TextBox::Create(); flex_layout->AddChild(text_box, 2); - window->GetWindowHost()->GetNativeWindowResolver()->Resolve()->SetVisible(true); + window->GetWindowHost()->GetNativeWindow()->SetVisible(true); return application->Run(); } diff --git a/include/cru/ui/ContentControl.hpp b/include/cru/ui/ContentControl.hpp index 19f13a1d..ba5b6b2f 100644 --- a/include/cru/ui/ContentControl.hpp +++ b/include/cru/ui/ContentControl.hpp @@ -4,26 +4,23 @@ namespace cru::ui { class ContentControl : public Control { protected: - ContentControl(); + ContentControl() = default; public: ContentControl(const ContentControl& other) = delete; ContentControl(ContentControl&& other) = delete; ContentControl& operator=(const ContentControl& other) = delete; ContentControl& operator=(ContentControl&& other) = delete; - ~ContentControl() override; + ~ContentControl() override = default; - const std::vector& GetChildren() const override final { - return child_vector_; - } - Control* GetChild() const { return child_; } + Control* GetChild() const; void SetChild(Control* child); protected: virtual void OnChildChanged(Control* old_child, Control* new_child); private: - std::vector child_vector_; - Control*& child_; + using Control::AddChild; + using Control::RemoveChild; }; } // namespace cru::ui diff --git a/include/cru/ui/Control.hpp b/include/cru/ui/Control.hpp index 692dcc9b..0021ad62 100644 --- a/include/cru/ui/Control.hpp +++ b/include/cru/ui/Control.hpp @@ -1,9 +1,9 @@ #pragma once #include "Base.hpp" +#include "UiEvent.hpp" #include "cru/common/Event.hpp" #include "render/Base.hpp" -#include "UiEvent.hpp" #include @@ -19,39 +19,31 @@ class Control : public Object { Control(Control&& other) = delete; Control& operator=(const Control& other) = delete; Control& operator=(Control&& other) = delete; - ~Control() override = default; + ~Control() override; public: virtual std::u16string_view GetControlType() const = 0; //*************** region: tree *************** public: - // Get the ui host if attached, otherwise, return nullptr. - WindowHost* GetWindowHost() const { return ui_host_; } + WindowHost* GetWindowHost() const; Control* GetParent() const { return parent_; } - virtual const std::vector& GetChildren() const = 0; + const std::vector& GetChildren() const { return children_; } // Traverse the tree rooted the control including itself. void TraverseDescendants(const std::function& predicate); - void _SetParent(Control* parent); - void _SetDescendantWindowHost(WindowHost* host); - - private: - static void _TraverseDescendants( - Control* control, const std::function& predicate); - public: virtual render::RenderObject* GetRenderObject() const = 0; //*************** region: focus *************** public: - bool RequestFocus(); - bool HasFocus(); + void SetFocus(); + //*************** region: mouse *************** public: bool IsMouseOver() const { return is_mouse_over_; } @@ -134,15 +126,22 @@ class Control : public Object { //*************** region: tree *************** protected: + void AddChild(Control* control, Index position); + void RemoveChild(Index position); + virtual void OnAddChild(Control* child, Index position); + virtual void OnRemoveChild(Control* child, Index position); virtual void OnParentChanged(Control* old_parent, Control* new_parent); virtual void OnAttachToHost(WindowHost* host); virtual void OnDetachFromHost(WindowHost* host); + protected: virtual void OnMouseHoverChange(bool newHover) { CRU_UNUSED(newHover) } private: - WindowHost* ui_host_ = nullptr; Control* parent_ = nullptr; + std::vector children_; + + WindowHost* window_host_ = nullptr; private: bool is_mouse_over_ = false; diff --git a/include/cru/ui/LayoutControl.hpp b/include/cru/ui/LayoutControl.hpp index 7997b37e..69d5cd0b 100644 --- a/include/cru/ui/LayoutControl.hpp +++ b/include/cru/ui/LayoutControl.hpp @@ -11,21 +11,9 @@ class LayoutControl : public Control { LayoutControl(LayoutControl&& other) = delete; LayoutControl& operator=(const LayoutControl& other) = delete; LayoutControl& operator=(LayoutControl&& other) = delete; - ~LayoutControl() override; + ~LayoutControl() override = default; - const std::vector& GetChildren() const override final { - return children_; - } - - void AddChild(Control* control, Index position); - - void RemoveChild(Index position); - - protected: - virtual void OnAddChild(Control* child, Index position); - virtual void OnRemoveChild(Control* child, Index position); - - private: - std::vector children_; + using Control::AddChild; + using Control::RemoveChild; }; } // namespace cru::ui diff --git a/include/cru/ui/NoChildControl.hpp b/include/cru/ui/NoChildControl.hpp index 1a31ae7e..0d8a8e34 100644 --- a/include/cru/ui/NoChildControl.hpp +++ b/include/cru/ui/NoChildControl.hpp @@ -3,9 +3,6 @@ namespace cru::ui { class NoChildControl : public Control { - private: - static const std::vector empty_control_vector; - protected: NoChildControl() = default; @@ -16,9 +13,8 @@ class NoChildControl : public Control { NoChildControl& operator=(NoChildControl&& other) = delete; ~NoChildControl() override = default; - protected: - const std::vector& GetChildren() const override final { - return empty_control_vector; - } + private: + using Control::AddChild; + using Control::RemoveChild; }; } // namespace cru::ui diff --git a/include/cru/ui/Window.hpp b/include/cru/ui/Window.hpp index 5ea24855..0739e3dc 100644 --- a/include/cru/ui/Window.hpp +++ b/include/cru/ui/Window.hpp @@ -1,10 +1,8 @@ #pragma once -#include "ContentControl.hpp" +#include "LayoutControl.hpp" namespace cru::ui { -class Window final : public ContentControl { - friend WindowHost; - +class Window final : public LayoutControl { public: static constexpr std::u16string_view control_type = u"Window"; @@ -12,9 +10,7 @@ class Window final : public ContentControl { static Window* CreateOverlapped(); private: - struct tag_overlapped_constructor {}; - - explicit Window(tag_overlapped_constructor); + Window(); public: Window(const Window& other) = delete; @@ -29,12 +25,12 @@ class Window final : public ContentControl { render::RenderObject* GetRenderObject() const override; protected: - void OnChildChanged(Control* old_child, Control* new_child) override; + void OnAddChild(Control* child, Index position) override; + void OnRemoveChild(Control* child, Index position) override; private: - std::unique_ptr managed_ui_host_; + std::unique_ptr window_host_; - // WindowHost is responsible to take care of lifetime of this. - render::WindowRenderObject* render_object_; + std::unique_ptr render_object_; }; } // namespace cru::ui diff --git a/include/cru/ui/WindowHost.hpp b/include/cru/ui/WindowHost.hpp index 83ef2f64..8efb505d 100644 --- a/include/cru/ui/WindowHost.hpp +++ b/include/cru/ui/WindowHost.hpp @@ -1,20 +1,22 @@ #pragma once -#include #include "Base.hpp" #include "cru/common/Event.hpp" -#include "cru/common/SelfResolvable.hpp" +#include "cru/platform/native/UiApplication.hpp" +#include "cru/platform/native/Window.hpp" #include "render/Base.hpp" +#include + namespace cru::ui { struct AfterLayoutEventArgs {}; -class WindowHost : public Object, public SelfResolvable { + +// The bridge between control tree and native window. +class WindowHost : public Object { CRU_DEFINE_CLASS_LOG_TAG(u"cru::ui::WindowHost") public: - // This will create root window render object and attach it to window. - // It will also create and manage a native window. - WindowHost(Window* window); + WindowHost(Control* root_control); CRU_DELETE_COPY(WindowHost) CRU_DELETE_MOVE(WindowHost) @@ -22,6 +24,8 @@ class WindowHost : public Object, public SelfResolvable { ~WindowHost() override; public: + platform::native::INativeWindow* GetNativeWindow() { return native_window_; } + // Mark the layout as invalid, and arrange a re-layout later. // This method could be called more than one times in a message cycle. But // layout only takes place once. @@ -36,13 +40,18 @@ class WindowHost : public Object, public SelfResolvable { return &after_layout_event_; } + void Relayout(); + void Relayout(const Size& available_size); + + // Is layout is invalid, wait for relayout and then run the action. Otherwist + // run it right now. + void RunAfterLayoutStable(std::function action); + // If true, preferred size of root render object is set to window size when // measure. Default is true. bool IsLayoutPreferToFillWindow() const; void SetLayoutPreferToFillWindow(bool value); - void Relayout(); - // Get current control that mouse hovers on. This ignores the mouse-capture // control. Even when mouse is captured by another control, this function // return the control under cursor. You can use `GetMouseCaptureControl` to @@ -51,12 +60,10 @@ class WindowHost : public Object, public SelfResolvable { //*************** region: focus *************** - // Request focus for specified control. - bool RequestFocusFor(Control* control); - - // Get the control that has focus. Control* GetFocusControl(); + void SetFocusControl(Control* control); + //*************** region: focus *************** // Pass nullptr to release capture. If mouse is already capture by a control, @@ -78,19 +85,6 @@ class WindowHost : public Object, public SelfResolvable { void UpdateCursor(); - std::shared_ptr - GetNativeWindowResolver() { - return native_window_resolver_; - } - - bool IsRetainAfterDestroy() { return retain_after_destroy_; } - - void SetRetainAfterDestroy(bool destroy) { retain_after_destroy_ = destroy; } - - // Is layout is invalid, wait for relayout and then run the action. Otherwist - // run it right now. - void RunAfterLayoutStable(std::function action); - private: //*************** region: native messages *************** void OnNativeDestroy(platform::native::INativeWindow* window, std::nullptr_t); @@ -126,33 +120,21 @@ class WindowHost : public Object, public SelfResolvable { bool no_enter); private: - bool need_layout_ = false; + Control* root_control_; + render::RenderObject* root_render_object_; + + platform::native::INativeWindow* native_window_; + bool need_layout_ = false; + platform::native::TimerAutoCanceler relayout_timer_canceler_; Event after_layout_event_; std::vector > after_layout_stable_action_; - std::shared_ptr - native_window_resolver_; - - // See remarks of WindowHost. - bool retain_after_destroy_ = false; - // See remarks of WindowHost. - bool deleting_ = false; - - // We need this because calling Resolve on resolver in handler of destroy - // event is bad and will always get the dying window. But we need to label the - // window as destroyed so the destructor will not destroy native window - // repeatedly. See remarks of WindowHost. - bool native_window_destroyed_ = false; - std::vector event_revoker_guards_; - Window* window_control_; - std::unique_ptr root_render_object_; - - Control* mouse_hover_control_; + Control* mouse_hover_control_ = nullptr; - Control* focus_control_; // "focus_control_" can't be nullptr + Control* focus_control_; Control* mouse_captured_control_; diff --git a/include/cru/ui/render/Base.hpp b/include/cru/ui/render/Base.hpp index 801d58bd..ac67349e 100644 --- a/include/cru/ui/render/Base.hpp +++ b/include/cru/ui/render/Base.hpp @@ -9,5 +9,4 @@ class FlexLayoutRenderObject; class ScrollRenderObject; class StackLayoutRenderObject; class TextRenderObject; -class WindowRenderObject; } // namespace cru::ui::render diff --git a/include/cru/ui/render/RenderObject.hpp b/include/cru/ui/render/RenderObject.hpp index f052221e..4e5c9060 100644 --- a/include/cru/ui/render/RenderObject.hpp +++ b/include/cru/ui/render/RenderObject.hpp @@ -3,12 +3,13 @@ #include "MeasureRequirement.hpp" #include "cru/common/Event.hpp" +#include "cru/ui/Base.hpp" +#include #include #include namespace cru::ui::render { - // Render object will not destroy its children when destroyed. Control must // manage lifecycle of its render objects. Since control will destroy its // children when destroyed, render objects will be destroyed along with it. @@ -38,7 +39,7 @@ namespace cru::ui::render { // Size OnMeasureContent(const MeasureRequirement& requirement) override; // void OnLayoutContent(const Rect& content_rect) override; class RenderObject : public Object { - friend WindowRenderObject; + friend WindowHost; CRU_DEFINE_CLASS_LOG_TAG(u"cru::ui::render::RenderObject") @@ -64,7 +65,7 @@ class RenderObject : public Object { Control* GetAttachedControl() const { return control_; } void SetAttachedControl(Control* new_control) { control_ = new_control; } - WindowHost* GetWindowHost() const { return ui_host_; } + WindowHost* GetWindowHost() const { return window_host_; } RenderObject* GetParent() const { return parent_; } @@ -132,6 +133,11 @@ class RenderObject : public Object { // Add offset before pass point to children. virtual RenderObject* HitTest(const Point& point) = 0; + IEvent* AttachToHostEvent() { return &attach_to_host_event_; } + IEvent* DetachFromHostEvent() { + return &detach_from_host_event_; + } + public: void InvalidateLayout(); void InvalidatePaint(); @@ -190,7 +196,6 @@ class RenderObject : public Object { virtual void OnLayoutContent(const Rect& content_rect) = 0; virtual void OnAfterLayout(); - static void NotifyAfterLayoutRecursive(RenderObject* render_object); virtual Rect GetPaddingRect() const; virtual Rect GetContentRect() const; @@ -198,11 +203,11 @@ class RenderObject : public Object { private: void SetParent(RenderObject* new_parent); - void SetRenderHostRecursive(WindowHost* host); + void SetWindowHostRecursive(WindowHost* host); private: Control* control_ = nullptr; - WindowHost* ui_host_ = nullptr; + WindowHost* window_host_ = nullptr; RenderObject* parent_ = nullptr; std::vector children_{}; @@ -217,5 +222,8 @@ class RenderObject : public Object { Thickness margin_{}; Thickness padding_{}; + + Event attach_to_host_event_; + Event detach_from_host_event_; }; } // namespace cru::ui::render diff --git a/include/cru/ui/render/WindowRenderObject.hpp b/include/cru/ui/render/WindowRenderObject.hpp deleted file mode 100644 index 23fd8748..00000000 --- a/include/cru/ui/render/WindowRenderObject.hpp +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once -#include "RenderObject.hpp" - -#include - -namespace cru::ui::render { -class WindowRenderObject : public RenderObject { - public: - WindowRenderObject(WindowHost* host); - WindowRenderObject(const WindowRenderObject& other) = delete; - WindowRenderObject(WindowRenderObject&& other) = delete; - WindowRenderObject& operator=(const WindowRenderObject& other) = delete; - WindowRenderObject& operator=(WindowRenderObject&& other) = delete; - ~WindowRenderObject() override = default; - - RenderObject* HitTest(const Point& point) override; - - public: - std::u16string_view GetName() const override; - - protected: - Size OnMeasureContent(const MeasureRequirement& requirement, - const MeasureSize& preferred_size) override; - void OnLayoutContent(const Rect& content_rect) override; - - private: - RenderObject* GetChild() const { - return GetChildren().empty() ? nullptr : GetChildren()[0]; - } - - private: - EventRevokerGuard after_layout_event_guard_; -}; -} // namespace cru::ui::render diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt index d59fd7da..045fea24 100644 --- a/src/ui/CMakeLists.txt +++ b/src/ui/CMakeLists.txt @@ -30,7 +30,6 @@ add_library(cru_ui STATIC render/ScrollRenderObject.cpp render/StackLayoutRenderObject.cpp render/TextRenderObject.cpp - render/WindowRenderObject.cpp ) target_sources(cru_ui PUBLIC ${CRU_UI_INCLUDE_DIR}/Base.hpp @@ -63,6 +62,5 @@ target_sources(cru_ui PUBLIC ${CRU_UI_INCLUDE_DIR}/render/ScrollRenderObject.hpp ${CRU_UI_INCLUDE_DIR}/render/StackLayoutRenderObject.hpp ${CRU_UI_INCLUDE_DIR}/render/TextRenderObject.hpp - ${CRU_UI_INCLUDE_DIR}/render/WindowRenderObject.hpp ) target_link_libraries(cru_ui PUBLIC cru_platform_native) diff --git a/src/ui/ContentControl.cpp b/src/ui/ContentControl.cpp index 60d944d6..19b1b06f 100644 --- a/src/ui/ContentControl.cpp +++ b/src/ui/ContentControl.cpp @@ -3,25 +3,19 @@ #include "cru/ui/Window.hpp" namespace cru::ui { -ContentControl::ContentControl() - : child_vector_{nullptr}, child_(child_vector_[0]) {} - -ContentControl::~ContentControl() { delete child_; } +Control* ContentControl::GetChild() const { + if (GetChildren().empty()) return nullptr; + return GetChildren()[0]; +} void ContentControl::SetChild(Control* child) { - Expects(!dynamic_cast(child)); // Can't add a window as child. - if (child == child_) return; - - const auto host = GetWindowHost(); - const auto old_child = child_; - child_ = child; - if (old_child) { - old_child->_SetParent(nullptr); - old_child->_SetDescendantWindowHost(nullptr); + Control* old_child = nullptr; + if (!GetChildren().empty()) { + old_child = GetChildren()[0]; + this->RemoveChild(0); } if (child) { - child->_SetParent(this); - child->_SetDescendantWindowHost(host); + this->AddChild(child, 0); } OnChildChanged(old_child, child); } diff --git a/src/ui/Control.cpp b/src/ui/Control.cpp index cd7ed0dc..13b1c780 100644 --- a/src/ui/Control.cpp +++ b/src/ui/Control.cpp @@ -1,10 +1,12 @@ #include "cru/ui/Control.hpp" +#include "RoutedEventDispatch.hpp" +#include "cru/common/Base.hpp" #include "cru/platform/native/Cursor.hpp" #include "cru/platform/native/UiApplication.hpp" #include "cru/ui/Base.hpp" #include "cru/ui/WindowHost.hpp" -#include "RoutedEventDispatch.hpp" +#include "cru/ui/render/RenderObject.hpp" #include @@ -25,49 +27,16 @@ Control::Control() { }); } -void Control::_SetParent(Control* parent) { - const auto old_parent = GetParent(); - parent_ = parent; - const auto new_parent = GetParent(); - if (old_parent != new_parent) OnParentChanged(old_parent, new_parent); +Control::~Control() { + for (const auto child : children_) delete child; } -void Control::_SetDescendantWindowHost(WindowHost* host) { - if (host == nullptr && ui_host_ == nullptr) return; - - // You can only attach or detach window. - Expects((host != nullptr && ui_host_ == nullptr) || - (host == nullptr && ui_host_ != nullptr)); - - if (host == nullptr) { - const auto old = ui_host_; - TraverseDescendants([old](Control* control) { - control->ui_host_ = nullptr; - control->OnDetachFromHost(old); - }); - } else - TraverseDescendants([host](Control* control) { - control->ui_host_ = host; - control->OnAttachToHost(host); - }); -} +WindowHost* Control::GetWindowHost() const { return window_host_; } void Control::TraverseDescendants( const std::function& predicate) { - _TraverseDescendants(this, predicate); -} - -void Control::_TraverseDescendants( - Control* control, const std::function& predicate) { - predicate(control); - for (auto c : control->GetChildren()) _TraverseDescendants(c, predicate); -} - -bool Control::RequestFocus() { - auto host = GetWindowHost(); - if (host == nullptr) return false; - - return host->RequestFocusFor(this); + predicate(this); + for (auto c : GetChildren()) c->TraverseDescendants(predicate); } bool Control::HasFocus() { @@ -84,6 +53,13 @@ bool Control::CaptureMouse() { return host->CaptureMouseFor(this); } +void Control::SetFocus() { + auto host = GetWindowHost(); + if (host == nullptr) return; + + host->SetFocusControl(this); +} + bool Control::ReleaseMouse() { auto host = GetWindowHost(); if (host == nullptr) return false; @@ -119,6 +95,58 @@ void Control::SetCursor(std::shared_ptr cursor) { } } +void Control::AddChild(Control* control, const Index position) { + Expects(control->GetParent() == + nullptr); // The control already has a parent. + Expects(position >= 0); + Expects(position <= static_cast( + children_.size())); // The position is out of range. + + children_.insert(children_.cbegin() + position, control); + + const auto old_parent = control->parent_; + control->parent_ = this; + + OnAddChild(control, position); + control->OnParentChanged(old_parent, this); + + if (window_host_) + control->TraverseDescendants([this](Control* control) { + control->window_host_ = window_host_; + control->OnAttachToHost(window_host_); + }); +} + +void Control::RemoveChild(const Index position) { + Expects(position >= 0); + Expects(position < static_cast( + children_.size())); // The position is out of range. + + const auto i = children_.cbegin() + position; + const auto control = *i; + + children_.erase(i); + control->parent_ = nullptr; + + OnRemoveChild(control, position); + control->OnParentChanged(this, nullptr); + + if (window_host_) + control->TraverseDescendants([this](Control* control) { + control->window_host_ = nullptr; + control->OnDetachFromHost(window_host_); + }); +} + +void Control::OnAddChild(Control* child, Index position) { + CRU_UNUSED(child) + CRU_UNUSED(position) +} +void Control::OnRemoveChild(Control* child, Index position) { + CRU_UNUSED(child) + CRU_UNUSED(position) +} + void Control::OnParentChanged(Control* old_parent, Control* new_parent) { CRU_UNUSED(old_parent) CRU_UNUSED(new_parent) diff --git a/src/ui/LayoutControl.cpp b/src/ui/LayoutControl.cpp index 05fcdc4a..351026f9 100644 --- a/src/ui/LayoutControl.cpp +++ b/src/ui/LayoutControl.cpp @@ -3,51 +3,4 @@ #include "cru/ui/Window.hpp" namespace cru::ui { -LayoutControl::~LayoutControl() { - for (const auto child : children_) delete child; -} - -void LayoutControl::AddChild(Control* control, const Index position) { - Expects(control->GetParent() == - nullptr); // The control already has a parent. - Expects(!dynamic_cast(control)); // Can't add a window as child. - Expects(position >= 0); - Expects(position <= - static_cast( - this->children_.size())); // The position is out of range. - - children_.insert(this->children_.cbegin() + position, control); - - control->_SetParent(this); - control->_SetDescendantWindowHost(GetWindowHost()); - - OnAddChild(control, position); -} - -void LayoutControl::RemoveChild(const Index position) { - Expects(position >= 0); - Expects(position < - static_cast( - this->children_.size())); // The position is out of range. - - const auto i = children_.cbegin() + position; - const auto child = *i; - - children_.erase(i); - - child->_SetParent(nullptr); - child->_SetDescendantWindowHost(nullptr); - - OnRemoveChild(child, position); -} - -void LayoutControl::OnAddChild(Control* child, const Index position) { - CRU_UNUSED(child) - CRU_UNUSED(position) -} - -void LayoutControl::OnRemoveChild(Control* child, const Index position) { - CRU_UNUSED(child) - CRU_UNUSED(position) -} } // namespace cru::ui diff --git a/src/ui/NoChildControl.cpp b/src/ui/NoChildControl.cpp index 86861049..8adbe3bc 100644 --- a/src/ui/NoChildControl.cpp +++ b/src/ui/NoChildControl.cpp @@ -1,5 +1,3 @@ #include "cru/ui/NoChildControl.hpp" -namespace cru::ui { -const std::vector NoChildControl::empty_control_vector{}; -} +namespace cru::ui {} diff --git a/src/ui/Window.cpp b/src/ui/Window.cpp index a8cb315b..6d507858 100644 --- a/src/ui/Window.cpp +++ b/src/ui/Window.cpp @@ -1,28 +1,31 @@ #include "cru/ui/Window.hpp" -#include "cru/ui/render/WindowRenderObject.hpp" +#include "cru/common/Base.hpp" #include "cru/ui/WindowHost.hpp" +#include "cru/ui/render/Base.hpp" +#include "cru/ui/render/StackLayoutRenderObject.hpp" namespace cru::ui { -Window* Window::CreateOverlapped() { - return new Window(tag_overlapped_constructor{}); -} +Window* Window::CreateOverlapped() { return new Window(); } -Window::Window(tag_overlapped_constructor) { - managed_ui_host_ = std::make_unique(this); +Window::Window() : render_object_(new render::StackLayoutRenderObject()) { + window_host_ = std::make_unique(this); } -Window::~Window() { - // explicit destroy ui host first. - managed_ui_host_.reset(); -} +Window::~Window() {} std::u16string_view Window::GetControlType() const { return control_type; } -render::RenderObject* Window::GetRenderObject() const { return render_object_; } +render::RenderObject* Window::GetRenderObject() const { + return render_object_.get(); +} + +void Window::OnAddChild(Control* child, Index position) { + render_object_->AddChild(child->GetRenderObject(), position); +} -void Window::OnChildChanged(Control* old_child, Control* new_child) { - if (old_child) render_object_->RemoveChild(0); - if (new_child) render_object_->AddChild(new_child->GetRenderObject(), 0); +void Window::OnRemoveChild(Control* child, Index position) { + CRU_UNUSED(child); + render_object_->RemoveChild(position); } } // namespace cru::ui diff --git a/src/ui/WindowHost.cpp b/src/ui/WindowHost.cpp index d3dec422..1dba4404 100644 --- a/src/ui/WindowHost.cpp +++ b/src/ui/WindowHost.cpp @@ -9,7 +9,9 @@ #include "cru/ui/DebugFlags.hpp" #include "cru/ui/Window.hpp" #include "cru/ui/render/MeasureRequirement.hpp" -#include "cru/ui/render/WindowRenderObject.hpp" +#include "cru/ui/render/RenderObject.hpp" + +#include namespace cru::ui { using platform::native::INativeWindow; @@ -97,25 +99,18 @@ inline void BindNativeEvent( } } // namespace -WindowHost::WindowHost(Window* window) - : window_control_(window), - mouse_hover_control_(nullptr), - focus_control_(window), - mouse_captured_control_(nullptr) { +WindowHost::WindowHost(Control* root_control) : root_control_(root_control) { const auto ui_application = IUiApplication::GetInstance(); - native_window_resolver_ = ui_application->CreateWindow(nullptr); - - const auto native_window = native_window_resolver_->Resolve(); - - auto input_method_context = - ui_application->GetInputMethodManager()->GetContext(native_window); - input_method_context->DisableIME(); + auto native_window = ui_application->CreateWindow(nullptr); + native_window_ = native_window; - window->ui_host_ = this; + root_control_->TraverseDescendants([this](Control* control) { + control->window_host_ = this; + control->OnAttachToHost(this); + }); - root_render_object_ = std::make_unique(this); - root_render_object_->SetAttachedControl(window); - window->render_object_ = root_render_object_.get(); + root_render_object_ = root_control->GetRenderObject(); + root_render_object_->SetWindowHostRecursive(this); BindNativeEvent(this, native_window, native_window->DestroyEvent(), &WindowHost::OnNativeDestroy, event_revoker_guards_); @@ -140,34 +135,24 @@ WindowHost::WindowHost(Window* window) } WindowHost::~WindowHost() { - deleting_ = true; - window_control_->TraverseDescendants( - [this](Control* control) { control->OnDetachFromHost(this); }); - if (!native_window_destroyed_) { - const auto native_window = native_window_resolver_->Resolve(); - if (native_window) { - native_window->Close(); - } + if (native_window_) { + native_window_->Close(); } } void WindowHost::InvalidatePaint() { - if (const auto native_window = native_window_resolver_->Resolve()) - native_window->RequestRepaint(); + if (native_window_) native_window_->RequestRepaint(); } void WindowHost::InvalidateLayout() { if constexpr (debug_flags::layout) log::TagDebug(log_tag, u"A relayout is requested."); if (!need_layout_) { - platform::native::IUiApplication::GetInstance()->SetImmediate( - [resolver = this->CreateResolver()] { - if (const auto host = resolver.Resolve()) { - host->Relayout(); - host->need_layout_ = false; - host->InvalidatePaint(); - } - }); + platform::native::IUiApplication::GetInstance()->SetImmediate([this] { + Relayout(); + need_layout_ = false; + InvalidatePaint(); + }); need_layout_ = true; } } @@ -183,15 +168,17 @@ void WindowHost::SetLayoutPreferToFillWindow(bool value) { } void WindowHost::Relayout() { - const auto native_window = native_window_resolver_->Resolve(); - const auto client_size = native_window - ? native_window->GetClientSize() - : Size{100, 100}; // a reasonable assumed size + const auto available_size = + native_window_ ? native_window_->GetClientSize() + : Size{100, 100}; // a reasonable assumed size + Relayout(available_size); +} +void WindowHost::Relayout(const Size& available_size) { root_render_object_->Measure( - render::MeasureRequirement{client_size, + render::MeasureRequirement{available_size, IsLayoutPreferToFillWindow() - ? render::MeasureSize(client_size) + ? render::MeasureSize(available_size) : render::MeasureSize::NotSpecified()}, render::MeasureSize::NotSpecified()); root_render_object_->Layout(Point{}); @@ -202,11 +189,11 @@ void WindowHost::Relayout() { log::TagDebug(log_tag, u"A relayout is finished."); } -bool WindowHost::RequestFocusFor(Control* control) { - Expects(control != nullptr); // The control to request focus can't be null. - // You can set it as the window. +Control* WindowHost::GetFocusControl() { return focus_control_; } - if (focus_control_ == control) return true; +void WindowHost::SetFocusControl(Control* control) { + if (focus_control_ == control) return; + if (control == nullptr) control = root_control_; const auto old_focus_control = focus_control_; @@ -217,15 +204,10 @@ bool WindowHost::RequestFocusFor(Control* control) { DispatchEvent(event_names::GainFocus, control, &Control::GainFocusEvent, nullptr, false); - - return true; } -Control* WindowHost::GetFocusControl() { return focus_control_; } - bool WindowHost::CaptureMouseFor(Control* control) { - const auto native_window = native_window_resolver_->Resolve(); - if (!native_window) return false; + if (!native_window_) return false; if (control == mouse_captured_control_) return true; @@ -236,7 +218,7 @@ bool WindowHost::CaptureMouseFor(Control* control) { if (old_capture_control != mouse_hover_control_) { DispatchMouseHoverControlChangeEvent( old_capture_control, mouse_hover_control_, - native_window->GetMousePosition(), true, false); + native_window_->GetMousePosition(), true, false); } UpdateCursor(); return true; @@ -247,7 +229,7 @@ bool WindowHost::CaptureMouseFor(Control* control) { mouse_captured_control_ = control; DispatchMouseHoverControlChangeEvent( mouse_hover_control_, mouse_captured_control_, - native_window->GetMousePosition(), false, true); + native_window_->GetMousePosition(), false, true); UpdateCursor(); return true; } @@ -266,8 +248,8 @@ void WindowHost::RunAfterLayoutStable(std::function action) { void WindowHost::OnNativeDestroy(INativeWindow* window, std::nullptr_t) { CRU_UNUSED(window) - native_window_destroyed_ = true; - if (!deleting_ && !retain_after_destroy_) delete window_control_; + this->relayout_timer_canceler_.Reset(); + this->native_window_ = nullptr; } void WindowHost::OnNativePaint(INativeWindow* window, std::nullptr_t) { @@ -399,9 +381,9 @@ void WindowHost::DispatchMouseHoverControlChangeEvent(Control* old_control, } void WindowHost::UpdateCursor() { - if (const auto native_window = native_window_resolver_->Resolve()) { + if (native_window_) { const auto capture = GetMouseCaptureControl(); - native_window->SetCursor( + native_window_->SetCursor( (capture ? capture : GetMouseHoverControl())->GetInheritedCursor()); } } @@ -413,6 +395,6 @@ Control* WindowHost::HitTest(const Point& point) { Ensures(control); return control; } - return window_control_; + return root_control_; } } // namespace cru::ui diff --git a/src/ui/controls/TextControlService.hpp b/src/ui/controls/TextControlService.hpp index 8c1bd32f..33a6bc36 100644 --- a/src/ui/controls/TextControlService.hpp +++ b/src/ui/controls/TextControlService.hpp @@ -61,7 +61,7 @@ class TextControlService : public Object { void SetEditable(bool editable) { this->editable_ = editable; - this->input_method_context_.reset(); + if (!editable) CancelComposition(); } std::u16string GetText() { return this->text_; } @@ -69,8 +69,8 @@ class TextControlService : public Object { void SetText(std::u16string text, bool stop_composition = false) { this->text_ = std::move(text); CoerceSelection(); - if (stop_composition && this->input_method_context_) { - this->input_method_context_->CancelComposition(); + if (stop_composition) { + CancelComposition(); } SyncTextRenderObject(); } @@ -83,8 +83,8 @@ class TextControlService : public Object { } this->text_.insert(this->text_.cbegin() + position, text.begin(), text.end()); - if (stop_composition && this->input_method_context_) { - this->input_method_context_->CancelComposition(); + if (stop_composition) { + CancelComposition(); } SyncTextRenderObject(); } @@ -129,15 +129,30 @@ class TextControlService : public Object { this->text_.erase(this->text_.cbegin() + range.GetStart(), this->text_.cbegin() + range.GetEnd()); this->CoerceSelection(); - if (stop_composition && this->input_method_context_) { - this->input_method_context_->CancelComposition(); + if (stop_composition) { + CancelComposition(); } this->SyncTextRenderObject(); } + platform::native::IInputMethodContext* GetInputMethodContext() { + WindowHost* host = this->control_->GetWindowHost(); + if (!host) return nullptr; + platform::native::INativeWindow* native_window = host->GetNativeWindow(); + if (!native_window) return nullptr; + return native_window->GetInputMethodContext(); + } + + void CancelComposition() { + auto input_method_context = GetInputMethodContext(); + if (input_method_context == nullptr) return; + input_method_context->CancelComposition(); + } + std::optional GetCompositionInfo() { - if (this->input_method_context_ == nullptr) return std::nullopt; - auto composition_info = this->input_method_context_->GetCompositionText(); + auto input_method_context = GetInputMethodContext(); + if (input_method_context == nullptr) return std::nullopt; + auto composition_info = input_method_context->GetCompositionText(); if (composition_info.text.empty()) return std::nullopt; return composition_info; } @@ -319,12 +334,11 @@ class TextControlService : public Object { } void MouseDownHandler(event::MouseButtonEventArgs& args) { - this->control_->RequestFocus(); if (this->select_down_button_.has_value()) { return; } else { + this->control_->SetFocus(); if (!this->control_->CaptureMouse()) return; - if (!this->control_->RequestFocus()) return; const auto text_render_object = this->GetTextRenderObject(); this->select_down_button_ = args.GetButton(); const auto result = text_render_object->TextHitTest( @@ -408,33 +422,35 @@ class TextControlService : public Object { void GainFocusHandler(event::FocusChangeEventArgs& args) { CRU_UNUSED(args); if (editable_) { - WindowHost* ui_host = this->control_->GetWindowHost(); - auto window = ui_host->GetNativeWindowResolver()->Resolve(); - if (window == nullptr) return; - input_method_context_ = - GetUiApplication()->GetInputMethodManager()->GetContext(window); - input_method_context_->EnableIME(); + auto input_method_context = GetInputMethodContext(); + if (input_method_context == nullptr) return; + input_method_context->EnableIME(); auto sync = [this](std::nullptr_t) { this->SyncTextRenderObject(); ScrollToCaret(); }; - input_method_context_->CompositionStartEvent()->AddHandler( - [this](std::nullptr_t) { this->DeleteSelectedText(); }); - input_method_context_->CompositionEvent()->AddHandler(sync); - input_method_context_->CompositionEndEvent()->AddHandler(sync); - input_method_context_->TextEvent()->AddHandler( - [this](const std::u16string_view& text) { - if (text == u"\b") return; - this->ReplaceSelectedText(text); - }); + input_method_context_event_guard_ += + input_method_context->CompositionStartEvent()->AddHandler( + [this](std::nullptr_t) { this->DeleteSelectedText(); }); + input_method_context_event_guard_ += + input_method_context->CompositionEvent()->AddHandler(sync); + input_method_context_event_guard_ += + input_method_context->CompositionEndEvent()->AddHandler(sync); + input_method_context_event_guard_ += + input_method_context->TextEvent()->AddHandler( + [this](const std::u16string_view& text) { + if (text == u"\b") return; + this->ReplaceSelectedText(text); + }); } } void LoseFocusHandler(event::FocusChangeEventArgs& args) { if (!args.IsWindow()) this->AbortSelection(); - if (input_method_context_) { - input_method_context_->DisableIME(); - input_method_context_.reset(); + input_method_context_event_guard_.Clear(); + auto input_method_context = GetInputMethodContext(); + if (input_method_context) { + input_method_context->DisableIME(); } SyncTextRenderObject(); } @@ -442,6 +458,7 @@ class TextControlService : public Object { private: gsl::not_null control_; EventRevokerListGuard event_guard_; + EventRevokerListGuard input_method_context_event_guard_; std::u16string text_; TextRange selection_; @@ -457,7 +474,5 @@ class TextControlService : public Object { // nullopt means not selecting std::optional select_down_button_; - - std::unique_ptr input_method_context_; }; // namespace cru::ui::controls } // namespace cru::ui::controls diff --git a/src/ui/render/RenderObject.cpp b/src/ui/render/RenderObject.cpp index 5266daaf..fd0c7712 100644 --- a/src/ui/render/RenderObject.cpp +++ b/src/ui/render/RenderObject.cpp @@ -24,7 +24,7 @@ void RenderObject::AddChild(RenderObject* render_object, const Index position) { children_.insert(children_.cbegin() + position, render_object); render_object->SetParent(this); - render_object->SetRenderHostRecursive(GetWindowHost()); + render_object->SetWindowHostRecursive(GetWindowHost()); OnAddChild(render_object, position); } @@ -37,7 +37,7 @@ void RenderObject::RemoveChild(const Index position) { const auto removed_child = *i; children_.erase(i); removed_child->SetParent(nullptr); - removed_child->SetRenderHostRecursive(nullptr); + removed_child->SetWindowHostRecursive(nullptr); OnRemoveChild(removed_child, position); } @@ -269,11 +269,11 @@ void RenderObject::SetParent(RenderObject* new_parent) { } void RenderObject::InvalidateLayout() { - if (ui_host_ != nullptr) ui_host_->InvalidateLayout(); + if (window_host_ != nullptr) window_host_->InvalidateLayout(); } void RenderObject::InvalidatePaint() { - if (ui_host_ != nullptr) ui_host_->InvalidatePaint(); + if (window_host_ != nullptr) window_host_->InvalidatePaint(); } constexpr std::u16string_view kUnamedName(u"UNNAMED"); @@ -297,17 +297,16 @@ std::u16string RenderObject::GetDebugPathInTree() const { return result; } -void RenderObject::NotifyAfterLayoutRecursive(RenderObject* render_object) { - render_object->OnAfterLayout(); - for (const auto o : render_object->GetChildren()) { - NotifyAfterLayoutRecursive(o); +void RenderObject::SetWindowHostRecursive(WindowHost* host) { + if (window_host_ != nullptr) { + detach_from_host_event_.Raise(nullptr); + } + window_host_ = host; + if (host != nullptr) { + attach_to_host_event_.Raise(nullptr); } -} - -void RenderObject::SetRenderHostRecursive(WindowHost* host) { - ui_host_ = host; for (const auto child : GetChildren()) { - child->SetRenderHostRecursive(host); + child->SetWindowHostRecursive(host); } } } // namespace cru::ui::render diff --git a/src/ui/render/WindowRenderObject.cpp b/src/ui/render/WindowRenderObject.cpp deleted file mode 100644 index 23cca12e..00000000 --- a/src/ui/render/WindowRenderObject.cpp +++ /dev/null @@ -1,44 +0,0 @@ -#include "cru/ui/render/WindowRenderObject.hpp" - -#include "../Helper.hpp" -#include "cru/platform/graph/util/Painter.hpp" -#include "cru/ui/WindowHost.hpp" - -namespace cru::ui::render { -WindowRenderObject::WindowRenderObject(WindowHost* host) { - SetChildMode(ChildMode::Single); - ui_host_ = host; - after_layout_event_guard_.Reset(host->AfterLayoutEvent()->AddHandler( - [this](auto) { NotifyAfterLayoutRecursive(this); })); -} - -RenderObject* WindowRenderObject::HitTest(const Point& point) { - if (const auto child = GetChild()) { - auto offset = child->GetOffset(); - Point p{point.x - offset.x, point.y - offset.y}; - const auto result = child->HitTest(p); - if (result != nullptr) { - return result; - } - } - return Rect{Point{}, GetSize()}.IsPointInside(point) ? this : nullptr; -} - -std::u16string_view WindowRenderObject::GetName() const { - return u"WindowRenderObject"; -} - -Size WindowRenderObject::OnMeasureContent(const MeasureRequirement& requirement, - const MeasureSize& preferred_size) { - if (const auto child = GetChild()) { - child->Measure(requirement, preferred_size); - return child->GetSize(); - } else { - return Size{}; - } -} - -void WindowRenderObject::OnLayoutContent(const Rect& content_rect) { - if (const auto child = GetChild()) child->Layout(content_rect.GetLeftTop()); -} -} // namespace cru::ui::render diff --git a/src/win/native/UiApplication.cpp b/src/win/native/UiApplication.cpp index 60ff8e8c..87ef0b81 100644 --- a/src/win/native/UiApplication.cpp +++ b/src/win/native/UiApplication.cpp @@ -43,7 +43,6 @@ WinUiApplication::WinUiApplication() { timer_manager_ = std::make_unique(god_window_.get()); window_manager_ = std::make_unique(this); cursor_manager_ = std::make_unique(); - input_method_manager_ = std::make_unique(this); } WinUiApplication::~WinUiApplication() { instance = nullptr; } @@ -116,8 +115,4 @@ cru::platform::graph::IGraphFactory* WinUiApplication::GetGraphFactory() { ICursorManager* WinUiApplication::GetCursorManager() { return cursor_manager_.get(); } - -IInputMethodManager* WinUiApplication::GetInputMethodManager() { - return input_method_manager_.get(); -} } // namespace cru::platform::native::win diff --git a/src/win/native/Window.cpp b/src/win/native/Window.cpp index d9237c4f..1a6fcb07 100644 --- a/src/win/native/Window.cpp +++ b/src/win/native/Window.cpp @@ -55,6 +55,7 @@ WinNativeWindow::WinNativeWindow(WinUiApplication* application, window_render_target_->SetDpi(dpi_, dpi_); input_method_context_ = std::make_unique(this); + input_method_context_->DisableIME(); } WinNativeWindow::~WinNativeWindow() { -- cgit v1.2.3