aboutsummaryrefslogtreecommitdiff
path: root/include/cru/ui
diff options
context:
space:
mode:
Diffstat (limited to 'include/cru/ui')
-rw-r--r--include/cru/ui/Base.hpp38
-rw-r--r--include/cru/ui/ContentControl.hpp29
-rw-r--r--include/cru/ui/DebugFlags.hpp9
-rw-r--r--include/cru/ui/LayoutControl.hpp31
-rw-r--r--include/cru/ui/UiHost.hpp172
-rw-r--r--include/cru/ui/UiManager.hpp19
-rw-r--r--include/cru/ui/Window.hpp40
-rw-r--r--include/cru/ui/components/Component.hpp19
-rw-r--r--include/cru/ui/components/Menu.hpp60
-rw-r--r--include/cru/ui/controls/Base.hpp22
-rw-r--r--include/cru/ui/controls/Button.hpp29
-rw-r--r--include/cru/ui/controls/Container.hpp5
-rw-r--r--include/cru/ui/controls/ContentControl.hpp38
-rw-r--r--include/cru/ui/controls/Control.hpp (renamed from include/cru/ui/Control.hpp)55
-rw-r--r--include/cru/ui/controls/FlexLayout.hpp9
-rw-r--r--include/cru/ui/controls/IBorderControl.hpp10
-rw-r--r--include/cru/ui/controls/IClickableControl.hpp12
-rw-r--r--include/cru/ui/controls/LayoutControl.hpp35
-rw-r--r--include/cru/ui/controls/NoChildControl.hpp (renamed from include/cru/ui/NoChildControl.hpp)12
-rw-r--r--include/cru/ui/controls/Popup.hpp24
-rw-r--r--include/cru/ui/controls/RootControl.hpp45
-rw-r--r--include/cru/ui/controls/StackLayout.hpp6
-rw-r--r--include/cru/ui/controls/TextBlock.hpp23
-rw-r--r--include/cru/ui/controls/TextBox.hpp27
-rw-r--r--include/cru/ui/controls/TextHostControlService.hpp137
-rw-r--r--include/cru/ui/controls/Window.hpp32
-rw-r--r--include/cru/ui/events/UiEvent.hpp (renamed from include/cru/ui/UiEvent.hpp)33
-rw-r--r--include/cru/ui/helper/ClickDetector.hpp (renamed from include/cru/ui/ClickDetector.hpp)20
-rw-r--r--include/cru/ui/helper/ShortcutHub.hpp134
-rw-r--r--include/cru/ui/host/LayoutPaintCycler.hpp39
-rw-r--r--include/cru/ui/host/WindowHost.hpp176
-rw-r--r--include/cru/ui/render/Base.hpp1
-rw-r--r--include/cru/ui/render/BorderRenderObject.hpp30
-rw-r--r--include/cru/ui/render/CanvasRenderObject.hpp2
-rw-r--r--include/cru/ui/render/FlexLayoutRenderObject.hpp4
-rw-r--r--include/cru/ui/render/LayoutRenderObject.hpp2
-rw-r--r--include/cru/ui/render/MeasureRequirement.hpp28
-rw-r--r--include/cru/ui/render/RenderObject.hpp61
-rw-r--r--include/cru/ui/render/ScrollBar.hpp218
-rw-r--r--include/cru/ui/render/ScrollRenderObject.hpp27
-rw-r--r--include/cru/ui/render/TextRenderObject.hpp47
-rw-r--r--include/cru/ui/render/WindowRenderObject.hpp29
-rw-r--r--include/cru/ui/style/ApplyBorderStyleInfo.hpp28
-rw-r--r--include/cru/ui/style/Condition.hpp123
-rw-r--r--include/cru/ui/style/StyleRule.hpp45
-rw-r--r--include/cru/ui/style/StyleRuleSet.hpp87
-rw-r--r--include/cru/ui/style/Styler.hpp80
47 files changed, 1637 insertions, 515 deletions
diff --git a/include/cru/ui/Base.hpp b/include/cru/ui/Base.hpp
index 6be359ab..fbdfec77 100644
--- a/include/cru/ui/Base.hpp
+++ b/include/cru/ui/Base.hpp
@@ -1,7 +1,7 @@
#pragma once
#include "cru/common/Base.hpp"
-#include "cru/platform/graph/Base.hpp"
-#include "cru/platform/native/Base.hpp"
+#include "cru/platform/graphics/Base.hpp"
+#include "cru/platform/gui/Base.hpp"
#include <functional>
#include <memory>
@@ -19,23 +19,35 @@ using cru::platform::RoundedRect;
using cru::platform::Size;
using cru::platform::TextRange;
using cru::platform::Thickness;
-using cru::platform::native::MouseButton;
+using cru::platform::gui::MouseButton;
-namespace mouse_buttons = cru::platform::native::mouse_buttons;
+namespace mouse_buttons = cru::platform::gui::mouse_buttons;
namespace colors = cru::platform::colors;
//-------------------- region: forward declaration --------------------
+
+namespace controls {
class Window;
class Control;
-class ClickDetector;
-class UiHost;
+} // namespace controls
+
+namespace host {
+class WindowHost;
+}
namespace render {
class RenderObject;
}
+namespace style {
+class StyleRuleSet;
+class StyleRuleSetBind;
+} // namespace style
+
//-------------------- region: basic types --------------------
+enum class Direction { Horizontal, Vertical };
+
namespace internal {
constexpr int align_start = 0;
constexpr int align_end = align_start + 1;
@@ -82,28 +94,20 @@ inline bool operator!=(const CornerRadius& left, const CornerRadius& right) {
return !(left == right);
}
-struct BorderStyle {
- std::shared_ptr<platform::graph::IBrush> border_brush;
- Thickness border_thickness;
- CornerRadius border_radius;
- std::shared_ptr<platform::graph::IBrush> foreground_brush;
- std::shared_ptr<platform::graph::IBrush> background_brush;
-};
-
class CanvasPaintEventArgs {
public:
- CanvasPaintEventArgs(platform::graph::IPainter* painter,
+ CanvasPaintEventArgs(platform::graphics::IPainter* painter,
const Size& paint_size)
: painter_(painter), paint_size_(paint_size) {}
CRU_DEFAULT_COPY(CanvasPaintEventArgs)
CRU_DEFAULT_MOVE(CanvasPaintEventArgs)
~CanvasPaintEventArgs() = default;
- platform::graph::IPainter* GetPainter() const { return painter_; }
+ platform::graphics::IPainter* GetPainter() const { return painter_; }
Size GetPaintSize() const { return paint_size_; }
private:
- platform::graph::IPainter* painter_;
+ platform::graphics::IPainter* painter_;
Size paint_size_;
};
diff --git a/include/cru/ui/ContentControl.hpp b/include/cru/ui/ContentControl.hpp
deleted file mode 100644
index 19f13a1d..00000000
--- a/include/cru/ui/ContentControl.hpp
+++ /dev/null
@@ -1,29 +0,0 @@
-#pragma once
-#include "Control.hpp"
-
-namespace cru::ui {
-class ContentControl : public Control {
- protected:
- ContentControl();
-
- public:
- ContentControl(const ContentControl& other) = delete;
- ContentControl(ContentControl&& other) = delete;
- ContentControl& operator=(const ContentControl& other) = delete;
- ContentControl& operator=(ContentControl&& other) = delete;
- ~ContentControl() override;
-
- const std::vector<Control*>& GetChildren() const override final {
- return child_vector_;
- }
- Control* GetChild() const { return child_; }
- void SetChild(Control* child);
-
- protected:
- virtual void OnChildChanged(Control* old_child, Control* new_child);
-
- private:
- std::vector<Control*> child_vector_;
- Control*& child_;
-};
-} // namespace cru::ui
diff --git a/include/cru/ui/DebugFlags.hpp b/include/cru/ui/DebugFlags.hpp
new file mode 100644
index 00000000..51482135
--- /dev/null
+++ b/include/cru/ui/DebugFlags.hpp
@@ -0,0 +1,9 @@
+#pragma once
+
+namespace cru::ui::debug_flags {
+constexpr bool routed_event = false;
+constexpr bool layout = false;
+constexpr bool shortcut = false;
+constexpr bool text_service = false;
+constexpr int click_detector = 0;
+} // namespace cru::ui::debug_flags
diff --git a/include/cru/ui/LayoutControl.hpp b/include/cru/ui/LayoutControl.hpp
deleted file mode 100644
index 7997b37e..00000000
--- a/include/cru/ui/LayoutControl.hpp
+++ /dev/null
@@ -1,31 +0,0 @@
-#pragma once
-#include "Control.hpp"
-
-namespace cru::ui {
-class LayoutControl : public Control {
- protected:
- LayoutControl() = default;
-
- public:
- LayoutControl(const LayoutControl& other) = delete;
- LayoutControl(LayoutControl&& other) = delete;
- LayoutControl& operator=(const LayoutControl& other) = delete;
- LayoutControl& operator=(LayoutControl&& other) = delete;
- ~LayoutControl() override;
-
- const std::vector<Control*>& GetChildren() const override final {
- return children_;
- }
-
- void AddChild(Control* control, Index position);
-
- void RemoveChild(Index position);
-
- protected:
- virtual void OnAddChild(Control* child, Index position);
- virtual void OnRemoveChild(Control* child, Index position);
-
- private:
- std::vector<Control*> children_;
-};
-} // namespace cru::ui
diff --git a/include/cru/ui/UiHost.hpp b/include/cru/ui/UiHost.hpp
deleted file mode 100644
index b1658ef6..00000000
--- a/include/cru/ui/UiHost.hpp
+++ /dev/null
@@ -1,172 +0,0 @@
-#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> {
- CRU_DEFINE_CLASS_LOG_TAG(u"cru::ui::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
diff --git a/include/cru/ui/UiManager.hpp b/include/cru/ui/UiManager.hpp
index e6facdbd..6c0d9500 100644
--- a/include/cru/ui/UiManager.hpp
+++ b/include/cru/ui/UiManager.hpp
@@ -2,15 +2,22 @@
#include "Base.hpp"
#include "controls/Base.hpp"
+#include "style/StyleRuleSet.hpp"
+
+#include <memory>
+#include <string>
namespace cru::ui {
struct ThemeResources {
- std::shared_ptr<platform::graph::IFont> default_font;
- std::shared_ptr<platform::graph::IBrush> text_brush;
- std::shared_ptr<platform::graph::IBrush> text_selection_brush;
- std::shared_ptr<platform::graph::IBrush> caret_brush;
- controls::ButtonStyle button_style;
- controls::TextBoxBorderStyle text_box_border_style;
+ std::u16string default_font_family;
+ std::shared_ptr<platform::graphics::IFont> default_font;
+ std::shared_ptr<platform::graphics::IBrush> text_brush;
+ std::shared_ptr<platform::graphics::IBrush> text_selection_brush;
+ std::shared_ptr<platform::graphics::IBrush> caret_brush;
+ style::StyleRuleSet button_style;
+ style::StyleRuleSet text_box_style;
+
+ style::StyleRuleSet menu_item_style;
};
class UiManager : public Object {
diff --git a/include/cru/ui/Window.hpp b/include/cru/ui/Window.hpp
deleted file mode 100644
index 450ea97b..00000000
--- a/include/cru/ui/Window.hpp
+++ /dev/null
@@ -1,40 +0,0 @@
-#pragma once
-#include "ContentControl.hpp"
-
-namespace cru::ui {
-class Window final : public ContentControl {
- friend UiHost;
-
- public:
- static constexpr std::u16string_view control_type = u"Window";
-
- public:
- static Window* CreateOverlapped();
-
- private:
- struct tag_overlapped_constructor {};
-
- explicit Window(tag_overlapped_constructor);
-
- public:
- Window(const Window& other) = delete;
- Window(Window&& other) = delete;
- Window& operator=(const Window& other) = delete;
- Window& operator=(Window&& other) = delete;
- ~Window() override;
-
- public:
- std::u16string_view GetControlType() const final;
-
- render::RenderObject* GetRenderObject() const override;
-
- protected:
- void OnChildChanged(Control* old_child, Control* new_child) override;
-
- private:
- std::unique_ptr<UiHost> managed_ui_host_;
-
- // UiHost is responsible to take care of lifetime of this.
- render::WindowRenderObject* render_object_;
-};
-} // namespace cru::ui
diff --git a/include/cru/ui/components/Component.hpp b/include/cru/ui/components/Component.hpp
new file mode 100644
index 00000000..0dfc587b
--- /dev/null
+++ b/include/cru/ui/components/Component.hpp
@@ -0,0 +1,19 @@
+#pragma once
+#include "../Base.hpp"
+
+namespace cru::ui::components {
+// In destructor, component should check all owned controls whether it is
+// attached to window, if not, destroy them, otherwise it is host's duty to
+// destroy them.
+class Component : public Object {
+ public:
+ Component() = default;
+
+ CRU_DELETE_COPY(Component)
+ CRU_DELETE_MOVE(Component)
+
+ ~Component() = default;
+
+ virtual controls::Control* GetRootControl() = 0;
+};
+} // namespace cru::ui::components
diff --git a/include/cru/ui/components/Menu.hpp b/include/cru/ui/components/Menu.hpp
new file mode 100644
index 00000000..dedf2bd5
--- /dev/null
+++ b/include/cru/ui/components/Menu.hpp
@@ -0,0 +1,60 @@
+#pragma once
+#include "Component.hpp"
+#include "cru/common/Base.hpp"
+#include "cru/ui/controls/Button.hpp"
+#include "cru/ui/controls/Control.hpp"
+#include "cru/ui/controls/FlexLayout.hpp"
+#include "cru/ui/controls/TextBlock.hpp"
+
+#include <string>
+#include <vector>
+
+namespace cru::ui::components {
+class MenuItem : public Component {
+ public:
+ MenuItem();
+ explicit MenuItem(std::u16string text);
+
+ CRU_DELETE_COPY(MenuItem)
+ CRU_DELETE_MOVE(MenuItem)
+
+ ~MenuItem();
+
+ public:
+ controls::Control* GetRootControl() override { return container_; }
+
+ void SetText(std::u16string text);
+
+ private:
+ controls::Button* container_;
+ controls::TextBlock* text_;
+};
+
+class Menu : public Component {
+ public:
+ Menu();
+
+ CRU_DELETE_COPY(Menu)
+ CRU_DELETE_MOVE(Menu)
+
+ ~Menu();
+
+ public:
+ gsl::index GetItemCount() const {
+ return static_cast<gsl::index>(items_.size());
+ }
+
+ void AddItem(Component* component) { AddItem(component, GetItemCount()); }
+ void AddItem(Component* component, gsl::index index);
+ Component* RemoveItem(gsl::index index);
+
+ void AddTextItem(std::u16string text) {
+ AddTextItem(std::move(text), GetItemCount());
+ }
+ void AddTextItem(std::u16string text, gsl::index index);
+
+ private:
+ controls::FlexLayout* container_;
+ std::vector<Component*> items_;
+};
+} // namespace cru::ui::components
diff --git a/include/cru/ui/controls/Base.hpp b/include/cru/ui/controls/Base.hpp
index b550601b..7c85cdb2 100644
--- a/include/cru/ui/controls/Base.hpp
+++ b/include/cru/ui/controls/Base.hpp
@@ -1,24 +1,4 @@
#pragma once
#include "../Base.hpp"
-namespace cru::ui::controls {
-using ButtonStateStyle = BorderStyle;
-
-struct ButtonStyle {
- // corresponds to ClickState::None
- ButtonStateStyle normal;
- // corresponds to ClickState::Hover
- ButtonStateStyle hover;
- // corresponds to ClickState::Press
- ButtonStateStyle press;
- // corresponds to ClickState::PressInactive
- ButtonStateStyle press_cancel;
-};
-
-struct TextBoxBorderStyle {
- BorderStyle normal;
- BorderStyle hover;
- BorderStyle focus;
- BorderStyle focus_hover;
-};
-} // namespace cru::ui::controls
+namespace cru::ui::controls {} // namespace cru::ui::controls
diff --git a/include/cru/ui/controls/Button.hpp b/include/cru/ui/controls/Button.hpp
index a4f727d6..1c9b1216 100644
--- a/include/cru/ui/controls/Button.hpp
+++ b/include/cru/ui/controls/Button.hpp
@@ -1,11 +1,16 @@
#pragma once
-#include "../ContentControl.hpp"
-#include "Base.hpp"
+#include "ContentControl.hpp"
-#include "../ClickDetector.hpp"
+#include "../helper/ClickDetector.hpp"
+#include "IBorderControl.hpp"
+#include "IClickableControl.hpp"
+#include "cru/common/Event.hpp"
+#include "cru/ui/style/ApplyBorderStyleInfo.hpp"
namespace cru::ui::controls {
-class Button : public ContentControl {
+class Button : public ContentControl,
+ public virtual IClickableControl,
+ public virtual IBorderControl {
public:
static constexpr std::u16string_view control_type = u"Button";
@@ -26,17 +31,19 @@ class Button : public ContentControl {
render::RenderObject* GetRenderObject() const override;
public:
- const ButtonStyle& GetStyle() const { return style_; }
- void SetStyle(ButtonStyle style);
+ helper::ClickState GetClickState() override {
+ return click_detector_.GetState();
+ }
- protected:
- void OnChildChanged(Control* old_child, Control* new_child) override;
+ IEvent<helper::ClickState>* ClickStateChangeEvent() override {
+ return click_detector_.StateChangeEvent();
+ }
+
+ void ApplyBorderStyle(const style::ApplyBorderStyleInfo& style) override;
private:
std::unique_ptr<render::BorderRenderObject> render_object_{};
- ButtonStyle style_;
-
- ClickDetector click_detector_;
+ helper::ClickDetector click_detector_;
};
} // namespace cru::ui::controls
diff --git a/include/cru/ui/controls/Container.hpp b/include/cru/ui/controls/Container.hpp
index 304d402c..18958837 100644
--- a/include/cru/ui/controls/Container.hpp
+++ b/include/cru/ui/controls/Container.hpp
@@ -1,5 +1,5 @@
#pragma once
-#include "../ContentControl.hpp"
+#include "ContentControl.hpp"
namespace cru::ui::controls {
class Container : public ContentControl {
@@ -19,9 +19,6 @@ class Container : public ContentControl {
render::RenderObject* GetRenderObject() const override;
- protected:
- void OnChildChanged(Control* old_child, Control* new_child) override;
-
private:
std::unique_ptr<render::BorderRenderObject> render_object_;
};
diff --git a/include/cru/ui/controls/ContentControl.hpp b/include/cru/ui/controls/ContentControl.hpp
new file mode 100644
index 00000000..1bdaf7e4
--- /dev/null
+++ b/include/cru/ui/controls/ContentControl.hpp
@@ -0,0 +1,38 @@
+#pragma once
+#include "Control.hpp"
+
+#include "cru/ui/render/RenderObject.hpp"
+
+namespace cru::ui::controls {
+class ContentControl : public Control {
+ protected:
+ ContentControl() = default;
+
+ public:
+ ContentControl(const ContentControl& other) = delete;
+ ContentControl(ContentControl&& other) = delete;
+ ContentControl& operator=(const ContentControl& other) = delete;
+ ContentControl& operator=(ContentControl&& other) = delete;
+ ~ContentControl() override = default;
+
+ Control* GetChild() const;
+ void SetChild(Control* child);
+
+ protected:
+ virtual void OnChildChanged(Control* old_child, Control* new_child);
+
+ render::RenderObject* GetContainerRenderObject() const {
+ return container_render_object_;
+ }
+ void SetContainerRenderObject(render::RenderObject* ro) {
+ container_render_object_ = ro;
+ }
+
+ private:
+ using Control::AddChild;
+ using Control::RemoveChild;
+
+ private:
+ render::RenderObject* container_render_object_ = nullptr;
+};
+} // namespace cru::ui::controls
diff --git a/include/cru/ui/Control.hpp b/include/cru/ui/controls/Control.hpp
index bd86bc2f..341b6ef2 100644
--- a/include/cru/ui/Control.hpp
+++ b/include/cru/ui/controls/Control.hpp
@@ -1,15 +1,15 @@
#pragma once
#include "Base.hpp"
+#include "../events/UiEvent.hpp"
+#include "../render/Base.hpp"
#include "cru/common/Event.hpp"
-#include "render/Base.hpp"
-#include "UiEvent.hpp"
#include <string_view>
-namespace cru::ui {
+namespace cru::ui::controls {
class Control : public Object {
- friend UiHost;
+ friend host::WindowHost;
protected:
Control();
@@ -19,39 +19,31 @@ class Control : public Object {
Control(Control&& other) = delete;
Control& operator=(const Control& other) = delete;
Control& operator=(Control&& other) = delete;
- ~Control() override = default;
+ ~Control() override;
public:
virtual std::u16string_view GetControlType() const = 0;
//*************** region: tree ***************
public:
- // Get the ui host if attached, otherwise, return nullptr.
- UiHost* GetUiHost() const { return ui_host_; }
+ host::WindowHost* GetWindowHost() const;
Control* GetParent() const { return parent_; }
- virtual const std::vector<Control*>& GetChildren() const = 0;
+ const std::vector<Control*>& GetChildren() const { return children_; }
// Traverse the tree rooted the control including itself.
void TraverseDescendants(const std::function<void(Control*)>& predicate);
- void _SetParent(Control* parent);
- void _SetDescendantUiHost(UiHost* host);
-
- private:
- static void _TraverseDescendants(
- Control* control, const std::function<void(Control*)>& predicate);
-
public:
virtual render::RenderObject* GetRenderObject() const = 0;
//*************** region: focus ***************
public:
- bool RequestFocus();
-
bool HasFocus();
+ void SetFocus();
+
//*************** region: mouse ***************
public:
bool IsMouseOver() const { return is_mouse_over_; }
@@ -66,13 +58,16 @@ class Control : public Object {
// Cursor is inherited from parent recursively if not set.
public:
// null for not set
- std::shared_ptr<platform::native::ICursor> GetCursor();
+ std::shared_ptr<platform::gui::ICursor> GetCursor();
// will not return nullptr
- std::shared_ptr<platform::native::ICursor> GetInheritedCursor();
+ std::shared_ptr<platform::gui::ICursor> GetInheritedCursor();
// null to unset
- void SetCursor(std::shared_ptr<platform::native::ICursor> cursor);
+ void SetCursor(std::shared_ptr<platform::gui::ICursor> cursor);
+
+ public:
+ style::StyleRuleSet* GetStyleRuleSet();
//*************** region: events ***************
public:
@@ -134,19 +129,29 @@ class Control : public Object {
//*************** region: tree ***************
protected:
+ void AddChild(Control* control, Index position);
+ void RemoveChild(Index position);
+ virtual void OnAddChild(Control* child, Index position);
+ virtual void OnRemoveChild(Control* child, Index position);
virtual void OnParentChanged(Control* old_parent, Control* new_parent);
- virtual void OnAttachToHost(UiHost* host);
- virtual void OnDetachFromHost(UiHost* host);
+ virtual void OnAttachToHost(host::WindowHost* host);
+ virtual void OnDetachFromHost(host::WindowHost* host);
+ protected:
virtual void OnMouseHoverChange(bool newHover) { CRU_UNUSED(newHover) }
private:
- UiHost* ui_host_ = nullptr;
Control* parent_ = nullptr;
+ std::vector<Control*> children_;
+
+ host::WindowHost* window_host_ = nullptr;
private:
bool is_mouse_over_ = false;
- std::shared_ptr<platform::native::ICursor> cursor_ = nullptr;
+ std::shared_ptr<platform::gui::ICursor> cursor_ = nullptr;
+
+ std::unique_ptr<style::StyleRuleSet> style_rule_set_;
+ std::unique_ptr<style::StyleRuleSetBind> style_rule_set_bind_;
};
-} // namespace cru::ui
+} // namespace cru::ui::controls
diff --git a/include/cru/ui/controls/FlexLayout.hpp b/include/cru/ui/controls/FlexLayout.hpp
index 87162569..4f6abfdb 100644
--- a/include/cru/ui/controls/FlexLayout.hpp
+++ b/include/cru/ui/controls/FlexLayout.hpp
@@ -1,5 +1,5 @@
#pragma once
-#include "../LayoutControl.hpp"
+#include "LayoutControl.hpp"
namespace cru::ui::controls {
class FlexLayout : public LayoutControl {
@@ -28,13 +28,12 @@ class FlexLayout : public LayoutControl {
FlexDirection GetFlexDirection() const;
void SetFlexDirection(FlexDirection direction);
+ FlexCrossAlignment GetItemCrossAlign() const;
+ void SetItemCrossAlign(FlexCrossAlignment alignment);
+
FlexChildLayoutData GetChildLayoutData(Control* control);
void SetChildLayoutData(Control* control, FlexChildLayoutData data);
- protected:
- void OnAddChild(Control* child, Index position) override;
- void OnRemoveChild(Control* child, Index position) override;
-
private:
std::shared_ptr<render::FlexLayoutRenderObject> render_object_;
};
diff --git a/include/cru/ui/controls/IBorderControl.hpp b/include/cru/ui/controls/IBorderControl.hpp
new file mode 100644
index 00000000..817305ef
--- /dev/null
+++ b/include/cru/ui/controls/IBorderControl.hpp
@@ -0,0 +1,10 @@
+#pragma once
+#include "../style/ApplyBorderStyleInfo.hpp"
+#include "Base.hpp"
+#include "cru/common/Base.hpp"
+
+namespace cru::ui::controls {
+struct IBorderControl : virtual Interface {
+ virtual void ApplyBorderStyle(const style::ApplyBorderStyleInfo& style) = 0;
+};
+} // namespace cru::ui::controls
diff --git a/include/cru/ui/controls/IClickableControl.hpp b/include/cru/ui/controls/IClickableControl.hpp
new file mode 100644
index 00000000..aa7f13ab
--- /dev/null
+++ b/include/cru/ui/controls/IClickableControl.hpp
@@ -0,0 +1,12 @@
+#pragma once
+#include "Base.hpp"
+
+#include "cru/common/Event.hpp"
+#include "cru/ui/helper/ClickDetector.hpp"
+
+namespace cru::ui::controls {
+struct IClickableControl : virtual Interface {
+ virtual helper::ClickState GetClickState() = 0;
+ virtual IEvent<helper::ClickState>* ClickStateChangeEvent() = 0;
+};
+} // namespace cru::ui::controls
diff --git a/include/cru/ui/controls/LayoutControl.hpp b/include/cru/ui/controls/LayoutControl.hpp
new file mode 100644
index 00000000..106dd94d
--- /dev/null
+++ b/include/cru/ui/controls/LayoutControl.hpp
@@ -0,0 +1,35 @@
+#pragma once
+#include "Control.hpp"
+
+namespace cru::ui::controls {
+class LayoutControl : public Control {
+ protected:
+ LayoutControl() = default;
+ explicit LayoutControl(render::RenderObject* container_render_object)
+ : container_render_object_(container_render_object) {}
+
+ public:
+ LayoutControl(const LayoutControl& other) = delete;
+ LayoutControl(LayoutControl&& other) = delete;
+ LayoutControl& operator=(const LayoutControl& other) = delete;
+ LayoutControl& operator=(LayoutControl&& other) = delete;
+ ~LayoutControl() override = default;
+
+ using Control::AddChild;
+ using Control::RemoveChild;
+
+ protected:
+ // If container render object is not null. Render object of added or removed
+ // child control will automatically sync to the container render object.
+ render::RenderObject* GetContainerRenderObject() const;
+ void SetContainerRenderObject(render::RenderObject* ro) {
+ container_render_object_ = ro;
+ }
+
+ void OnAddChild(Control* child, Index position) override;
+ void OnRemoveChild(Control* child, Index position) override;
+
+ private:
+ render::RenderObject* container_render_object_ = nullptr;
+};
+} // namespace cru::ui::controls
diff --git a/include/cru/ui/NoChildControl.hpp b/include/cru/ui/controls/NoChildControl.hpp
index 1a31ae7e..562137f1 100644
--- a/include/cru/ui/NoChildControl.hpp
+++ b/include/cru/ui/controls/NoChildControl.hpp
@@ -1,11 +1,8 @@
#pragma once
#include "Control.hpp"
-namespace cru::ui {
+namespace cru::ui::controls {
class NoChildControl : public Control {
- private:
- static const std::vector<Control*> empty_control_vector;
-
protected:
NoChildControl() = default;
@@ -16,9 +13,8 @@ class NoChildControl : public Control {
NoChildControl& operator=(NoChildControl&& other) = delete;
~NoChildControl() override = default;
- protected:
- const std::vector<Control*>& GetChildren() const override final {
- return empty_control_vector;
- }
+ private:
+ using Control::AddChild;
+ using Control::RemoveChild;
};
} // namespace cru::ui
diff --git a/include/cru/ui/controls/Popup.hpp b/include/cru/ui/controls/Popup.hpp
new file mode 100644
index 00000000..d76e1211
--- /dev/null
+++ b/include/cru/ui/controls/Popup.hpp
@@ -0,0 +1,24 @@
+#pragma once
+#include "RootControl.hpp"
+
+#include "cru/ui/Base.hpp"
+#include "cru/platform/gui/Base.hpp"
+
+#include <memory>
+
+namespace cru::ui::controls {
+class Popup : public RootControl {
+ public:
+ explicit Popup(Control* attached_control = nullptr);
+
+ CRU_DELETE_COPY(Popup)
+ CRU_DELETE_MOVE(Popup)
+
+ ~Popup() override;
+
+ protected:
+ gsl::not_null<platform::gui::INativeWindow*> CreateNativeWindow(
+ gsl::not_null<host::WindowHost*> host,
+ platform::gui::INativeWindow* parent) override;
+};
+} // namespace cru::ui::controls
diff --git a/include/cru/ui/controls/RootControl.hpp b/include/cru/ui/controls/RootControl.hpp
new file mode 100644
index 00000000..53e69e7c
--- /dev/null
+++ b/include/cru/ui/controls/RootControl.hpp
@@ -0,0 +1,45 @@
+#pragma once
+#include "LayoutControl.hpp"
+
+#include "cru/common/Base.hpp"
+#include "cru/platform/gui/Base.hpp"
+#include "cru/ui/Base.hpp"
+
+namespace cru::ui::controls {
+class RootControl : public LayoutControl {
+ protected:
+ explicit RootControl(Control* attached_control);
+
+ public:
+ CRU_DELETE_COPY(RootControl)
+ CRU_DELETE_MOVE(RootControl)
+ ~RootControl() override;
+
+ public:
+ render::RenderObject* GetRenderObject() const override;
+
+ void EnsureWindowCreated();
+
+ // If create is false and native window is not create, it will not be created
+ // and shown.
+ void Show(bool create = true);
+
+ Rect GetRect();
+ void SetRect(const Rect& rect);
+
+ protected:
+ virtual gsl::not_null<platform::gui::INativeWindow*> CreateNativeWindow(
+ gsl::not_null<host::WindowHost*> host,
+ platform::gui::INativeWindow* parent) = 0;
+
+ private:
+ platform::gui::INativeWindow* GetNativeWindow(bool create);
+
+ private:
+ std::unique_ptr<host::WindowHost> window_host_;
+
+ std::unique_ptr<render::StackLayoutRenderObject> render_object_;
+
+ Control* attached_control_;
+};
+} // namespace cru::ui::controls
diff --git a/include/cru/ui/controls/StackLayout.hpp b/include/cru/ui/controls/StackLayout.hpp
index c0b95044..aa9440c2 100644
--- a/include/cru/ui/controls/StackLayout.hpp
+++ b/include/cru/ui/controls/StackLayout.hpp
@@ -1,5 +1,5 @@
#pragma once
-#include "../LayoutControl.hpp"
+#include "LayoutControl.hpp"
namespace cru::ui::controls {
class StackLayout : public LayoutControl {
@@ -21,10 +21,6 @@ class StackLayout : public LayoutControl {
render::RenderObject* GetRenderObject() const override;
- protected:
- void OnAddChild(Control* child, Index position) override;
- void OnRemoveChild(Control* child, Index position) override;
-
private:
std::shared_ptr<render::StackLayoutRenderObject> render_object_;
};
diff --git a/include/cru/ui/controls/TextBlock.hpp b/include/cru/ui/controls/TextBlock.hpp
index 8a9a3bff..be31816c 100644
--- a/include/cru/ui/controls/TextBlock.hpp
+++ b/include/cru/ui/controls/TextBlock.hpp
@@ -1,15 +1,15 @@
#pragma once
-#include "../NoChildControl.hpp"
+#include "NoChildControl.hpp"
-namespace cru::ui::controls {
-template <typename TControl>
-class TextControlService;
+#include "TextHostControlService.hpp"
-class TextBlock : public NoChildControl {
+namespace cru::ui::controls {
+class TextBlock : public NoChildControl, public virtual ITextHostControl {
public:
static constexpr std::u16string_view control_type = u"TextBlock";
- static TextBlock* Create() { return new TextBlock(); }
+ static TextBlock* Create();
+ static TextBlock* Create(std::u16string text, bool selectable = false);
protected:
TextBlock();
@@ -28,12 +28,17 @@ class TextBlock : public NoChildControl {
std::u16string GetText() const;
void SetText(std::u16string text);
- gsl::not_null<render::TextRenderObject*> GetTextRenderObject();
- render::ScrollRenderObject* GetScrollRenderObject() { return nullptr; }
+ bool IsSelectable() const;
+ void SetSelectable(bool value);
+
+ gsl::not_null<render::TextRenderObject*> GetTextRenderObject() override;
+ render::ScrollRenderObject* GetScrollRenderObject() override {
+ return nullptr;
+ }
private:
std::unique_ptr<render::TextRenderObject> text_render_object_;
- std::unique_ptr<TextControlService<TextBlock>> service_;
+ std::unique_ptr<TextHostControlService> service_;
};
} // namespace cru::ui::controls
diff --git a/include/cru/ui/controls/TextBox.hpp b/include/cru/ui/controls/TextBox.hpp
index 5976f6da..5693b315 100644
--- a/include/cru/ui/controls/TextBox.hpp
+++ b/include/cru/ui/controls/TextBox.hpp
@@ -1,6 +1,8 @@
#pragma once
-#include "../NoChildControl.hpp"
-#include "Base.hpp"
+#include "NoChildControl.hpp"
+
+#include "IBorderControl.hpp"
+#include "TextHostControlService.hpp"
#include <memory>
@@ -8,7 +10,9 @@ namespace cru::ui::controls {
template <typename TControl>
class TextControlService;
-class TextBox : public NoChildControl {
+class TextBox : public NoChildControl,
+ public virtual IBorderControl,
+ public virtual ITextHostControl {
public:
static constexpr std::u16string_view control_type = u"TextBox";
@@ -27,25 +31,16 @@ class TextBox : public NoChildControl {
render::RenderObject* GetRenderObject() const override;
- gsl::not_null<render::TextRenderObject*> GetTextRenderObject();
- render::ScrollRenderObject* GetScrollRenderObject();
-
- const TextBoxBorderStyle& GetBorderStyle();
- void SetBorderStyle(TextBoxBorderStyle border_style);
-
- protected:
- void OnMouseHoverChange(bool newHover) override;
+ gsl::not_null<render::TextRenderObject*> GetTextRenderObject() override;
+ render::ScrollRenderObject* GetScrollRenderObject() override;
- private:
- void UpdateBorderStyle();
+ void ApplyBorderStyle(const style::ApplyBorderStyleInfo& style) override;
private:
std::unique_ptr<render::BorderRenderObject> border_render_object_;
std::unique_ptr<render::ScrollRenderObject> scroll_render_object_;
std::unique_ptr<render::TextRenderObject> text_render_object_;
- TextBoxBorderStyle border_style_;
-
- std::unique_ptr<TextControlService<TextBox>> service_;
+ std::unique_ptr<TextHostControlService> service_;
};
} // namespace cru::ui::controls
diff --git a/include/cru/ui/controls/TextHostControlService.hpp b/include/cru/ui/controls/TextHostControlService.hpp
new file mode 100644
index 00000000..340228fe
--- /dev/null
+++ b/include/cru/ui/controls/TextHostControlService.hpp
@@ -0,0 +1,137 @@
+#pragma once
+#include "Base.hpp"
+
+#include "cru/platform/gui/InputMethod.hpp"
+#include "cru/platform/gui/UiApplication.hpp"
+#include "cru/ui/controls/Control.hpp"
+#include "cru/ui/helper/ShortcutHub.hpp"
+
+#include <functional>
+#include <string>
+
+namespace cru::ui::render {
+class TextRenderObject;
+class ScrollRenderObject;
+} // namespace cru::ui::render
+
+namespace cru::ui::controls {
+constexpr int k_default_caret_blink_duration = 500;
+
+struct ITextHostControl : virtual Interface {
+ virtual gsl::not_null<render::TextRenderObject*> GetTextRenderObject() = 0;
+ // May return nullptr.
+ virtual render::ScrollRenderObject* GetScrollRenderObject() = 0;
+};
+
+class TextHostControlService : public Object {
+ CRU_DEFINE_CLASS_LOG_TAG(u"cru::ui::controls::TextControlService")
+
+ public:
+ TextHostControlService(gsl::not_null<Control*> control);
+
+ CRU_DELETE_COPY(TextHostControlService)
+ CRU_DELETE_MOVE(TextHostControlService)
+
+ ~TextHostControlService() = default;
+
+ public:
+ bool IsEnabled() { return enable_; }
+ void SetEnabled(bool enable);
+
+ bool IsEditable() { return this->editable_; }
+ void SetEditable(bool editable);
+
+ std::u16string GetText() { return this->text_; }
+ std::u16string_view GetTextView() { return this->text_; }
+ void SetText(std::u16string text, bool stop_composition = false);
+
+ void InsertText(gsl::index position, std::u16string_view text,
+ bool stop_composition = false);
+ void DeleteChar(gsl::index position, bool stop_composition = false);
+
+ // Return the position of deleted character.
+ gsl::index DeleteCharPrevious(gsl::index position,
+ bool stop_composition = false);
+ void DeleteText(TextRange range, bool stop_composition = false);
+
+ void CancelComposition();
+
+ std::optional<platform::gui::CompositionText> GetCompositionInfo();
+
+ bool IsCaretVisible() { return caret_visible_; }
+ void SetCaretVisible(bool visible);
+
+ int GetCaretBlinkDuration() { return caret_blink_duration_; }
+ void SetCaretBlinkDuration(int milliseconds);
+
+ gsl::index GetCaretPosition() { return selection_.GetEnd(); }
+ TextRange GetSelection() { return selection_; }
+
+ void SetSelection(gsl::index caret_position);
+ void SetSelection(TextRange selection, bool scroll_to_caret = true);
+
+ void ChangeSelectionEnd(gsl::index new_end);
+ void AbortSelection();
+
+ void DeleteSelectedText();
+ // If some text is selected, then they are deleted first. Then insert text
+ // into caret position.
+ void ReplaceSelectedText(std::u16string_view text);
+
+ void ScrollToCaret();
+
+ private:
+ gsl::not_null<render::TextRenderObject*> GetTextRenderObject();
+ render::ScrollRenderObject* GetScrollRenderObject();
+
+ // May return nullptr.
+ platform::gui::IInputMethodContext* GetInputMethodContext();
+
+ void CoerceSelection();
+
+ void SetupCaret();
+ void TearDownCaret();
+
+ void SyncTextRenderObject();
+
+ void UpdateInputMethodPosition();
+
+ template <typename TArgs>
+ void SetupOneHandler(event::RoutedEvent<TArgs>* (Control::*event)(),
+ void (TextHostControlService::*handler)(
+ typename event::RoutedEvent<TArgs>::EventArgs)) {
+ this->event_guard_ += (this->control_->*event)()->Bubble()->AddHandler(
+ std::bind(handler, this, std::placeholders::_1));
+ }
+
+ void MouseMoveHandler(event::MouseEventArgs& args);
+ void MouseDownHandler(event::MouseButtonEventArgs& args);
+ void MouseUpHandler(event::MouseButtonEventArgs& args);
+ void GainFocusHandler(event::FocusChangeEventArgs& args);
+ void LoseFocusHandler(event::FocusChangeEventArgs& args);
+
+ void SetUpShortcuts();
+
+ private:
+ gsl::not_null<Control*> control_;
+ gsl::not_null<ITextHostControl*> text_host_control_;
+
+ EventRevokerListGuard event_guard_;
+ EventRevokerListGuard input_method_context_event_guard_;
+
+ std::u16string text_;
+ TextRange selection_;
+
+ bool enable_ = false;
+ bool editable_ = false;
+
+ bool caret_visible_ = false;
+ platform::gui::TimerAutoCanceler caret_timer_canceler_;
+ int caret_blink_duration_ = k_default_caret_blink_duration;
+
+ helper::ShortcutHub shortcut_hub_;
+
+ // true if left mouse is down and selecting
+ bool mouse_move_selecting_ = false;
+};
+} // namespace cru::ui::controls
diff --git a/include/cru/ui/controls/Window.hpp b/include/cru/ui/controls/Window.hpp
new file mode 100644
index 00000000..cca56b64
--- /dev/null
+++ b/include/cru/ui/controls/Window.hpp
@@ -0,0 +1,32 @@
+#pragma once
+#include "cru/platform/gui/Base.hpp"
+#include "cru/ui/controls/RootControl.hpp"
+
+#include "cru/common/Base.hpp"
+
+namespace cru::ui::controls {
+class Window final : public RootControl {
+ public:
+ static constexpr std::u16string_view control_type = u"Window";
+
+ public:
+ static Window* Create(Control* attached_control = nullptr);
+
+ private:
+ explicit Window(Control* attached_control);
+
+ public:
+ CRU_DELETE_COPY(Window)
+ CRU_DELETE_MOVE(Window)
+
+ ~Window() override;
+
+ public:
+ std::u16string_view GetControlType() const final { return control_type; }
+
+ protected:
+ gsl::not_null<platform::gui::INativeWindow*> CreateNativeWindow(
+ gsl::not_null<host::WindowHost*> host,
+ platform::gui::INativeWindow* parent) override;
+};
+} // namespace cru::ui::controls
diff --git a/include/cru/ui/UiEvent.hpp b/include/cru/ui/events/UiEvent.hpp
index 5adace8a..22ad0150 100644
--- a/include/cru/ui/UiEvent.hpp
+++ b/include/cru/ui/events/UiEvent.hpp
@@ -1,15 +1,15 @@
#pragma once
-#include "Base.hpp"
+#include "../Base.hpp"
#include "cru/common/Event.hpp"
-#include "cru/platform/native/Keyboard.hpp"
+#include "cru/platform/gui/Keyboard.hpp"
#include <memory>
#include <optional>
#include <string>
#include <type_traits>
-namespace cru::platform::graph {
+namespace cru::platform::graphics {
struct IPainter;
}
@@ -84,6 +84,7 @@ class MouseEventArgs : public UiEventArgs {
// This point is relative to window client lefttop.
Point GetPoint() const { return point_.value_or(Point{}); }
+ Point GetPoint(render::RenderObject* render_target) const;
Point GetPointToContent(render::RenderObject* render_target) const;
private:
@@ -94,13 +95,13 @@ class MouseButtonEventArgs : public MouseEventArgs {
public:
MouseButtonEventArgs(Object* sender, Object* original_sender,
const Point& point, const MouseButton button,
- platform::native::KeyModifier key_modifier)
+ platform::gui::KeyModifier key_modifier)
: MouseEventArgs(sender, original_sender, point),
button_(button),
key_modifier_(key_modifier) {}
MouseButtonEventArgs(Object* sender, Object* original_sender,
const MouseButton button,
- platform::native::KeyModifier key_modifier)
+ platform::gui::KeyModifier key_modifier)
: MouseEventArgs(sender, original_sender),
button_(button),
key_modifier_(key_modifier) {}
@@ -111,11 +112,11 @@ class MouseButtonEventArgs : public MouseEventArgs {
~MouseButtonEventArgs() override = default;
MouseButton GetButton() const { return button_; }
- platform::native::KeyModifier GetKeyModifier() const { return key_modifier_; }
+ platform::gui::KeyModifier GetKeyModifier() const { return key_modifier_; }
private:
MouseButton button_;
- platform::native::KeyModifier key_modifier_;
+ platform::gui::KeyModifier key_modifier_;
};
class MouseWheelEventArgs : public MouseEventArgs {
@@ -138,7 +139,7 @@ class MouseWheelEventArgs : public MouseEventArgs {
class PaintEventArgs : public UiEventArgs {
public:
PaintEventArgs(Object* sender, Object* original_sender,
- platform::graph::IPainter* painter)
+ platform::graphics::IPainter* painter)
: UiEventArgs(sender, original_sender), painter_(painter) {}
PaintEventArgs(const PaintEventArgs& other) = default;
PaintEventArgs(PaintEventArgs&& other) = default;
@@ -146,10 +147,10 @@ class PaintEventArgs : public UiEventArgs {
PaintEventArgs& operator=(PaintEventArgs&& other) = default;
~PaintEventArgs() = default;
- platform::graph::IPainter* GetPainter() const { return painter_; }
+ platform::graphics::IPainter* GetPainter() const { return painter_; }
private:
- platform::graph::IPainter* painter_;
+ platform::graphics::IPainter* painter_;
};
class FocusChangeEventArgs : public UiEventArgs {
@@ -191,8 +192,8 @@ class ToggleEventArgs : public UiEventArgs {
class KeyEventArgs : public UiEventArgs {
public:
KeyEventArgs(Object* sender, Object* original_sender,
- platform::native::KeyCode key_code,
- platform::native::KeyModifier key_modifier)
+ platform::gui::KeyCode key_code,
+ platform::gui::KeyModifier key_modifier)
: UiEventArgs(sender, original_sender),
key_code_(key_code),
key_modifier_(key_modifier) {}
@@ -202,12 +203,12 @@ class KeyEventArgs : public UiEventArgs {
KeyEventArgs& operator=(KeyEventArgs&& other) = default;
~KeyEventArgs() override = default;
- platform::native::KeyCode GetKeyCode() const { return key_code_; }
- platform::native::KeyModifier GetKeyModifier() const { return key_modifier_; }
+ platform::gui::KeyCode GetKeyCode() const { return key_code_; }
+ platform::gui::KeyModifier GetKeyModifier() const { return key_modifier_; }
private:
- platform::native::KeyCode key_code_;
- platform::native::KeyModifier key_modifier_;
+ platform::gui::KeyCode key_code_;
+ platform::gui::KeyModifier key_modifier_;
};
class CharEventArgs : public UiEventArgs {
diff --git a/include/cru/ui/ClickDetector.hpp b/include/cru/ui/helper/ClickDetector.hpp
index 4ffe5d05..b58297b1 100644
--- a/include/cru/ui/ClickDetector.hpp
+++ b/include/cru/ui/helper/ClickDetector.hpp
@@ -1,10 +1,10 @@
#pragma once
-#include "Control.hpp"
+#include "../controls/Control.hpp"
-namespace cru::ui {
+namespace cru::ui::helper {
class ClickEventArgs : Object {
public:
- ClickEventArgs(Control* sender, const Point& down_point,
+ ClickEventArgs(controls::Control* sender, const Point& down_point,
const Point& up_point, MouseButton button)
: sender_(sender),
down_point_(down_point),
@@ -16,13 +16,13 @@ class ClickEventArgs : Object {
~ClickEventArgs() override = default;
- Control* GetSender() const { return sender_; }
+ controls::Control* GetSender() const { return sender_; }
Point GetDownPoint() const { return down_point_; }
Point GetUpPoint() const { return up_point_; }
MouseButton GetButton() const { return button_; }
private:
- Control* sender_;
+ controls::Control* sender_;
Point down_point_;
Point up_point_;
MouseButton button_;
@@ -39,14 +39,14 @@ class ClickDetector : public Object {
CRU_DEFINE_CLASS_LOG_TAG(u"cru::ui::ClickDetector")
public:
- explicit ClickDetector(Control* control);
+ explicit ClickDetector(controls::Control* control);
CRU_DELETE_COPY(ClickDetector)
CRU_DELETE_MOVE(ClickDetector)
~ClickDetector() override = default;
- Control* GetControl() const { return control_; }
+ controls::Control* GetControl() const { return control_; }
ClickState GetState() const { return state_; }
@@ -69,9 +69,9 @@ class ClickDetector : public Object {
void SetState(ClickState state);
private:
- Control* control_;
+ controls::Control* control_;
- ClickState state_;
+ ClickState state_ = ClickState::None;
bool enable_ = true;
MouseButton trigger_button_ = mouse_buttons::left | mouse_buttons::right;
@@ -84,4 +84,4 @@ class ClickDetector : public Object {
Point down_point_;
MouseButton button_;
};
-} // namespace cru::ui
+} // namespace cru::ui::helper
diff --git a/include/cru/ui/helper/ShortcutHub.hpp b/include/cru/ui/helper/ShortcutHub.hpp
new file mode 100644
index 00000000..fe3414fe
--- /dev/null
+++ b/include/cru/ui/helper/ShortcutHub.hpp
@@ -0,0 +1,134 @@
+#pragma once
+#include "../Base.hpp"
+
+#include "../events/UiEvent.hpp"
+#include "cru/common/Event.hpp"
+#include "cru/platform/gui/Keyboard.hpp"
+
+#include <cstddef>
+#include <functional>
+#include <memory>
+#include <optional>
+#include <string>
+#include <string_view>
+#include <type_traits>
+#include <unordered_map>
+#include <vector>
+
+namespace cru::ui::helper {
+
+class ShortcutKeyBind {
+ public:
+ ShortcutKeyBind(
+ platform::gui::KeyCode key,
+ platform::gui::KeyModifier modifier = platform::gui::KeyModifiers::none)
+ : key_(key), modifier_(modifier) {}
+
+ CRU_DEFAULT_COPY(ShortcutKeyBind)
+ CRU_DEFAULT_MOVE(ShortcutKeyBind)
+
+ ~ShortcutKeyBind() = default;
+
+ platform::gui::KeyCode GetKey() const { return key_; }
+ platform::gui::KeyModifier GetModifier() const { return modifier_; }
+
+ bool Is(platform::gui::KeyCode key,
+ platform::gui::KeyModifier modifier) const {
+ return key == key_ && modifier == modifier_;
+ }
+
+ bool operator==(const ShortcutKeyBind& other) const {
+ return this->key_ == other.key_ && this->modifier_ == other.modifier_;
+ }
+
+ bool operator!=(const ShortcutKeyBind& other) const {
+ return !this->operator==(other);
+ }
+
+ std::u16string ToString() {
+ std::u16string result = u"(";
+ result += platform::gui::ToString(modifier_);
+ result += u")";
+ result += platform::gui::ToString(key_);
+ return result;
+ }
+
+ private:
+ platform::gui::KeyCode key_;
+ platform::gui::KeyModifier modifier_;
+};
+} // namespace cru::ui::helper
+
+namespace std {
+template <>
+struct hash<cru::ui::helper::ShortcutKeyBind> {
+ std::size_t operator()(const cru::ui::helper::ShortcutKeyBind& value) const {
+ std::size_t result = 0;
+ cru::hash_combine(result, static_cast<int>(value.GetKey()));
+ cru::hash_combine(result, static_cast<int>(value.GetModifier()));
+ return result;
+ }
+};
+} // namespace std
+
+namespace cru::ui::helper {
+struct Shortcut {
+ // Just for debug.
+ std::u16string name;
+ ShortcutKeyBind key_bind;
+ // Return true if it consumes the shortcut. Or return false if it does not
+ // handle the shortcut.
+ std::function<bool()> handler;
+};
+
+struct ShortcutInfo {
+ int id;
+ std::u16string name;
+ ShortcutKeyBind key_bind;
+ std::function<bool()> handler;
+};
+
+class ShortcutHub : public Object {
+ public:
+ ShortcutHub() = default;
+
+ CRU_DELETE_COPY(ShortcutHub)
+ CRU_DELETE_MOVE(ShortcutHub)
+
+ ~ShortcutHub() override = default;
+
+ int RegisterShortcut(std::u16string name, ShortcutKeyBind bind,
+ std::function<bool()> handler) {
+ return RegisterShortcut({std::move(name), bind, std::move(handler)});
+ }
+
+ // Return an id used for unregistering.
+ int RegisterShortcut(Shortcut shortcut);
+
+ void UnregisterShortcut(int id);
+
+ std::vector<ShortcutInfo> GetAllShortcuts() const;
+ std::optional<ShortcutInfo> GetShortcut(int id) const;
+ const std::vector<ShortcutInfo>& GetShortcutByKeyBind(
+ const ShortcutKeyBind& key_bind) const;
+
+ IEvent<event::KeyEventArgs&>* FallbackKeyEvent() { return &fallback_event_; }
+
+ void Install(controls::Control* control);
+ void Uninstall();
+
+ private:
+ void OnKeyDown(event::KeyEventArgs& event);
+
+ private:
+ std::unordered_map<ShortcutKeyBind, std::vector<ShortcutInfo>> map_;
+
+ const std::vector<ShortcutInfo> empty_list_;
+
+ int current_id_ = 1;
+
+ Event<event::KeyEventArgs&> fallback_event_;
+
+ EventRevokerListGuard event_guard_;
+};
+} // namespace cru::ui::helper
diff --git a/include/cru/ui/host/LayoutPaintCycler.hpp b/include/cru/ui/host/LayoutPaintCycler.hpp
new file mode 100644
index 00000000..ed543afa
--- /dev/null
+++ b/include/cru/ui/host/LayoutPaintCycler.hpp
@@ -0,0 +1,39 @@
+#pragma once
+#include "../Base.hpp"
+
+#include "cru/platform/gui/UiApplication.hpp"
+
+#include <chrono>
+
+namespace cru::ui::host {
+class LayoutPaintCycler {
+ public:
+ explicit LayoutPaintCycler(WindowHost* host);
+
+ CRU_DELETE_COPY(LayoutPaintCycler)
+ CRU_DELETE_MOVE(LayoutPaintCycler)
+
+ ~LayoutPaintCycler();
+
+ public:
+ void InvalidateLayout();
+ void InvalidatePaint();
+
+ bool IsLayoutDirty() { return layout_dirty_; }
+
+ private:
+ void OnCycle();
+
+ private:
+ WindowHost* host_;
+
+ platform::gui::TimerAutoCanceler timer_canceler_;
+
+ bool layout_dirty_ = true;
+ bool paint_dirty_ = true;
+
+ std::chrono::steady_clock::time_point last_cycle_time_;
+ std::chrono::steady_clock::duration cycle_threshold_ =
+ std::chrono::milliseconds(1000) / 144;
+};
+} // namespace cru::ui::host
diff --git a/include/cru/ui/host/WindowHost.hpp b/include/cru/ui/host/WindowHost.hpp
new file mode 100644
index 00000000..bd2f7c16
--- /dev/null
+++ b/include/cru/ui/host/WindowHost.hpp
@@ -0,0 +1,176 @@
+#pragma once
+#include "../Base.hpp"
+
+#include "../render/Base.hpp"
+#include "cru/common/Event.hpp"
+#include "cru/platform/gui/UiApplication.hpp"
+#include "cru/platform/gui/Window.hpp"
+
+#include <functional>
+#include <memory>
+#include <optional>
+
+namespace cru::ui::host {
+class LayoutPaintCycler;
+
+struct AfterLayoutEventArgs {};
+
+struct CreateWindowParams {
+ CreateWindowParams(platform::gui::INativeWindow* parent = nullptr,
+ platform::gui::CreateWindowFlag flag = {})
+ : parent(parent), flag(flag) {}
+
+ platform::gui::INativeWindow* parent;
+ platform::gui::CreateWindowFlag flag;
+};
+
+// The bridge between control tree and native window.
+class WindowHost : public Object {
+ CRU_DEFINE_CLASS_LOG_TAG(u"cru::ui::host::WindowHost")
+
+ public:
+ WindowHost(controls::Control* root_control);
+
+ CRU_DELETE_COPY(WindowHost)
+ CRU_DELETE_MOVE(WindowHost)
+
+ ~WindowHost() override;
+
+ public:
+ platform::gui::INativeWindow* GetNativeWindow() { return native_window_; }
+
+ // Do nothing if native window is already created.
+ gsl::not_null<platform::gui::INativeWindow*> CreateNativeWindow(
+ CreateWindowParams create_window_params = {});
+
+ // 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();
+ void Relayout(const Size& available_size);
+
+ void Repaint();
+
+ // Is layout is invalid, wait for relayout and then run the action. Otherwist
+ // run it right now.
+ void RunAfterLayoutStable(std::function<void()> action);
+
+ // 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.
+ controls::Control* GetMouseHoverControl() const {
+ return mouse_hover_control_;
+ }
+
+ //*************** region: focus ***************
+
+ controls::Control* GetFocusControl();
+
+ void SetFocusControl(controls::Control* control);
+
+ //*************** 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(controls::Control* control);
+
+ // Return null if not captured.
+ controls::Control* GetMouseCaptureControl();
+
+ controls::Control* HitTest(const Point& point);
+
+ void UpdateCursor();
+
+ IEvent<platform::gui::INativeWindow*>* NativeWindowChangeEvent() {
+ return &native_window_change_event_;
+ }
+
+ // If window exist, return window actual size. Otherwise if saved rect exists,
+ // return it. Otherwise return 0.
+ Rect GetWindowRect();
+
+ void SetSavedWindowRect(std::optional<Rect> rect);
+
+ void SetWindowRect(const Rect& rect);
+
+ private:
+ //*************** region: native messages ***************
+ 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 OnNativeKeyDown(platform::gui::INativeWindow* window,
+ const platform::gui::NativeKeyEventArgs& args);
+ void OnNativeKeyUp(platform::gui::INativeWindow* window,
+ const platform::gui::NativeKeyEventArgs& args);
+
+ //*************** region: event dispatcher helper ***************
+
+ void DispatchMouseHoverControlChangeEvent(controls::Control* old_control,
+ controls::Control* new_control,
+ const Point& point, bool no_leave,
+ bool no_enter);
+
+ private:
+ controls::Control* root_control_ = nullptr;
+ render::RenderObject* root_render_object_ = nullptr;
+
+ platform::gui::INativeWindow* native_window_ = nullptr;
+
+ std::unique_ptr<LayoutPaintCycler> layout_paint_cycler_;
+
+ Event<AfterLayoutEventArgs> after_layout_event_;
+ std::vector<std::function<void()> > after_layout_stable_action_;
+
+ std::vector<EventRevokerGuard> event_revoker_guards_;
+
+ controls::Control* mouse_hover_control_ = nullptr;
+
+ controls::Control* focus_control_;
+
+ controls::Control* mouse_captured_control_ = nullptr;
+
+ bool layout_prefer_to_fill_window_ = true;
+
+ Event<platform::gui::INativeWindow*> native_window_change_event_;
+
+ std::optional<Rect> saved_rect_;
+};
+} // namespace cru::ui::host
diff --git a/include/cru/ui/render/Base.hpp b/include/cru/ui/render/Base.hpp
index 801d58bd..ac67349e 100644
--- a/include/cru/ui/render/Base.hpp
+++ b/include/cru/ui/render/Base.hpp
@@ -9,5 +9,4 @@ class FlexLayoutRenderObject;
class ScrollRenderObject;
class StackLayoutRenderObject;
class TextRenderObject;
-class WindowRenderObject;
} // namespace cru::ui::render
diff --git a/include/cru/ui/render/BorderRenderObject.hpp b/include/cru/ui/render/BorderRenderObject.hpp
index 587f051a..3d4f4dad 100644
--- a/include/cru/ui/render/BorderRenderObject.hpp
+++ b/include/cru/ui/render/BorderRenderObject.hpp
@@ -1,5 +1,7 @@
#pragma once
+#include "../style/ApplyBorderStyleInfo.hpp"
#include "RenderObject.hpp"
+#include "cru/ui/Base.hpp"
namespace cru::ui::render {
class BorderRenderObject : public RenderObject {
@@ -16,11 +18,11 @@ class BorderRenderObject : public RenderObject {
bool IsBorderEnabled() const { return is_border_enabled_; }
void SetBorderEnabled(bool enabled) { is_border_enabled_ = enabled; }
- std::shared_ptr<platform::graph::IBrush> GetBorderBrush() {
+ std::shared_ptr<platform::graphics::IBrush> GetBorderBrush() {
return border_brush_;
}
- void SetBorderBrush(std::shared_ptr<platform::graph::IBrush> brush) {
+ void SetBorderBrush(std::shared_ptr<platform::graphics::IBrush> brush) {
if (brush == border_brush_) return;
border_brush_ = std::move(brush);
InvalidatePaint();
@@ -42,32 +44,32 @@ class BorderRenderObject : public RenderObject {
RecreateGeometry();
}
- std::shared_ptr<platform::graph::IBrush> GetForegroundBrush() {
+ std::shared_ptr<platform::graphics::IBrush> GetForegroundBrush() {
return foreground_brush_;
}
- void SetForegroundBrush(std::shared_ptr<platform::graph::IBrush> brush) {
+ void SetForegroundBrush(std::shared_ptr<platform::graphics::IBrush> brush) {
if (brush == foreground_brush_) return;
foreground_brush_ = std::move(brush);
InvalidatePaint();
}
- std::shared_ptr<platform::graph::IBrush> GetBackgroundBrush() {
+ std::shared_ptr<platform::graphics::IBrush> GetBackgroundBrush() {
return background_brush_;
}
- void SetBackgroundBrush(std::shared_ptr<platform::graph::IBrush> brush) {
+ void SetBackgroundBrush(std::shared_ptr<platform::graphics::IBrush> brush) {
if (brush == background_brush_) return;
background_brush_ = std::move(brush);
InvalidatePaint();
}
- void SetBorderStyle(const BorderStyle& style);
+ void ApplyBorderStyle(const style::ApplyBorderStyleInfo& style);
RenderObject* HitTest(const Point& point) override;
protected:
- void OnDrawCore(platform::graph::IPainter* painter) override;
+ void OnDrawCore(platform::graphics::IPainter* painter) override;
Size OnMeasureCore(const MeasureRequirement& requirement,
const MeasureSize& preferred_size) override;
@@ -87,19 +89,19 @@ class BorderRenderObject : public RenderObject {
private:
bool is_border_enabled_ = false;
- std::shared_ptr<platform::graph::IBrush> border_brush_;
+ std::shared_ptr<platform::graphics::IBrush> border_brush_;
Thickness border_thickness_;
CornerRadius border_radius_;
- std::shared_ptr<platform::graph::IBrush> foreground_brush_;
- std::shared_ptr<platform::graph::IBrush> background_brush_;
+ std::shared_ptr<platform::graphics::IBrush> foreground_brush_;
+ std::shared_ptr<platform::graphics::IBrush> background_brush_;
// The ring. Used for painting.
- std::unique_ptr<platform::graph::IGeometry> geometry_;
+ std::unique_ptr<platform::graphics::IGeometry> geometry_;
// Area including inner area of the border. Used for painting foreground and
// background.
- std::unique_ptr<platform::graph::IGeometry> border_inner_geometry_;
+ std::unique_ptr<platform::graphics::IGeometry> border_inner_geometry_;
// Area including border ring and inner area. Used for hit test.
- std::unique_ptr<platform::graph::IGeometry> border_outer_geometry_;
+ std::unique_ptr<platform::graphics::IGeometry> border_outer_geometry_;
};
} // namespace cru::ui::render
diff --git a/include/cru/ui/render/CanvasRenderObject.hpp b/include/cru/ui/render/CanvasRenderObject.hpp
index 3216f08c..58fee59c 100644
--- a/include/cru/ui/render/CanvasRenderObject.hpp
+++ b/include/cru/ui/render/CanvasRenderObject.hpp
@@ -22,7 +22,7 @@ class CanvasRenderObject : public RenderObject {
IEvent<CanvasPaintEventArgs>* PaintEvent() { return &paint_event_; }
protected:
- void OnDrawContent(platform::graph::IPainter* painter) override;
+ void OnDrawContent(platform::graphics::IPainter* painter) override;
Size OnMeasureContent(const MeasureRequirement& requirement,
const MeasureSize& preferred_size) override;
diff --git a/include/cru/ui/render/FlexLayoutRenderObject.hpp b/include/cru/ui/render/FlexLayoutRenderObject.hpp
index ee29d1e4..a8154487 100644
--- a/include/cru/ui/render/FlexLayoutRenderObject.hpp
+++ b/include/cru/ui/render/FlexLayoutRenderObject.hpp
@@ -1,6 +1,8 @@
#pragma once
#include "LayoutRenderObject.hpp"
+#include <string_view>
+
namespace cru::ui::render {
// Measure Logic (v0.1):
// Cross axis measure logic is the same as stack layout.
@@ -85,6 +87,8 @@ class FlexLayoutRenderObject : public LayoutRenderObject<FlexChildLayoutData> {
FlexLayoutRenderObject& operator=(FlexLayoutRenderObject&& other) = delete;
~FlexLayoutRenderObject() override = default;
+ std::u16string_view GetName() const override;
+
FlexDirection GetFlexDirection() const { return direction_; }
void SetFlexDirection(FlexDirection direction) {
direction_ = direction;
diff --git a/include/cru/ui/render/LayoutRenderObject.hpp b/include/cru/ui/render/LayoutRenderObject.hpp
index b46ba0d0..732031a1 100644
--- a/include/cru/ui/render/LayoutRenderObject.hpp
+++ b/include/cru/ui/render/LayoutRenderObject.hpp
@@ -1,7 +1,7 @@
#pragma once
#include "RenderObject.hpp"
-#include "cru/platform/graph/util/Painter.hpp"
+#include "cru/platform/graphics/util/Painter.hpp"
namespace cru::ui::render {
template <typename TChildLayoutData>
diff --git a/include/cru/ui/render/MeasureRequirement.hpp b/include/cru/ui/render/MeasureRequirement.hpp
index 2be159f8..6a0c6952 100644
--- a/include/cru/ui/render/MeasureRequirement.hpp
+++ b/include/cru/ui/render/MeasureRequirement.hpp
@@ -1,8 +1,12 @@
#pragma once
#include "Base.hpp"
+#include "cru/common/Format.hpp"
+
+#include <fmt/core.h>
#include <algorithm>
#include <limits>
+#include <string>
namespace cru::ui::render {
constexpr Size Min(const Size& left, const Size& right) {
@@ -112,6 +116,11 @@ class MeasureLength final {
}
}
+ std::u16string ToDebugString() const {
+ return IsSpecified() ? ToUtf16String(GetLengthOrUndefined())
+ : u"UNSPECIFIED";
+ }
+
private:
// -1 for not specify
float length_;
@@ -160,6 +169,11 @@ struct MeasureSize {
};
}
+ std::u16string ToDebugString() const {
+ return fmt::format(u"({}, {})", width.ToDebugString(),
+ height.ToDebugString());
+ }
+
constexpr static MeasureSize NotSpecified() {
return MeasureSize{MeasureLength::NotSpecified(),
MeasureLength::NotSpecified()};
@@ -187,10 +201,11 @@ struct MeasureRequirement {
: max(max), min(min) {}
constexpr bool Satisfy(const Size& size) const {
- return max.width.GetLengthOrMax() >= size.width &&
- max.height.GetLengthOrMax() >= size.height &&
- min.width.GetLengthOr0() <= size.width &&
- min.height.GetLengthOr0() <= size.height;
+ auto normalized = Normalize();
+ return normalized.max.width.GetLengthOrMax() >= size.width &&
+ normalized.max.height.GetLengthOrMax() >= size.height &&
+ normalized.min.width.GetLengthOr0() <= size.width &&
+ normalized.min.height.GetLengthOr0() <= size.height;
}
constexpr MeasureRequirement Normalize() const {
@@ -225,6 +240,11 @@ struct MeasureRequirement {
return result;
}
+ std::u16string ToDebugString() const {
+ return fmt::format(u"{{min: {}, max: {}}}", min.ToDebugString(),
+ max.ToDebugString());
+ }
+
constexpr static MeasureRequirement Merge(const MeasureRequirement& left,
const MeasureRequirement& right) {
return MeasureRequirement{MeasureSize::Min(left.max, right.max),
diff --git a/include/cru/ui/render/RenderObject.hpp b/include/cru/ui/render/RenderObject.hpp
index f820f029..8bcd4c62 100644
--- a/include/cru/ui/render/RenderObject.hpp
+++ b/include/cru/ui/render/RenderObject.hpp
@@ -2,10 +2,15 @@
#include "Base.hpp"
#include "MeasureRequirement.hpp"
+#include "cru/common/Base.hpp"
#include "cru/common/Event.hpp"
+#include "cru/ui/Base.hpp"
-namespace cru::ui::render {
+#include <cstddef>
+#include <string>
+#include <string_view>
+namespace cru::ui::render {
// Render object will not destroy its children when destroyed. Control must
// manage lifecycle of its render objects. Since control will destroy its
// children when destroyed, render objects will be destroyed along with it.
@@ -29,13 +34,13 @@ namespace cru::ui::render {
//
// To write a custom RenderObject, override following methods:
// public:
-// void Draw(platform::graph::IPainter* painter) override;
+// void Draw(platform::graphics::IPainter* painter) override;
// RenderObject* HitTest(const Point& point) override;
// protected:
// Size OnMeasureContent(const MeasureRequirement& requirement) override;
// void OnLayoutContent(const Rect& content_rect) override;
class RenderObject : public Object {
- friend WindowRenderObject;
+ friend host::WindowHost;
CRU_DEFINE_CLASS_LOG_TAG(u"cru::ui::render::RenderObject")
@@ -58,10 +63,10 @@ class RenderObject : public Object {
RenderObject& operator=(RenderObject&& other) = delete;
~RenderObject() override = default;
- Control* GetAttachedControl() const { return control_; }
- void SetAttachedControl(Control* new_control) { control_ = new_control; }
+ controls::Control* GetAttachedControl() const { return control_; }
+ void SetAttachedControl(controls::Control* new_control);
- UiHost* GetUiHost() const { return ui_host_; }
+ host::WindowHost* GetWindowHost() const { return window_host_; }
RenderObject* GetParent() const { return parent_; }
@@ -70,6 +75,9 @@ class RenderObject : public Object {
void AddChild(RenderObject* render_object, Index position);
void RemoveChild(Index position);
+ RenderObject* GetFirstChild() const;
+ void TraverseDescendants(const std::function<void(RenderObject*)>& action);
+
// Offset from parent's lefttop to lefttop of this render object. Margin is
// accounted for.
Point GetOffset() const { return offset_; }
@@ -123,16 +131,30 @@ class RenderObject : public Object {
// This will set offset of this render object and call OnLayoutCore.
void Layout(const Point& offset);
- void Draw(platform::graph::IPainter* painter);
+ virtual Rect GetPaddingRect() const;
+ virtual Rect GetContentRect() const;
+
+ void Draw(platform::graphics::IPainter* painter);
// Param point must be relative the lefttop of render object including margin.
// Add offset before pass point to children.
virtual RenderObject* HitTest(const Point& point) = 0;
+ IEvent<host::WindowHost*>* AttachToHostEvent() {
+ return &attach_to_host_event_;
+ }
+ IEvent<std::nullptr_t>* DetachFromHostEvent() {
+ return &detach_from_host_event_;
+ }
+
public:
void InvalidateLayout();
void InvalidatePaint();
+ public:
+ virtual std::u16string_view GetName() const;
+ std::u16string GetDebugPathInTree() const;
+
protected:
void SetChildMode(ChildMode mode) { child_mode_ = mode; }
@@ -148,15 +170,15 @@ class RenderObject : public Object {
virtual void OnRemoveChild(RenderObject* removed_child, Index position);
// Draw all children with offset.
- void DefaultDrawChildren(platform::graph::IPainter* painter);
+ void DefaultDrawChildren(platform::graphics::IPainter* painter);
// Draw all children with translation of content rect lefttop.
- void DefaultDrawContent(platform::graph::IPainter* painter);
+ void DefaultDrawContent(platform::graphics::IPainter* painter);
// Call DefaultDrawContent. Then call DefaultDrawChildren.
- virtual void OnDrawCore(platform::graph::IPainter* painter);
+ virtual void OnDrawCore(platform::graphics::IPainter* painter);
- virtual void OnDrawContent(platform::graph::IPainter* painter);
+ virtual void OnDrawContent(platform::graphics::IPainter* painter);
// Size measure including margin and padding. Please reduce margin and padding
// or other custom things and pass the result content measure requirement and
@@ -182,20 +204,20 @@ class RenderObject : public Object {
// Lefttop of content_rect should be added when calculated children's offset.
virtual void OnLayoutContent(const Rect& content_rect) = 0;
- virtual void OnAfterLayout();
- static void NotifyAfterLayoutRecursive(RenderObject* render_object);
+ virtual void OnAttachedControlChanged(controls::Control* control) {
+ CRU_UNUSED(control)
+ }
- virtual Rect GetPaddingRect() const;
- virtual Rect GetContentRect() const;
+ virtual void OnAfterLayout();
private:
void SetParent(RenderObject* new_parent);
- void SetRenderHostRecursive(UiHost* host);
+ void SetWindowHostRecursive(host::WindowHost* host);
private:
- Control* control_ = nullptr;
- UiHost* ui_host_ = nullptr;
+ controls::Control* control_ = nullptr;
+ host::WindowHost* window_host_ = nullptr;
RenderObject* parent_ = nullptr;
std::vector<RenderObject*> children_{};
@@ -210,5 +232,8 @@ class RenderObject : public Object {
Thickness margin_{};
Thickness padding_{};
+
+ Event<host::WindowHost*> attach_to_host_event_;
+ Event<std::nullptr_t> detach_from_host_event_;
};
} // namespace cru::ui::render
diff --git a/include/cru/ui/render/ScrollBar.hpp b/include/cru/ui/render/ScrollBar.hpp
new file mode 100644
index 00000000..3293e9d0
--- /dev/null
+++ b/include/cru/ui/render/ScrollBar.hpp
@@ -0,0 +1,218 @@
+#pragma once
+#include "Base.hpp"
+#include "cru/common/Base.hpp"
+#include "cru/common/Event.hpp"
+#include "cru/platform/graphics/Base.hpp"
+#include "cru/platform/graphics/Geometry.hpp"
+#include "cru/platform/graphics/Painter.hpp"
+#include "cru/platform/gui/Cursor.hpp"
+#include "cru/platform/gui/UiApplication.hpp"
+#include "cru/ui/Base.hpp"
+#include "cru/ui/controls/Control.hpp"
+
+#include <gsl/pointers>
+#include <memory>
+#include <optional>
+
+namespace cru::ui::render {
+class ScrollRenderObject;
+
+enum class ScrollKind { Absolute, Relative, Page, Line };
+
+struct Scroll {
+ Direction direction;
+ ScrollKind kind;
+ // For absolute, the new scroll position. Otherwise, offset.
+ float value;
+};
+
+enum class ScrollBarAreaKind {
+ UpArrow, // Line up
+ DownArrow, // Line down
+ UpSlot, // Page up
+ DownSlot, // Page down
+ Thumb
+};
+
+class ScrollBar : public Object {
+ public:
+ ScrollBar(gsl::not_null<ScrollRenderObject*> render_object,
+ Direction direction);
+
+ CRU_DELETE_COPY(ScrollBar)
+ CRU_DELETE_MOVE(ScrollBar)
+
+ ~ScrollBar() override;
+
+ public:
+ Direction GetDirection() const { return direction_; }
+
+ bool IsEnabled() const { return is_enabled_; }
+ void SetEnabled(bool value);
+
+ bool IsExpanded() const { return is_expanded_; }
+ void SetExpanded(bool value);
+
+ void Draw(platform::graphics::IPainter* painter);
+
+ IEvent<Scroll>* ScrollAttemptEvent() { return &scroll_attempt_event_; }
+
+ void InstallHandlers(controls::Control* control);
+ void UninstallHandlers() { InstallHandlers(nullptr); }
+
+ gsl::not_null<std::shared_ptr<platform::graphics::IBrush>>
+ GetCollapsedThumbBrush() const;
+ gsl::not_null<std::shared_ptr<platform::graphics::IBrush>>
+ GetExpandedThumbBrush() const;
+ gsl::not_null<std::shared_ptr<platform::graphics::IBrush>>
+ GetExpandedSlotBrush() const;
+ gsl::not_null<std::shared_ptr<platform::graphics::IBrush>>
+ GetExpandedArrowBrush() const;
+ gsl::not_null<std::shared_ptr<platform::graphics::IBrush>>
+ GetExpandedArrowBackgroundBrush() const;
+
+ protected:
+ void OnDraw(platform::graphics::IPainter* painter, bool expand);
+
+ virtual void DrawUpArrow(platform::graphics::IPainter* painter,
+ const Rect& area) = 0;
+ virtual void DrawDownArrow(platform::graphics::IPainter* painter,
+ const Rect& area) = 0;
+
+ std::optional<ScrollBarAreaKind> ExpandedHitTest(const Point& point);
+
+ virtual bool IsShowBar() = 0;
+
+ virtual std::optional<Rect> GetExpandedAreaRect(
+ ScrollBarAreaKind area_kind) = 0;
+ virtual std::optional<Rect> GetCollapsedTriggerExpandAreaRect() = 0;
+ virtual std::optional<Rect> GetCollapsedThumbRect() = 0;
+
+ virtual float CalculateNewScrollPosition(const Rect& thumb_original_rect,
+ const Point& mouse_offset) = 0;
+
+ private:
+ void SetCursor();
+ void RestoreCursor();
+
+ void BeginAutoCollapseTimer();
+ void StopAutoCollapseTimer();
+
+ void OnMouseLeave();
+
+ protected:
+ gsl::not_null<ScrollRenderObject*> render_object_;
+
+ std::unique_ptr<platform::graphics::IGeometry> arrow_geometry_;
+
+ private:
+ Direction direction_;
+
+ bool is_enabled_ = true;
+
+ bool is_expanded_ = false;
+
+ std::shared_ptr<platform::graphics::IBrush> collapsed_thumb_brush_;
+ std::shared_ptr<platform::graphics::IBrush> expanded_thumb_brush_;
+ std::shared_ptr<platform::graphics::IBrush> expanded_slot_brush_;
+ std::shared_ptr<platform::graphics::IBrush> expanded_arrow_brush_;
+ std::shared_ptr<platform::graphics::IBrush> expanded_arrow_background_brush_;
+
+ Rect move_thumb_thumb_original_rect_;
+ std::optional<Point> move_thumb_start_;
+
+ EventRevokerListGuard event_guard_;
+
+ Event<Scroll> scroll_attempt_event_;
+
+ std::optional<std::shared_ptr<platform::gui::ICursor>> old_cursor_;
+
+ platform::gui::TimerAutoCanceler auto_collapse_timer_canceler_;
+};
+
+class HorizontalScrollBar : public ScrollBar {
+ public:
+ explicit HorizontalScrollBar(
+ gsl::not_null<ScrollRenderObject*> render_object);
+
+ CRU_DELETE_COPY(HorizontalScrollBar)
+ CRU_DELETE_MOVE(HorizontalScrollBar)
+
+ ~HorizontalScrollBar() override = default;
+
+ protected:
+ void DrawUpArrow(platform::graphics::IPainter* painter,
+ const Rect& area) override;
+ void DrawDownArrow(platform::graphics::IPainter* painter,
+ const Rect& area) override;
+
+ bool IsShowBar() override;
+
+ std::optional<Rect> GetExpandedAreaRect(ScrollBarAreaKind area_kind) override;
+ std::optional<Rect> GetCollapsedTriggerExpandAreaRect() override;
+ std::optional<Rect> GetCollapsedThumbRect() override;
+
+ float CalculateNewScrollPosition(const Rect& thumb_original_rect,
+ const Point& mouse_offset) override;
+};
+
+class VerticalScrollBar : public ScrollBar {
+ public:
+ explicit VerticalScrollBar(gsl::not_null<ScrollRenderObject*> render_object);
+
+ CRU_DELETE_COPY(VerticalScrollBar)
+ CRU_DELETE_MOVE(VerticalScrollBar)
+
+ ~VerticalScrollBar() override = default;
+
+ protected:
+ void DrawUpArrow(platform::graphics::IPainter* painter,
+ const Rect& area) override;
+ void DrawDownArrow(platform::graphics::IPainter* painter,
+ const Rect& area) override;
+
+ bool IsShowBar() override;
+
+ std::optional<Rect> GetExpandedAreaRect(ScrollBarAreaKind area_kind) override;
+ std::optional<Rect> GetCollapsedTriggerExpandAreaRect() override;
+ std::optional<Rect> GetCollapsedThumbRect() override;
+
+ float CalculateNewScrollPosition(const Rect& thumb_original_rect,
+ const Point& mouse_offset) override;
+};
+
+// A delegate to draw scrollbar and register related events.
+class ScrollBarDelegate : public Object {
+ public:
+ explicit ScrollBarDelegate(gsl::not_null<ScrollRenderObject*> render_object);
+
+ CRU_DELETE_COPY(ScrollBarDelegate)
+ CRU_DELETE_MOVE(ScrollBarDelegate)
+
+ ~ScrollBarDelegate() override = default;
+
+ public:
+ bool IsHorizontalBarEnabled() const { return horizontal_bar_.IsEnabled(); }
+ void SetHorizontalBarEnabled(bool value) {
+ horizontal_bar_.SetEnabled(value);
+ }
+
+ bool IsVerticalBarEnabled() const { return horizontal_bar_.IsEnabled(); }
+ void SetVerticalBarEnabled(bool value) { horizontal_bar_.SetEnabled(value); }
+
+ IEvent<Scroll>* ScrollAttemptEvent() { return &scroll_attempt_event_; }
+
+ void DrawScrollBar(platform::graphics::IPainter* painter);
+
+ void InstallHandlers(controls::Control* control);
+ void UninstallHandlers() { InstallHandlers(nullptr); }
+
+ private:
+ gsl::not_null<ScrollRenderObject*> render_object_;
+
+ HorizontalScrollBar horizontal_bar_;
+ VerticalScrollBar vertical_bar_;
+
+ Event<Scroll> scroll_attempt_event_;
+};
+} // namespace cru::ui::render
diff --git a/include/cru/ui/render/ScrollRenderObject.hpp b/include/cru/ui/render/ScrollRenderObject.hpp
index 9b0cbf9a..aed25f8e 100644
--- a/include/cru/ui/render/ScrollRenderObject.hpp
+++ b/include/cru/ui/render/ScrollRenderObject.hpp
@@ -1,8 +1,11 @@
#pragma once
#include "RenderObject.hpp"
-#include "cru/platform/graph/util/Painter.hpp"
+#include "cru/platform/graphics/util/Painter.hpp"
+#include "cru/ui/Base.hpp"
+#include "cru/ui/render/ScrollBar.hpp"
+#include <memory>
#include <optional>
namespace cru::ui::render {
@@ -16,7 +19,7 @@ namespace cru::ui::render {
// Or layout by scroll state.
class ScrollRenderObject : public RenderObject {
public:
- ScrollRenderObject() : RenderObject(ChildMode::Single) {}
+ ScrollRenderObject();
CRU_DELETE_COPY(ScrollRenderObject)
CRU_DELETE_MOVE(ScrollRenderObject)
@@ -27,8 +30,22 @@ class ScrollRenderObject : public RenderObject {
// Return the coerced scroll offset.
Point GetScrollOffset();
+ float GetScrollOffset(Direction direction) {
+ return direction == Direction::Horizontal ? GetScrollOffset().x
+ : GetScrollOffset().y;
+ }
void SetScrollOffset(const Point& offset);
void SetScrollOffset(std::optional<float> x, std::optional<float> y);
+ void SetScrollOffset(Direction direction, std::optional<float> value) {
+ if (direction == Direction::Horizontal) {
+ SetScrollOffset(value, std::nullopt);
+ } else {
+ SetScrollOffset(std::nullopt, value);
+ }
+ }
+
+ void Scroll(const Scroll& scroll);
+
Point GetRawScrollOffset() const { return scroll_offset_; }
// Return the viewable area rect.
@@ -44,7 +61,7 @@ class ScrollRenderObject : public RenderObject {
void ScrollToContain(const Rect& rect, const Thickness& margin = Thickness{});
protected:
- void OnDrawCore(platform::graph::IPainter* painter) override;
+ void OnDrawCore(platform::graphics::IPainter* painter) override;
// Logic:
// If available size is bigger than child's preferred size, then child's
@@ -54,7 +71,11 @@ class ScrollRenderObject : public RenderObject {
const MeasureSize& preferred_size) override;
void OnLayoutContent(const Rect& content_rect) override;
+ void OnAttachedControlChanged(controls::Control* control) override;
+
private:
Point scroll_offset_;
+
+ std::unique_ptr<ScrollBarDelegate> scroll_bar_delegate_;
};
} // namespace cru::ui::render
diff --git a/include/cru/ui/render/TextRenderObject.hpp b/include/cru/ui/render/TextRenderObject.hpp
index 3be42bbb..bdec18d1 100644
--- a/include/cru/ui/render/TextRenderObject.hpp
+++ b/include/cru/ui/render/TextRenderObject.hpp
@@ -24,10 +24,10 @@ class TextRenderObject : public RenderObject {
constexpr static float default_caret_width = 2;
public:
- TextRenderObject(std::shared_ptr<platform::graph::IBrush> brush,
- std::shared_ptr<platform::graph::IFont> font,
- std::shared_ptr<platform::graph::IBrush> selection_brush,
- std::shared_ptr<platform::graph::IBrush> caret_brush);
+ TextRenderObject(std::shared_ptr<platform::graphics::IBrush> brush,
+ std::shared_ptr<platform::graphics::IFont> font,
+ std::shared_ptr<platform::graphics::IBrush> selection_brush,
+ std::shared_ptr<platform::graphics::IBrush> caret_brush);
TextRenderObject(const TextRenderObject& other) = delete;
TextRenderObject(TextRenderObject&& other) = delete;
TextRenderObject& operator=(const TextRenderObject& other) = delete;
@@ -38,25 +38,27 @@ class TextRenderObject : public RenderObject {
std::u16string_view GetTextView() const;
void SetText(std::u16string new_text);
- std::shared_ptr<platform::graph::IBrush> GetBrush() const { return brush_; }
- void SetBrush(std::shared_ptr<platform::graph::IBrush> new_brush);
+ std::shared_ptr<platform::graphics::IBrush> GetBrush() const {
+ return brush_;
+ }
+ void SetBrush(std::shared_ptr<platform::graphics::IBrush> new_brush);
- std::shared_ptr<platform::graph::IFont> GetFont() const;
- void SetFont(std::shared_ptr<platform::graph::IFont> font);
+ std::shared_ptr<platform::graphics::IFont> GetFont() const;
+ void SetFont(std::shared_ptr<platform::graphics::IFont> font);
std::vector<Rect> TextRangeRect(const TextRange& text_range);
Point TextSinglePoint(gsl::index position, bool trailing);
- platform::graph::TextHitTestResult TextHitTest(const Point& point);
+ platform::graphics::TextHitTestResult TextHitTest(const Point& point);
std::optional<TextRange> GetSelectionRange() const {
return selection_range_;
}
void SetSelectionRange(std::optional<TextRange> new_range);
- std::shared_ptr<platform::graph::IBrush> GetSelectionBrush() const {
+ std::shared_ptr<platform::graphics::IBrush> GetSelectionBrush() const {
return selection_brush_;
}
- void SetSelectionBrush(std::shared_ptr<platform::graph::IBrush> new_brush);
+ void SetSelectionBrush(std::shared_ptr<platform::graphics::IBrush> new_brush);
bool IsDrawCaret() const { return draw_caret_; }
void SetDrawCaret(bool draw_caret);
@@ -72,18 +74,23 @@ class TextRenderObject : public RenderObject {
// Lefttop relative to render object lefttop.
Rect GetCaretRect();
- std::shared_ptr<platform::graph::IBrush> GetCaretBrush() const {
+ std::shared_ptr<platform::graphics::IBrush> GetCaretBrush() const {
return caret_brush_;
}
- void GetCaretBrush(std::shared_ptr<platform::graph::IBrush> brush);
+ void GetCaretBrush(std::shared_ptr<platform::graphics::IBrush> brush);
float GetCaretWidth() const { return caret_width_; }
void SetCaretWidth(float width);
+ bool IsMeasureIncludingTrailingSpace() const {
+ return is_measure_including_trailing_space_;
+ }
+ void SetMeasureIncludingTrailingSpace(bool including);
+
RenderObject* HitTest(const Point& point) override;
protected:
- void OnDrawContent(platform::graph::IPainter* painter) override;
+ void OnDrawContent(platform::graphics::IPainter* painter) override;
// See remarks of this class.
Size OnMeasureContent(const MeasureRequirement& requirement,
@@ -93,16 +100,18 @@ class TextRenderObject : public RenderObject {
void OnAfterLayout() override;
private:
- std::shared_ptr<platform::graph::IBrush> brush_;
- std::shared_ptr<platform::graph::IFont> font_;
- std::unique_ptr<platform::graph::ITextLayout> text_layout_;
+ std::shared_ptr<platform::graphics::IBrush> brush_;
+ std::shared_ptr<platform::graphics::IFont> font_;
+ std::unique_ptr<platform::graphics::ITextLayout> text_layout_;
std::optional<TextRange> selection_range_ = std::nullopt;
- std::shared_ptr<platform::graph::IBrush> selection_brush_;
+ std::shared_ptr<platform::graphics::IBrush> selection_brush_;
bool draw_caret_ = false;
gsl::index caret_position_ = 0;
- std::shared_ptr<platform::graph::IBrush> caret_brush_;
+ std::shared_ptr<platform::graphics::IBrush> caret_brush_;
float caret_width_ = default_caret_width;
+
+ bool is_measure_including_trailing_space_ = false;
};
} // namespace cru::ui::render
diff --git a/include/cru/ui/render/WindowRenderObject.hpp b/include/cru/ui/render/WindowRenderObject.hpp
deleted file mode 100644
index 4c254f42..00000000
--- a/include/cru/ui/render/WindowRenderObject.hpp
+++ /dev/null
@@ -1,29 +0,0 @@
-#pragma once
-#include "RenderObject.hpp"
-
-namespace cru::ui::render {
-class WindowRenderObject : public RenderObject {
- public:
- WindowRenderObject(UiHost* host);
- WindowRenderObject(const WindowRenderObject& other) = delete;
- WindowRenderObject(WindowRenderObject&& other) = delete;
- WindowRenderObject& operator=(const WindowRenderObject& other) = delete;
- WindowRenderObject& operator=(WindowRenderObject&& other) = delete;
- ~WindowRenderObject() override = default;
-
- RenderObject* HitTest(const Point& point) override;
-
- protected:
- Size OnMeasureContent(const MeasureRequirement& requirement,
- const MeasureSize& preferred_size) override;
- void OnLayoutContent(const Rect& content_rect) override;
-
- private:
- RenderObject* GetChild() const {
- return GetChildren().empty() ? nullptr : GetChildren()[0];
- }
-
- private:
- EventRevokerGuard after_layout_event_guard_;
-};
-} // namespace cru::ui::render
diff --git a/include/cru/ui/style/ApplyBorderStyleInfo.hpp b/include/cru/ui/style/ApplyBorderStyleInfo.hpp
new file mode 100644
index 00000000..5058b51f
--- /dev/null
+++ b/include/cru/ui/style/ApplyBorderStyleInfo.hpp
@@ -0,0 +1,28 @@
+#pragma once
+#include <optional>
+#include "../Base.hpp"
+
+namespace cru::ui::style {
+struct ApplyBorderStyleInfo {
+ explicit ApplyBorderStyleInfo(
+ std::optional<std::shared_ptr<platform::graphics::IBrush>> border_brush =
+ std::nullopt,
+ std::optional<Thickness> border_thickness = std::nullopt,
+ std::optional<CornerRadius> border_radius = std::nullopt,
+ std::optional<std::shared_ptr<platform::graphics::IBrush>>
+ foreground_brush = std::nullopt,
+ std::optional<std::shared_ptr<platform::graphics::IBrush>>
+ background_brush = std::nullopt)
+ : border_brush(std::move(border_brush)),
+ border_thickness(std::move(border_thickness)),
+ border_radius(std::move(border_radius)),
+ foreground_brush(std::move(foreground_brush)),
+ background_brush(std::move(background_brush)) {}
+
+ std::optional<std::shared_ptr<platform::graphics::IBrush>> border_brush;
+ std::optional<Thickness> border_thickness;
+ std::optional<CornerRadius> border_radius;
+ std::optional<std::shared_ptr<platform::graphics::IBrush>> foreground_brush;
+ std::optional<std::shared_ptr<platform::graphics::IBrush>> background_brush;
+};
+} // namespace cru::ui::style
diff --git a/include/cru/ui/style/Condition.hpp b/include/cru/ui/style/Condition.hpp
new file mode 100644
index 00000000..d5cf16f2
--- /dev/null
+++ b/include/cru/ui/style/Condition.hpp
@@ -0,0 +1,123 @@
+#pragma once
+#include "../Base.hpp"
+#include "cru/common/Base.hpp"
+#include "cru/common/ClonablePtr.hpp"
+#include "cru/common/Event.hpp"
+#include "cru/ui/controls/IClickableControl.hpp"
+#include "cru/ui/helper/ClickDetector.hpp"
+
+#include <memory>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+namespace cru::ui::style {
+class Condition : public Object {
+ public:
+ virtual std::vector<IBaseEvent*> ChangeOn(
+ controls::Control* control) const = 0;
+ virtual bool Judge(controls::Control* control) const = 0;
+
+ virtual Condition* Clone() const = 0;
+};
+
+class NoCondition : public Condition {
+ public:
+ static ClonablePtr<NoCondition> Create() {
+ return ClonablePtr<NoCondition>(new NoCondition);
+ };
+
+ std::vector<IBaseEvent*> ChangeOn(controls::Control*) const override {
+ return {};
+ }
+
+ bool Judge(controls::Control*) const override { return true; }
+
+ NoCondition* Clone() const override { return new NoCondition; }
+};
+
+class CompoundCondition : public Condition {
+ public:
+ explicit CompoundCondition(std::vector<ClonablePtr<Condition>> conditions);
+
+ std::vector<IBaseEvent*> ChangeOn(controls::Control* control) const override;
+
+ protected:
+ std::vector<ClonablePtr<Condition>> conditions_;
+};
+
+class AndCondition : public CompoundCondition {
+ public:
+ using CompoundCondition::CompoundCondition;
+
+ bool Judge(controls::Control* control) const override;
+
+ AndCondition* Clone() const override { return new AndCondition(conditions_); }
+};
+
+class OrCondition : public CompoundCondition {
+ public:
+ using CompoundCondition::CompoundCondition;
+
+ bool Judge(controls::Control* control) const override;
+
+ OrCondition* Clone() const override { return new OrCondition(conditions_); }
+};
+
+class FocusCondition : public Condition {
+ public:
+ static ClonablePtr<FocusCondition> Create(bool has_focus) {
+ return ClonablePtr<FocusCondition>(new FocusCondition(has_focus));
+ }
+
+ explicit FocusCondition(bool has_focus);
+
+ std::vector<IBaseEvent*> ChangeOn(controls::Control* control) const override;
+ bool Judge(controls::Control* control) const override;
+
+ FocusCondition* Clone() const override {
+ return new FocusCondition(has_focus_);
+ }
+
+ private:
+ bool has_focus_;
+};
+
+class HoverCondition : public Condition {
+ public:
+ static ClonablePtr<HoverCondition> Create(bool hover) {
+ return ClonablePtr<HoverCondition>(new HoverCondition(hover));
+ }
+
+ explicit HoverCondition(bool hover) : hover_(hover) {}
+
+ std::vector<IBaseEvent*> ChangeOn(controls::Control* control) const override;
+ bool Judge(controls::Control* control) const override;
+
+ HoverCondition* Clone() const override { return new HoverCondition(hover_); }
+
+ private:
+ bool hover_;
+};
+
+class ClickStateCondition : public Condition {
+ public:
+ static ClonablePtr<ClickStateCondition> Create(
+ helper::ClickState click_state) {
+ return ClonablePtr<ClickStateCondition>(
+ new ClickStateCondition(click_state));
+ }
+
+ explicit ClickStateCondition(helper::ClickState click_state);
+
+ std::vector<IBaseEvent*> ChangeOn(controls::Control* control) const override;
+ bool Judge(controls::Control* control) const override;
+
+ ClickStateCondition* Clone() const override {
+ return new ClickStateCondition(click_state_);
+ }
+
+ private:
+ helper::ClickState click_state_;
+};
+} // namespace cru::ui::style
diff --git a/include/cru/ui/style/StyleRule.hpp b/include/cru/ui/style/StyleRule.hpp
new file mode 100644
index 00000000..8ac42cd0
--- /dev/null
+++ b/include/cru/ui/style/StyleRule.hpp
@@ -0,0 +1,45 @@
+#pragma once
+#include "Condition.hpp"
+#include "Styler.hpp"
+#include "cru/common/Base.hpp"
+#include "cru/common/ClonablePtr.hpp"
+#include "cru/ui/Base.hpp"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace cru::ui::style {
+class StyleRule : public Object {
+ public:
+ StyleRule(ClonablePtr<Condition> condition, ClonablePtr<Styler> styler,
+ std::u16string name = {});
+
+ CRU_DEFAULT_COPY(StyleRule)
+ CRU_DEFAULT_MOVE(StyleRule)
+
+ ~StyleRule() override = default;
+
+ public:
+ const std::u16string& GetName() const { return name_; }
+ Condition* GetCondition() const { return condition_.get(); }
+ Styler* GetStyler() const { return styler_.get(); }
+
+ StyleRule WithNewCondition(ClonablePtr<Condition> condition,
+ std::u16string name = {}) const {
+ return StyleRule{std::move(condition), styler_, std::move(name)};
+ }
+
+ StyleRule WithNewStyler(ClonablePtr<Styler> styler,
+ std::u16string name = {}) const {
+ return StyleRule{condition_, std::move(styler), std::move(name)};
+ }
+
+ bool CheckAndApply(controls::Control* control) const;
+
+ private:
+ ClonablePtr<Condition> condition_;
+ ClonablePtr<Styler> styler_;
+ std::u16string name_;
+};
+} // namespace cru::ui::style
diff --git a/include/cru/ui/style/StyleRuleSet.hpp b/include/cru/ui/style/StyleRuleSet.hpp
new file mode 100644
index 00000000..e62dd2de
--- /dev/null
+++ b/include/cru/ui/style/StyleRuleSet.hpp
@@ -0,0 +1,87 @@
+#pragma once
+#include "StyleRule.hpp"
+#include "cru/common/Base.hpp"
+#include "cru/common/Event.hpp"
+
+#include <cstddef>
+
+namespace cru::ui::style {
+class StyleRuleSet : public Object {
+ public:
+ StyleRuleSet() = default;
+ explicit StyleRuleSet(StyleRuleSet* parent);
+
+ CRU_DELETE_COPY(StyleRuleSet)
+ CRU_DELETE_MOVE(StyleRuleSet)
+
+ ~StyleRuleSet() override = default;
+
+ public:
+ StyleRuleSet* GetParent() const { return parent_; }
+ void SetParent(StyleRuleSet* parent);
+
+ gsl::index GetSize() const { return static_cast<gsl::index>(rules_.size()); }
+ const std::vector<StyleRule>& GetRules() const { return rules_; }
+
+ void AddStyleRule(StyleRule rule) {
+ AddStyleRule(std::move(rule), GetSize());
+ }
+
+ void AddStyleRule(StyleRule rule, gsl::index index);
+
+ template <typename Iter>
+ void AddStyleRuleRange(Iter start, Iter end, gsl::index index) {
+ Expects(index >= 0 && index <= GetSize());
+ rules_.insert(rules_.cbegin() + index, std::move(start), std::move(end));
+ UpdateChangeListener();
+ UpdateStyle();
+ }
+
+ void RemoveStyleRule(gsl::index index, gsl::index count = 1);
+
+ void Clear() { RemoveStyleRule(0, GetSize()); }
+
+ void Set(const StyleRuleSet& other, bool set_parent = false);
+
+ const StyleRule& operator[](gsl::index index) const { return rules_[index]; }
+
+ // Triggered whenever a change happened to this (rule add or remove, parent
+ // change ...). Subscribe to this and update style change listeners and style.
+ IEvent<std::nullptr_t>* ChangeEvent() { return &change_event_; }
+
+ private:
+ void RaiseChangeEvent() { change_event_.Raise(nullptr); }
+
+ private:
+ Event<std::nullptr_t> change_event_;
+
+ StyleRuleSet* parent_ = nullptr;
+ EventRevokerGuard parent_change_event_guard_;
+
+ std::vector<StyleRule> rules_;
+};
+
+class StyleRuleSetBind {
+ public:
+ StyleRuleSetBind(controls::Control* control, StyleRuleSet* ruleset);
+
+ CRU_DELETE_COPY(StyleRuleSetBind)
+ CRU_DELETE_MOVE(StyleRuleSetBind)
+
+ ~StyleRuleSetBind() = default;
+
+ private:
+ void UpdateRuleSetChainCache();
+ void UpdateChangeListener();
+ void UpdateStyle();
+
+ private:
+ controls::Control* control_;
+ StyleRuleSet* ruleset_;
+
+ // child first, parent last.
+ std::vector<StyleRuleSet*> ruleset_chain_cache_;
+
+ EventRevokerListGuard guard_;
+};
+} // namespace cru::ui::style
diff --git a/include/cru/ui/style/Styler.hpp b/include/cru/ui/style/Styler.hpp
new file mode 100644
index 00000000..865cbbaf
--- /dev/null
+++ b/include/cru/ui/style/Styler.hpp
@@ -0,0 +1,80 @@
+#pragma once
+#include "../Base.hpp"
+#include "ApplyBorderStyleInfo.hpp"
+#include "cru/common/Base.hpp"
+#include "cru/common/ClonablePtr.hpp"
+#include "cru/platform/gui/Cursor.hpp"
+#include "cru/ui/controls/Control.hpp"
+
+#include <memory>
+#include <vector>
+
+namespace cru::ui::style {
+class Styler : public Object {
+ public:
+ virtual void Apply(controls::Control* control) const = 0;
+
+ virtual Styler* Clone() const = 0;
+};
+
+class CompoundStyler : public Styler {
+ public:
+ template <typename... S>
+ static ClonablePtr<CompoundStyler> Create(ClonablePtr<S>... s) {
+ return ClonablePtr<CompoundStyler>(
+ new CompoundStyler(std::vector<ClonablePtr<Styler>>{std::move(s)...}));
+ }
+
+ explicit CompoundStyler(std::vector<ClonablePtr<Styler>> stylers)
+ : stylers_(std::move(stylers)) {}
+
+ void Apply(controls::Control* control) const override {
+ for (const auto& styler : stylers_) {
+ styler->Apply(control);
+ }
+ }
+
+ virtual CompoundStyler* Clone() const override {
+ return new CompoundStyler(stylers_);
+ }
+
+ private:
+ std::vector<ClonablePtr<Styler>> stylers_;
+};
+
+class BorderStyler : public Styler {
+ public:
+ static ClonablePtr<BorderStyler> Create(ApplyBorderStyleInfo style) {
+ return ClonablePtr<BorderStyler>(new BorderStyler(std::move(style)));
+ }
+
+ explicit BorderStyler(ApplyBorderStyleInfo style);
+
+ void Apply(controls::Control* control) const override;
+
+ BorderStyler* Clone() const override { return new BorderStyler(style_); }
+
+ private:
+ ApplyBorderStyleInfo style_;
+};
+
+class CursorStyler : public Styler {
+ public:
+ static ClonablePtr<CursorStyler> Create(
+ std::shared_ptr<platform::gui::ICursor> cursor) {
+ return ClonablePtr<CursorStyler>(new CursorStyler(std::move(cursor)));
+ }
+
+ static ClonablePtr<CursorStyler> Create(platform::gui::SystemCursorType type);
+
+ explicit CursorStyler(std::shared_ptr<platform::gui::ICursor> cursor)
+ : cursor_(std::move(cursor)) {}
+
+ void Apply(controls::Control* control) const override;
+
+ CursorStyler* Clone() const override { return new CursorStyler(cursor_); }
+
+ private:
+ std::shared_ptr<platform::gui::ICursor> cursor_;
+};
+} // namespace cru::ui::style