From a176c40ba0f913f98e966f11bad557833ae6dc57 Mon Sep 17 00:00:00 2001 From: crupest Date: Fri, 30 Oct 2020 11:25:57 +0800 Subject: ... --- src/ui/CMakeLists.txt | 6 +- src/ui/Control.cpp | 9 +- src/ui/RoutedEventDispatch.hpp | 110 --------- src/ui/Window.cpp | 4 +- src/ui/WindowHost.cpp | 400 --------------------------------- src/ui/controls/TextControlService.hpp | 4 +- src/ui/host/RoutedEventDispatch.hpp | 110 +++++++++ src/ui/host/WindowHost.cpp | 400 +++++++++++++++++++++++++++++++++ src/ui/render/RenderObject.cpp | 4 +- 9 files changed, 523 insertions(+), 524 deletions(-) delete mode 100644 src/ui/RoutedEventDispatch.hpp delete mode 100644 src/ui/WindowHost.cpp create mode 100644 src/ui/host/RoutedEventDispatch.hpp create mode 100644 src/ui/host/WindowHost.cpp (limited to 'src') diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt index 45f64c88..d7b924cb 100644 --- a/src/ui/CMakeLists.txt +++ b/src/ui/CMakeLists.txt @@ -2,7 +2,7 @@ set(CRU_UI_INCLUDE_DIR ${CRU_INCLUDE_DIR}/cru/ui) add_library(cru_ui STATIC Helper.hpp - RoutedEventDispatch.hpp + host/RoutedEventDispatch.hpp ClickDetector.cpp ContentControl.cpp @@ -14,7 +14,6 @@ add_library(cru_ui STATIC UiEvent.cpp UiManager.cpp Window.cpp - WindowHost.cpp controls/Button.cpp controls/Container.cpp controls/FlexLayout.cpp @@ -22,6 +21,7 @@ add_library(cru_ui STATIC controls/TextBlock.cpp controls/TextBox.cpp controls/TextControlService.hpp + host/WindowHost.cpp render/BorderRenderObject.cpp render/CanvasRenderObject.cpp render/FlexLayoutRenderObject.cpp @@ -43,7 +43,6 @@ target_sources(cru_ui PUBLIC ${CRU_UI_INCLUDE_DIR}/UiEvent.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 @@ -51,6 +50,7 @@ target_sources(cru_ui PUBLIC ${CRU_UI_INCLUDE_DIR}/controls/StackLayout.hpp ${CRU_UI_INCLUDE_DIR}/controls/TextBox.hpp ${CRU_UI_INCLUDE_DIR}/controls/TextBlock.hpp + ${CRU_UI_INCLUDE_DIR}/host/WindowHost.hpp ${CRU_UI_INCLUDE_DIR}/render/Base.hpp ${CRU_UI_INCLUDE_DIR}/render/BorderRenderObject.hpp ${CRU_UI_INCLUDE_DIR}/render/CanvasRenderObject.hpp diff --git a/src/ui/Control.cpp b/src/ui/Control.cpp index cae48c66..23a3cef2 100644 --- a/src/ui/Control.cpp +++ b/src/ui/Control.cpp @@ -1,11 +1,10 @@ #include "cru/ui/Control.hpp" -#include "RoutedEventDispatch.hpp" #include "cru/common/Base.hpp" #include "cru/platform/gui/Cursor.hpp" #include "cru/platform/gui/UiApplication.hpp" #include "cru/ui/Base.hpp" -#include "cru/ui/WindowHost.hpp" +#include "cru/ui/host/WindowHost.hpp" #include "cru/ui/render/RenderObject.hpp" #include @@ -31,7 +30,7 @@ Control::~Control() { for (const auto child : children_) delete child; } -WindowHost* Control::GetWindowHost() const { return window_host_; } +host::WindowHost* Control::GetWindowHost() const { return window_host_; } void Control::TraverseDescendants( const std::function& predicate) { @@ -152,7 +151,7 @@ void Control::OnParentChanged(Control* old_parent, Control* new_parent) { CRU_UNUSED(new_parent) } -void Control::OnAttachToHost(WindowHost* host) { CRU_UNUSED(host) } +void Control::OnAttachToHost(host::WindowHost* host) { CRU_UNUSED(host) } -void Control::OnDetachFromHost(WindowHost* host) { CRU_UNUSED(host) } +void Control::OnDetachFromHost(host::WindowHost* host) { CRU_UNUSED(host) } } // namespace cru::ui diff --git a/src/ui/RoutedEventDispatch.hpp b/src/ui/RoutedEventDispatch.hpp deleted file mode 100644 index de94a598..00000000 --- a/src/ui/RoutedEventDispatch.hpp +++ /dev/null @@ -1,110 +0,0 @@ -#pragma once -#include "cru/ui/Control.hpp" - -#include "cru/common/Logger.hpp" -#include "cru/ui/DebugFlags.hpp" - -#include - -namespace cru::ui { -// 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 -void DispatchEvent(const std::u16string_view& event_name, - Control* const original_sender, - event::RoutedEvent* (Control::*event_ptr)(), - Control* const last_receiver, Args&&... args) { - CRU_UNUSED(event_name) - - if (original_sender == last_receiver) { - if constexpr (debug_flags::routed_event) - log::Debug( - "Routed event {} no need to dispatch (original_sender == " - "last_receiver). Original sender is {}.", - event_name, original_sender->GetControlType()); - return; - } - - std::vector receive_list; - - auto parent = original_sender; - while (parent != last_receiver) { - receive_list.push_back(parent); - parent = parent->GetParent(); - } - - if constexpr (debug_flags::routed_event) { - std::u16string log = u"Dispatch routed event "; - log += event_name; - log += u". Path (parent first): "; - auto i = receive_list.crbegin(); - const auto end = --receive_list.crend(); - for (; i != end; ++i) { - log += (*i)->GetControlType(); - log += u" -> "; - } - log += (*i)->GetControlType(); - log::Debug(log); - } - - auto handled = false; - - int count = 0; - - // tunnel - for (auto i = receive_list.crbegin(); i != receive_list.crend(); ++i) { - count++; - EventArgs event_args(*i, original_sender, std::forward(args)...); - static_cast*>(((*i)->*event_ptr)()->Tunnel()) - ->Raise(event_args); - if (event_args.IsHandled()) { - handled = true; - if constexpr (debug_flags::routed_event) - log::Debug( - u"Routed event is short-circuit in TUNNEL at {}-st control (count " - u"from parent).", - count); - break; - } - } - - // bubble - if (!handled) { - for (auto i : receive_list) { - count--; - EventArgs event_args(i, original_sender, std::forward(args)...); - static_cast*>((i->*event_ptr)()->Bubble()) - ->Raise(event_args); - if (event_args.IsHandled()) { - if constexpr (debug_flags::routed_event) - log::Debug( - u"Routed event is short-circuit in BUBBLE at {}-st control " - u"(count from parent).", - count); - break; - } - } - } - - // direct - for (auto i : receive_list) { - EventArgs event_args(i, original_sender, std::forward(args)...); - static_cast*>((i->*event_ptr)()->Direct()) - ->Raise(event_args); - } - - if constexpr (debug_flags::routed_event) - log::Debug(u"Routed event dispatch finished."); -} -} // namespace cru::ui diff --git a/src/ui/Window.cpp b/src/ui/Window.cpp index 051e67ef..c49140a4 100644 --- a/src/ui/Window.cpp +++ b/src/ui/Window.cpp @@ -1,7 +1,7 @@ #include "cru/ui/Window.hpp" #include "cru/common/Base.hpp" -#include "cru/ui/WindowHost.hpp" +#include "cru/ui/host/WindowHost.hpp" #include "cru/ui/render/Base.hpp" #include "cru/ui/render/StackLayoutRenderObject.hpp" @@ -10,7 +10,7 @@ Window* Window::CreateOverlapped() { return new Window(); } Window::Window() : render_object_(new render::StackLayoutRenderObject()) { render_object_->SetAttachedControl(this); - window_host_ = std::make_unique(this); + window_host_ = std::make_unique(this); } Window::~Window() {} diff --git a/src/ui/WindowHost.cpp b/src/ui/WindowHost.cpp deleted file mode 100644 index b0908e4f..00000000 --- a/src/ui/WindowHost.cpp +++ /dev/null @@ -1,400 +0,0 @@ -#include "cru/ui/WindowHost.hpp" - -#include "RoutedEventDispatch.hpp" -#include "cru/common/Logger.hpp" -#include "cru/platform/graphics/Painter.hpp" -#include "cru/platform/gui/InputMethod.hpp" -#include "cru/platform/gui/UiApplication.hpp" -#include "cru/platform/gui/Window.hpp" -#include "cru/ui/DebugFlags.hpp" -#include "cru/ui/Window.hpp" -#include "cru/ui/render/MeasureRequirement.hpp" -#include "cru/ui/render/RenderObject.hpp" - -#include - -namespace cru::ui { -using platform::gui::INativeWindow; -using platform::gui::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(Control* root_control) - : root_control_(root_control), focus_control_(root_control) { - const auto ui_application = IUiApplication::GetInstance(); - auto native_window = ui_application->CreateWindow(nullptr); - native_window_ = native_window; - - root_control_->TraverseDescendants([this](Control* control) { - control->window_host_ = this; - control->OnAttachToHost(this); - }); - - root_render_object_ = root_control->GetRenderObject(); - root_render_object_->SetWindowHostRecursive(this); - - 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() { - if (native_window_) { - native_window_->Close(); - } -} - -void WindowHost::InvalidatePaint() { - if (native_window_) native_window_->RequestRepaint(); -} - -void WindowHost::InvalidateLayout() { - need_layout_ = true; - this->InvalidatePaint(); -} - -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{100, 100}; // a reasonable assumed size - Relayout(available_size); -} - -void WindowHost::Relayout(const Size& available_size) { - root_render_object_->Measure( - render::MeasureRequirement{available_size, - IsLayoutPreferToFillWindow() - ? render::MeasureSize(available_size) - : render::MeasureSize::NotSpecified()}, - render::MeasureSize::NotSpecified()); - root_render_object_->Layout(Point{}); - for (auto& action : after_layout_stable_action_) action(); - after_layout_event_.Raise(AfterLayoutEventArgs{}); - root_render_object_->TraverseDescendants( - [](render::RenderObject* render_object) { - render_object->OnAfterLayout(); - }); - after_layout_stable_action_.clear(); - if constexpr (debug_flags::layout) - log::TagDebug(log_tag, u"A relayout is finished."); -} - -Control* WindowHost::GetFocusControl() { return focus_control_; } - -void WindowHost::SetFocusControl(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, - &Control::LoseFocusEvent, nullptr, false); - - DispatchEvent(event_names::GainFocus, control, &Control::GainFocusEvent, - nullptr, false); -} - -bool WindowHost::CaptureMouseFor(Control* control) { - 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) - this->native_window_ = nullptr; -} - -void WindowHost::OnNativePaint(INativeWindow* window, std::nullptr_t) { - if (need_layout_) { - Relayout(); - need_layout_ = false; - } - 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::gui::FocusChangeType focus) { - CRU_UNUSED(window) - - focus == platform::gui::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::gui::MouseEnterLeaveType type) { - CRU_UNUSED(window) - - if (type == platform::gui::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::gui::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::gui::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::gui::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::gui::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 (native_window_) { - 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 root_control_; -} -} // namespace cru::ui diff --git a/src/ui/controls/TextControlService.hpp b/src/ui/controls/TextControlService.hpp index 8ad87416..a7e4e440 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/WindowHost.hpp" +#include "cru/ui/host/WindowHost.hpp" #include "cru/ui/render/CanvasRenderObject.hpp" #include "cru/ui/render/ScrollRenderObject.hpp" #include "cru/ui/render/TextRenderObject.hpp" @@ -136,7 +136,7 @@ class TextControlService : public Object { } platform::gui::IInputMethodContext* GetInputMethodContext() { - WindowHost* host = this->control_->GetWindowHost(); + host::WindowHost* host = this->control_->GetWindowHost(); if (!host) return nullptr; platform::gui::INativeWindow* native_window = host->GetNativeWindow(); if (!native_window) return nullptr; diff --git a/src/ui/host/RoutedEventDispatch.hpp b/src/ui/host/RoutedEventDispatch.hpp new file mode 100644 index 00000000..de94a598 --- /dev/null +++ b/src/ui/host/RoutedEventDispatch.hpp @@ -0,0 +1,110 @@ +#pragma once +#include "cru/ui/Control.hpp" + +#include "cru/common/Logger.hpp" +#include "cru/ui/DebugFlags.hpp" + +#include + +namespace cru::ui { +// 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 +void DispatchEvent(const std::u16string_view& event_name, + Control* const original_sender, + event::RoutedEvent* (Control::*event_ptr)(), + Control* const last_receiver, Args&&... args) { + CRU_UNUSED(event_name) + + if (original_sender == last_receiver) { + if constexpr (debug_flags::routed_event) + log::Debug( + "Routed event {} no need to dispatch (original_sender == " + "last_receiver). Original sender is {}.", + event_name, original_sender->GetControlType()); + return; + } + + std::vector receive_list; + + auto parent = original_sender; + while (parent != last_receiver) { + receive_list.push_back(parent); + parent = parent->GetParent(); + } + + if constexpr (debug_flags::routed_event) { + std::u16string log = u"Dispatch routed event "; + log += event_name; + log += u". Path (parent first): "; + auto i = receive_list.crbegin(); + const auto end = --receive_list.crend(); + for (; i != end; ++i) { + log += (*i)->GetControlType(); + log += u" -> "; + } + log += (*i)->GetControlType(); + log::Debug(log); + } + + auto handled = false; + + int count = 0; + + // tunnel + for (auto i = receive_list.crbegin(); i != receive_list.crend(); ++i) { + count++; + EventArgs event_args(*i, original_sender, std::forward(args)...); + static_cast*>(((*i)->*event_ptr)()->Tunnel()) + ->Raise(event_args); + if (event_args.IsHandled()) { + handled = true; + if constexpr (debug_flags::routed_event) + log::Debug( + u"Routed event is short-circuit in TUNNEL at {}-st control (count " + u"from parent).", + count); + break; + } + } + + // bubble + if (!handled) { + for (auto i : receive_list) { + count--; + EventArgs event_args(i, original_sender, std::forward(args)...); + static_cast*>((i->*event_ptr)()->Bubble()) + ->Raise(event_args); + if (event_args.IsHandled()) { + if constexpr (debug_flags::routed_event) + log::Debug( + u"Routed event is short-circuit in BUBBLE at {}-st control " + u"(count from parent).", + count); + break; + } + } + } + + // direct + for (auto i : receive_list) { + EventArgs event_args(i, original_sender, std::forward(args)...); + static_cast*>((i->*event_ptr)()->Direct()) + ->Raise(event_args); + } + + if constexpr (debug_flags::routed_event) + log::Debug(u"Routed event dispatch finished."); +} +} // namespace cru::ui diff --git a/src/ui/host/WindowHost.cpp b/src/ui/host/WindowHost.cpp new file mode 100644 index 00000000..eac247c1 --- /dev/null +++ b/src/ui/host/WindowHost.cpp @@ -0,0 +1,400 @@ +#include "cru/ui/host/WindowHost.hpp" + +#include "RoutedEventDispatch.hpp" +#include "cru/common/Logger.hpp" +#include "cru/platform/graphics/Painter.hpp" +#include "cru/platform/gui/InputMethod.hpp" +#include "cru/platform/gui/UiApplication.hpp" +#include "cru/platform/gui/Window.hpp" +#include "cru/ui/DebugFlags.hpp" +#include "cru/ui/Window.hpp" +#include "cru/ui/render/MeasureRequirement.hpp" +#include "cru/ui/render/RenderObject.hpp" + +#include + +namespace cru::ui::host { +using platform::gui::INativeWindow; +using platform::gui::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(Control* root_control) + : root_control_(root_control), focus_control_(root_control) { + const auto ui_application = IUiApplication::GetInstance(); + auto native_window = ui_application->CreateWindow(nullptr); + native_window_ = native_window; + + root_control_->TraverseDescendants([this](Control* control) { + control->window_host_ = this; + control->OnAttachToHost(this); + }); + + root_render_object_ = root_control->GetRenderObject(); + root_render_object_->SetWindowHostRecursive(this); + + 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() { + if (native_window_) { + native_window_->Close(); + } +} + +void WindowHost::InvalidatePaint() { + if (native_window_) native_window_->RequestRepaint(); +} + +void WindowHost::InvalidateLayout() { + need_layout_ = true; + this->InvalidatePaint(); +} + +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{100, 100}; // a reasonable assumed size + Relayout(available_size); +} + +void WindowHost::Relayout(const Size& available_size) { + root_render_object_->Measure( + render::MeasureRequirement{available_size, + IsLayoutPreferToFillWindow() + ? render::MeasureSize(available_size) + : render::MeasureSize::NotSpecified()}, + render::MeasureSize::NotSpecified()); + root_render_object_->Layout(Point{}); + for (auto& action : after_layout_stable_action_) action(); + after_layout_event_.Raise(AfterLayoutEventArgs{}); + root_render_object_->TraverseDescendants( + [](render::RenderObject* render_object) { + render_object->OnAfterLayout(); + }); + after_layout_stable_action_.clear(); + if constexpr (debug_flags::layout) + log::TagDebug(log_tag, u"A relayout is finished."); +} + +Control* WindowHost::GetFocusControl() { return focus_control_; } + +void WindowHost::SetFocusControl(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, + &Control::LoseFocusEvent, nullptr, false); + + DispatchEvent(event_names::GainFocus, control, &Control::GainFocusEvent, + nullptr, false); +} + +bool WindowHost::CaptureMouseFor(Control* control) { + 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) + this->native_window_ = nullptr; +} + +void WindowHost::OnNativePaint(INativeWindow* window, std::nullptr_t) { + if (need_layout_) { + Relayout(); + need_layout_ = false; + } + 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::gui::FocusChangeType focus) { + CRU_UNUSED(window) + + focus == platform::gui::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::gui::MouseEnterLeaveType type) { + CRU_UNUSED(window) + + if (type == platform::gui::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::gui::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::gui::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::gui::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::gui::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 (native_window_) { + 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 root_control_; +} +} // namespace cru::ui diff --git a/src/ui/render/RenderObject.cpp b/src/ui/render/RenderObject.cpp index 09410113..a40ce9b8 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/graphics/util/Painter.hpp" #include "cru/ui/DebugFlags.hpp" -#include "cru/ui/WindowHost.hpp" +#include "cru/ui/host/WindowHost.hpp" #include #include @@ -303,7 +303,7 @@ std::u16string RenderObject::GetDebugPathInTree() const { return result; } -void RenderObject::SetWindowHostRecursive(WindowHost* host) { +void RenderObject::SetWindowHostRecursive(host::WindowHost* host) { if (window_host_ != nullptr) { detach_from_host_event_.Raise(nullptr); } -- cgit v1.2.3