aboutsummaryrefslogtreecommitdiff
path: root/src/ui/UiHost.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/ui/UiHost.cpp')
-rw-r--r--src/ui/UiHost.cpp368
1 files changed, 368 insertions, 0 deletions
diff --git a/src/ui/UiHost.cpp b/src/ui/UiHost.cpp
new file mode 100644
index 00000000..069e68de
--- /dev/null
+++ b/src/ui/UiHost.cpp
@@ -0,0 +1,368 @@
+#include "cru/ui/UiHost.hpp"
+
+#include "cru/common/Logger.hpp"
+#include "cru/platform/graph/Painter.hpp"
+#include "cru/platform/native/UiApplication.hpp"
+#include "cru/platform/native/Window.hpp"
+#include "cru/ui/render/WindowRenderObject.hpp"
+#include "cru/ui/Window.hpp"
+#include "RoutedEventDispatch.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->ui_host_ = 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() {
+ deleting_ = true;
+ window_control_->TraverseDescendants(
+ [this](Control* control) { control->OnDetachFromHost(this); });
+ if (!native_window_destroyed_) {
+ const auto native_window = native_window_resolver_->Resolve();
+ if (native_window) {
+ native_window->Close();
+ }
+ }
+}
+
+void UiHost::InvalidatePaint() {
+ if (const auto native_window = native_window_resolver_->Resolve())
+ native_window->RequestRepaint();
+}
+
+void UiHost::InvalidateLayout() {
+ if (!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)
+ native_window_destroyed_ = true;
+ if (!deleting_ && !retain_after_destroy_) delete window_control_;
+}
+
+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