aboutsummaryrefslogtreecommitdiff
path: root/include/cru/ui/UiHost.hpp
diff options
context:
space:
mode:
Diffstat (limited to 'include/cru/ui/UiHost.hpp')
-rw-r--r--include/cru/ui/UiHost.hpp170
1 files changed, 170 insertions, 0 deletions
diff --git a/include/cru/ui/UiHost.hpp b/include/cru/ui/UiHost.hpp
new file mode 100644
index 00000000..651dab81
--- /dev/null
+++ b/include/cru/ui/UiHost.hpp
@@ -0,0 +1,170 @@
+#pragma once
+#include "Base.hpp"
+
+#include "cru/common/Event.hpp"
+#include "cru/common/SelfResolvable.hpp"
+#include "render/Base.hpp"
+
+namespace cru::ui {
+struct AfterLayoutEventArgs {};
+
+// The host of all controls and render objects.
+//
+// 3 situations on destroy:
+// 1. Native window destroyed, IsRetainAfterDestroy: false:
+// OnNativeDestroy(set native_window_destroyed_ to true, call ~Window due to
+// deleting_ is false and IsRetainAfterDestroy is false) -> ~Window ->
+// ~UiHost(not destroy native window repeatedly due to native_window_destroyed_
+// is true)
+// 2. Native window destroyed, IsRetainAfterDestroy: true:
+// OnNativeDestroy(set native_window_destroyed_ to true, not call ~Window
+// because deleting_ is false and IsRetainAfterDestroy is true)
+// then, ~Window -> ~UiHost(not destroy native window repeatedly due to
+// native_window_destroyed_ is true)
+// 3. Native window not destroyed, ~Window is called:
+// ~Window -> ~UiHost(set deleting_ to true, destroy native window
+// due to native_window_destroyed is false) -> OnNativeDestroy(not call ~Window
+// due to deleting_ is true and IsRetainAfterDestroy is whatever)
+// In conclusion:
+// 1. Set native_window_destroyed_ to true at the beginning of OnNativeDestroy.
+// 2. Set deleting_ to true at the beginning of ~UiHost.
+// 3. Destroy native window when native_window_destroy_ is false in ~Window.
+// 4. Delete Window when deleting_ is false and IsRetainAfterDestroy is false in
+// OnNativeDestroy.
+class UiHost : public Object, public SelfResolvable<UiHost> {
+ public:
+ // This will create root window render object and attach it to window.
+ // It will also create and manage a native window.
+ UiHost(Window* window);
+
+ CRU_DELETE_COPY(UiHost)
+ CRU_DELETE_MOVE(UiHost)
+
+ ~UiHost() override;
+
+ public:
+ // 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();
+
+ // 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_; }
+
+ //*************** region: focus ***************
+
+ // Request focus for specified control.
+ bool RequestFocusFor(Control* control);
+
+ // Get the control that has focus.
+ Control* GetFocusControl();
+
+ //*************** 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(Control* control);
+
+ // Return null if not captured.
+ Control* GetMouseCaptureControl();
+
+ Control* HitTest(const Point& point);
+
+ void UpdateCursor();
+
+ std::shared_ptr<platform::native::INativeWindowResolver>
+ GetNativeWindowResolver() {
+ return native_window_resolver_;
+ }
+
+ bool IsRetainAfterDestroy() { return retain_after_destroy_; }
+
+ void SetRetainAfterDestroy(bool destroy) { retain_after_destroy_ = destroy; }
+
+ private:
+ //*************** region: native messages ***************
+ void OnNativeDestroy(platform::native::INativeWindow* window, std::nullptr_t);
+ void OnNativePaint(platform::native::INativeWindow* window, std::nullptr_t);
+ void OnNativeResize(platform::native::INativeWindow* window,
+ const Size& size);
+
+ void OnNativeFocus(platform::native::INativeWindow* window,
+ cru::platform::native::FocusChangeType focus);
+
+ void OnNativeMouseEnterLeave(
+ platform::native::INativeWindow* window,
+ cru::platform::native::MouseEnterLeaveType enter);
+ void OnNativeMouseMove(platform::native::INativeWindow* window,
+ const Point& point);
+ void OnNativeMouseDown(
+ platform::native::INativeWindow* window,
+ const platform::native::NativeMouseButtonEventArgs& args);
+ void OnNativeMouseUp(
+ platform::native::INativeWindow* window,
+ const platform::native::NativeMouseButtonEventArgs& args);
+
+ void OnNativeKeyDown(platform::native::INativeWindow* window,
+ const platform::native::NativeKeyEventArgs& args);
+ void OnNativeKeyUp(platform::native::INativeWindow* window,
+ const platform::native::NativeKeyEventArgs& args);
+
+ //*************** region: event dispatcher helper ***************
+
+ void DispatchMouseHoverControlChangeEvent(Control* old_control,
+ Control* new_control,
+ const Point& point, bool no_leave,
+ bool no_enter);
+
+ private:
+ bool need_layout_ = false;
+
+ Event<AfterLayoutEventArgs> after_layout_event_;
+
+ std::shared_ptr<platform::native::INativeWindowResolver>
+ native_window_resolver_;
+
+ // See remarks of UiHost.
+ bool retain_after_destroy_ = false;
+ // See remarks of UiHost.
+ bool deleting_ = false;
+
+ // We need this because calling Resolve on resolver in handler of destroy
+ // event is bad and will always get the dying window. But we need to label the
+ // window as destroyed so the destructor will not destroy native window
+ // repeatedly. See remarks of UiHost.
+ bool native_window_destroyed_ = false;
+
+ std::vector<EventRevokerGuard> event_revoker_guards_;
+
+ Window* window_control_;
+ std::unique_ptr<render::WindowRenderObject> root_render_object_;
+
+ Control* mouse_hover_control_;
+
+ Control* focus_control_; // "focus_control_" can't be nullptr
+
+ Control* mouse_captured_control_;
+};
+} // namespace cru::ui