diff options
author | crupest <crupest@outlook.com> | 2020-04-11 00:01:51 +0800 |
---|---|---|
committer | crupest <crupest@outlook.com> | 2020-04-11 00:01:51 +0800 |
commit | 23ef59b6aa14874e3b68c8716c137eb65583cd63 (patch) | |
tree | 3f15dbfe3115b03a19a49abb7fcbc27d9c440769 /src | |
parent | 41e17e281ba31e9eff612017f5a2dafd847278b0 (diff) | |
download | cru-23ef59b6aa14874e3b68c8716c137eb65583cd63.tar.gz cru-23ef59b6aa14874e3b68c8716c137eb65583cd63.tar.bz2 cru-23ef59b6aa14874e3b68c8716c137eb65583cd63.zip |
...
Diffstat (limited to 'src')
-rw-r--r-- | src/ui/CMakeLists.txt | 4 | ||||
-rw-r--r-- | src/ui/content_control.cpp | 6 | ||||
-rw-r--r-- | src/ui/control.cpp | 68 | ||||
-rw-r--r-- | src/ui/layout_control.cpp | 4 | ||||
-rw-r--r-- | src/ui/render/render_object.cpp | 16 | ||||
-rw-r--r-- | src/ui/render/window_render_object.cpp | 65 | ||||
-rw-r--r-- | src/ui/ui_host.cpp | 360 | ||||
-rw-r--r-- | src/ui/window.cpp | 340 |
8 files changed, 430 insertions, 433 deletions
diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt index e132f3df..a8fd46a5 100644 --- a/src/ui/CMakeLists.txt +++ b/src/ui/CMakeLists.txt @@ -9,7 +9,8 @@ add_library(cru_ui STATIC control.cpp helper.cpp layout_control.cpp - no_child_control.cpp + no_child_control.cpp + ui_host.cpp ui_manager.cpp window.cpp controls/button.cpp @@ -36,6 +37,7 @@ target_sources(cru_ui PUBLIC ${CRU_UI_INCLUDE_DIR}/layout_control.hpp ${CRU_UI_INCLUDE_DIR}/no_child_control.hpp ${CRU_UI_INCLUDE_DIR}/ui_event.hpp + ${CRU_UI_INCLUDE_DIR}/ui_host.hpp ${CRU_UI_INCLUDE_DIR}/ui_manager.hpp ${CRU_UI_INCLUDE_DIR}/window.hpp ${CRU_UI_INCLUDE_DIR}/controls/base.hpp diff --git a/src/ui/content_control.cpp b/src/ui/content_control.cpp index 5dcd89d9..eb13f4cb 100644 --- a/src/ui/content_control.cpp +++ b/src/ui/content_control.cpp @@ -12,16 +12,16 @@ void ContentControl::SetChild(Control* child) { Expects(!dynamic_cast<Window*>(child)); // Can't add a window as child. if (child == child_) return; - const auto window = GetWindow(); + const auto host = GetUiHost(); const auto old_child = child_; child_ = child; if (old_child) { old_child->_SetParent(nullptr); - old_child->_SetDescendantWindow(nullptr); + old_child->_SetDescendantUiHost(nullptr); } if (child) { child->_SetParent(this); - child->_SetDescendantWindow(window); + child->_SetDescendantUiHost(host); } OnChildChanged(old_child, child); } diff --git a/src/ui/control.cpp b/src/ui/control.cpp index 180ba476..7691dac2 100644 --- a/src/ui/control.cpp +++ b/src/ui/control.cpp @@ -2,9 +2,8 @@ #include "cru/platform/native/cursor.hpp" #include "cru/platform/native/ui_application.hpp" -#include "cru/platform/native/window.hpp" #include "cru/ui/base.hpp" -#include "cru/ui/window.hpp" +#include "cru/ui/ui_host.hpp" #include "routed_event_dispatch.hpp" #include <memory> @@ -33,23 +32,23 @@ void Control::_SetParent(Control* parent) { if (old_parent != new_parent) OnParentChanged(old_parent, new_parent); } -void Control::_SetDescendantWindow(Window* window) { - if (window == nullptr && window_ == nullptr) return; - +void Control::_SetDescendantUiHost(UiHost* host) { // You can only attach or detach window. - Expects((window != nullptr && window_ == nullptr) || - (window == nullptr && window_ != nullptr)); + Expects((host != nullptr && ui_host_ == nullptr) || + (host == nullptr && ui_host_ != nullptr)); + + if (host == nullptr && ui_host_ == nullptr) return; - if (window == nullptr) { - const auto old = window_; + if (host == nullptr) { + const auto old = ui_host_; TraverseDescendants([old](Control* control) { - control->window_ = nullptr; - control->OnDetachToWindow(old); + control->ui_host_ = nullptr; + control->OnDetachFromHost(old); }); } else - TraverseDescendants([window](Control* control) { - control->window_ = window; - control->OnAttachToWindow(window); + TraverseDescendants([host](Control* control) { + control->ui_host_ = host; + control->OnAttachToHost(host); }); } @@ -65,25 +64,38 @@ void Control::_TraverseDescendants( } bool Control::RequestFocus() { - auto window = GetWindow(); - if (window == nullptr) return false; + auto host = GetUiHost(); + if (host == nullptr) return false; - return window->RequestFocusFor(this); + return host->RequestFocusFor(this); } bool Control::HasFocus() { - auto window = GetWindow(); - if (window == nullptr) return false; + auto host = GetUiHost(); + if (host == nullptr) return false; - return window->GetFocusControl() == this; + return host->GetFocusControl() == this; } -bool Control::CaptureMouse() { return GetWindow()->CaptureMouseFor(this); } +bool Control::CaptureMouse() { + auto host = GetUiHost(); + if (host == nullptr) return false; + + return host->CaptureMouseFor(this); +} -bool Control::ReleaseMouse() { return GetWindow()->CaptureMouseFor(nullptr); } +bool Control::ReleaseMouse() { + auto host = GetUiHost(); + if (host == nullptr) return false; + + return host->CaptureMouseFor(nullptr); +} bool Control::IsMouseCaptured() { - return GetWindow()->GetMouseCaptureControl() == this; + auto host = GetUiHost(); + if (host == nullptr) return false; + + return host->GetMouseCaptureControl() == this; } std::shared_ptr<ICursor> Control::GetCursor() { return cursor_; } @@ -101,9 +113,9 @@ std::shared_ptr<ICursor> Control::GetInheritedCursor() { void Control::SetCursor(std::shared_ptr<ICursor> cursor) { cursor_ = std::move(cursor); - const auto window = GetWindow(); - if (window != nullptr) { - window->UpdateCursor(); + const auto host = GetUiHost(); + if (host != nullptr) { + host->UpdateCursor(); } } @@ -112,7 +124,7 @@ void Control::OnParentChanged(Control* old_parent, Control* new_parent) { CRU_UNUSED(new_parent) } -void Control::OnAttachToWindow(Window* window) { CRU_UNUSED(window) } +void Control::OnAttachToHost(UiHost* host) { CRU_UNUSED(host) } -void Control::OnDetachToWindow(Window* window) { CRU_UNUSED(window) } +void Control::OnDetachFromHost(UiHost* host) { CRU_UNUSED(host) } } // namespace cru::ui diff --git a/src/ui/layout_control.cpp b/src/ui/layout_control.cpp index 90a825ff..1d5d1ede 100644 --- a/src/ui/layout_control.cpp +++ b/src/ui/layout_control.cpp @@ -19,7 +19,7 @@ void LayoutControl::AddChild(Control* control, const Index position) { children_.insert(this->children_.cbegin() + position, control); control->_SetParent(this); - control->_SetDescendantWindow(GetWindow()); + control->_SetDescendantUiHost(GetUiHost()); OnAddChild(control, position); } @@ -36,7 +36,7 @@ void LayoutControl::RemoveChild(const Index position) { children_.erase(i); child->_SetParent(nullptr); - child->_SetDescendantWindow(nullptr); + child->_SetDescendantUiHost(nullptr); OnRemoveChild(child, position); } diff --git a/src/ui/render/render_object.cpp b/src/ui/render/render_object.cpp index e329d150..4a1bedf3 100644 --- a/src/ui/render/render_object.cpp +++ b/src/ui/render/render_object.cpp @@ -1,6 +1,7 @@ #include "cru/ui/render/render_object.hpp" #include "cru/common/logger.hpp" +#include "cru/ui/ui_host.hpp" #include <algorithm> @@ -18,7 +19,7 @@ void RenderObject::AddChild(RenderObject* render_object, const Index position) { children_.insert(children_.cbegin() + position, render_object); render_object->SetParent(this); - render_object->SetRenderHostRecursive(GetRenderHost()); + render_object->SetRenderHostRecursive(GetUiHost()); OnAddChild(render_object, position); } @@ -165,6 +166,14 @@ void RenderObject::SetParent(RenderObject* new_parent) { OnParentChanged(old_parent, new_parent); } +void RenderObject::InvalidateLayout() { + if (ui_host_ != nullptr) ui_host_->InvalidateLayout(); +} + +void RenderObject::InvalidatePaint() { + if (ui_host_ != nullptr) ui_host_->InvalidatePaint(); +} + void RenderObject::NotifyAfterLayoutRecursive(RenderObject* render_object) { render_object->OnAfterLayout(); for (const auto o : render_object->GetChildren()) { @@ -172,11 +181,10 @@ void RenderObject::NotifyAfterLayoutRecursive(RenderObject* render_object) { } } -void RenderObject::SetRenderHostRecursive(IRenderHost* host) { - SetRenderHost(host); +void RenderObject::SetRenderHostRecursive(UiHost* host) { + ui_host_ = host; for (const auto child : GetChildren()) { child->SetRenderHostRecursive(host); } } - } // namespace cru::ui::render diff --git a/src/ui/render/window_render_object.cpp b/src/ui/render/window_render_object.cpp index 29313645..abed6fa4 100644 --- a/src/ui/render/window_render_object.cpp +++ b/src/ui/render/window_render_object.cpp @@ -1,74 +1,17 @@ #include "cru/ui/render/window_render_object.hpp" #include "../helper.hpp" -#include "cru/common/logger.hpp" #include "cru/platform/graph/util/painter.hpp" -#include "cru/platform/native/ui_application.hpp" -#include "cru/platform/native/window.hpp" -#include "cru/ui/window.hpp" +#include "cru/ui/ui_host.hpp" namespace cru::ui::render { -class WindowRenderHost : public IRenderHost, - public SelfResolvable<WindowRenderHost> { - public: - WindowRenderHost(WindowRenderObject* render_object) - : render_object_(render_object) { - Expects(render_object != nullptr); - } - - void InvalidateLayout() override; - - void InvalidatePaint() override { - if (const auto native_window = - render_object_->GetWindow()->ResolveNativeWindow()) - native_window->RequestRepaint(); - } - - IEvent<AfterLayoutEventArgs>* AfterLayoutEvent() override { - return &after_layout_event_; - } - - private: - WindowRenderObject* render_object_; - - bool need_layout_ = false; - - Event<AfterLayoutEventArgs> after_layout_event_; -}; - -void WindowRenderHost::InvalidateLayout() { - if (!need_layout_) { - log::Debug("A relayout is required."); - GetUiApplication()->InvokeLater([resolver = this->CreateResolver()] { - if (const auto host = resolver.Resolve()) { - host->render_object_->Relayout(); - host->need_layout_ = false; - host->after_layout_event_.Raise(AfterLayoutEventArgs{}); - log::Debug("A relayout finished."); - host->InvalidatePaint(); - } - }); - need_layout_ = true; - } -} - -WindowRenderObject::WindowRenderObject(Window* window) - : window_(window), render_host_(new WindowRenderHost(this)) { +WindowRenderObject::WindowRenderObject(UiHost* host) { SetChildMode(ChildMode::Single); - SetRenderHost(render_host_.get()); - after_layout_event_guard_.Reset(render_host_->AfterLayoutEvent()->AddHandler( + ui_host_ = host; + after_layout_event_guard_.Reset(host->AfterLayoutEvent()->AddHandler( [this](auto) { NotifyAfterLayoutRecursive(this); })); } -void WindowRenderObject::Relayout() { - const auto native_window = window_->ResolveNativeWindow(); - const auto client_size = native_window - ? native_window->GetClientSize() - : Size{100, 100}; // a reasonable assumed size - Measure(client_size); - Layout(Rect{Point{}, client_size}); -} - void WindowRenderObject::Draw(platform::graph::IPainter* painter) { painter->Clear(colors::white); if (const auto child = GetChild()) { diff --git a/src/ui/ui_host.cpp b/src/ui/ui_host.cpp new file mode 100644 index 00000000..c82709dd --- /dev/null +++ b/src/ui/ui_host.cpp @@ -0,0 +1,360 @@ +#include "cru/ui/ui_host.hpp" + +#include "cru/common/logger.hpp" +#include "cru/platform/graph/painter.hpp" +#include "cru/platform/native/ui_application.hpp" +#include "cru/platform/native/window.hpp" +#include "cru/ui/render/window_render_object.hpp" +#include "cru/ui/window.hpp" +#include "routed_event_dispatch.hpp" + +namespace cru::ui { +using platform::native::INativeWindow; +using platform::native::IUiApplication; + +namespace event_names { +#ifdef CRU_DEBUG +#define CRU_DEFINE_EVENT_NAME(name) constexpr const char* name = #name; +#else +#define CRU_DEFINE_EVENT_NAME(name) constexpr const char* name = ""; +#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) +CRU_DEFINE_EVENT_NAME(Char) + +#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; +} + +std::list<Control*> GetAncestorList(Control* control) { + std::list<Control*> l; + while (control != nullptr) { + l.push_front(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.front() != right_list.front()) return nullptr; + + // find the last same control or the last control (one is ancestor of the + // other) + auto left_i = left_list.cbegin(); + auto right_i = right_list.cbegin(); + + while (true) { + if (left_i == left_list.cend()) { + return *(--left_i); + } + if (right_i == right_list.cend()) { + return *(--right_i); + } + if (*left_i != *right_i) { + return *(--left_i); + } + ++left_i; + ++right_i; + } +} +} // namespace + +namespace { +template <typename T> +inline void BindNativeEvent( + UiHost* host, INativeWindow* native_window, IEvent<T>* event, + void (UiHost::*handler)(INativeWindow*, typename IEvent<T>::EventArgs), + std::vector<EventRevokerGuard>& guard_pool) { + guard_pool.push_back(EventRevokerGuard(event->AddHandler( + std::bind(handler, host, native_window, std::placeholders::_1)))); +} +} // namespace + +UiHost::UiHost(Window* window) + : mouse_hover_control_(nullptr), + focus_control_(window), + mouse_captured_control_(nullptr), + window_control_(window) { + native_window_resolver_ = + IUiApplication::GetInstance()->CreateWindow(nullptr); + + const auto native_window = native_window_resolver_->Resolve(); + window->_SetDescendantUiHost(this); + + root_render_object_ = std::make_unique<render::WindowRenderObject>(this); + root_render_object_->SetAttachedControl(window); + window->render_object_ = root_render_object_.get(); + + BindNativeEvent(this, native_window, native_window->DestroyEvent(), + &UiHost::OnNativeDestroy, event_revoker_guards_); + BindNativeEvent(this, native_window, native_window->PaintEvent(), + &UiHost::OnNativePaint, event_revoker_guards_); + BindNativeEvent(this, native_window, native_window->ResizeEvent(), + &UiHost::OnNativeResize, event_revoker_guards_); + BindNativeEvent(this, native_window, native_window->FocusEvent(), + &UiHost::OnNativeFocus, event_revoker_guards_); + BindNativeEvent(this, native_window, native_window->MouseEnterLeaveEvent(), + &UiHost::OnNativeMouseEnterLeave, event_revoker_guards_); + BindNativeEvent(this, native_window, native_window->MouseMoveEvent(), + &UiHost::OnNativeMouseMove, event_revoker_guards_); + BindNativeEvent(this, native_window, native_window->MouseDownEvent(), + &UiHost::OnNativeMouseDown, event_revoker_guards_); + BindNativeEvent(this, native_window, native_window->MouseUpEvent(), + &UiHost::OnNativeMouseUp, event_revoker_guards_); + BindNativeEvent(this, native_window, native_window->KeyDownEvent(), + &UiHost::OnNativeKeyDown, event_revoker_guards_); + BindNativeEvent(this, native_window, native_window->KeyUpEvent(), + &UiHost::OnNativeKeyUp, event_revoker_guards_); +} + +UiHost::~UiHost() { + window_control_->TraverseDescendants( + [this](Control* control) { control->OnDetachFromHost(this); }); +} + +void UiHost::InvalidatePaint() { + if (const auto native_window = native_window_resolver_->Resolve()) + native_window->RequestRepaint(); +} + +void UiHost::InvalidateLayout() { + if (!need_layout_) { + platform::native::IUiApplication::GetInstance()->InvokeLater( + [resolver = this->CreateResolver()] { + if (const auto host = resolver.Resolve()) { + host->Relayout(); + host->need_layout_ = false; + host->after_layout_event_.Raise(AfterLayoutEventArgs{}); + log::Debug("A relayout finished."); + host->InvalidatePaint(); + } + }); + need_layout_ = true; + } +} + +void UiHost::Relayout() { + const auto native_window = native_window_resolver_->Resolve(); + const auto client_size = native_window + ? native_window->GetClientSize() + : Size{100, 100}; // a reasonable assumed size + root_render_object_->Measure(client_size); + root_render_object_->Layout(Rect{Point{}, client_size}); +} + +bool UiHost::RequestFocusFor(Control* control) { + Expects(control != nullptr); // The control to request focus can't be null. + // You can set it as the window. + + if (focus_control_ == control) return true; + + DispatchEvent(event_names::LoseFocus, focus_control_, + &Control::LoseFocusEvent, nullptr, false); + + focus_control_ = control; + + DispatchEvent(event_names::GainFocus, control, &Control::GainFocusEvent, + nullptr, false); + + return true; +} + +Control* UiHost::GetFocusControl() { return focus_control_; } + +bool UiHost::CaptureMouseFor(Control* control) { + const auto native_window = native_window_resolver_->Resolve(); + if (!native_window) return false; + + if (control == mouse_captured_control_) return true; + + if (control == nullptr) { + const auto old_capture_control = mouse_captured_control_; + mouse_captured_control_ = + nullptr; // update this in case this is used in event handlers + if (old_capture_control != mouse_hover_control_) { + DispatchMouseHoverControlChangeEvent( + old_capture_control, mouse_hover_control_, + native_window->GetMousePosition(), true, false); + } + UpdateCursor(); + return true; + } + + if (mouse_captured_control_) return false; + + mouse_captured_control_ = control; + DispatchMouseHoverControlChangeEvent( + mouse_hover_control_, mouse_captured_control_, + native_window->GetMousePosition(), false, true); + UpdateCursor(); + return true; +} + +Control* UiHost::GetMouseCaptureControl() { return mouse_captured_control_; } + +void UiHost::OnNativeDestroy(INativeWindow* window, std::nullptr_t) { + CRU_UNUSED(window) + delete this; // TODO: Develop this. +} + +void UiHost::OnNativePaint(INativeWindow* window, std::nullptr_t) { + auto painter = window->BeginPaint(); + root_render_object_->Draw(painter.get()); + painter->EndDraw(); +} + +void UiHost::OnNativeResize(INativeWindow* window, const Size& size) { + CRU_UNUSED(window) + CRU_UNUSED(size) + + InvalidateLayout(); +} + +void UiHost::OnNativeFocus(INativeWindow* window, + platform::native::FocusChangeType focus) { + CRU_UNUSED(window) + + focus == platform::native::FocusChangeType::Gain + ? DispatchEvent(event_names::GainFocus, focus_control_, + &Control::GainFocusEvent, nullptr, true) + : DispatchEvent(event_names::LoseFocus, focus_control_, + &Control::LoseFocusEvent, nullptr, true); +} + +void UiHost::OnNativeMouseEnterLeave( + INativeWindow* window, platform::native::MouseEnterLeaveType type) { + CRU_UNUSED(window) + + if (type == platform::native::MouseEnterLeaveType::Leave) { + DispatchEvent(event_names::MouseLeave, mouse_hover_control_, + &Control::MouseLeaveEvent, nullptr); + mouse_hover_control_ = nullptr; + } +} + +void UiHost::OnNativeMouseMove(INativeWindow* window, const Point& point) { + CRU_UNUSED(window) + + // Find the first control that hit test succeed. + const auto new_mouse_hover_control = HitTest(point); + const auto old_mouse_hover_control = mouse_hover_control_; + mouse_hover_control_ = new_mouse_hover_control; + + if (mouse_captured_control_) { + const auto n = FindLowestCommonAncestor(new_mouse_hover_control, + mouse_captured_control_); + const auto o = FindLowestCommonAncestor(old_mouse_hover_control, + mouse_captured_control_); + bool a = IsAncestor(o, n); + if (a) { + DispatchEvent(event_names::MouseLeave, o, &Control::MouseLeaveEvent, n); + } else { + DispatchEvent(event_names::MouseEnter, n, &Control::MouseEnterEvent, o, + point); + } + DispatchEvent(event_names::MouseMove, mouse_captured_control_, + &Control::MouseMoveEvent, nullptr, point); + UpdateCursor(); + return; + } + + DispatchMouseHoverControlChangeEvent( + old_mouse_hover_control, new_mouse_hover_control, point, false, false); + DispatchEvent(event_names::MouseMove, new_mouse_hover_control, + &Control::MouseMoveEvent, nullptr, point); + UpdateCursor(); +} + +void UiHost::OnNativeMouseDown( + INativeWindow* window, + const platform::native::NativeMouseButtonEventArgs& args) { + CRU_UNUSED(window) + + Control* control = + mouse_captured_control_ ? mouse_captured_control_ : HitTest(args.point); + DispatchEvent(event_names::MouseDown, control, &Control::MouseDownEvent, + nullptr, args.point, args.button, args.modifier); +} + +void UiHost::OnNativeMouseUp( + INativeWindow* window, + const platform::native::NativeMouseButtonEventArgs& args) { + CRU_UNUSED(window) + + Control* control = + mouse_captured_control_ ? mouse_captured_control_ : HitTest(args.point); + DispatchEvent(event_names::MouseUp, control, &Control::MouseUpEvent, nullptr, + args.point, args.button, args.modifier); +} + +void UiHost::OnNativeKeyDown(INativeWindow* window, + const platform::native::NativeKeyEventArgs& args) { + CRU_UNUSED(window) + + DispatchEvent(event_names::KeyDown, focus_control_, &Control::KeyDownEvent, + nullptr, args.key, args.modifier); +} + +void UiHost::OnNativeKeyUp(INativeWindow* window, + const platform::native::NativeKeyEventArgs& args) { + CRU_UNUSED(window) + + DispatchEvent(event_names::KeyUp, focus_control_, &Control::KeyUpEvent, + nullptr, args.key, args.modifier); +} + +void UiHost::DispatchMouseHoverControlChangeEvent(Control* old_control, + Control* new_control, + const Point& point, + bool no_leave, + bool no_enter) { + if (new_control != old_control) // if the mouse-hover-on control changed + { + const auto lowest_common_ancestor = + FindLowestCommonAncestor(old_control, new_control); + if (!no_leave && old_control != nullptr) + DispatchEvent(event_names::MouseLeave, old_control, + &Control::MouseLeaveEvent, + lowest_common_ancestor); // dispatch mouse leave event. + if (!no_enter && new_control != nullptr) { + DispatchEvent(event_names::MouseEnter, new_control, + &Control::MouseEnterEvent, lowest_common_ancestor, + point); // dispatch mouse enter event. + } + } +} + +void UiHost::UpdateCursor() { + if (const auto native_window = native_window_resolver_->Resolve()) { + const auto capture = GetMouseCaptureControl(); + native_window->SetCursor( + (capture ? capture : GetMouseHoverControl())->GetInheritedCursor()); + } +} + +Control* UiHost::HitTest(const Point& point) { + return root_render_object_->HitTest(point)->GetAttachedControl(); +} +} // namespace cru::ui diff --git a/src/ui/window.cpp b/src/ui/window.cpp index a3279ba7..4f414385 100644 --- a/src/ui/window.cpp +++ b/src/ui/window.cpp @@ -1,357 +1,29 @@ #include "cru/ui/window.hpp" -#include "cru/platform/graph/painter.hpp" -#include "cru/platform/native/ui_application.hpp" -#include "cru/platform/native/window.hpp" #include "cru/ui/render/window_render_object.hpp" -#include "routed_event_dispatch.hpp" - -#include <list> +#include "cru/ui/ui_host.hpp" namespace cru::ui { -using cru::platform::native::FocusChangeType; -using cru::platform::native::INativeWindow; -using cru::platform::native::INativeWindowResolver; -using cru::platform::native::IUiApplication; -using cru::platform::native::MouseEnterLeaveType; - -namespace event_names { -#ifdef CRU_DEBUG -#define CRU_DEFINE_EVENT_NAME(name) constexpr const char* name = #name; -#else -#define CRU_DEFINE_EVENT_NAME(name) constexpr const char* name = ""; -#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) -CRU_DEFINE_EVENT_NAME(Char) - -#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; -} - -std::list<Control*> GetAncestorList(Control* control) { - std::list<Control*> l; - while (control != nullptr) { - l.push_front(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.front() != right_list.front()) return nullptr; - - // find the last same control or the last control (one is ancestor of the - // other) - auto left_i = left_list.cbegin(); - auto right_i = right_list.cbegin(); - - while (true) { - if (left_i == left_list.cend()) { - return *(--left_i); - } - if (right_i == right_list.cend()) { - return *(--right_i); - } - if (*left_i != *right_i) { - return *(--left_i); - } - ++left_i; - ++right_i; - } -} -} // namespace Window* Window::CreateOverlapped() { return new Window(tag_overlapped_constructor{}); } -namespace { -template <typename T> -inline void BindNativeEvent( - Window* window, INativeWindow* native_window, IEvent<T>* event, - void (Window::*handler)(INativeWindow*, typename IEvent<T>::EventArgs), - std::vector<EventRevokerGuard>& guard_pool) { - guard_pool.push_back(EventRevokerGuard(event->AddHandler( - std::bind(handler, window, native_window, std::placeholders::_1)))); -} -} // namespace - -Window::Window(tag_overlapped_constructor) - : mouse_hover_control_(nullptr), - focus_control_(this), - mouse_captured_control_(nullptr) { - window_ = this; - native_window_resolver_ = - IUiApplication::GetInstance()->CreateWindow(nullptr); - - const auto native_window = native_window_resolver_->Resolve(); - - render_object_.reset(new render::WindowRenderObject(this)); - render_object_->SetAttachedControl(this); - - BindNativeEvent(this, native_window, native_window->DestroyEvent(), - &Window::OnNativeDestroy, event_revoker_guards_); - BindNativeEvent(this, native_window, native_window->PaintEvent(), - &Window::OnNativePaint, event_revoker_guards_); - BindNativeEvent(this, native_window, native_window->ResizeEvent(), - &Window::OnNativeResize, event_revoker_guards_); - BindNativeEvent(this, native_window, native_window->FocusEvent(), - &Window::OnNativeFocus, event_revoker_guards_); - BindNativeEvent(this, native_window, native_window->MouseEnterLeaveEvent(), - &Window::OnNativeMouseEnterLeave, event_revoker_guards_); - BindNativeEvent(this, native_window, native_window->MouseMoveEvent(), - &Window::OnNativeMouseMove, event_revoker_guards_); - BindNativeEvent(this, native_window, native_window->MouseDownEvent(), - &Window::OnNativeMouseDown, event_revoker_guards_); - BindNativeEvent(this, native_window, native_window->MouseUpEvent(), - &Window::OnNativeMouseUp, event_revoker_guards_); - BindNativeEvent(this, native_window, native_window->KeyDownEvent(), - &Window::OnNativeKeyDown, event_revoker_guards_); - BindNativeEvent(this, native_window, native_window->KeyUpEvent(), - &Window::OnNativeKeyUp, event_revoker_guards_); +Window::Window(tag_overlapped_constructor) { + managed_ui_host_ = std::make_unique<UiHost>(this); } Window::~Window() { - TraverseDescendants( - [this](Control* control) { control->OnDetachToWindow(this); }); + // explicit destroy ui host first. + managed_ui_host_.reset(); } std::string_view Window::GetControlType() const { return control_type; } -render::RenderObject* Window::GetRenderObject() const { - return render_object_.get(); -} - -platform::native::INativeWindow* Window::ResolveNativeWindow() { - return native_window_resolver_->Resolve(); -} - -bool Window::RequestFocusFor(Control* control) { - Expects(control != nullptr); // The control to request focus can't be null. - // You can set it as the window. - - if (focus_control_ == control) return true; - - DispatchEvent(event_names::LoseFocus, focus_control_, - &Control::LoseFocusEvent, nullptr, false); - - focus_control_ = control; - - DispatchEvent(event_names::GainFocus, control, &Control::GainFocusEvent, - nullptr, false); - - return true; -} - -Control* Window::GetFocusControl() { return focus_control_; } - -bool Window::CaptureMouseFor(Control* control) { - const auto native_window = ResolveNativeWindow(); - 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* Window::GetMouseCaptureControl() { return mouse_captured_control_; } +render::RenderObject* Window::GetRenderObject() const { return render_object_; } void Window::OnChildChanged(Control* old_child, Control* new_child) { if (old_child) render_object_->RemoveChild(0); if (new_child) render_object_->AddChild(new_child->GetRenderObject(), 0); } - -Control* Window::HitTest(const Point& point) { - return render_object_->HitTest(point)->GetAttachedControl(); -} - -void Window::OnNativeDestroy(INativeWindow* window, std::nullptr_t) { - CRU_UNUSED(window) - delete this; // TODO: Finally change this. -} - -void Window::OnNativePaint(INativeWindow* window, std::nullptr_t) { - auto painter = window->BeginPaint(); - render_object_->Draw(painter.get()); - painter->EndDraw(); -} - -void Window::OnNativeResize(INativeWindow* window, const Size& size) { - CRU_UNUSED(window) - CRU_UNUSED(size) - - render_object_->GetRenderHost()->InvalidateLayout(); -} - -void Window::OnNativeFocus(INativeWindow* window, FocusChangeType focus) { - CRU_UNUSED(window) - - focus == FocusChangeType::Gain - ? DispatchEvent(event_names::GainFocus, focus_control_, - &Control::GainFocusEvent, nullptr, true) - : DispatchEvent(event_names::LoseFocus, focus_control_, - &Control::LoseFocusEvent, nullptr, true); -} - -void Window::OnNativeMouseEnterLeave(INativeWindow* window, - MouseEnterLeaveType type) { - CRU_UNUSED(window) - - if (type == MouseEnterLeaveType::Leave) { - DispatchEvent(event_names::MouseLeave, mouse_hover_control_, - &Control::MouseLeaveEvent, nullptr); - mouse_hover_control_ = nullptr; - } -} - -void Window::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 Window::OnNativeMouseDown( - INativeWindow* window, - const platform::native::NativeMouseButtonEventArgs& args) { - CRU_UNUSED(window) - - Control* control = - mouse_captured_control_ ? mouse_captured_control_ : HitTest(args.point); - DispatchEvent(event_names::MouseDown, control, &Control::MouseDownEvent, - nullptr, args.point, args.button, args.modifier); -} - -void Window::OnNativeMouseUp( - INativeWindow* window, - const platform::native::NativeMouseButtonEventArgs& args) { - CRU_UNUSED(window) - - Control* control = - mouse_captured_control_ ? mouse_captured_control_ : HitTest(args.point); - DispatchEvent(event_names::MouseUp, control, &Control::MouseUpEvent, nullptr, - args.point, args.button, args.modifier); -} - -void Window::OnNativeKeyDown(INativeWindow* window, - const platform::native::NativeKeyEventArgs& args) { - CRU_UNUSED(window) - - DispatchEvent(event_names::KeyDown, focus_control_, &Control::KeyDownEvent, - nullptr, args.key, args.modifier); -} - -void Window::OnNativeKeyUp(INativeWindow* window, - const platform::native::NativeKeyEventArgs& args) { - CRU_UNUSED(window) - - DispatchEvent(event_names::KeyUp, focus_control_, &Control::KeyUpEvent, - nullptr, args.key, args.modifier); -} - -void Window::OnNativeChar(platform::native::INativeWindow* window, - std::string c) { - CRU_UNUSED(window) - - DispatchEvent(event_names::Char, focus_control_, &Control::CharEvent, nullptr, - std::move(c)); -} - -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(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 Window::UpdateCursor() { - if (const auto native_window = ResolveNativeWindow()) { - const auto capture = GetMouseCaptureControl(); - native_window->SetCursor( - (capture ? capture : GetMouseHoverControl())->GetInheritedCursor()); - } -} } // namespace cru::ui |