diff options
| author | Yuqian Yang <crupest@crupest.life> | 2025-11-17 12:54:55 +0800 |
|---|---|---|
| committer | Yuqian Yang <crupest@crupest.life> | 2025-11-17 12:54:55 +0800 |
| commit | 0f8f98b9005803ab154b43dcad0db1f292072a4d (patch) | |
| tree | 0a43d5a9c4e3b747ad955fc30a143aa07ab5888d | |
| parent | b68f9f52a3ecdd8e379dd60ac1c1366e76695464 (diff) | |
| download | cru-0f8f98b9005803ab154b43dcad0db1f292072a4d.tar.gz cru-0f8f98b9005803ab154b43dcad0db1f292072a4d.tar.bz2 cru-0f8f98b9005803ab154b43dcad0db1f292072a4d.zip | |
Refactor window host.
36 files changed, 780 insertions, 1213 deletions
diff --git a/cru-words.txt b/cru-words.txt index e9967942..8205dad2 100644 --- a/cru-words.txt +++ b/cru-words.txt @@ -5,3 +5,4 @@ IWYU # cmake endfunction +relayout diff --git a/demos/ScrollView/main.cpp b/demos/ScrollView/main.cpp index f8e2adda..ba240bae 100644 --- a/demos/ScrollView/main.cpp +++ b/demos/ScrollView/main.cpp @@ -80,7 +80,7 @@ The cold never bothered me anyway)", scroll_view.SetChild(text_block.get()); - window.GetWindowHost()->GetNativeWindow()->SetVisibility( + window.GetNativeWindow()->SetVisibility( cru::platform::gui::WindowVisibilityType::Show); return application->Run(); diff --git a/demos/main/main.cpp b/demos/main/main.cpp index 91c9e85f..70861539 100644 --- a/demos/main/main.cpp +++ b/demos/main/main.cpp @@ -1,9 +1,7 @@ #include "cru/platform/bootstrap/Bootstrap.h" -#include "cru/platform/gui/Base.h" #include "cru/platform/gui/UiApplication.h" #include "cru/platform/gui/Window.h" #include "cru/ui/Base.h" -#include "cru/ui/components/Menu.h" #include "cru/ui/components/PopupButton.h" #include "cru/ui/components/Select.h" #include "cru/ui/controls/Button.h" @@ -11,8 +9,6 @@ #include "cru/ui/controls/TextBlock.h" #include "cru/ui/controls/TextBox.h" #include "cru/ui/controls/Window.h" -#include "cru/ui/events/UiEvents.h" -#include "cru/ui/host/WindowHost.h" using cru::platform::gui::IUiApplication; using namespace cru::ui::controls; @@ -66,7 +62,7 @@ int main() { select.SetItems({"Item 1", "Item 2", "Item 3"}); flex_layout.AddChild(select.GetRootControl()); - window.GetWindowHost()->GetNativeWindow()->SetVisibility( + window.GetNativeWindow()->SetVisibility( cru::platform::gui::WindowVisibilityType::Show); return application->Run(); diff --git a/include/cru/base/Event.h b/include/cru/base/Event.h index 276c313f..c26bea83 100644 --- a/include/cru/base/Event.h +++ b/include/cru/base/Event.h @@ -128,6 +128,13 @@ class Event : public EventBase, public IEvent<TEventArgs> { EventHandlerToken current_token_ = 0; }; +#define CRU_DEFINE_EVENT(name, arg_type) \ + private: \ + ::cru::Event<arg_type> name##Event_; \ + \ + public: \ + ::cru::IEvent<arg_type>* name##Event() { return &name##Event_; } + namespace details { struct EventHandlerRevokerDestroyer { void operator()(EventHandlerRevoker* p) { diff --git a/include/cru/ui/Base.h b/include/cru/ui/Base.h index 3f270b39..9e24cfa2 100644 --- a/include/cru/ui/Base.h +++ b/include/cru/ui/Base.h @@ -35,12 +35,9 @@ namespace colors = cru::platform::colors; namespace controls { class Control; +class Window; } // namespace controls -namespace host { -class WindowHost; -} - //-------------------- region: basic types -------------------- enum class Direction { Horizontal, Vertical }; enum class Alignment { Start, End, Center, Stretch }; @@ -67,21 +64,12 @@ struct CornerRadius { return *this; } + bool operator==(const CornerRadius& other) const = default; + Point left_top; Point right_top; Point left_bottom; Point right_bottom; }; -inline bool operator==(const CornerRadius& left, const CornerRadius& right) { - return left.left_top == right.left_top && - left.left_bottom == right.left_bottom && - left.right_top == right.right_top && - left.right_bottom == right.right_bottom; -} - -inline bool operator!=(const CornerRadius& left, const CornerRadius& right) { - return !(left == right); -} - } // namespace cru::ui diff --git a/include/cru/ui/DeleteLater.h b/include/cru/ui/DeleteLater.h index ddbf2ce1..95301bc0 100644 --- a/include/cru/ui/DeleteLater.h +++ b/include/cru/ui/DeleteLater.h @@ -13,9 +13,6 @@ class CRU_UI_API DeleteLaterImpl { virtual ~DeleteLaterImpl(); void DeleteLater(); - protected: - virtual void OnPrepareDelete(); - private: bool delete_later_scheduled_; }; diff --git a/include/cru/ui/components/Component.h b/include/cru/ui/components/Component.h index 6d31ae79..d8966a89 100644 --- a/include/cru/ui/components/Component.h +++ b/include/cru/ui/components/Component.h @@ -10,9 +10,6 @@ namespace cru::ui::components { */ class CRU_UI_API Component : public Object, public DeleteLaterImpl { public: - Component() = default; - ~Component() = default; - virtual controls::Control* GetRootControl() = 0; bool IsDeleteByParent() const { return delete_by_parent_; } @@ -21,9 +18,6 @@ class CRU_UI_API Component : public Object, public DeleteLaterImpl { } void DeleteIfDeleteByParent(bool delete_later = true); - protected: - void OnPrepareDelete() override; - private: bool delete_by_parent_ = false; }; diff --git a/include/cru/ui/components/Menu.h b/include/cru/ui/components/Menu.h index 554a8898..92731f2e 100644 --- a/include/cru/ui/components/Menu.h +++ b/include/cru/ui/components/Menu.h @@ -1,11 +1,10 @@ #pragma once +#include "../controls/Button.h" +#include "../controls/Control.h" +#include "../controls/FlexLayout.h" +#include "../controls/TextBlock.h" +#include "../controls/Window.h" #include "Component.h" -#include "cru/base/Base.h" -#include "cru/ui/controls/Button.h" -#include "cru/ui/controls/Control.h" -#include "cru/ui/controls/FlexLayout.h" -#include "cru/ui/controls/Popup.h" -#include "cru/ui/controls/TextBlock.h" #include <functional> #include <vector> @@ -16,11 +15,6 @@ class CRU_UI_API MenuItem : public Component { MenuItem(); explicit MenuItem(std::string text); - CRU_DELETE_COPY(MenuItem) - CRU_DELETE_MOVE(MenuItem) - - ~MenuItem(); - public: controls::Control* GetRootControl() override { return &container_; } @@ -39,10 +33,6 @@ class CRU_UI_API MenuItem : public Component { class CRU_UI_API Menu : public Component { public: Menu(); - - CRU_DELETE_COPY(Menu) - CRU_DELETE_MOVE(Menu) - ~Menu(); public: @@ -58,7 +48,8 @@ class CRU_UI_API Menu : public Component { void AddTextItem(std::string text, std::function<void()> on_click) { AddTextItemAt(std::move(text), GetItemCount(), std::move(on_click)); } - void AddTextItemAt(std::string text, Index index, std::function<void()> on_click); + void AddTextItemAt(std::string text, Index index, + std::function<void()> on_click); void SetOnItemClick(std::function<void(Index)> on_item_click) { on_item_click_ = std::move(on_item_click); @@ -74,16 +65,12 @@ class CRU_UI_API Menu : public Component { class CRU_UI_API PopupMenu : public Component { public: explicit PopupMenu(controls::Control* attached_control = nullptr); - - CRU_DELETE_COPY(PopupMenu) - CRU_DELETE_MOVE(PopupMenu) - ~PopupMenu(); public: controls::Control* GetRootControl() override; - controls::Popup* GetPopup() { return &popup_; } + controls::Window* GetPopup() { return popup_; } Menu* GetMenu() { return &menu_; } // position relative to screen left top. @@ -99,7 +86,7 @@ class CRU_UI_API PopupMenu : public Component { private: controls::Control* attached_control_; - controls::Popup popup_; + controls::Window* popup_; Menu menu_; }; } // namespace cru::ui::components diff --git a/include/cru/ui/components/PopupButton.h b/include/cru/ui/components/PopupButton.h index 5fa69044..0e3d5314 100644 --- a/include/cru/ui/components/PopupButton.h +++ b/include/cru/ui/components/PopupButton.h @@ -4,7 +4,6 @@ #include "cru/ui/components/Menu.h" #include "cru/ui/controls/Button.h" #include "cru/ui/controls/IconButton.h" -#include "cru/ui/controls/Popup.h" #include "cru/ui/controls/TextBlock.h" namespace cru::ui::components { diff --git a/include/cru/ui/controls/Control.h b/include/cru/ui/controls/Control.h index 77f5f392..684bc960 100644 --- a/include/cru/ui/controls/Control.h +++ b/include/cru/ui/controls/Control.h @@ -4,6 +4,8 @@ #include "../events/UiEvents.h" #include "../render/RenderObject.h" #include "../style/StyleRuleSet.h" +#include "cru/ui/events/KeyEventArgs.h" +#include "cru/ui/events/MouseWheelEventArgs.h" #include "cru/ui/render/MeasureRequirement.h" namespace cru::ui::controls { @@ -31,9 +33,11 @@ class CRU_UI_API Control : public Object, public DeleteLaterImpl { public: virtual std::string GetControlType() const = 0; + std::string GetDebugId() const; + //*************** region: tree *************** public: - host::WindowHost* GetWindowHost() const { return window_host_; } + Window* GetWindow(); Control* GetParent() const { return parent_; } void SetParent(Control* parent); @@ -48,6 +52,8 @@ class CRU_UI_API Control : public Object, public DeleteLaterImpl { void RemoveFromParent(); + controls::Control* HitTest(const Point& point); + public: virtual render::RenderObject* GetRenderObject() const = 0; @@ -107,83 +113,30 @@ class CRU_UI_API Control : public Object, public DeleteLaterImpl { // the mouse, this event is raised as regular. But if mouse is captured by // another control, the control will not receive any mouse enter event. You // can use `IsMouseCaptured` to get more info. - events::RoutedEvent<events::MouseEventArgs>* MouseEnterEvent() { - return &mouse_enter_event_; - } + CRU_DEFINE_ROUTED_EVENT(MouseEnter, events::MouseEventArgs) + // Raised when mouse is leave the control. Even when the control itself // captures the mouse, this event is raised as regular. But if mouse is // captured by another control, the control will not receive any mouse leave // event. You can use `IsMouseCaptured` to get more info. - events::RoutedEvent<events::MouseEventArgs>* MouseLeaveEvent() { - return &mouse_leave_event_; - } - // Raised when mouse is move in the control. - events::RoutedEvent<events::MouseEventArgs>* MouseMoveEvent() { - return &mouse_move_event_; - } - // Raised when a mouse button is pressed in the control. - events::RoutedEvent<events::MouseButtonEventArgs>* MouseDownEvent() { - return &mouse_down_event_; - } - // Raised when a mouse button is released in the control. - events::RoutedEvent<events::MouseButtonEventArgs>* MouseUpEvent() { - return &mouse_up_event_; - } - events::RoutedEvent<events::MouseWheelEventArgs>* MouseWheelEvent() { - return &mouse_wheel_event_; - } - events::RoutedEvent<events::KeyEventArgs>* KeyDownEvent() { - return &key_down_event_; - } - events::RoutedEvent<events::KeyEventArgs>* KeyUpEvent() { - return &key_up_event_; - } - events::RoutedEvent<events::FocusChangeEventArgs>* GainFocusEvent() { - return &gain_focus_event_; - } - events::RoutedEvent<events::FocusChangeEventArgs>* LoseFocusEvent() { - return &lose_focus_event_; - } + CRU_DEFINE_ROUTED_EVENT(MouseLeave, events::MouseEventArgs) - private: - events::RoutedEvent<events::MouseEventArgs> mouse_enter_event_; - events::RoutedEvent<events::MouseEventArgs> mouse_leave_event_; - events::RoutedEvent<events::MouseEventArgs> mouse_move_event_; - events::RoutedEvent<events::MouseButtonEventArgs> mouse_down_event_; - events::RoutedEvent<events::MouseButtonEventArgs> mouse_up_event_; - events::RoutedEvent<events::MouseWheelEventArgs> mouse_wheel_event_; - - events::RoutedEvent<events::KeyEventArgs> key_down_event_; - events::RoutedEvent<events::KeyEventArgs> key_up_event_; - - events::RoutedEvent<events::FocusChangeEventArgs> gain_focus_event_; - events::RoutedEvent<events::FocusChangeEventArgs> lose_focus_event_; + CRU_DEFINE_ROUTED_EVENT(MouseMove, events::MouseEventArgs) + CRU_DEFINE_ROUTED_EVENT(MouseDown, events::MouseButtonEventArgs) + CRU_DEFINE_ROUTED_EVENT(MouseUp, events::MouseButtonEventArgs) + CRU_DEFINE_ROUTED_EVENT(MouseWheel, events::MouseWheelEventArgs) + CRU_DEFINE_ROUTED_EVENT(KeyDown, events::KeyEventArgs) + CRU_DEFINE_ROUTED_EVENT(KeyUp, events::KeyEventArgs) + CRU_DEFINE_ROUTED_EVENT(GainFocus, events::FocusChangeEventArgs) + CRU_DEFINE_ROUTED_EVENT(LoseFocus, events::FocusChangeEventArgs) //*************** region: tree *************** protected: virtual void OnParentChanged(Control* old_parent, Control* new_parent) {} - virtual void OnWindowHostChanged(host::WindowHost* old_host, - host::WindowHost* new_host) {} - - protected: - virtual void OnMouseHoverChange(bool newHover) { CRU_UNUSED(newHover) } - - void OnPrepareDelete() override; - - private: - void OnParentChangedCore(Control* old_parent, Control* new_parent); - void OnWindowHostChangedCore(host::WindowHost* old_host, - host::WindowHost* new_host); - private: - bool in_destruction_ = false; - Control* parent_ = nullptr; - host::WindowHost* window_host_ = nullptr; - - private: bool is_mouse_over_ = false; std::shared_ptr<platform::gui::ICursor> cursor_ = nullptr; diff --git a/include/cru/ui/controls/Popup.h b/include/cru/ui/controls/Popup.h deleted file mode 100644 index 7c57d257..00000000 --- a/include/cru/ui/controls/Popup.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once -#include "RootControl.h" - -#include "cru/platform/gui/Base.h" - -#include <memory> - -namespace cru::ui::controls { -class CRU_UI_API Popup : public RootControl { - public: - static constexpr std::string_view kControlType = "Popup"; - - explicit Popup(Control* attached_control = nullptr); - - CRU_DELETE_COPY(Popup) - CRU_DELETE_MOVE(Popup) - - ~Popup() override; - - std::string GetControlType() const override { return std::string(kControlType); } -}; -} // namespace cru::ui::controls diff --git a/include/cru/ui/controls/RootControl.h b/include/cru/ui/controls/RootControl.h deleted file mode 100644 index e662b655..00000000 --- a/include/cru/ui/controls/RootControl.h +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once -#include "LayoutControl.h" - -#include "cru/base/Event.h" -#include "cru/platform/gui/Window.h" -#include "cru/ui/host/WindowHost.h" -#include "cru/ui/render/StackLayoutRenderObject.h" - -namespace cru::ui::controls { -class CRU_UI_API RootControl - : public LayoutControl<render::StackLayoutRenderObject> { - protected: - explicit RootControl(Control* attached_control); - - public: - CRU_DELETE_COPY(RootControl) - CRU_DELETE_MOVE(RootControl) - ~RootControl() override; - - public: - platform::gui::INativeWindow* GetNativeWindow(); - - protected: - void SetGainFocusOnCreateAndDestroyWhenLoseFocus(bool value); - - private: - std::unique_ptr<host::WindowHost> window_host_; - - Control* attached_control_; - - EventHandlerRevokerListGuard - gain_focus_on_create_and_destroy_when_lose_focus_event_guard_; -}; -} // namespace cru::ui::controls diff --git a/include/cru/ui/controls/TextHostControlService.h b/include/cru/ui/controls/TextHostControlService.h index 454be09b..40ba017a 100644 --- a/include/cru/ui/controls/TextHostControlService.h +++ b/include/cru/ui/controls/TextHostControlService.h @@ -201,6 +201,8 @@ class CRU_UI_API TextHostControlService : public Object { platform::gui::TimerAutoCanceler caret_timer_canceler_; int caret_blink_duration_ = k_default_caret_blink_duration; + platform::gui::TimerAutoCanceler scroll_to_caret_timer_canceler_; + helper::ShortcutHub shortcut_hub_; // true if left mouse is down and selecting diff --git a/include/cru/ui/controls/Window.h b/include/cru/ui/controls/Window.h index c4ba94d9..e25694ba 100644 --- a/include/cru/ui/controls/Window.h +++ b/include/cru/ui/controls/Window.h @@ -1,20 +1,188 @@ #pragma once -#include "cru/ui/controls/RootControl.h" +#include "../render/StackLayoutRenderObject.h" +#include "LayoutControl.h" -#include "cru/base/Base.h" +#include <cru/base/Base.h> +#include <cru/base/Event.h> +#include <cru/base/Guard.h> +#include <cru/base/log/Logger.h> +#include <cru/platform/gui/UiApplication.h> +#include <cru/platform/gui/Window.h> namespace cru::ui::controls { -class CRU_UI_API Window final : public RootControl { +class CRU_UI_API Window + : public LayoutControl<render::StackLayoutRenderObject> { + CRU_DEFINE_CLASS_LOG_TAG("cru::ui::controls::Window") public: - static constexpr std::string_view control_type = "Window"; + static constexpr std::string_view kControlType = "Window"; - public: - explicit Window(Control* attached_control = nullptr); - CRU_DELETE_COPY(Window) - CRU_DELETE_MOVE(Window) + Window(); ~Window() override; - public: - std::string GetControlType() const final { return std::string(control_type); } + static Window* CreatePopup(); + + std::string GetControlType() const override; + + void SetAttachedControl(Control* control); + + platform::gui::INativeWindow* GetNativeWindow(); + + void InvalidateLayout(); + void InvalidatePaint(); + + void Repaint(); + void Relayout(); + void RelayoutWithSize(const Size& available_size = Size::Infinite(), + bool set_window_size_to_fit_content = false); + + void SetGainFocusOnCreateAndDestroyWhenLoseFocus(bool value); + + // 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); + + // 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_; } + + Control* GetFocusControl(); + void SetFocusControl(Control* control); + + Control* GetMouseCaptureControl(); + bool SetMouseCaptureControl(Control* control); + + std::shared_ptr<platform::gui::ICursor> GetOverrideCursor(); + void SetOverrideCursor(std::shared_ptr<platform::gui::ICursor> cursor); + + bool IsInEventHandling(); + void UpdateCursor(); + + CRU_DEFINE_EVENT(AfterLayout, std::nullptr_t) + + private: + std::unique_ptr<platform::gui::INativeWindow> CreateNativeWindow(); + + void OnNativeDestroy(platform::gui::INativeWindow* window, std::nullptr_t); + void OnNativePaint(platform::gui::INativeWindow* window, std::nullptr_t); + void OnNativeResize(platform::gui::INativeWindow* window, const Size& size); + void OnNativeFocus(platform::gui::INativeWindow* window, + cru::platform::gui::FocusChangeType focus); + void OnNativeMouseEnterLeave(platform::gui::INativeWindow* window, + cru::platform::gui::MouseEnterLeaveType enter); + void OnNativeMouseMove(platform::gui::INativeWindow* window, + const Point& point); + void OnNativeMouseDown(platform::gui::INativeWindow* window, + const platform::gui::NativeMouseButtonEventArgs& args); + void OnNativeMouseUp(platform::gui::INativeWindow* window, + const platform::gui::NativeMouseButtonEventArgs& args); + void OnNativeMouseWheel(platform::gui::INativeWindow* window, + const platform::gui::NativeMouseWheelEventArgs& args); + void OnNativeKeyDown(platform::gui::INativeWindow* window, + const platform::gui::NativeKeyEventArgs& args); + void OnNativeKeyUp(platform::gui::INativeWindow* window, + const platform::gui::NativeKeyEventArgs& args); + + void DispatchFocusControlChangeEvent(Control* old_control, + Control* new_control, bool is_window); + void DispatchMouseHoverControlChangeEvent(Control* old_control, + Control* new_control, + const Point& point, bool no_leave, + bool no_enter); + + template <typename EventArgs, typename... Args> + void DispatchEvent(Control* const original_sender, + events::RoutedEvent<EventArgs>* (Control::*event_ptr)(), + Control* const last_receiver, Args&&... args) { + constexpr auto kLogTag = "cru::ui::controls::DispatchEvent"; + + event_handling_count_++; + Guard event_handling_count_guard([this] { event_handling_count_--; }); + + if (original_sender == nullptr || original_sender == last_receiver) return; + + std::string log = "Begin dispatching routed event " + + (original_sender->*event_ptr)()->GetName() + ":\n\tTunnel:"; + + Guard logging_guard([&] { + log += "\nEnd dispatching routed event " + + (original_sender->*event_ptr)()->GetName() + "."; + CRU_LOG_TAG_DEBUG("{}", log); + }); + + std::vector<Control*> receive_list; + + auto parent = original_sender; + while (parent != last_receiver) { + receive_list.push_back(parent); + parent = parent->GetParent(); + } + + auto handled = false; + + // tunnel + for (auto i = receive_list.crbegin(); i != receive_list.crend(); ++i) { + auto control = *i; + log += " "; + log += control->GetDebugId(); + EventArgs event_args(control, original_sender, + std::forward<Args>(args)...); + (control->*event_ptr)()->tunnel_.Raise(event_args); + if (event_args.IsHandled()) { + log += " marked as handled."; + handled = true; + break; + } + } + + // bubble + if (!handled) { + log += "\n\tBubble:"; + for (auto control : receive_list) { + log += " "; + log += control->GetDebugId(); + EventArgs event_args(control, original_sender, + std::forward<Args>(args)...); + (control->*event_ptr)()->bubble_.Raise(event_args); + if (event_args.IsHandled()) { + log += " marked as handled."; + break; + } + } + } + + log += "\n\tDirect:"; + // direct + for (auto control : receive_list) { + log += " "; + log += control->GetDebugId(); + EventArgs event_args(control, original_sender, + std::forward<Args>(args)...); + (control->*event_ptr)()->direct_.Raise(event_args); + } + } + + private: + int event_handling_count_; + + std::unique_ptr<platform::gui::INativeWindow> native_window_; + + Control* focus_control_; + Control* mouse_hover_control_; + Control* mouse_captured_control_; + + std::shared_ptr<platform::gui::ICursor> override_cursor_; + + bool layout_prefer_to_fill_window_; + + platform::gui::TimerAutoCanceler repaint_schedule_canceler_; + platform::gui::TimerAutoCanceler relayout_schedule_canceler_; + + Control* attached_control_; + + EventHandlerRevokerListGuard + gain_focus_on_create_and_destroy_when_lose_focus_event_guard_; }; } // namespace cru::ui::controls diff --git a/include/cru/ui/events/RoutedEvent.h b/include/cru/ui/events/RoutedEvent.h index aa3331a6..58e50d63 100644 --- a/include/cru/ui/events/RoutedEvent.h +++ b/include/cru/ui/events/RoutedEvent.h @@ -8,6 +8,8 @@ namespace cru::ui::events { // EventArgs must be reference because the IsHandled property must be settable. template <typename TEventArgs> class CRU_UI_API RoutedEvent { + friend controls::Window; + public: static_assert(std::is_base_of_v<UiEventArgs, TEventArgs>, "TEventArgs must be subclass of UiEventArgs."); @@ -16,13 +18,29 @@ class CRU_UI_API RoutedEvent { using EventArgs = TEventArgs&; + explicit RoutedEvent(std::string name) : name_(std::move(name)) {} + + std::string GetName() const { return name_; } + IEvent<TEventArgs&>* Direct() { return &direct_; } IEvent<TEventArgs&>* Bubble() { return &bubble_; } IEvent<TEventArgs&>* Tunnel() { return &tunnel_; } private: + std::string name_; + Event<TEventArgs&> direct_; Event<TEventArgs&> bubble_; Event<TEventArgs&> tunnel_; }; -} // namespace cru::ui::event + +#define CRU_DEFINE_ROUTED_EVENT(name, arg_type) \ + private: \ + ::cru::ui::events::RoutedEvent<arg_type> name##Event_{#name}; \ + \ + public: \ + ::cru::ui::events::RoutedEvent<arg_type>* name##Event() { \ + return &name##Event_; \ + } + +} // namespace cru::ui::events diff --git a/include/cru/ui/host/LayoutPaintCycler.h b/include/cru/ui/host/LayoutPaintCycler.h deleted file mode 100644 index e4ff7aa8..00000000 --- a/include/cru/ui/host/LayoutPaintCycler.h +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once -#include "../Base.h" - -#include "cru/platform/gui/UiApplication.h" - -#include <chrono> - -namespace cru::ui::host { -class CRU_UI_API LayoutPaintCycler { - public: - explicit LayoutPaintCycler(WindowHost* host); - - CRU_DELETE_COPY(LayoutPaintCycler) - CRU_DELETE_MOVE(LayoutPaintCycler) - - ~LayoutPaintCycler(); - - public: - void InvalidateLayout(); - void InvalidatePaint(); - - bool IsLayoutDirty() { return layout_dirty_; } - - private: - void OnCycle(); - - private: - WindowHost* host_; - - platform::gui::TimerAutoCanceler timer_canceler_; - - bool layout_dirty_ = true; - bool paint_dirty_ = true; - - std::chrono::steady_clock::time_point last_cycle_time_; - std::chrono::steady_clock::duration cycle_threshold_ = - std::chrono::milliseconds(1000) / 144; -}; -} // namespace cru::ui::host diff --git a/include/cru/ui/host/WindowHost.h b/include/cru/ui/host/WindowHost.h deleted file mode 100644 index 13b06b07..00000000 --- a/include/cru/ui/host/WindowHost.h +++ /dev/null @@ -1,172 +0,0 @@ -#pragma once -#include "../Base.h" - -#include "../render/RenderObject.h" -#include "cru/base/Event.h" -#include "cru/platform/gui/Cursor.h" -#include "cru/platform/gui/UiApplication.h" -#include "cru/platform/gui/Window.h" - -#include <functional> -#include <memory> - -namespace cru::ui::host { -class LayoutPaintCycler; - -struct AfterLayoutEventArgs {}; - -// The bridge between control tree and native window. -class CRU_UI_API WindowHost : public Object { - friend controls::Control; - CRU_DEFINE_CLASS_LOG_TAG("WindowHost") - - private: - static int event_handling_depth_; - - public: - static bool IsInEventHandling() { return event_handling_depth_ > 0; } - static void EnterEventHandling(); - static void LeaveEventHandling(); - - public: - explicit WindowHost(controls::Control* root_control); - ~WindowHost() override; - - public: - platform::gui::INativeWindow* GetNativeWindow() { - return native_window_.get(); - } - - // 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<AfterLayoutEventArgs>* AfterLayoutEvent() { - return &after_layout_event_; - } - - void Relayout(); - void RelayoutWithSize(const Size& available_size = Size::Infinite(), - bool set_window_size_to_fit_content = false); - - void Repaint(); - - // Is layout is invalid, wait for relayout and then run the action. Otherwist - // run it right now. - void RunAfterLayoutStable(std::function<void()> 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); - - // 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. - controls::Control* GetMouseHoverControl() const { - return mouse_hover_control_; - } - - //*************** region: focus *************** - - controls::Control* GetFocusControl(); - - void SetFocusControl(controls::Control* control); - - //*************** 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(controls::Control* control); - - // Return null if not captured. - controls::Control* GetMouseCaptureControl(); - - controls::Control* HitTest(const Point& point); - - void UpdateCursor(); - - IEvent<platform::gui::INativeWindow*>* NativeWindowChangeEvent() { - return &native_window_change_event_; - } - - std::shared_ptr<platform::gui::ICursor> GetOverrideCursor(); - void SetOverrideCursor(std::shared_ptr<platform::gui::ICursor> cursor); - - private: - std::unique_ptr<platform::gui::INativeWindow> CreateNativeWindow(); - - //*************** region: native messages *************** - void OnNativeDestroy(platform::gui::INativeWindow* window, std::nullptr_t); - void OnNativePaint(platform::gui::INativeWindow* window, std::nullptr_t); - void OnNativeResize(platform::gui::INativeWindow* window, const Size& size); - - void OnNativeFocus(platform::gui::INativeWindow* window, - cru::platform::gui::FocusChangeType focus); - - void OnNativeMouseEnterLeave(platform::gui::INativeWindow* window, - cru::platform::gui::MouseEnterLeaveType enter); - void OnNativeMouseMove(platform::gui::INativeWindow* window, - const Point& point); - void OnNativeMouseDown(platform::gui::INativeWindow* window, - const platform::gui::NativeMouseButtonEventArgs& args); - void OnNativeMouseUp(platform::gui::INativeWindow* window, - const platform::gui::NativeMouseButtonEventArgs& args); - void OnNativeMouseWheel(platform::gui::INativeWindow* window, - const platform::gui::NativeMouseWheelEventArgs& args); - - void OnNativeKeyDown(platform::gui::INativeWindow* window, - const platform::gui::NativeKeyEventArgs& args); - void OnNativeKeyUp(platform::gui::INativeWindow* window, - const platform::gui::NativeKeyEventArgs& args); - - //*************** region: event dispatcher helper *************** - - void DispatchMouseHoverControlChangeEvent(controls::Control* old_control, - controls::Control* new_control, - const Point& point, bool no_leave, - bool no_enter); - - void OnControlDetach(controls::Control* control); - - private: - controls::Control* root_control_ = nullptr; - render::RenderObject* root_render_object_ = nullptr; - - std::unique_ptr<platform::gui::INativeWindow> native_window_; - - std::unique_ptr<LayoutPaintCycler> layout_paint_cycler_; - - Event<AfterLayoutEventArgs> after_layout_event_; - std::vector<std::function<void()> > after_layout_stable_action_; - - std::vector<EventHandlerRevokerGuard> event_revoker_guards_; - - controls::Control* mouse_hover_control_ = nullptr; - - controls::Control* focus_control_; - - controls::Control* mouse_captured_control_ = nullptr; - - bool layout_prefer_to_fill_window_ = false; - - Event<platform::gui::INativeWindow*> native_window_change_event_; - - std::shared_ptr<platform::gui::ICursor> override_cursor_; -}; -} // namespace cru::ui::host diff --git a/include/cru/ui/render/RenderObject.h b/include/cru/ui/render/RenderObject.h index 34761b51..c299ea24 100644 --- a/include/cru/ui/render/RenderObject.h +++ b/include/cru/ui/render/RenderObject.h @@ -137,7 +137,7 @@ class CRU_UI_API RenderObject : public Object { virtual RenderObject* HitTest(const Point& point) = 0; public: - host::WindowHost* GetWindowHost(); + controls::Window* GetWindow(); void InvalidateLayout(); void InvalidatePaint(); diff --git a/include/cru/ui/render/ScrollBar.h b/include/cru/ui/render/ScrollBar.h index 13c7d8b0..2325acd1 100644 --- a/include/cru/ui/render/ScrollBar.h +++ b/include/cru/ui/render/ScrollBar.h @@ -5,11 +5,9 @@ #include "cru/platform/graphics/Brush.h" #include "cru/platform/graphics/Geometry.h" #include "cru/platform/graphics/Painter.h" -#include "cru/platform/gui/Cursor.h" #include "cru/platform/gui/UiApplication.h" #include "cru/ui/Base.h" #include "cru/ui/controls/Control.h" -#include "cru/ui/helper/ClickDetector.h" #include <memory> #include <optional> @@ -38,8 +36,8 @@ enum class ScrollBarAreaKind { enum class ScrollBarBrushUsageKind { Arrow, ArrowBackground, Slot, Thumb }; enum class ScrollBarBrushStateKind { Normal, Hover, Press, Disable }; -std::string CRU_UI_API GenerateScrollBarThemeColorKey(ScrollBarBrushUsageKind usage, - ScrollBarBrushStateKind state); +std::string CRU_UI_API GenerateScrollBarThemeColorKey( + ScrollBarBrushUsageKind usage, ScrollBarBrushStateKind state); class CRU_UI_API ScrollBar : public Object { public: @@ -139,7 +137,7 @@ class CRU_UI_API ScrollBar : public Object { Event<Scroll> scroll_attempt_event_; - bool cursor_overrided_ = false; + bool cursor_overridden_ = false; platform::gui::TimerAutoCanceler auto_collapse_timer_canceler_; }; diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt index 4dfee0cb..84849d44 100644 --- a/src/ui/CMakeLists.txt +++ b/src/ui/CMakeLists.txt @@ -15,8 +15,6 @@ add_library(CruUi controls/FlexLayout.cpp controls/IconButton.cpp controls/NoChildControl.cpp - controls/Popup.cpp - controls/RootControl.cpp controls/ScrollView.cpp controls/StackLayout.cpp controls/TextBlock.cpp @@ -30,8 +28,6 @@ add_library(CruUi events/MouseEventArgs.cpp helper/ClickDetector.cpp helper/ShortcutHub.cpp - host/LayoutPaintCycler.cpp - host/WindowHost.cpp mapper/BorderStyleMapper.cpp mapper/BrushMapper.cpp mapper/ColorMapper.cpp diff --git a/src/ui/DeleteLater.cpp b/src/ui/DeleteLater.cpp index 27ec2155..90b07b71 100644 --- a/src/ui/DeleteLater.cpp +++ b/src/ui/DeleteLater.cpp @@ -20,6 +20,4 @@ void DeleteLaterImpl::DeleteLater() { delete_later_scheduled_ = true; } } - -void DeleteLaterImpl::OnPrepareDelete() {} } // namespace cru::ui diff --git a/src/ui/components/Component.cpp b/src/ui/components/Component.cpp index d0525a1c..77a659dc 100644 --- a/src/ui/components/Component.cpp +++ b/src/ui/components/Component.cpp @@ -1,10 +1,6 @@ #include "cru/ui/components/Component.h" -#include "cru/ui/controls/Control.h" - namespace cru::ui::components { -void Component::OnPrepareDelete() { GetRootControl()->RemoveFromParent(); } - void Component::DeleteIfDeleteByParent(bool delete_later) { if (delete_by_parent_) { if (delete_later) { diff --git a/src/ui/components/Menu.cpp b/src/ui/components/Menu.cpp index 04227d66..c89c7fc9 100644 --- a/src/ui/components/Menu.cpp +++ b/src/ui/components/Menu.cpp @@ -4,10 +4,9 @@ #include "cru/ui/controls/Button.h" #include "cru/ui/controls/Control.h" #include "cru/ui/controls/FlexLayout.h" -#include "cru/ui/controls/Popup.h" #include "cru/ui/controls/TextBlock.h" +#include "cru/ui/controls/Window.h" #include "cru/ui/helper/ClickDetector.h" -#include "cru/ui/host/WindowHost.h" #include "cru/ui/style/StyleRuleSet.h" namespace cru::ui::components { @@ -22,8 +21,6 @@ MenuItem::MenuItem() { MenuItem::MenuItem(std::string text) : MenuItem() { SetText(std::move(text)); } -MenuItem::~MenuItem() {} - void MenuItem::SetText(std::string text) { text_.SetText(std::move(text)); } Menu::Menu() { @@ -33,7 +30,7 @@ Menu::Menu() { Menu::~Menu() { for (auto item : items_) { - item->DeleteIfDeleteByParent(false); + item->DeleteIfDeleteByParent(); } } @@ -78,27 +75,29 @@ void Menu::AddTextItemAt(std::string text, Index index, } PopupMenu::PopupMenu(controls::Control* attached_control) - : attached_control_(attached_control), popup_(attached_control) { - menu_.SetOnItemClick([this](Index) { popup_.GetNativeWindow()->Close(); }); - popup_.AddChildAt(menu_.GetRootControl(), 0); + : attached_control_(attached_control) { + menu_.SetOnItemClick([this](Index) { popup_->GetNativeWindow()->Close(); }); + popup_ = controls::Window::CreatePopup(); + popup_->SetAttachedControl(attached_control); + popup_->AddChildAt(menu_.GetRootControl(), 0); } -PopupMenu::~PopupMenu() {} +PopupMenu::~PopupMenu() { delete popup_; } -controls::Control* PopupMenu::GetRootControl() { return &popup_; } +controls::Control* PopupMenu::GetRootControl() { return popup_; } void PopupMenu::SetPosition(const Point& position) { - auto window = popup_.GetWindowHost()->GetNativeWindow(); + auto window = popup_->GetNativeWindow(); window->SetClientRect(Rect{position, window->GetClientSize()}); } void PopupMenu::Show() { - auto native_window = popup_.GetWindowHost()->GetNativeWindow(); + auto native_window = popup_->GetNativeWindow(); native_window->SetVisibility(platform::gui::WindowVisibilityType::Show); - popup_.GetWindowHost()->RelayoutWithSize(Size::Infinite(), true); + popup_->RelayoutWithSize(Size::Infinite(), true); native_window->RequestFocus(); native_window->SetToForeground(); } -void PopupMenu::Close() { popup_.GetWindowHost()->GetNativeWindow()->Close(); } +void PopupMenu::Close() { popup_->GetNativeWindow()->Close(); } } // namespace cru::ui::components diff --git a/src/ui/components/PopupButton.cpp b/src/ui/components/PopupButton.cpp index 5ea41d78..1869ba31 100644 --- a/src/ui/components/PopupButton.cpp +++ b/src/ui/components/PopupButton.cpp @@ -1,6 +1,5 @@ #include "cru/ui/components/PopupButton.h" #include "cru/ui/components/Menu.h" -#include "cru/ui/controls/Popup.h" #include "cru/ui/helper/ClickDetector.h" namespace cru::ui::components { diff --git a/src/ui/controls/Control.cpp b/src/ui/controls/Control.cpp index 93213ecf..e903b5b3 100644 --- a/src/ui/controls/Control.cpp +++ b/src/ui/controls/Control.cpp @@ -1,10 +1,12 @@ #include "cru/ui/controls/Control.h" +#include "cru/ui/controls/Window.h" #include "cru/platform/gui/Cursor.h" #include "cru/platform/gui/UiApplication.h" -#include "cru/ui/host/WindowHost.h" #include "cru/ui/style/StyleRuleSet.h" +#include <format> + namespace cru::ui::controls { using platform::gui::ICursor; using platform::gui::IUiApplication; @@ -15,32 +17,45 @@ Control::Control() { style_rule_set_bind_ = std::make_unique<style::StyleRuleSetBind>(this, style_rule_set_); - MouseEnterEvent()->Direct()->AddHandler([this](events::MouseEventArgs&) { - this->is_mouse_over_ = true; - this->OnMouseHoverChange(true); - }); + MouseEnterEvent()->Direct()->AddHandler( + [this](events::MouseEventArgs&) { this->is_mouse_over_ = true; }); - MouseLeaveEvent()->Direct()->AddHandler([this](events::MouseEventArgs&) { - this->is_mouse_over_ = false; - this->OnMouseHoverChange(true); - }); + MouseLeaveEvent()->Direct()->AddHandler( + [this](events::MouseEventArgs&) { this->is_mouse_over_ = false; }); } Control::~Control() { - if (host::WindowHost::IsInEventHandling()) { - // Don't delete control during event handling. Use DeleteLater. - std::terminate(); + if (auto window = GetWindow()) { + if (window->IsInEventHandling()) { + // Don't delete control during event handling. Use DeleteLater. + std::terminate(); + } } - in_destruction_ = true; RemoveFromParent(); } +std::string Control::GetDebugId() const { + return std::format("{}({})", GetControlType(), + static_cast<const void*>(this)); +} + +Window* Control::GetWindow() { + auto parent = this; + while (parent) { + if (auto window = dynamic_cast<Window*>(parent)) { + return window; + } + parent = parent->GetParent(); + } + return nullptr; +} + void Control::SetParent(Control* parent) { if (parent_ == parent) return; auto old_parent = parent_; parent_ = parent; - OnParentChangedCore(old_parent, parent); + OnParentChanged(old_parent, parent); } void Control::RemoveFromParent() { @@ -49,39 +64,49 @@ void Control::RemoveFromParent() { } } +controls::Control* Control::HitTest(const Point& point) { + const auto render_object = GetRenderObject()->HitTest(point); + if (render_object) { + const auto control = render_object->GetAttachedControl(); + assert(control); + return control; + } + return nullptr; +} + bool Control::HasFocus() { - auto host = GetWindowHost(); - if (host == nullptr) return false; + auto window = GetWindow(); + if (window == nullptr) return false; - return host->GetFocusControl() == this; + return window->GetFocusControl() == this; } bool Control::CaptureMouse() { - auto host = GetWindowHost(); - if (host == nullptr) return false; + auto window = GetWindow(); + if (window == nullptr) return false; - return host->CaptureMouseFor(this); + return window->SetMouseCaptureControl(this); } void Control::SetFocus() { - auto host = GetWindowHost(); - if (host == nullptr) return; + auto window = GetWindow(); + if (window == nullptr) return; - host->SetFocusControl(this); + window->SetFocusControl(this); } bool Control::ReleaseMouse() { - auto host = GetWindowHost(); - if (host == nullptr) return false; - - return host->CaptureMouseFor(nullptr); + auto window = GetWindow(); + if (window == nullptr) return false; + if (window->GetMouseCaptureControl() != this) return false; + return window->SetMouseCaptureControl(nullptr); } bool Control::IsMouseCaptured() { - auto host = GetWindowHost(); - if (host == nullptr) return false; + auto window = GetWindow(); + if (window == nullptr) return false; - return host->GetMouseCaptureControl() == this; + return window->GetMouseCaptureControl() == this; } std::shared_ptr<ICursor> Control::GetCursor() { return cursor_; } @@ -99,42 +124,13 @@ std::shared_ptr<ICursor> Control::GetInheritedCursor() { void Control::SetCursor(std::shared_ptr<ICursor> cursor) { cursor_ = std::move(cursor); - const auto host = GetWindowHost(); - if (host != nullptr) { - host->UpdateCursor(); + const auto window = GetWindow(); + if (window != nullptr) { + window->UpdateCursor(); } } std::shared_ptr<style::StyleRuleSet> Control::GetStyleRuleSet() { return style_rule_set_; } - -void Control::OnParentChangedCore(Control* old_parent, Control* new_parent) { - auto new_window_host = - new_parent == nullptr ? nullptr : new_parent->GetWindowHost(); - if (window_host_ != new_window_host) { - auto old_host = window_host_; - window_host_ = new_window_host; - OnWindowHostChangedCore(old_host, new_window_host); - } - - if (!in_destruction_) OnParentChanged(old_parent, new_parent); -} - -void Control::OnWindowHostChangedCore(host::WindowHost* old_host, - host::WindowHost* new_host) { - if (old_host != nullptr) { - old_host->OnControlDetach(this); - } - - if (!in_destruction_) { - ForEachChild([old_host, new_host](Control* child) { - child->window_host_ = new_host; - child->OnWindowHostChangedCore(old_host, new_host); - }); - OnWindowHostChanged(old_host, new_host); - } -} - -void Control::OnPrepareDelete() { RemoveFromParent(); } } // namespace cru::ui::controls diff --git a/src/ui/controls/Popup.cpp b/src/ui/controls/Popup.cpp deleted file mode 100644 index 238ddbd4..00000000 --- a/src/ui/controls/Popup.cpp +++ /dev/null @@ -1,18 +0,0 @@ -#include "cru/ui/controls/Popup.h" - -#include "cru/platform/gui/UiApplication.h" -#include "cru/ui/controls/RootControl.h" -#include "cru/ui/host/WindowHost.h" -#include "cru/ui/render/StackLayoutRenderObject.h" - -#include <memory> - -namespace cru::ui::controls { -Popup::Popup(Control* attached_control) : RootControl(attached_control) { - GetWindowHost()->GetNativeWindow()->SetStyleFlag( - cru::platform::gui::WindowStyleFlags::NoCaptionAndBorder); - SetGainFocusOnCreateAndDestroyWhenLoseFocus(true); -} - -Popup::~Popup() = default; -} // namespace cru::ui::controls diff --git a/src/ui/controls/RootControl.cpp b/src/ui/controls/RootControl.cpp deleted file mode 100644 index 7be1c630..00000000 --- a/src/ui/controls/RootControl.cpp +++ /dev/null @@ -1,49 +0,0 @@ -#include "cru/ui/controls/RootControl.h" - -#include "cru/platform/gui/Window.h" -#include "cru/ui/Base.h" -#include "cru/ui/host/WindowHost.h" - -#include <memory> - -namespace cru::ui::controls { -RootControl::RootControl(Control* attached_control) - : attached_control_(attached_control) { - GetContainerRenderObject()->SetDefaultHorizontalAlignment(Alignment::Stretch); - GetContainerRenderObject()->SetDefaultVertialAlignment(Alignment::Stretch); - window_host_ = std::make_unique<host::WindowHost>(this); - - Control::window_host_ = this->window_host_.get(); - - window_host_->SetLayoutPreferToFillWindow(true); -} - -RootControl::~RootControl() {} - -platform::gui::INativeWindow* RootControl::GetNativeWindow() { - return window_host_->GetNativeWindow(); -} - -void RootControl::SetGainFocusOnCreateAndDestroyWhenLoseFocus(bool value) { - gain_focus_on_create_and_destroy_when_lose_focus_event_guard_.Clear(); - if (value) { - auto native_window = window_host_->GetNativeWindow(); - - gain_focus_on_create_and_destroy_when_lose_focus_event_guard_ += - native_window->VisibilityChangeEvent()->AddHandler( - [native_window](platform::gui::WindowVisibilityType type) { - if (type == platform::gui::WindowVisibilityType::Show) { - native_window->RequestFocus(); - } - }); - - gain_focus_on_create_and_destroy_when_lose_focus_event_guard_ += - native_window->FocusEvent()->AddHandler( - [native_window](platform::gui::FocusChangeType type) { - if (type == platform::gui::FocusChangeType::Lose) { - native_window->Close(); - } - }); - } -} -} // namespace cru::ui::controls diff --git a/src/ui/controls/TextHostControlService.cpp b/src/ui/controls/TextHostControlService.cpp index 99090951..f51199c9 100644 --- a/src/ui/controls/TextHostControlService.cpp +++ b/src/ui/controls/TextHostControlService.cpp @@ -13,8 +13,8 @@ #include "cru/ui/Base.h" #include "cru/ui/DebugFlags.h" #include "cru/ui/components/Menu.h" +#include "cru/ui/controls/Window.h" #include "cru/ui/helper/ShortcutHub.h" -#include "cru/ui/host/WindowHost.h" #include "cru/ui/render/ScrollRenderObject.h" #include "cru/ui/render/TextRenderObject.h" @@ -293,9 +293,9 @@ void TextHostControlService::DeleteText(TextRange range, platform::gui::IInputMethodContext* TextHostControlService ::GetInputMethodContext() { - host::WindowHost* host = this->control_->GetWindowHost(); - if (!host) return nullptr; - platform::gui::INativeWindow* native_window = host->GetNativeWindow(); + Window* window = this->control_->GetWindow(); + if (!window) return nullptr; + platform::gui::INativeWindow* native_window = window->GetNativeWindow(); if (!native_window) return nullptr; return native_window->GetInputMethodContext(); } @@ -340,12 +340,13 @@ void TextHostControlService::SetCaretBlinkDuration(int milliseconds) { void TextHostControlService::ScrollToCaret() { if (const auto scroll_render_object = this->GetScrollRenderObject()) { - auto window_host = this->control_->GetWindowHost(); - if (window_host) - window_host->RunAfterLayoutStable([this, scroll_render_object]() { - const auto caret_rect = this->GetTextRenderObject()->GetCaretRect(); - scroll_render_object->ScrollToContain(caret_rect, Thickness{5.f}); - }); + scroll_to_caret_timer_canceler_.Reset( + platform::gui::IUiApplication::GetInstance()->SetImmediate( + [this, scroll_render_object] { + const auto caret_rect = + this->GetTextRenderObject()->GetCaretRect(); + scroll_render_object->ScrollToContain(caret_rect, Thickness{5.f}); + })); } } @@ -587,10 +588,10 @@ void TextHostControlService::GainFocusHandler( this->ReplaceSelectedText(text); }); - host::WindowHost* window_host = control_->GetWindowHost(); - if (window_host) + auto window = control_->GetWindow(); + if (window) input_method_context_event_guard_ += - window_host->AfterLayoutEvent()->AddHandler( + window->AfterLayoutEvent()->AddHandler( [this](auto) { this->UpdateInputMethodPosition(); }); SetCaretVisible(true); } @@ -700,7 +701,9 @@ void TextHostControlService::SetUpShortcuts() { void TextHostControlService::OpenContextMenu(const Point& position, ContextMenuItem items) { CRU_LOG_TAG_DEBUG("Open context menu."); - context_menu_.reset(new components::PopupMenu()); + if (!context_menu_) { + context_menu_.reset(new components::PopupMenu()); + } auto menu = context_menu_->GetMenu(); menu->ClearItems(); if (items & ContextMenuItem::kSelectAll) { diff --git a/src/ui/controls/Window.cpp b/src/ui/controls/Window.cpp index b1881136..e6abc48d 100644 --- a/src/ui/controls/Window.cpp +++ b/src/ui/controls/Window.cpp @@ -1,7 +1,431 @@ #include "cru/ui/controls/Window.h" +#include "cru/platform/gui/UiApplication.h" +#include "cru/platform/gui/Window.h" +#include "cru/ui/Base.h" + +#include <cassert> + namespace cru::ui::controls { -Window::Window(Control* attached_control) : RootControl(attached_control) {} +Window::Window() + : event_handling_count_(0), + native_window_(CreateNativeWindow()), + focus_control_(this), + mouse_hover_control_(nullptr), + mouse_captured_control_(nullptr), + layout_prefer_to_fill_window_(true), + attached_control_(nullptr) { + GetContainerRenderObject()->SetDefaultHorizontalAlignment(Alignment::Stretch); + GetContainerRenderObject()->SetDefaultVertialAlignment(Alignment::Stretch); +} Window::~Window() {} + +Window* Window::CreatePopup() { + auto window = new Window(); + window->GetNativeWindow()->SetStyleFlag( + platform::gui::WindowStyleFlags::NoCaptionAndBorder); + window->SetGainFocusOnCreateAndDestroyWhenLoseFocus(true); + return window; +} + +std::string Window::GetControlType() const { return std::string(kControlType); } + +void Window::SetAttachedControl(Control* control) { + attached_control_ = control; +} + +platform::gui::INativeWindow* Window::GetNativeWindow() { + return native_window_.get(); +} + +void Window::SetGainFocusOnCreateAndDestroyWhenLoseFocus(bool value) { + gain_focus_on_create_and_destroy_when_lose_focus_event_guard_.Clear(); + if (value) { + gain_focus_on_create_and_destroy_when_lose_focus_event_guard_ += + native_window_->VisibilityChangeEvent()->AddHandler( + [this](platform::gui::WindowVisibilityType type) { + if (type == platform::gui::WindowVisibilityType::Show) { + native_window_->RequestFocus(); + } + }); + + gain_focus_on_create_and_destroy_when_lose_focus_event_guard_ += + native_window_->FocusEvent()->AddHandler( + [this](platform::gui::FocusChangeType type) { + if (type == platform::gui::FocusChangeType::Lose) { + native_window_->Close(); + } + }); + } +} + +namespace { +template <typename T> +inline void BindNativeEvent( + Window* window, platform::gui::INativeWindow* native_window, + IEvent<T>* event, + void (Window::*handler)(platform::gui::INativeWindow*, + typename IEvent<T>::Args)) { + event->AddHandler( + std::bind(handler, window, native_window, std::placeholders::_1)); +} +} // namespace + +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<Control*> GetAncestorList(Control* control) { + if (control == nullptr) return {}; + + std::vector<Control*> 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 + +std::unique_ptr<platform::gui::INativeWindow> Window::CreateNativeWindow() { + const auto ui_application = platform::gui::IUiApplication::GetInstance(); + + auto native_window = ui_application->CreateWindow(); + assert(native_window); + + BindNativeEvent(this, native_window, native_window->DestroyEvent(), + &Window::OnNativeDestroy); + BindNativeEvent(this, native_window, native_window->PaintEvent(), + &Window::OnNativePaint); + BindNativeEvent(this, native_window, native_window->ResizeEvent(), + &Window::OnNativeResize); + BindNativeEvent(this, native_window, native_window->FocusEvent(), + &Window::OnNativeFocus); + BindNativeEvent(this, native_window, native_window->MouseEnterLeaveEvent(), + &Window::OnNativeMouseEnterLeave); + BindNativeEvent(this, native_window, native_window->MouseMoveEvent(), + &Window::OnNativeMouseMove); + BindNativeEvent(this, native_window, native_window->MouseDownEvent(), + &Window::OnNativeMouseDown); + BindNativeEvent(this, native_window, native_window->MouseUpEvent(), + &Window::OnNativeMouseUp); + BindNativeEvent(this, native_window, native_window->MouseWheelEvent(), + &Window::OnNativeMouseWheel); + BindNativeEvent(this, native_window, native_window->KeyDownEvent(), + &Window::OnNativeKeyDown); + BindNativeEvent(this, native_window, native_window->KeyUpEvent(), + &Window::OnNativeKeyUp); + + return std::unique_ptr<platform::gui::INativeWindow>(native_window); +} + +void Window::InvalidatePaint() { + repaint_schedule_canceler_.Reset( + platform::gui::IUiApplication::GetInstance()->SetImmediate( + [this] { Repaint(); })); +} + +void Window::InvalidateLayout() { + relayout_schedule_canceler_.Reset( + platform::gui::IUiApplication::GetInstance()->SetImmediate( + [this] { Relayout(); })); +} + +bool Window::IsLayoutPreferToFillWindow() const { + return layout_prefer_to_fill_window_; +} + +void Window::SetLayoutPreferToFillWindow(bool value) { + if (value == layout_prefer_to_fill_window_) return; + layout_prefer_to_fill_window_ = value; + InvalidateLayout(); +} + +void Window::Repaint() { + auto painter = native_window_->BeginPaint(); + painter->Clear(colors::white); + GetRenderObject()->Draw(painter.get()); + painter->EndDraw(); +} + +void Window::Relayout() { RelayoutWithSize(native_window_->GetClientSize()); } + +void Window::RelayoutWithSize(const Size& available_size, + bool set_window_size_to_fit_content) { + auto render_object = GetRenderObject(); + render_object->Measure( + render::MeasureRequirement{ + available_size, + !set_window_size_to_fit_content && IsLayoutPreferToFillWindow() + ? render::MeasureSize(available_size) + : render::MeasureSize::NotSpecified()}, + render::MeasureSize::NotSpecified()); + + if (set_window_size_to_fit_content) { + native_window_->SetClientSize(render_object->GetDesiredSize()); + } + + render_object->Layout(Point{}); + CRU_LOG_TAG_DEBUG("A relayout is finished."); + + AfterLayoutEvent_.Raise(nullptr); + + InvalidatePaint(); +} + +Control* Window::GetFocusControl() { return focus_control_; } + +void Window::SetFocusControl(Control* control) { + if (control == nullptr) control = this; + if (focus_control_ == control) return; + + const auto old_focus_control = focus_control_; + + focus_control_ = control; + + DispatchFocusControlChangeEvent(old_focus_control, focus_control_, false); +} + +Control* Window::GetMouseCaptureControl() { return mouse_captured_control_; } + +bool Window::SetMouseCaptureControl(Control* control) { + if (!native_window_->CaptureMouse()) return false; + + if (control == mouse_captured_control_) return true; + + if (control == nullptr) { + native_window_->ReleaseMouse(); + 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; +} + +std::shared_ptr<platform::gui::ICursor> Window::GetOverrideCursor() { + return override_cursor_; +} + +void Window::SetOverrideCursor(std::shared_ptr<platform::gui::ICursor> cursor) { + if (cursor == override_cursor_) return; + override_cursor_ = cursor; + UpdateCursor(); +} + +bool Window::IsInEventHandling() { return event_handling_count_; } + +void Window::UpdateCursor() { + if (override_cursor_) { + native_window_->SetCursor(override_cursor_); + } else { + const auto capture = GetMouseCaptureControl(); + native_window_->SetCursor( + (capture ? capture : GetMouseHoverControl())->GetInheritedCursor()); + } +} + +void Window::OnNativeDestroy(platform::gui::INativeWindow* window, + std::nullptr_t) { + CRU_UNUSED(window) +} + +void Window::OnNativePaint(platform::gui::INativeWindow* window, + std::nullptr_t) { + CRU_UNUSED(window) + InvalidatePaint(); +} + +void Window::OnNativeResize(platform::gui::INativeWindow* window, + const Size& size) { + CRU_UNUSED(window) + CRU_UNUSED(size) + + InvalidateLayout(); +} + +void Window::OnNativeFocus(platform::gui::INativeWindow* window, + platform::gui::FocusChangeType focus) { + CRU_UNUSED(window) + + focus == platform::gui::FocusChangeType::Gain + ? DispatchEvent(focus_control_, &Control::GainFocusEvent, nullptr, true) + : DispatchEvent(focus_control_, &Control::LoseFocusEvent, nullptr, true); +} + +void Window::OnNativeMouseEnterLeave(platform::gui::INativeWindow* window, + platform::gui::MouseEnterLeaveType type) { + CRU_UNUSED(window) + + if (type == platform::gui::MouseEnterLeaveType::Leave) { + DispatchEvent(mouse_hover_control_, &Control::MouseLeaveEvent, nullptr); + mouse_hover_control_ = nullptr; + } +} + +void Window::OnNativeMouseMove(platform::gui::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(o, &Control::MouseLeaveEvent, n); + } else { + DispatchEvent(n, &Control::MouseEnterEvent, o, point); + } + DispatchEvent(mouse_captured_control_, &Control::MouseMoveEvent, nullptr, + point); + UpdateCursor(); + return; + } + + DispatchMouseHoverControlChangeEvent( + old_mouse_hover_control, new_mouse_hover_control, point, false, false); + DispatchEvent(new_mouse_hover_control, &Control::MouseMoveEvent, nullptr, + point); + UpdateCursor(); +} + +void Window::OnNativeMouseDown( + platform::gui::INativeWindow* window, + const platform::gui::NativeMouseButtonEventArgs& args) { + CRU_UNUSED(window) + + Control* control = + mouse_captured_control_ ? mouse_captured_control_ : HitTest(args.point); + DispatchEvent(control, &Control::MouseDownEvent, nullptr, args.point, + args.button, args.modifier); +} + +void Window::OnNativeMouseUp( + platform::gui::INativeWindow* window, + const platform::gui::NativeMouseButtonEventArgs& args) { + CRU_UNUSED(window) + + Control* control = + mouse_captured_control_ ? mouse_captured_control_ : HitTest(args.point); + DispatchEvent(control, &Control::MouseUpEvent, nullptr, args.point, + args.button, args.modifier); +} + +void Window::OnNativeMouseWheel( + platform::gui::INativeWindow* window, + const platform::gui::NativeMouseWheelEventArgs& args) { + CRU_UNUSED(window) + + Control* control = + mouse_captured_control_ ? mouse_captured_control_ : HitTest(args.point); + DispatchEvent(control, &Control::MouseWheelEvent, nullptr, args.point, + args.delta, args.modifier); +} + +void Window::OnNativeKeyDown(platform::gui::INativeWindow* window, + const platform::gui::NativeKeyEventArgs& args) { + CRU_UNUSED(window) + + DispatchEvent(focus_control_, &Control::KeyDownEvent, nullptr, args.key, + args.modifier); +} + +void Window::OnNativeKeyUp(platform::gui::INativeWindow* window, + const platform::gui::NativeKeyEventArgs& args) { + CRU_UNUSED(window) + + DispatchEvent(focus_control_, &Control::KeyUpEvent, nullptr, args.key, + args.modifier); +} + +void Window::DispatchFocusControlChangeEvent(Control* old_control, + Control* new_control, + bool is_window) { + if (new_control != old_control) { + const auto lowest_common_ancestor = + FindLowestCommonAncestor(old_control, new_control); + DispatchEvent(old_control, &Control::LoseFocusEvent, lowest_common_ancestor, + is_window); + DispatchEvent(new_control, &Control::GainFocusEvent, lowest_common_ancestor, + is_window); + } +} + +void Window::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(old_control, &Control::MouseLeaveEvent, + lowest_common_ancestor); // dispatch mouse leave event. + if (!no_enter && new_control != nullptr) { + DispatchEvent(new_control, &Control::MouseEnterEvent, + lowest_common_ancestor, + point); // dispatch mouse enter event. + } + } +} + } // namespace cru::ui::controls diff --git a/src/ui/events/MouseEventArgs.cpp b/src/ui/events/MouseEventArgs.cpp index cad860d7..bbbf5cd4 100644 --- a/src/ui/events/MouseEventArgs.cpp +++ b/src/ui/events/MouseEventArgs.cpp @@ -1,7 +1,7 @@ #include "cru/ui/events/MouseEventArgs.h" #include "cru/ui/controls/Control.h" -#include "cru/ui/host/WindowHost.h" +#include "cru/ui/controls/Window.h" #include "cru/ui/render/RenderObject.h" namespace cru::ui::events { @@ -17,11 +17,9 @@ Point MouseEventArgs::GetPointToContent( Point MouseEventArgs::GetPointOfScreen() const { auto sender = GetSender(); if (auto control = dynamic_cast<controls::Control*>(sender)) { - if (auto host = control->GetWindowHost()) - return GetPoint() + control->GetWindowHost() - ->GetNativeWindow() - ->GetClientRect() - .GetLeftTop(); + if (auto window = control->GetWindow()) + return GetPoint() + + window->GetNativeWindow()->GetClientRect().GetLeftTop(); } return GetPoint(); } diff --git a/src/ui/helper/ClickDetector.cpp b/src/ui/helper/ClickDetector.cpp index 796c3ad4..be1cbca2 100644 --- a/src/ui/helper/ClickDetector.cpp +++ b/src/ui/helper/ClickDetector.cpp @@ -3,16 +3,14 @@ #include "cru/base/log/Logger.h" #include "cru/ui/DebugFlags.h" #include "cru/ui/controls/Control.h" -#include "cru/ui/host/WindowHost.h" - -#include <string> +#include "cru/ui/controls/Window.h" namespace cru::ui::helper { Point ClickEventArgs::GetDownPointOfScreen() const { - auto window_host = sender_->GetWindowHost(); - if (window_host != nullptr) { - auto window = window_host->GetNativeWindow(); - return down_point_ + window->GetClientRect().GetLeftTop(); + auto window = sender_->GetWindow(); + if (window != nullptr) { + return down_point_ + + window->GetNativeWindow()->GetClientRect().GetLeftTop(); } else { return down_point_; } diff --git a/src/ui/host/LayoutPaintCycler.cpp b/src/ui/host/LayoutPaintCycler.cpp deleted file mode 100644 index 7f8523d4..00000000 --- a/src/ui/host/LayoutPaintCycler.cpp +++ /dev/null @@ -1,35 +0,0 @@ -#include "cru/ui/host/LayoutPaintCycler.h" -#include <chrono> - -#include "../Helper.h" -#include "cru/ui/Base.h" -#include "cru/ui/host/WindowHost.h" - -namespace cru::ui::host { -LayoutPaintCycler::LayoutPaintCycler(WindowHost* host) : host_(host) { - timer_canceler_ = GetUiApplication()->SetInterval( - std::chrono::duration_cast<std::chrono::milliseconds>( - this->cycle_threshold_), - [this] { OnCycle(); }); -} - -LayoutPaintCycler::~LayoutPaintCycler() = default; - -void LayoutPaintCycler::InvalidateLayout() { layout_dirty_ = true; } - -void LayoutPaintCycler::InvalidatePaint() { paint_dirty_ = true; } - -void LayoutPaintCycler::OnCycle() { - last_cycle_time_ = std::chrono::steady_clock::now(); - if (layout_dirty_) { - host_->Relayout(); - host_->Repaint(); - } else { - if (paint_dirty_) { - host_->Repaint(); - } - } - layout_dirty_ = false; - paint_dirty_ = false; -} -} // namespace cru::ui::host diff --git a/src/ui/host/RoutedEventDispatch.h b/src/ui/host/RoutedEventDispatch.h deleted file mode 100644 index 6e8b4af1..00000000 --- a/src/ui/host/RoutedEventDispatch.h +++ /dev/null @@ -1,122 +0,0 @@ -#pragma once -#include "cru/base/log/Logger.h" -#include "cru/ui/DebugFlags.h" -#include "cru/ui/controls/Control.h" -#include "cru/ui/host/WindowHost.h" - -#include <vector> - -namespace cru::ui::host { -// Dispatch the event. -// -// This will raise routed event of the control and its parent and parent's -// parent ... (until "last_receiver" if it's not nullptr) with appropriate args. -// -// First tunnel from top to bottom possibly stopped by "handled" flag in -// EventArgs. Second bubble from bottom to top possibly stopped by "handled" -// flag in EventArgs. Last direct to each control. -// -// Args is of type "EventArgs". The first init argument is "sender", which is -// automatically bound to each receiving control. The second init argument is -// "original_sender", which is unchanged. And "args" will be perfectly forwarded -// as the rest arguments. -template <typename EventArgs, typename... Args> -void DispatchEvent( - const std::string& event_name, controls::Control* const original_sender, - events::RoutedEvent<EventArgs>* (controls::Control::*event_ptr)(), - controls::Control* const last_receiver, Args&&... args) { - constexpr auto kLogTag = "DispatchEvent"; - - if (original_sender == nullptr) return; - - CRU_UNUSED(event_name) - - if (original_sender == last_receiver) { - if constexpr (debug_flags::routed_event) - CRU_LOG_TAG_DEBUG( - "Routed event {} no need to dispatch (original_sender == " - "last_receiver). Original sender is {}.", - event_name, original_sender->GetControlType()); - return; - } - - WindowHost::EnterEventHandling(); - - std::vector<controls::Control*> receive_list; - - auto parent = original_sender; - while (parent != last_receiver) { - receive_list.push_back(parent); - auto p = parent->GetParent(); - assert(!(p == nullptr && last_receiver != nullptr)); - parent = p; - } - - if constexpr (debug_flags::routed_event) { - std::string log = "Dispatch routed event "; - log += event_name; - log += ". Path (parent first): "; - auto i = receive_list.crbegin(); - const auto end = --receive_list.crend(); - for (; i != end; ++i) { - log += (*i)->GetControlType(); - log += " -> "; - } - log += (*i)->GetControlType(); - CRU_LOG_TAG_DEBUG("{}", log); - } - - auto handled = false; - - int count = 0; - - // tunnel - for (auto i = receive_list.crbegin(); i != receive_list.crend(); ++i) { - count++; - auto control = *i; - EventArgs event_args(control, original_sender, std::forward<Args>(args)...); - static_cast<Event<EventArgs&>*>((control->*event_ptr)()->Tunnel()) - ->Raise(event_args); - if (event_args.IsHandled()) { - handled = true; - if constexpr (debug_flags::routed_event) - CRU_LOG_TAG_DEBUG( - "Routed event is short-circuit in TUNNEL at {}-st control (count " - "from parent).", - count); - break; - } - } - - // bubble - if (!handled) { - for (auto control : receive_list) { - count--; - EventArgs event_args(control, original_sender, - std::forward<Args>(args)...); - static_cast<Event<EventArgs&>*>((control->*event_ptr)()->Bubble()) - ->Raise(event_args); - if (event_args.IsHandled()) { - if constexpr (debug_flags::routed_event) - CRU_LOG_TAG_DEBUG( - "Routed event is short-circuit in BUBBLE at {}-st control " - "(count from parent).", - count); - break; - } - } - } - - // direct - for (auto control : receive_list) { - EventArgs event_args(control, original_sender, std::forward<Args>(args)...); - static_cast<Event<EventArgs&>*>((control->*event_ptr)()->Direct()) - ->Raise(event_args); - } - - if constexpr (debug_flags::routed_event) - CRU_LOG_TAG_DEBUG("Routed event dispatch finished."); - - WindowHost::LeaveEventHandling(); -} -} // namespace cru::ui::host diff --git a/src/ui/host/WindowHost.cpp b/src/ui/host/WindowHost.cpp deleted file mode 100644 index ec956bb4..00000000 --- a/src/ui/host/WindowHost.cpp +++ /dev/null @@ -1,457 +0,0 @@ -#include "cru/ui/host/WindowHost.h" - -#include "RoutedEventDispatch.h" -#include "cru/base/Base.h" -#include "cru/base/log/Logger.h" -#include "cru/platform/graphics/Painter.h" -#include "cru/platform/gui/UiApplication.h" -#include "cru/platform/gui/Window.h" -#include "cru/ui/Base.h" -#include "cru/ui/DebugFlags.h" -#include "cru/ui/host/LayoutPaintCycler.h" -#include "cru/ui/render/MeasureRequirement.h" -#include "cru/ui/render/RenderObject.h" - -#include <cstddef> -#include <memory> - -namespace cru::ui::host { -using platform::gui::INativeWindow; -using platform::gui::IUiApplication; - -namespace event_names { -#define CRU_DEFINE_EVENT_NAME(name) constexpr const char* name = #name; - -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(MouseWheel) -CRU_DEFINE_EVENT_NAME(KeyDown) -CRU_DEFINE_EVENT_NAME(KeyUp) - -#undef CRU_DEFINE_EVENT_NAME -} // namespace event_names - -namespace { -bool IsAncestor(controls::Control* control, controls::Control* ancestor) { - while (control != nullptr) { - if (control == ancestor) return true; - control = control->GetParent(); - } - return false; -} - -// Ancestor at last. -std::vector<controls::Control*> GetAncestorList(controls::Control* control) { - if (control == nullptr) return {}; - - std::vector<controls::Control*> l; - while (control != nullptr) { - l.push_back(control); - control = control->GetParent(); - } - return l; -} - -controls::Control* FindLowestCommonAncestor(controls::Control* left, - controls::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 <typename T> -inline void BindNativeEvent( - WindowHost* host, INativeWindow* native_window, IEvent<T>* event, - void (WindowHost::*handler)(INativeWindow*, typename IEvent<T>::Args), - std::vector<EventHandlerRevokerGuard>& guard_pool) { - guard_pool.push_back(EventHandlerRevokerGuard(event->AddHandler( - std::bind(handler, host, native_window, std::placeholders::_1)))); -} -} // namespace - -int WindowHost::event_handling_depth_ = 0; - -void WindowHost::EnterEventHandling() { event_handling_depth_++; } - -void WindowHost::LeaveEventHandling() { - Expects(event_handling_depth_ > 0); - event_handling_depth_--; -} - -WindowHost::WindowHost(controls::Control* root_control) - : root_control_(root_control), focus_control_(root_control) { - root_render_object_ = root_control->GetRenderObject(); - - this->layout_paint_cycler_ = std::make_unique<LayoutPaintCycler>(this); - - native_window_ = CreateNativeWindow(); -} - -WindowHost::~WindowHost() {} - -std::unique_ptr<platform::gui::INativeWindow> WindowHost::CreateNativeWindow() { - const auto ui_application = IUiApplication::GetInstance(); - - auto native_window = ui_application->CreateWindow(); - Ensures(native_window); - - 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->MouseWheelEvent(), - &WindowHost::OnNativeMouseWheel, 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_); - - native_window_change_event_.Raise(native_window); - - return std::unique_ptr<INativeWindow>(native_window); -} - -void WindowHost::InvalidatePaint() { - if (layout_paint_cycler_) layout_paint_cycler_->InvalidatePaint(); -} - -void WindowHost::InvalidateLayout() { - if (layout_paint_cycler_) layout_paint_cycler_->InvalidateLayout(); -} - -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 available_size = - native_window_ ? native_window_->GetClientSize() : Size::Infinite(); - RelayoutWithSize(available_size); -} - -void WindowHost::RelayoutWithSize(const Size& available_size, - bool set_window_size_to_fit_content) { - root_render_object_->Measure( - render::MeasureRequirement{ - available_size, - !set_window_size_to_fit_content && IsLayoutPreferToFillWindow() - ? render::MeasureSize(available_size) - : render::MeasureSize::NotSpecified()}, - render::MeasureSize::NotSpecified()); - - if (set_window_size_to_fit_content) { - native_window_->SetClientSize(root_render_object_->GetDesiredSize()); - } - - root_render_object_->Layout(Point{}); - for (auto& action : after_layout_stable_action_) action(); - after_layout_event_.Raise(AfterLayoutEventArgs{}); - after_layout_stable_action_.clear(); - if constexpr (debug_flags::layout) - CRU_LOG_TAG_DEBUG("A relayout is finished."); -} - -void WindowHost::Repaint() { - if (native_window_ == nullptr) return; - auto painter = native_window_->BeginPaint(); - painter->Clear(colors::white); - root_render_object_->Draw(painter.get()); - painter->EndDraw(); -} - -controls::Control* WindowHost::GetFocusControl() { return focus_control_; } - -void WindowHost::SetFocusControl(controls::Control* control) { - if (focus_control_ == control) return; - if (control == nullptr) control = root_control_; - - const auto old_focus_control = focus_control_; - - focus_control_ = control; - - DispatchEvent(event_names::LoseFocus, old_focus_control, - &controls::Control::LoseFocusEvent, nullptr, false); - - DispatchEvent(event_names::GainFocus, control, - &controls::Control::GainFocusEvent, nullptr, false); -} - -bool WindowHost::CaptureMouseFor(controls::Control* control) { - if (!native_window_) return false; - if (!native_window_->CaptureMouse()) return false; - - if (control == mouse_captured_control_) return true; - - if (control == nullptr) { - native_window_->ReleaseMouse(); - 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; -} - -controls::Control* WindowHost::GetMouseCaptureControl() { - return mouse_captured_control_; -} - -void WindowHost::RunAfterLayoutStable(std::function<void()> action) { - if (layout_paint_cycler_->IsLayoutDirty()) { - after_layout_stable_action_.push_back(std::move(action)); - } else { - action(); - } -} - -void WindowHost::OnNativeDestroy(INativeWindow* window, std::nullptr_t) { - CRU_UNUSED(window) -} - -void WindowHost::OnNativePaint(INativeWindow* window, std::nullptr_t) { - CRU_UNUSED(window) - layout_paint_cycler_->InvalidatePaint(); -} - -void WindowHost::OnNativeResize(INativeWindow* window, const Size& size) { - CRU_UNUSED(window) - CRU_UNUSED(size) - - InvalidateLayout(); -} - -void WindowHost::OnNativeFocus(INativeWindow* window, - platform::gui::FocusChangeType focus) { - CRU_UNUSED(window) - - focus == platform::gui::FocusChangeType::Gain - ? DispatchEvent(event_names::GainFocus, focus_control_, - &controls::Control::GainFocusEvent, nullptr, true) - : DispatchEvent(event_names::LoseFocus, focus_control_, - &controls::Control::LoseFocusEvent, nullptr, true); -} - -void WindowHost::OnNativeMouseEnterLeave( - INativeWindow* window, platform::gui::MouseEnterLeaveType type) { - CRU_UNUSED(window) - - if (type == platform::gui::MouseEnterLeaveType::Leave) { - DispatchEvent(event_names::MouseLeave, mouse_hover_control_, - &controls::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, - &controls::Control::MouseLeaveEvent, n); - } else { - DispatchEvent(event_names::MouseEnter, n, - &controls::Control::MouseEnterEvent, o, point); - } - DispatchEvent(event_names::MouseMove, mouse_captured_control_, - &controls::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, - &controls::Control::MouseMoveEvent, nullptr, point); - UpdateCursor(); -} - -void WindowHost::OnNativeMouseDown( - INativeWindow* window, - const platform::gui::NativeMouseButtonEventArgs& args) { - CRU_UNUSED(window) - - controls::Control* control = - mouse_captured_control_ ? mouse_captured_control_ : HitTest(args.point); - DispatchEvent(event_names::MouseDown, control, - &controls::Control::MouseDownEvent, nullptr, args.point, - args.button, args.modifier); -} - -void WindowHost::OnNativeMouseUp( - INativeWindow* window, - const platform::gui::NativeMouseButtonEventArgs& args) { - CRU_UNUSED(window) - - controls::Control* control = - mouse_captured_control_ ? mouse_captured_control_ : HitTest(args.point); - DispatchEvent(event_names::MouseUp, control, &controls::Control::MouseUpEvent, - nullptr, args.point, args.button, args.modifier); -} - -void WindowHost::OnNativeMouseWheel( - platform::gui::INativeWindow* window, - const platform::gui::NativeMouseWheelEventArgs& args) { - CRU_UNUSED(window) - - controls::Control* control = - mouse_captured_control_ ? mouse_captured_control_ : HitTest(args.point); - DispatchEvent(event_names::MouseWheel, control, - &controls::Control::MouseWheelEvent, nullptr, args.point, - args.delta, args.modifier); -} - -void WindowHost::OnNativeKeyDown( - INativeWindow* window, const platform::gui::NativeKeyEventArgs& args) { - CRU_UNUSED(window) - - DispatchEvent(event_names::KeyDown, focus_control_, - &controls::Control::KeyDownEvent, nullptr, args.key, - args.modifier); -} - -void WindowHost::OnNativeKeyUp(INativeWindow* window, - const platform::gui::NativeKeyEventArgs& args) { - CRU_UNUSED(window) - - DispatchEvent(event_names::KeyUp, focus_control_, - &controls::Control::KeyUpEvent, nullptr, args.key, - args.modifier); -} - -void WindowHost::DispatchMouseHoverControlChangeEvent( - controls::Control* old_control, controls::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, - &controls::Control::MouseLeaveEvent, - lowest_common_ancestor); // dispatch mouse leave event. - if (!no_enter && new_control != nullptr) { - DispatchEvent(event_names::MouseEnter, new_control, - &controls::Control::MouseEnterEvent, lowest_common_ancestor, - point); // dispatch mouse enter event. - } - } -} - -void WindowHost::UpdateCursor() { - if (native_window_) { - if (override_cursor_) { - native_window_->SetCursor(override_cursor_); - } else { - const auto capture = GetMouseCaptureControl(); - native_window_->SetCursor( - (capture ? capture : GetMouseHoverControl())->GetInheritedCursor()); - } - } -} - -controls::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 root_control_; -} - -std::shared_ptr<platform::gui::ICursor> WindowHost::GetOverrideCursor() { - return override_cursor_; -} - -void WindowHost::SetOverrideCursor( - std::shared_ptr<platform::gui::ICursor> cursor) { - if (cursor == override_cursor_) return; - override_cursor_ = cursor; - UpdateCursor(); -} - -void WindowHost::OnControlDetach(controls::Control* control) { - if (GetFocusControl() == control) { - SetFocusControl(nullptr); - } - if (GetMouseCaptureControl() == control) { - CaptureMouseFor(nullptr); - } - if (GetMouseHoverControl() == control) { - mouse_hover_control_ = HitTest(GetNativeWindow()->GetMousePosition()); - } -} -} // namespace cru::ui::host diff --git a/src/ui/render/RenderObject.cpp b/src/ui/render/RenderObject.cpp index fcd44143..a4da2414 100644 --- a/src/ui/render/RenderObject.cpp +++ b/src/ui/render/RenderObject.cpp @@ -4,7 +4,7 @@ #include "cru/platform/GraphicsBase.h" #include "cru/ui/DebugFlags.h" #include "cru/ui/controls/Control.h" -#include "cru/ui/host/WindowHost.h" +#include "cru/ui/controls/Window.h" namespace cru::ui::render { const BoxConstraint BoxConstraint::kNotLimit{Size::kMax, Size::kZero}; @@ -265,22 +265,22 @@ Rect RenderObject::GetContentRect() const { return rect; } -host::WindowHost* RenderObject::GetWindowHost() { +controls::Window* RenderObject::GetWindow() { if (control_) { - return control_->GetWindowHost(); + return control_->GetWindow(); } return nullptr; } void RenderObject::InvalidateLayout() { - if (auto window_host = GetWindowHost()) { - window_host->InvalidateLayout(); + if (auto window = GetWindow()) { + window->InvalidateLayout(); } } void RenderObject::InvalidatePaint() { - if (auto window_host = GetWindowHost()) { - window_host->InvalidatePaint(); + if (auto window = GetWindow()) { + window->InvalidatePaint(); } } diff --git a/src/ui/render/ScrollBar.cpp b/src/ui/render/ScrollBar.cpp index 804395e4..3834fffb 100644 --- a/src/ui/render/ScrollBar.cpp +++ b/src/ui/render/ScrollBar.cpp @@ -8,7 +8,7 @@ #include "cru/platform/gui/Cursor.h" #include "cru/ui/Base.h" #include "cru/ui/ThemeManager.h" -#include "cru/ui/host/WindowHost.h" +#include "cru/ui/controls/Window.h" #include "cru/ui/render/ScrollRenderObject.h" #include <algorithm> @@ -317,23 +317,23 @@ void ScrollBar::OnDraw(platform::graphics::IPainter* painter, void ScrollBar::SetCursor() { if (const auto control = render_object_->GetAttachedControl()) { - if (const auto window_host = control->GetWindowHost()) { - window_host->SetOverrideCursor( + if (const auto window = control->GetWindow()) { + window->SetOverrideCursor( GetUiApplication()->GetCursorManager()->GetSystemCursor( platform::gui::SystemCursorType::Arrow)); - cursor_overrided_ = true; + cursor_overridden_ = true; } } } void ScrollBar::RestoreCursor() { - if (cursor_overrided_) { + if (cursor_overridden_) { if (const auto control = render_object_->GetAttachedControl()) { - if (const auto window_host = control->GetWindowHost()) { - window_host->SetOverrideCursor(nullptr); + if (const auto window = control->GetWindow()) { + window->SetOverrideCursor(nullptr); } } - cursor_overrided_ = false; + cursor_overridden_ = false; } } |
