diff options
Diffstat (limited to 'include/cru/ui')
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 |