diff options
| author | Yuqian Yang <crupest@crupest.life> | 2025-11-18 00:46:27 +0800 |
|---|---|---|
| committer | Yuqian Yang <crupest@crupest.life> | 2025-11-18 00:46:27 +0800 |
| commit | 6b4edc9be8ec556147c195cf2047d92b9439efd7 (patch) | |
| tree | a1d7b7d1e821b4e1911fd00761f77a24ee483f4a /include/cru/ui/controls/ControlHost.h | |
| parent | f7c4d19df66c602d74795e98ce2ee4390d06fbb4 (diff) | |
| download | cru-6b4edc9be8ec556147c195cf2047d92b9439efd7.tar.gz cru-6b4edc9be8ec556147c195cf2047d92b9439efd7.tar.bz2 cru-6b4edc9be8ec556147c195cf2047d92b9439efd7.zip | |
Bring back ControlHost and refactor tree management of control.
Diffstat (limited to 'include/cru/ui/controls/ControlHost.h')
| -rw-r--r-- | include/cru/ui/controls/ControlHost.h | 191 |
1 files changed, 191 insertions, 0 deletions
diff --git a/include/cru/ui/controls/ControlHost.h b/include/cru/ui/controls/ControlHost.h new file mode 100644 index 00000000..c67e8a72 --- /dev/null +++ b/include/cru/ui/controls/ControlHost.h @@ -0,0 +1,191 @@ +#pragma once +#include "Control.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 ControlHost : public Object { + CRU_DEFINE_CLASS_LOG_TAG("cru::ui::controls::ControlHost") + friend Control; + + public: + explicit ControlHost(Control* root_control); + ~ControlHost() override; + + 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); + + // 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(); + + 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<ObjectResolver<Control>> receive_list; + + auto parent = original_sender; + while (parent != last_receiver) { + receive_list.push_back(parent->CreateResolver()); + parent = parent->GetParent(); + } + + auto handled = false; + + // tunnel + for (auto i = receive_list.crbegin(); i != receive_list.crend(); ++i) { + auto control = i->Resolve(); + log += " "; + if (!control) { + log += "(deleted)"; + continue; + } + 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 resolver : receive_list) { + auto control = resolver.Resolve(); + log += " "; + if (!control) { + log += "(deleted)"; + continue; + } + 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 resolver : receive_list) { + auto control = resolver.Resolve(); + log += " "; + if (!control) { + log += "(deleted)"; + continue; + } + log += control->GetDebugId(); + EventArgs event_args(control, original_sender, + std::forward<Args>(args)...); + (control->*event_ptr)()->direct_.Raise(event_args); + } + } + + void UpdateCursor(); + void NotifyControlParentChange(Control* control, Control* old_parent, Control* new_parent); + + private: + int event_handling_count_; + + Control* root_control_; + 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_; +}; +} // namespace cru::ui::controls |
