From c072432e68d7a3d7659add0994b2f8caf387ddf2 Mon Sep 17 00:00:00 2001 From: crupest Date: Sun, 18 Oct 2020 21:28:35 +0800 Subject: ... --- include/cru/ui/controls/FlexLayout.hpp | 3 +++ 1 file changed, 3 insertions(+) (limited to 'include/cru/ui/controls') diff --git a/include/cru/ui/controls/FlexLayout.hpp b/include/cru/ui/controls/FlexLayout.hpp index 87162569..0ffedba5 100644 --- a/include/cru/ui/controls/FlexLayout.hpp +++ b/include/cru/ui/controls/FlexLayout.hpp @@ -28,6 +28,9 @@ 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); -- cgit v1.2.3 From 2188845a7acffa653015a1000139ec0a9a3984bc Mon Sep 17 00:00:00 2001 From: crupest Date: Sun, 8 Nov 2020 17:45:41 +0800 Subject: ... --- demos/main/main.cpp | 6 +- include/cru/platform/gui/UiApplication.hpp | 19 ++- include/cru/ui/Base.hpp | 4 +- include/cru/ui/ClickDetector.hpp | 87 ----------- include/cru/ui/ContentControl.hpp | 26 ---- include/cru/ui/Control.hpp | 151 ------------------- include/cru/ui/LayoutControl.hpp | 19 --- include/cru/ui/NoChildControl.hpp | 20 --- include/cru/ui/ShortcutHub.hpp | 130 ---------------- include/cru/ui/UiEvent.hpp | 228 ----------------------------- include/cru/ui/Window.hpp | 36 ----- include/cru/ui/controls/Base.hpp | 10 +- include/cru/ui/controls/Button.hpp | 7 +- include/cru/ui/controls/Container.hpp | 2 +- include/cru/ui/controls/ContentControl.hpp | 26 ++++ include/cru/ui/controls/Control.hpp | 151 +++++++++++++++++++ include/cru/ui/controls/FlexLayout.hpp | 2 +- include/cru/ui/controls/LayoutControl.hpp | 19 +++ include/cru/ui/controls/NoChildControl.hpp | 20 +++ include/cru/ui/controls/StackLayout.hpp | 2 +- include/cru/ui/controls/TextBlock.hpp | 2 +- include/cru/ui/controls/TextBox.hpp | 3 +- include/cru/ui/controls/Window.hpp | 36 +++++ include/cru/ui/events/UiEvent.hpp | 228 +++++++++++++++++++++++++++++ include/cru/ui/helper/ClickDetector.hpp | 87 +++++++++++ include/cru/ui/helper/ShortcutHub.hpp | 129 ++++++++++++++++ include/cru/ui/host/WindowHost.hpp | 50 +++---- include/cru/ui/render/RenderObject.hpp | 12 +- include/cru/win/gui/UiApplication.hpp | 2 +- src/ui/CMakeLists.txt | 32 ++-- src/ui/ClickDetector.cpp | 131 ----------------- src/ui/ContentControl.cpp | 27 ---- src/ui/Control.cpp | 157 -------------------- src/ui/LayoutControl.cpp | 6 - src/ui/NoChildControl.cpp | 3 - src/ui/ShortcutHub.cpp | 120 --------------- src/ui/UiEvent.cpp | 10 -- src/ui/Window.cpp | 32 ---- src/ui/controls/Button.cpp | 17 +-- src/ui/controls/ContentControl.cpp | 25 ++++ src/ui/controls/Control.cpp | 157 ++++++++++++++++++++ src/ui/controls/LayoutControl.cpp | 3 + src/ui/controls/NoChildControl.cpp | 3 + src/ui/controls/TextControlService.hpp | 8 +- src/ui/controls/Window.cpp | 32 ++++ src/ui/events/UiEvent.cpp | 10 ++ src/ui/helper/ClickDetector.cpp | 131 +++++++++++++++++ src/ui/helper/ShortcutHub.cpp | 120 +++++++++++++++ src/ui/host/RoutedEventDispatch.hpp | 14 +- src/ui/host/WindowHost.cpp | 83 ++++++----- src/win/gui/UiApplication.cpp | 12 +- 51 files changed, 1333 insertions(+), 1314 deletions(-) delete mode 100644 include/cru/ui/ClickDetector.hpp delete mode 100644 include/cru/ui/ContentControl.hpp delete mode 100644 include/cru/ui/Control.hpp delete mode 100644 include/cru/ui/LayoutControl.hpp delete mode 100644 include/cru/ui/NoChildControl.hpp delete mode 100644 include/cru/ui/ShortcutHub.hpp delete mode 100644 include/cru/ui/UiEvent.hpp delete mode 100644 include/cru/ui/Window.hpp create mode 100644 include/cru/ui/controls/ContentControl.hpp create mode 100644 include/cru/ui/controls/Control.hpp create mode 100644 include/cru/ui/controls/LayoutControl.hpp create mode 100644 include/cru/ui/controls/NoChildControl.hpp create mode 100644 include/cru/ui/controls/Window.hpp create mode 100644 include/cru/ui/events/UiEvent.hpp create mode 100644 include/cru/ui/helper/ClickDetector.hpp create mode 100644 include/cru/ui/helper/ShortcutHub.hpp delete mode 100644 src/ui/ClickDetector.cpp delete mode 100644 src/ui/ContentControl.cpp delete mode 100644 src/ui/Control.cpp delete mode 100644 src/ui/LayoutControl.cpp delete mode 100644 src/ui/NoChildControl.cpp delete mode 100644 src/ui/ShortcutHub.cpp delete mode 100644 src/ui/UiEvent.cpp delete mode 100644 src/ui/Window.cpp create mode 100644 src/ui/controls/ContentControl.cpp create mode 100644 src/ui/controls/Control.cpp create mode 100644 src/ui/controls/LayoutControl.cpp create mode 100644 src/ui/controls/NoChildControl.cpp create mode 100644 src/ui/controls/Window.cpp create mode 100644 src/ui/events/UiEvent.cpp create mode 100644 src/ui/helper/ClickDetector.cpp create mode 100644 src/ui/helper/ShortcutHub.cpp (limited to 'include/cru/ui/controls') diff --git a/demos/main/main.cpp b/demos/main/main.cpp index 5546f3dd..dd3e31e9 100644 --- a/demos/main/main.cpp +++ b/demos/main/main.cpp @@ -2,19 +2,19 @@ #include "cru/platform/gui/UiApplication.hpp" #include "cru/platform/gui/Window.hpp" #include "cru/ui/Base.hpp" -#include "cru/ui/Window.hpp" -#include "cru/ui/host/WindowHost.hpp" #include "cru/ui/controls/Button.hpp" #include "cru/ui/controls/FlexLayout.hpp" #include "cru/ui/controls/TextBlock.hpp" #include "cru/ui/controls/TextBox.hpp" +#include "cru/ui/controls/Window.hpp" +#include "cru/ui/host/WindowHost.hpp" using cru::platform::gui::CreateUiApplication; -using cru::ui::Window; using cru::ui::controls::Button; using cru::ui::controls::FlexLayout; using cru::ui::controls::TextBlock; using cru::ui::controls::TextBox; +using cru::ui::controls::Window; int main() { #ifdef CRU_DEBUG diff --git a/include/cru/platform/gui/UiApplication.hpp b/include/cru/platform/gui/UiApplication.hpp index ff947dfc..ba85020a 100644 --- a/include/cru/platform/gui/UiApplication.hpp +++ b/include/cru/platform/gui/UiApplication.hpp @@ -1,12 +1,24 @@ #pragma once #include "Base.hpp" +#include "cru/common/Bitmask.hpp" + #include #include #include #include namespace cru::platform::gui { +namespace details { +struct CreateWindowFlagTag; +} + +using CreateWindowFlag = Bitmask; + +struct CreateWindowFlags { + static constexpr CreateWindowFlag NoCaptionAndBorder{0b1}; +}; + // The entry point of a ui application. struct IUiApplication : public virtual INativeResource { public: @@ -43,7 +55,12 @@ struct IUiApplication : public virtual INativeResource { virtual void CancelTimer(long long id) = 0; virtual std::vector GetAllWindow() = 0; - virtual INativeWindow* CreateWindow(INativeWindow* parent) = 0; + + INativeWindow* CreateWindow(INativeWindow* parent) { + return this->CreateWindow(parent, CreateWindowFlag(0)); + }; + virtual INativeWindow* CreateWindow(INativeWindow* parent, + CreateWindowFlag flags) = 0; virtual cru::platform::graphics::IGraphFactory* GetGraphFactory() = 0; diff --git a/include/cru/ui/Base.hpp b/include/cru/ui/Base.hpp index 39fbb035..8595258d 100644 --- a/include/cru/ui/Base.hpp +++ b/include/cru/ui/Base.hpp @@ -26,9 +26,11 @@ namespace mouse_buttons = cru::platform::gui::mouse_buttons; namespace colors = cru::platform::colors; //-------------------- region: forward declaration -------------------- + +namespace controls { class Window; class Control; -class ClickDetector; +} // namespace controls namespace host { class WindowHost; diff --git a/include/cru/ui/ClickDetector.hpp b/include/cru/ui/ClickDetector.hpp deleted file mode 100644 index 4ffe5d05..00000000 --- a/include/cru/ui/ClickDetector.hpp +++ /dev/null @@ -1,87 +0,0 @@ -#pragma once -#include "Control.hpp" - -namespace cru::ui { -class ClickEventArgs : Object { - public: - ClickEventArgs(Control* sender, const Point& down_point, - const Point& up_point, MouseButton button) - : sender_(sender), - down_point_(down_point), - up_point_(up_point), - button_(button) {} - - CRU_DEFAULT_COPY(ClickEventArgs) - CRU_DEFAULT_MOVE(ClickEventArgs) - - ~ClickEventArgs() override = default; - - 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_; - Point down_point_; - Point up_point_; - MouseButton button_; -}; - -enum class ClickState { - None, // Mouse is outside the control. - Hover, // Mouse hovers on the control but not pressed - Press, // Mouse is pressed and if released click is done. - PressInactive // Mouse is pressed but if released click is canceled. -}; - -class ClickDetector : public Object { - CRU_DEFINE_CLASS_LOG_TAG(u"cru::ui::ClickDetector") - - public: - explicit ClickDetector(Control* control); - - CRU_DELETE_COPY(ClickDetector) - CRU_DELETE_MOVE(ClickDetector) - - ~ClickDetector() override = default; - - Control* GetControl() const { return control_; } - - ClickState GetState() const { return state_; } - - // Default is enable. - bool IsEnabled() const { return enable_; } - // If disable when user is pressing, the pressing is deactivated. - void SetEnabled(bool enable); - - // Default is left and right. - MouseButton GetTriggerButton() const { return trigger_button_; } - // If unset the trigger button when user is pressing, the pressing is - // deactivated. - void SetTriggerButton(MouseButton trigger_button); - - IEvent* ClickEvent() { return &event_; } - - IEvent* StateChangeEvent() { return &state_change_event_; } - - private: - void SetState(ClickState state); - - private: - Control* control_; - - ClickState state_; - - bool enable_ = true; - MouseButton trigger_button_ = mouse_buttons::left | mouse_buttons::right; - - Event event_; - Event state_change_event_; - - std::vector event_rovoker_guards_; - - Point down_point_; - MouseButton button_; -}; -} // namespace cru::ui diff --git a/include/cru/ui/ContentControl.hpp b/include/cru/ui/ContentControl.hpp deleted file mode 100644 index ba5b6b2f..00000000 --- a/include/cru/ui/ContentControl.hpp +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once -#include "Control.hpp" - -namespace cru::ui { -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); - - private: - using Control::AddChild; - using Control::RemoveChild; -}; -} // namespace cru::ui diff --git a/include/cru/ui/Control.hpp b/include/cru/ui/Control.hpp deleted file mode 100644 index fe50624a..00000000 --- a/include/cru/ui/Control.hpp +++ /dev/null @@ -1,151 +0,0 @@ -#pragma once -#include "Base.hpp" - -#include "UiEvent.hpp" -#include "cru/common/Event.hpp" -#include "render/Base.hpp" - -#include - -namespace cru::ui { -class Control : public Object { - friend host::WindowHost; - - protected: - Control(); - - public: - Control(const Control& other) = delete; - Control(Control&& other) = delete; - Control& operator=(const Control& other) = delete; - Control& operator=(Control&& other) = delete; - ~Control() override; - - public: - virtual std::u16string_view GetControlType() const = 0; - - //*************** region: tree *************** - public: - host::WindowHost* GetWindowHost() const; - - Control* GetParent() const { return parent_; } - - const std::vector& GetChildren() const { return children_; } - - // Traverse the tree rooted the control including itself. - void TraverseDescendants(const std::function& predicate); - - public: - virtual render::RenderObject* GetRenderObject() const = 0; - - //*************** region: focus *************** - public: - bool HasFocus(); - - void SetFocus(); - - //*************** region: mouse *************** - public: - bool IsMouseOver() const { return is_mouse_over_; } - - bool CaptureMouse(); - - bool ReleaseMouse(); - - bool IsMouseCaptured(); - - //*************** region: cursor *************** - // Cursor is inherited from parent recursively if not set. - public: - // null for not set - std::shared_ptr GetCursor(); - - // will not return nullptr - std::shared_ptr GetInheritedCursor(); - - // null to unset - void SetCursor(std::shared_ptr cursor); - - //*************** region: events *************** - public: - // Raised when mouse enter the control. Even when the control itself captures - // the mouse, this event is raised as regular. But if mouse is captured by - // another control, the control will not receive any mouse enter event. You - // can use `IsMouseCaptured` to get more info. - event::RoutedEvent* MouseEnterEvent() { - return &mouse_enter_event_; - } - // Raised when mouse is leave the control. Even when the control itself - // captures the mouse, this event is raised as regular. But if mouse is - // captured by another control, the control will not receive any mouse leave - // event. You can use `IsMouseCaptured` to get more info. - event::RoutedEvent* MouseLeaveEvent() { - return &mouse_leave_event_; - } - // Raised when mouse is move in the control. - event::RoutedEvent* MouseMoveEvent() { - return &mouse_move_event_; - } - // Raised when a mouse button is pressed in the control. - event::RoutedEvent* MouseDownEvent() { - return &mouse_down_event_; - } - // Raised when a mouse button is released in the control. - event::RoutedEvent* MouseUpEvent() { - return &mouse_up_event_; - } - event::RoutedEvent* MouseWheelEvent() { - return &mouse_wheel_event_; - } - event::RoutedEvent* KeyDownEvent() { - return &key_down_event_; - } - event::RoutedEvent* KeyUpEvent() { - return &key_up_event_; - } - event::RoutedEvent* GainFocusEvent() { - return &gain_focus_event_; - } - event::RoutedEvent* LoseFocusEvent() { - return &lose_focus_event_; - } - - private: - event::RoutedEvent mouse_enter_event_; - event::RoutedEvent mouse_leave_event_; - event::RoutedEvent mouse_move_event_; - event::RoutedEvent mouse_down_event_; - event::RoutedEvent mouse_up_event_; - event::RoutedEvent mouse_wheel_event_; - - event::RoutedEvent key_down_event_; - event::RoutedEvent key_up_event_; - - event::RoutedEvent gain_focus_event_; - event::RoutedEvent lose_focus_event_; - - //*************** 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(host::WindowHost* host); - virtual void OnDetachFromHost(host::WindowHost* host); - - protected: - virtual void OnMouseHoverChange(bool newHover) { CRU_UNUSED(newHover) } - - private: - Control* parent_ = nullptr; - std::vector children_; - - host::WindowHost* window_host_ = nullptr; - - private: - bool is_mouse_over_ = false; - - std::shared_ptr cursor_ = nullptr; -}; -} // namespace cru::ui diff --git a/include/cru/ui/LayoutControl.hpp b/include/cru/ui/LayoutControl.hpp deleted file mode 100644 index 69d5cd0b..00000000 --- a/include/cru/ui/LayoutControl.hpp +++ /dev/null @@ -1,19 +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 = default; - - using Control::AddChild; - using Control::RemoveChild; -}; -} // namespace cru::ui diff --git a/include/cru/ui/NoChildControl.hpp b/include/cru/ui/NoChildControl.hpp deleted file mode 100644 index 0d8a8e34..00000000 --- a/include/cru/ui/NoChildControl.hpp +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once -#include "Control.hpp" - -namespace cru::ui { -class NoChildControl : public Control { - protected: - NoChildControl() = default; - - public: - NoChildControl(const NoChildControl& other) = delete; - NoChildControl(NoChildControl&& other) = delete; - NoChildControl& operator=(const NoChildControl& other) = delete; - NoChildControl& operator=(NoChildControl&& other) = delete; - ~NoChildControl() override = default; - - private: - using Control::AddChild; - using Control::RemoveChild; -}; -} // namespace cru::ui diff --git a/include/cru/ui/ShortcutHub.hpp b/include/cru/ui/ShortcutHub.hpp deleted file mode 100644 index 1145c661..00000000 --- a/include/cru/ui/ShortcutHub.hpp +++ /dev/null @@ -1,130 +0,0 @@ -#pragma once -#include "Base.hpp" - -#include "cru/common/Base.hpp" -#include "cru/common/Event.hpp" -#include "cru/platform/gui/Keyboard.hpp" -#include "cru/ui/UiEvent.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace cru::ui { - -class ShortcutKeyBind { - public: - ShortcutKeyBind(platform::gui::KeyCode key, - platform::gui::KeyModifier modifier) - : 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 - -namespace std { -template <> -struct hash { - std::size_t operator()(const cru::ui::ShortcutKeyBind& value) const { - std::size_t result = 0; - cru::hash_combine(result, static_cast(value.GetKey())); - cru::hash_combine(result, static_cast(value.GetModifier())); - return result; - } -}; -} // namespace std - -namespace cru::ui { -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 handler; -}; - -struct ShortcutInfo { - int id; - std::u16string name; - ShortcutKeyBind key_bind; - std::function 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 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 GetAllShortcuts() const; - std::optional GetShortcut(int id) const; - const std::vector& GetShortcutByKeyBind( - const ShortcutKeyBind& key_bind) const; - - void Install(Control* control); - void Uninstall(); - - private: - void OnKeyDown(event::KeyEventArgs& event); - - private: - std::unordered_map> map_; - - const std::vector empty_list_; - - int current_id_ = 1; - - EventRevokerListGuard event_guard_; -}; -} // namespace cru::ui diff --git a/include/cru/ui/UiEvent.hpp b/include/cru/ui/UiEvent.hpp deleted file mode 100644 index c0b2a902..00000000 --- a/include/cru/ui/UiEvent.hpp +++ /dev/null @@ -1,228 +0,0 @@ -#pragma once -#include "Base.hpp" - -#include "cru/common/Event.hpp" -#include "cru/platform/gui/Keyboard.hpp" - -#include -#include -#include -#include - -namespace cru::platform::graphics { -struct IPainter; -} - -namespace cru::ui::event { -class UiEventArgs : public Object { - public: - UiEventArgs(Object* sender, Object* original_sender) - : sender_(sender), original_sender_(original_sender), handled_(false) {} - - UiEventArgs(const UiEventArgs& other) = default; - UiEventArgs(UiEventArgs&& other) = default; - UiEventArgs& operator=(const UiEventArgs& other) = default; - UiEventArgs& operator=(UiEventArgs&& other) = default; - ~UiEventArgs() override = default; - - Object* GetSender() const { return sender_; } - - Object* GetOriginalSender() const { return original_sender_; } - - bool IsHandled() const { return handled_; } - void SetHandled(const bool handled = true) { handled_ = handled; } - - private: - Object* sender_; - Object* original_sender_; - bool handled_; -}; - -// TEventArgs must not be a reference type. This class help add reference. -// EventArgs must be reference because the IsHandled property must be settable. -template -class RoutedEvent { - public: - static_assert(std::is_base_of_v, - "TEventArgs must be subclass of UiEventArgs."); - static_assert(!std::is_reference_v, - "TEventArgs must not be reference."); - - using RawEventArgs = TEventArgs; - using IEventType = IEvent; - using EventArgs = typename IEventType::EventArgs; - - RoutedEvent() = default; - RoutedEvent(const RoutedEvent& other) = delete; - RoutedEvent(RoutedEvent&& other) = delete; - RoutedEvent& operator=(const RoutedEvent& other) = delete; - RoutedEvent& operator=(RoutedEvent&& other) = delete; - ~RoutedEvent() = default; - - IEvent* Direct() { return &direct_; } - - IEvent* Bubble() { return &bubble_; } - - IEvent* Tunnel() { return &tunnel_; } - - private: - Event direct_; - Event bubble_; - Event tunnel_; -}; - -class MouseEventArgs : public UiEventArgs { - public: - MouseEventArgs(Object* sender, Object* original_sender, - const std::optional& point = std::nullopt) - : UiEventArgs(sender, original_sender), point_(point) {} - MouseEventArgs(const MouseEventArgs& other) = default; - MouseEventArgs(MouseEventArgs&& other) = default; - MouseEventArgs& operator=(const MouseEventArgs& other) = default; - MouseEventArgs& operator=(MouseEventArgs&& other) = default; - ~MouseEventArgs() override = default; - - // This point is relative to window client lefttop. - Point GetPoint() const { return point_.value_or(Point{}); } - Point GetPointToContent(render::RenderObject* render_target) const; - - private: - std::optional point_; -}; - -class MouseButtonEventArgs : public MouseEventArgs { - public: - MouseButtonEventArgs(Object* sender, Object* original_sender, - const Point& point, const MouseButton button, - 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::gui::KeyModifier key_modifier) - : MouseEventArgs(sender, original_sender), - button_(button), - key_modifier_(key_modifier) {} - MouseButtonEventArgs(const MouseButtonEventArgs& other) = default; - MouseButtonEventArgs(MouseButtonEventArgs&& other) = default; - MouseButtonEventArgs& operator=(const MouseButtonEventArgs& other) = default; - MouseButtonEventArgs& operator=(MouseButtonEventArgs&& other) = default; - ~MouseButtonEventArgs() override = default; - - MouseButton GetButton() const { return button_; } - platform::gui::KeyModifier GetKeyModifier() const { return key_modifier_; } - - private: - MouseButton button_; - platform::gui::KeyModifier key_modifier_; -}; - -class MouseWheelEventArgs : public MouseEventArgs { - public: - MouseWheelEventArgs(Object* sender, Object* original_sender, - const Point& point, const float delta) - : MouseEventArgs(sender, original_sender, point), delta_(delta) {} - MouseWheelEventArgs(const MouseWheelEventArgs& other) = default; - MouseWheelEventArgs(MouseWheelEventArgs&& other) = default; - MouseWheelEventArgs& operator=(const MouseWheelEventArgs& other) = default; - MouseWheelEventArgs& operator=(MouseWheelEventArgs&& other) = default; - ~MouseWheelEventArgs() override = default; - - float GetDelta() const { return delta_; } - - private: - float delta_; -}; - -class PaintEventArgs : public UiEventArgs { - public: - PaintEventArgs(Object* sender, Object* original_sender, - platform::graphics::IPainter* painter) - : UiEventArgs(sender, original_sender), painter_(painter) {} - PaintEventArgs(const PaintEventArgs& other) = default; - PaintEventArgs(PaintEventArgs&& other) = default; - PaintEventArgs& operator=(const PaintEventArgs& other) = default; - PaintEventArgs& operator=(PaintEventArgs&& other) = default; - ~PaintEventArgs() = default; - - platform::graphics::IPainter* GetPainter() const { return painter_; } - - private: - platform::graphics::IPainter* painter_; -}; - -class FocusChangeEventArgs : public UiEventArgs { - public: - FocusChangeEventArgs(Object* sender, Object* original_sender, - const bool is_window = false) - : UiEventArgs(sender, original_sender), is_window_(is_window) {} - FocusChangeEventArgs(const FocusChangeEventArgs& other) = default; - FocusChangeEventArgs(FocusChangeEventArgs&& other) = default; - FocusChangeEventArgs& operator=(const FocusChangeEventArgs& other) = default; - FocusChangeEventArgs& operator=(FocusChangeEventArgs&& other) = default; - ~FocusChangeEventArgs() override = default; - - // Return whether the focus change is caused by the window-wide focus change. - bool IsWindow() const { return is_window_; } - - private: - bool is_window_; -}; - -/* -class ToggleEventArgs : public UiEventArgs { - public: - ToggleEventArgs(Object* sender, Object* original_sender, bool new_state) - : UiEventArgs(sender, original_sender), new_state_(new_state) {} - ToggleEventArgs(const ToggleEventArgs& other) = default; - ToggleEventArgs(ToggleEventArgs&& other) = default; - ToggleEventArgs& operator=(const ToggleEventArgs& other) = default; - ToggleEventArgs& operator=(ToggleEventArgs&& other) = default; - ~ToggleEventArgs() override = default; - - bool GetNewState() const { return new_state_; } - - private: - bool new_state_; -}; -*/ - -class KeyEventArgs : public UiEventArgs { - public: - KeyEventArgs(Object* sender, Object* original_sender, - platform::gui::KeyCode key_code, - platform::gui::KeyModifier key_modifier) - : UiEventArgs(sender, original_sender), - key_code_(key_code), - key_modifier_(key_modifier) {} - KeyEventArgs(const KeyEventArgs& other) = default; - KeyEventArgs(KeyEventArgs&& other) = default; - KeyEventArgs& operator=(const KeyEventArgs& other) = default; - KeyEventArgs& operator=(KeyEventArgs&& other) = default; - ~KeyEventArgs() override = default; - - platform::gui::KeyCode GetKeyCode() const { return key_code_; } - platform::gui::KeyModifier GetKeyModifier() const { return key_modifier_; } - - private: - platform::gui::KeyCode key_code_; - platform::gui::KeyModifier key_modifier_; -}; - -class CharEventArgs : public UiEventArgs { - public: - CharEventArgs(Object* sender, Object* original_sender, std::u16string c) - : UiEventArgs(sender, original_sender), c_(std::move(c)) {} - CharEventArgs(const CharEventArgs& other) = default; - CharEventArgs(CharEventArgs&& other) = default; - CharEventArgs& operator=(const CharEventArgs& other) = default; - CharEventArgs& operator=(CharEventArgs&& other) = default; - ~CharEventArgs() override = default; - - std::u16string GetChar() const { return c_; } - - private: - std::u16string c_; -}; -} // namespace cru::ui::event diff --git a/include/cru/ui/Window.hpp b/include/cru/ui/Window.hpp deleted file mode 100644 index 70423a14..00000000 --- a/include/cru/ui/Window.hpp +++ /dev/null @@ -1,36 +0,0 @@ -#pragma once -#include "LayoutControl.hpp" - -namespace cru::ui { -class Window final : public LayoutControl { - public: - static constexpr std::u16string_view control_type = u"Window"; - - public: - static Window* CreateOverlapped(); - - private: - Window(); - - 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 OnAddChild(Control* child, Index position) override; - void OnRemoveChild(Control* child, Index position) override; - - private: - std::unique_ptr window_host_; - - std::unique_ptr render_object_; -}; -} // namespace cru::ui diff --git a/include/cru/ui/controls/Base.hpp b/include/cru/ui/controls/Base.hpp index b550601b..82c31d1e 100644 --- a/include/cru/ui/controls/Base.hpp +++ b/include/cru/ui/controls/Base.hpp @@ -2,7 +2,7 @@ #include "../Base.hpp" namespace cru::ui::controls { -using ButtonStateStyle = BorderStyle; +using ButtonStateStyle = ui::BorderStyle; struct ButtonStyle { // corresponds to ClickState::None @@ -16,9 +16,9 @@ struct ButtonStyle { }; struct TextBoxBorderStyle { - BorderStyle normal; - BorderStyle hover; - BorderStyle focus; - BorderStyle focus_hover; + ui::BorderStyle normal; + ui::BorderStyle hover; + ui::BorderStyle focus; + ui::BorderStyle focus_hover; }; } // namespace cru::ui::controls diff --git a/include/cru/ui/controls/Button.hpp b/include/cru/ui/controls/Button.hpp index a4f727d6..e8285507 100644 --- a/include/cru/ui/controls/Button.hpp +++ b/include/cru/ui/controls/Button.hpp @@ -1,8 +1,7 @@ #pragma once -#include "../ContentControl.hpp" -#include "Base.hpp" +#include "ContentControl.hpp" -#include "../ClickDetector.hpp" +#include "../helper/ClickDetector.hpp" namespace cru::ui::controls { class Button : public ContentControl { @@ -37,6 +36,6 @@ class Button : public ContentControl { 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..d9cb8aec 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 { diff --git a/include/cru/ui/controls/ContentControl.hpp b/include/cru/ui/controls/ContentControl.hpp new file mode 100644 index 00000000..47720a87 --- /dev/null +++ b/include/cru/ui/controls/ContentControl.hpp @@ -0,0 +1,26 @@ +#pragma once +#include "Control.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); + + private: + using Control::AddChild; + using Control::RemoveChild; +}; +} // namespace cru::ui::controls diff --git a/include/cru/ui/controls/Control.hpp b/include/cru/ui/controls/Control.hpp new file mode 100644 index 00000000..96aad2bd --- /dev/null +++ b/include/cru/ui/controls/Control.hpp @@ -0,0 +1,151 @@ +#pragma once +#include "Base.hpp" + +#include "../events/UiEvent.hpp" +#include "../render/Base.hpp" +#include "cru/common/Event.hpp" + +#include + +namespace cru::ui::controls { +class Control : public Object { + friend host::WindowHost; + + protected: + Control(); + + public: + Control(const Control& other) = delete; + Control(Control&& other) = delete; + Control& operator=(const Control& other) = delete; + Control& operator=(Control&& other) = delete; + ~Control() override; + + public: + virtual std::u16string_view GetControlType() const = 0; + + //*************** region: tree *************** + public: + host::WindowHost* GetWindowHost() const; + + Control* GetParent() const { return parent_; } + + const std::vector& GetChildren() const { return children_; } + + // Traverse the tree rooted the control including itself. + void TraverseDescendants(const std::function& predicate); + + public: + virtual render::RenderObject* GetRenderObject() const = 0; + + //*************** region: focus *************** + public: + bool HasFocus(); + + void SetFocus(); + + //*************** region: mouse *************** + public: + bool IsMouseOver() const { return is_mouse_over_; } + + bool CaptureMouse(); + + bool ReleaseMouse(); + + bool IsMouseCaptured(); + + //*************** region: cursor *************** + // Cursor is inherited from parent recursively if not set. + public: + // null for not set + std::shared_ptr GetCursor(); + + // will not return nullptr + std::shared_ptr GetInheritedCursor(); + + // null to unset + void SetCursor(std::shared_ptr cursor); + + //*************** region: events *************** + public: + // Raised when mouse enter the control. Even when the control itself captures + // the mouse, this event is raised as regular. But if mouse is captured by + // another control, the control will not receive any mouse enter event. You + // can use `IsMouseCaptured` to get more info. + event::RoutedEvent* MouseEnterEvent() { + return &mouse_enter_event_; + } + // Raised when mouse is leave the control. Even when the control itself + // captures the mouse, this event is raised as regular. But if mouse is + // captured by another control, the control will not receive any mouse leave + // event. You can use `IsMouseCaptured` to get more info. + event::RoutedEvent* MouseLeaveEvent() { + return &mouse_leave_event_; + } + // Raised when mouse is move in the control. + event::RoutedEvent* MouseMoveEvent() { + return &mouse_move_event_; + } + // Raised when a mouse button is pressed in the control. + event::RoutedEvent* MouseDownEvent() { + return &mouse_down_event_; + } + // Raised when a mouse button is released in the control. + event::RoutedEvent* MouseUpEvent() { + return &mouse_up_event_; + } + event::RoutedEvent* MouseWheelEvent() { + return &mouse_wheel_event_; + } + event::RoutedEvent* KeyDownEvent() { + return &key_down_event_; + } + event::RoutedEvent* KeyUpEvent() { + return &key_up_event_; + } + event::RoutedEvent* GainFocusEvent() { + return &gain_focus_event_; + } + event::RoutedEvent* LoseFocusEvent() { + return &lose_focus_event_; + } + + private: + event::RoutedEvent mouse_enter_event_; + event::RoutedEvent mouse_leave_event_; + event::RoutedEvent mouse_move_event_; + event::RoutedEvent mouse_down_event_; + event::RoutedEvent mouse_up_event_; + event::RoutedEvent mouse_wheel_event_; + + event::RoutedEvent key_down_event_; + event::RoutedEvent key_up_event_; + + event::RoutedEvent gain_focus_event_; + event::RoutedEvent lose_focus_event_; + + //*************** 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(host::WindowHost* host); + virtual void OnDetachFromHost(host::WindowHost* host); + + protected: + virtual void OnMouseHoverChange(bool newHover) { CRU_UNUSED(newHover) } + + private: + Control* parent_ = nullptr; + std::vector children_; + + host::WindowHost* window_host_ = nullptr; + + private: + bool is_mouse_over_ = false; + + std::shared_ptr cursor_ = nullptr; +}; +} // namespace cru::ui::controls diff --git a/include/cru/ui/controls/FlexLayout.hpp b/include/cru/ui/controls/FlexLayout.hpp index 0ffedba5..a6c6a40c 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 { diff --git a/include/cru/ui/controls/LayoutControl.hpp b/include/cru/ui/controls/LayoutControl.hpp new file mode 100644 index 00000000..cbdb8aa2 --- /dev/null +++ b/include/cru/ui/controls/LayoutControl.hpp @@ -0,0 +1,19 @@ +#pragma once +#include "Control.hpp" + +namespace cru::ui::controls { +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 = default; + + using Control::AddChild; + using Control::RemoveChild; +}; +} // namespace cru::ui diff --git a/include/cru/ui/controls/NoChildControl.hpp b/include/cru/ui/controls/NoChildControl.hpp new file mode 100644 index 00000000..562137f1 --- /dev/null +++ b/include/cru/ui/controls/NoChildControl.hpp @@ -0,0 +1,20 @@ +#pragma once +#include "Control.hpp" + +namespace cru::ui::controls { +class NoChildControl : public Control { + protected: + NoChildControl() = default; + + public: + NoChildControl(const NoChildControl& other) = delete; + NoChildControl(NoChildControl&& other) = delete; + NoChildControl& operator=(const NoChildControl& other) = delete; + NoChildControl& operator=(NoChildControl&& other) = delete; + ~NoChildControl() override = default; + + private: + using Control::AddChild; + using Control::RemoveChild; +}; +} // namespace cru::ui diff --git a/include/cru/ui/controls/StackLayout.hpp b/include/cru/ui/controls/StackLayout.hpp index c0b95044..373b4681 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 { diff --git a/include/cru/ui/controls/TextBlock.hpp b/include/cru/ui/controls/TextBlock.hpp index 8a9a3bff..fdfdb2fa 100644 --- a/include/cru/ui/controls/TextBlock.hpp +++ b/include/cru/ui/controls/TextBlock.hpp @@ -1,5 +1,5 @@ #pragma once -#include "../NoChildControl.hpp" +#include "NoChildControl.hpp" namespace cru::ui::controls { template diff --git a/include/cru/ui/controls/TextBox.hpp b/include/cru/ui/controls/TextBox.hpp index 5976f6da..91d38c61 100644 --- a/include/cru/ui/controls/TextBox.hpp +++ b/include/cru/ui/controls/TextBox.hpp @@ -1,6 +1,5 @@ #pragma once -#include "../NoChildControl.hpp" -#include "Base.hpp" +#include "NoChildControl.hpp" #include diff --git a/include/cru/ui/controls/Window.hpp b/include/cru/ui/controls/Window.hpp new file mode 100644 index 00000000..616e2ee7 --- /dev/null +++ b/include/cru/ui/controls/Window.hpp @@ -0,0 +1,36 @@ +#pragma once +#include "LayoutControl.hpp" + +namespace cru::ui::controls { +class Window final : public LayoutControl { + public: + static constexpr std::u16string_view control_type = u"Window"; + + public: + static Window* CreateOverlapped(); + + private: + Window(); + + 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 OnAddChild(Control* child, Index position) override; + void OnRemoveChild(Control* child, Index position) override; + + private: + std::unique_ptr window_host_; + + std::unique_ptr render_object_; +}; +} // namespace cru::ui diff --git a/include/cru/ui/events/UiEvent.hpp b/include/cru/ui/events/UiEvent.hpp new file mode 100644 index 00000000..660b33f5 --- /dev/null +++ b/include/cru/ui/events/UiEvent.hpp @@ -0,0 +1,228 @@ +#pragma once +#include "../Base.hpp" + +#include "cru/common/Event.hpp" +#include "cru/platform/gui/Keyboard.hpp" + +#include +#include +#include +#include + +namespace cru::platform::graphics { +struct IPainter; +} + +namespace cru::ui::event { +class UiEventArgs : public Object { + public: + UiEventArgs(Object* sender, Object* original_sender) + : sender_(sender), original_sender_(original_sender), handled_(false) {} + + UiEventArgs(const UiEventArgs& other) = default; + UiEventArgs(UiEventArgs&& other) = default; + UiEventArgs& operator=(const UiEventArgs& other) = default; + UiEventArgs& operator=(UiEventArgs&& other) = default; + ~UiEventArgs() override = default; + + Object* GetSender() const { return sender_; } + + Object* GetOriginalSender() const { return original_sender_; } + + bool IsHandled() const { return handled_; } + void SetHandled(const bool handled = true) { handled_ = handled; } + + private: + Object* sender_; + Object* original_sender_; + bool handled_; +}; + +// TEventArgs must not be a reference type. This class help add reference. +// EventArgs must be reference because the IsHandled property must be settable. +template +class RoutedEvent { + public: + static_assert(std::is_base_of_v, + "TEventArgs must be subclass of UiEventArgs."); + static_assert(!std::is_reference_v, + "TEventArgs must not be reference."); + + using RawEventArgs = TEventArgs; + using IEventType = IEvent; + using EventArgs = typename IEventType::EventArgs; + + RoutedEvent() = default; + RoutedEvent(const RoutedEvent& other) = delete; + RoutedEvent(RoutedEvent&& other) = delete; + RoutedEvent& operator=(const RoutedEvent& other) = delete; + RoutedEvent& operator=(RoutedEvent&& other) = delete; + ~RoutedEvent() = default; + + IEvent* Direct() { return &direct_; } + + IEvent* Bubble() { return &bubble_; } + + IEvent* Tunnel() { return &tunnel_; } + + private: + Event direct_; + Event bubble_; + Event tunnel_; +}; + +class MouseEventArgs : public UiEventArgs { + public: + MouseEventArgs(Object* sender, Object* original_sender, + const std::optional& point = std::nullopt) + : UiEventArgs(sender, original_sender), point_(point) {} + MouseEventArgs(const MouseEventArgs& other) = default; + MouseEventArgs(MouseEventArgs&& other) = default; + MouseEventArgs& operator=(const MouseEventArgs& other) = default; + MouseEventArgs& operator=(MouseEventArgs&& other) = default; + ~MouseEventArgs() override = default; + + // This point is relative to window client lefttop. + Point GetPoint() const { return point_.value_or(Point{}); } + Point GetPointToContent(render::RenderObject* render_target) const; + + private: + std::optional point_; +}; + +class MouseButtonEventArgs : public MouseEventArgs { + public: + MouseButtonEventArgs(Object* sender, Object* original_sender, + const Point& point, const MouseButton button, + 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::gui::KeyModifier key_modifier) + : MouseEventArgs(sender, original_sender), + button_(button), + key_modifier_(key_modifier) {} + MouseButtonEventArgs(const MouseButtonEventArgs& other) = default; + MouseButtonEventArgs(MouseButtonEventArgs&& other) = default; + MouseButtonEventArgs& operator=(const MouseButtonEventArgs& other) = default; + MouseButtonEventArgs& operator=(MouseButtonEventArgs&& other) = default; + ~MouseButtonEventArgs() override = default; + + MouseButton GetButton() const { return button_; } + platform::gui::KeyModifier GetKeyModifier() const { return key_modifier_; } + + private: + MouseButton button_; + platform::gui::KeyModifier key_modifier_; +}; + +class MouseWheelEventArgs : public MouseEventArgs { + public: + MouseWheelEventArgs(Object* sender, Object* original_sender, + const Point& point, const float delta) + : MouseEventArgs(sender, original_sender, point), delta_(delta) {} + MouseWheelEventArgs(const MouseWheelEventArgs& other) = default; + MouseWheelEventArgs(MouseWheelEventArgs&& other) = default; + MouseWheelEventArgs& operator=(const MouseWheelEventArgs& other) = default; + MouseWheelEventArgs& operator=(MouseWheelEventArgs&& other) = default; + ~MouseWheelEventArgs() override = default; + + float GetDelta() const { return delta_; } + + private: + float delta_; +}; + +class PaintEventArgs : public UiEventArgs { + public: + PaintEventArgs(Object* sender, Object* original_sender, + platform::graphics::IPainter* painter) + : UiEventArgs(sender, original_sender), painter_(painter) {} + PaintEventArgs(const PaintEventArgs& other) = default; + PaintEventArgs(PaintEventArgs&& other) = default; + PaintEventArgs& operator=(const PaintEventArgs& other) = default; + PaintEventArgs& operator=(PaintEventArgs&& other) = default; + ~PaintEventArgs() = default; + + platform::graphics::IPainter* GetPainter() const { return painter_; } + + private: + platform::graphics::IPainter* painter_; +}; + +class FocusChangeEventArgs : public UiEventArgs { + public: + FocusChangeEventArgs(Object* sender, Object* original_sender, + const bool is_window = false) + : UiEventArgs(sender, original_sender), is_window_(is_window) {} + FocusChangeEventArgs(const FocusChangeEventArgs& other) = default; + FocusChangeEventArgs(FocusChangeEventArgs&& other) = default; + FocusChangeEventArgs& operator=(const FocusChangeEventArgs& other) = default; + FocusChangeEventArgs& operator=(FocusChangeEventArgs&& other) = default; + ~FocusChangeEventArgs() override = default; + + // Return whether the focus change is caused by the window-wide focus change. + bool IsWindow() const { return is_window_; } + + private: + bool is_window_; +}; + +/* +class ToggleEventArgs : public UiEventArgs { + public: + ToggleEventArgs(Object* sender, Object* original_sender, bool new_state) + : UiEventArgs(sender, original_sender), new_state_(new_state) {} + ToggleEventArgs(const ToggleEventArgs& other) = default; + ToggleEventArgs(ToggleEventArgs&& other) = default; + ToggleEventArgs& operator=(const ToggleEventArgs& other) = default; + ToggleEventArgs& operator=(ToggleEventArgs&& other) = default; + ~ToggleEventArgs() override = default; + + bool GetNewState() const { return new_state_; } + + private: + bool new_state_; +}; +*/ + +class KeyEventArgs : public UiEventArgs { + public: + KeyEventArgs(Object* sender, Object* original_sender, + platform::gui::KeyCode key_code, + platform::gui::KeyModifier key_modifier) + : UiEventArgs(sender, original_sender), + key_code_(key_code), + key_modifier_(key_modifier) {} + KeyEventArgs(const KeyEventArgs& other) = default; + KeyEventArgs(KeyEventArgs&& other) = default; + KeyEventArgs& operator=(const KeyEventArgs& other) = default; + KeyEventArgs& operator=(KeyEventArgs&& other) = default; + ~KeyEventArgs() override = default; + + platform::gui::KeyCode GetKeyCode() const { return key_code_; } + platform::gui::KeyModifier GetKeyModifier() const { return key_modifier_; } + + private: + platform::gui::KeyCode key_code_; + platform::gui::KeyModifier key_modifier_; +}; + +class CharEventArgs : public UiEventArgs { + public: + CharEventArgs(Object* sender, Object* original_sender, std::u16string c) + : UiEventArgs(sender, original_sender), c_(std::move(c)) {} + CharEventArgs(const CharEventArgs& other) = default; + CharEventArgs(CharEventArgs&& other) = default; + CharEventArgs& operator=(const CharEventArgs& other) = default; + CharEventArgs& operator=(CharEventArgs&& other) = default; + ~CharEventArgs() override = default; + + std::u16string GetChar() const { return c_; } + + private: + std::u16string c_; +}; +} // namespace cru::ui::event diff --git a/include/cru/ui/helper/ClickDetector.hpp b/include/cru/ui/helper/ClickDetector.hpp new file mode 100644 index 00000000..0df77c60 --- /dev/null +++ b/include/cru/ui/helper/ClickDetector.hpp @@ -0,0 +1,87 @@ +#pragma once +#include "../controls/Control.hpp" + +namespace cru::ui::helper { +class ClickEventArgs : Object { + public: + ClickEventArgs(controls::Control* sender, const Point& down_point, + const Point& up_point, MouseButton button) + : sender_(sender), + down_point_(down_point), + up_point_(up_point), + button_(button) {} + + CRU_DEFAULT_COPY(ClickEventArgs) + CRU_DEFAULT_MOVE(ClickEventArgs) + + ~ClickEventArgs() override = default; + + controls::Control* GetSender() const { return sender_; } + Point GetDownPoint() const { return down_point_; } + Point GetUpPoint() const { return up_point_; } + MouseButton GetButton() const { return button_; } + + private: + controls::Control* sender_; + Point down_point_; + Point up_point_; + MouseButton button_; +}; + +enum class ClickState { + None, // Mouse is outside the control. + Hover, // Mouse hovers on the control but not pressed + Press, // Mouse is pressed and if released click is done. + PressInactive // Mouse is pressed but if released click is canceled. +}; + +class ClickDetector : public Object { + CRU_DEFINE_CLASS_LOG_TAG(u"cru::ui::ClickDetector") + + public: + explicit ClickDetector(controls::Control* control); + + CRU_DELETE_COPY(ClickDetector) + CRU_DELETE_MOVE(ClickDetector) + + ~ClickDetector() override = default; + + controls::Control* GetControl() const { return control_; } + + ClickState GetState() const { return state_; } + + // Default is enable. + bool IsEnabled() const { return enable_; } + // If disable when user is pressing, the pressing is deactivated. + void SetEnabled(bool enable); + + // Default is left and right. + MouseButton GetTriggerButton() const { return trigger_button_; } + // If unset the trigger button when user is pressing, the pressing is + // deactivated. + void SetTriggerButton(MouseButton trigger_button); + + IEvent* ClickEvent() { return &event_; } + + IEvent* StateChangeEvent() { return &state_change_event_; } + + private: + void SetState(ClickState state); + + private: + controls::Control* control_; + + ClickState state_; + + bool enable_ = true; + MouseButton trigger_button_ = mouse_buttons::left | mouse_buttons::right; + + Event event_; + Event state_change_event_; + + std::vector event_rovoker_guards_; + + Point down_point_; + MouseButton button_; +}; +} // 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..a4ff2da2 --- /dev/null +++ b/include/cru/ui/helper/ShortcutHub.hpp @@ -0,0 +1,129 @@ +#pragma once +#include "../Base.hpp" + +#include "../events/UiEvent.hpp" +#include "cru/common/Event.hpp" +#include "cru/platform/gui/Keyboard.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace cru::ui::helper { + +class ShortcutKeyBind { + public: + ShortcutKeyBind(platform::gui::KeyCode key, + platform::gui::KeyModifier modifier) + : 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 { + std::size_t operator()(const cru::ui::helper::ShortcutKeyBind& value) const { + std::size_t result = 0; + cru::hash_combine(result, static_cast(value.GetKey())); + cru::hash_combine(result, static_cast(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 handler; +}; + +struct ShortcutInfo { + int id; + std::u16string name; + ShortcutKeyBind key_bind; + std::function 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 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 GetAllShortcuts() const; + std::optional GetShortcut(int id) const; + const std::vector& GetShortcutByKeyBind( + const ShortcutKeyBind& key_bind) const; + + void Install(controls::Control* control); + void Uninstall(); + + private: + void OnKeyDown(event::KeyEventArgs& event); + + private: + std::unordered_map> map_; + + const std::vector empty_list_; + + int current_id_ = 1; + + EventRevokerListGuard event_guard_; +}; +} // namespace cru::ui::helper diff --git a/include/cru/ui/host/WindowHost.hpp b/include/cru/ui/host/WindowHost.hpp index 81eabb52..56f37382 100644 --- a/include/cru/ui/host/WindowHost.hpp +++ b/include/cru/ui/host/WindowHost.hpp @@ -1,10 +1,10 @@ #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 "../render/Base.hpp" #include #include @@ -19,7 +19,7 @@ class WindowHost : public Object { CRU_DEFINE_CLASS_LOG_TAG(u"cru::ui::host::WindowHost") public: - WindowHost(Control* root_control); + WindowHost(controls::Control* root_control); CRU_DELETE_COPY(WindowHost) CRU_DELETE_MOVE(WindowHost) @@ -61,13 +61,15 @@ class WindowHost : public Object { // 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_; } + controls::Control* GetMouseHoverControl() const { + return mouse_hover_control_; + } //*************** region: focus *************** - Control* GetFocusControl(); + controls::Control* GetFocusControl(); - void SetFocusControl(Control* control); + void SetFocusControl(controls::Control* control); //*************** region: focus *************** @@ -81,12 +83,12 @@ class WindowHost : public Object { // 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); + bool CaptureMouseFor(controls::Control* control); // Return null if not captured. - Control* GetMouseCaptureControl(); + controls::Control* GetMouseCaptureControl(); - Control* HitTest(const Point& point); + controls::Control* HitTest(const Point& point); void UpdateCursor(); @@ -94,23 +96,19 @@ class WindowHost : public Object { //*************** 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 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 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 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); @@ -119,13 +117,13 @@ class WindowHost : public Object { //*************** region: event dispatcher helper *************** - void DispatchMouseHoverControlChangeEvent(Control* old_control, - Control* new_control, + void DispatchMouseHoverControlChangeEvent(controls::Control* old_control, + controls::Control* new_control, const Point& point, bool no_leave, bool no_enter); private: - Control* root_control_ = nullptr; + controls::Control* root_control_ = nullptr; render::RenderObject* root_render_object_ = nullptr; platform::gui::INativeWindow* native_window_ = nullptr; @@ -137,12 +135,12 @@ class WindowHost : public Object { std::vector event_revoker_guards_; - Control* mouse_hover_control_ = nullptr; + controls::Control* mouse_hover_control_ = nullptr; - Control* focus_control_; + controls::Control* focus_control_; - Control* mouse_captured_control_ = nullptr; + controls::Control* mouse_captured_control_ = nullptr; bool layout_prefer_to_fill_window_ = true; }; -} // namespace cru::ui +} // namespace cru::ui::host diff --git a/include/cru/ui/render/RenderObject.hpp b/include/cru/ui/render/RenderObject.hpp index 635a541e..2b166efc 100644 --- a/include/cru/ui/render/RenderObject.hpp +++ b/include/cru/ui/render/RenderObject.hpp @@ -62,8 +62,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) { + control_ = new_control; + } host::WindowHost* GetWindowHost() const { return window_host_; } @@ -135,7 +137,9 @@ class RenderObject : public Object { // Add offset before pass point to children. virtual RenderObject* HitTest(const Point& point) = 0; - IEvent* AttachToHostEvent() { return &attach_to_host_event_; } + IEvent* AttachToHostEvent() { + return &attach_to_host_event_; + } IEvent* DetachFromHostEvent() { return &detach_from_host_event_; } @@ -208,7 +212,7 @@ class RenderObject : public Object { void SetWindowHostRecursive(host::WindowHost* host); private: - Control* control_ = nullptr; + controls::Control* control_ = nullptr; host::WindowHost* window_host_ = nullptr; RenderObject* parent_ = nullptr; diff --git a/include/cru/win/gui/UiApplication.hpp b/include/cru/win/gui/UiApplication.hpp index 0f733cd4..4cf46858 100644 --- a/include/cru/win/gui/UiApplication.hpp +++ b/include/cru/win/gui/UiApplication.hpp @@ -41,7 +41,7 @@ class WinUiApplication : public WinNativeResource, void CancelTimer(long long id) override; std::vector GetAllWindow() override; - INativeWindow* CreateWindow(INativeWindow* parent) override; + INativeWindow* CreateWindow(INativeWindow* parent, CreateWindowFlag flag) override; cru::platform::graphics::IGraphFactory* GetGraphFactory() override; diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt index 449fb4d9..2f0eb10d 100644 --- a/src/ui/CMakeLists.txt +++ b/src/ui/CMakeLists.txt @@ -4,23 +4,23 @@ add_library(cru_ui STATIC Helper.hpp host/RoutedEventDispatch.hpp - ClickDetector.cpp - ContentControl.cpp - Control.cpp Helper.cpp - LayoutControl.cpp - NoChildControl.cpp - ShortcutHub.cpp - UiEvent.cpp UiManager.cpp - Window.cpp controls/Button.cpp controls/Container.cpp + controls/ContentControl.cpp + controls/Control.cpp controls/FlexLayout.cpp + controls/LayoutControl.cpp + controls/NoChildControl.cpp controls/StackLayout.cpp controls/TextBlock.cpp controls/TextBox.cpp controls/TextControlService.hpp + controls/Window.cpp + events/UiEvent.cpp + helper/ClickDetector.cpp + helper/ShortcutHub.cpp host/LayoutPaintCycler.cpp host/WindowHost.cpp render/BorderRenderObject.cpp @@ -34,23 +34,23 @@ add_library(cru_ui STATIC ) target_sources(cru_ui PUBLIC ${CRU_UI_INCLUDE_DIR}/Base.hpp - ${CRU_UI_INCLUDE_DIR}/ClickDetector.hpp - ${CRU_UI_INCLUDE_DIR}/ContentControl.hpp - ${CRU_UI_INCLUDE_DIR}/Control.hpp ${CRU_UI_INCLUDE_DIR}/DebugFlags.hpp - ${CRU_UI_INCLUDE_DIR}/LayoutControl.hpp - ${CRU_UI_INCLUDE_DIR}/NoChildControl.hpp - ${CRU_UI_INCLUDE_DIR}/ShortcutHub.hpp - ${CRU_UI_INCLUDE_DIR}/UiEvent.hpp ${CRU_UI_INCLUDE_DIR}/UiManager.hpp - ${CRU_UI_INCLUDE_DIR}/Window.hpp ${CRU_UI_INCLUDE_DIR}/controls/Base.hpp ${CRU_UI_INCLUDE_DIR}/controls/Button.hpp ${CRU_UI_INCLUDE_DIR}/controls/Container.hpp + ${CRU_UI_INCLUDE_DIR}/controls/ContentControl.hpp + ${CRU_UI_INCLUDE_DIR}/controls/Control.hpp ${CRU_UI_INCLUDE_DIR}/controls/FlexLayout.hpp + ${CRU_UI_INCLUDE_DIR}/controls/LayoutControl.hpp + ${CRU_UI_INCLUDE_DIR}/controls/NoChildControl.hpp ${CRU_UI_INCLUDE_DIR}/controls/StackLayout.hpp ${CRU_UI_INCLUDE_DIR}/controls/TextBox.hpp ${CRU_UI_INCLUDE_DIR}/controls/TextBlock.hpp + ${CRU_UI_INCLUDE_DIR}/controls/Window.hpp + ${CRU_UI_INCLUDE_DIR}/events/UiEvent.hpp + ${CRU_UI_INCLUDE_DIR}/helper/ClickDetector.hpp + ${CRU_UI_INCLUDE_DIR}/helper/ShortcutHub.hpp ${CRU_UI_INCLUDE_DIR}/host/LayoutPaintCycler.hpp ${CRU_UI_INCLUDE_DIR}/host/WindowHost.hpp ${CRU_UI_INCLUDE_DIR}/render/Base.hpp diff --git a/src/ui/ClickDetector.cpp b/src/ui/ClickDetector.cpp deleted file mode 100644 index 09f208cd..00000000 --- a/src/ui/ClickDetector.cpp +++ /dev/null @@ -1,131 +0,0 @@ -#include "cru/ui/ClickDetector.hpp" - -#include "cru/common/Logger.hpp" - -#include - -namespace cru::ui { -ClickDetector::ClickDetector(Control* control) { - Expects(control); - control_ = control; - - event_rovoker_guards_.push_back( - EventRevokerGuard(control->MouseEnterEvent()->Direct()->AddHandler( - [this](event::MouseEventArgs&) { - if (this->enable_) { - if (this->state_ == ClickState::PressInactive) { - if ((this->button_ & this->trigger_button_)) { - this->SetState(ClickState::Press); - } - } else { - this->SetState(ClickState::Hover); - } - } - }))); - - event_rovoker_guards_.push_back( - EventRevokerGuard(control->MouseLeaveEvent()->Direct()->AddHandler( - [this](event::MouseEventArgs&) { - if (this->enable_) { - if (this->state_ == ClickState::Press) { - if ((this->button_ & this->trigger_button_)) { - this->SetState(ClickState::PressInactive); - } - } else { - this->SetState(ClickState::None); - } - } - }))); - - event_rovoker_guards_.push_back( - EventRevokerGuard(control->MouseDownEvent()->Direct()->AddHandler( - [this](event::MouseButtonEventArgs& args) { - const auto button = args.GetButton(); - if (this->enable_ && (button & this->trigger_button_) && - this->state_ == ClickState::Hover) { - if (!this->control_->CaptureMouse()) { - log::TagDebug(log_tag, - u"Failed to capture mouse when begin click."); - return; - } - this->down_point_ = args.GetPoint(); - this->button_ = button; - this->SetState(ClickState::Press); - } - }))); - - event_rovoker_guards_.push_back( - EventRevokerGuard(control->MouseUpEvent()->Direct()->AddHandler( - [this](event::MouseButtonEventArgs& args) { - const auto button = args.GetButton(); - if (this->enable_ && (button & this->trigger_button_) && - button == button_) { - if (this->state_ == ClickState::Press) { - this->SetState(ClickState::Hover); - this->event_.Raise(ClickEventArgs{this->control_, - this->down_point_, - args.GetPoint(), button}); - this->control_->ReleaseMouse(); - } else if (this->state_ == ClickState::PressInactive) { - this->SetState(ClickState::None); - this->control_->ReleaseMouse(); - } - } - }))); -} // namespace cru::ui - -void ClickDetector::SetEnabled(bool enable) { - if (enable == enable_) { - return; - } - - enable_ = enable; - if (enable) { - SetState(control_->IsMouseOver() ? ClickState::Hover : ClickState::None); - } else { - if (state_ == ClickState::Press || state_ == ClickState::PressInactive) { - SetState(ClickState::None); - control_->ReleaseMouse(); - } else if (state_ == ClickState::Hover) { - SetState(ClickState::None); - } - } -} - -void ClickDetector::SetTriggerButton(MouseButton trigger_button) { - if (trigger_button == trigger_button_) { - return; - } - - trigger_button_ = trigger_button; - if ((state_ == ClickState::Press || state_ == ClickState::PressInactive) && - !(button_ & trigger_button)) { - SetState(control_->IsMouseOver() ? ClickState::Hover : ClickState::None); - control_->ReleaseMouse(); - } -} - -void ClickDetector::SetState(ClickState state) { -#ifdef CRU_DEBUG - auto to_string = [](ClickState state) -> std::u16string_view { - switch (state) { - case ClickState::None: - return u"None"; - case ClickState::Hover: - return u"Hover"; - case ClickState::Press: - return u"Press"; - case ClickState::PressInactive: - return u"PressInvactive"; - default: - UnreachableCode(); - } - }; - log::TagDebug(log_tag, u"Click state changed, new state: {}.", - to_string(state)); -#endif - - state_ = state; - state_change_event_.Raise(state); -} -} // namespace cru::ui diff --git a/src/ui/ContentControl.cpp b/src/ui/ContentControl.cpp deleted file mode 100644 index 19b1b06f..00000000 --- a/src/ui/ContentControl.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#include "cru/ui/ContentControl.hpp" - -#include "cru/ui/Window.hpp" - -namespace cru::ui { -Control* ContentControl::GetChild() const { - if (GetChildren().empty()) return nullptr; - return GetChildren()[0]; -} - -void ContentControl::SetChild(Control* child) { - Control* old_child = nullptr; - if (!GetChildren().empty()) { - old_child = GetChildren()[0]; - this->RemoveChild(0); - } - if (child) { - this->AddChild(child, 0); - } - OnChildChanged(old_child, child); -} - -void ContentControl::OnChildChanged(Control* old_child, Control* new_child) { - CRU_UNUSED(old_child) - CRU_UNUSED(new_child) -} -} // namespace cru::ui diff --git a/src/ui/Control.cpp b/src/ui/Control.cpp deleted file mode 100644 index 23a3cef2..00000000 --- a/src/ui/Control.cpp +++ /dev/null @@ -1,157 +0,0 @@ -#include "cru/ui/Control.hpp" - -#include "cru/common/Base.hpp" -#include "cru/platform/gui/Cursor.hpp" -#include "cru/platform/gui/UiApplication.hpp" -#include "cru/ui/Base.hpp" -#include "cru/ui/host/WindowHost.hpp" -#include "cru/ui/render/RenderObject.hpp" - -#include - -namespace cru::ui { -using platform::gui::ICursor; -using platform::gui::IUiApplication; -using platform::gui::SystemCursorType; - -Control::Control() { - MouseEnterEvent()->Direct()->AddHandler([this](event::MouseEventArgs&) { - this->is_mouse_over_ = true; - this->OnMouseHoverChange(true); - }); - - MouseLeaveEvent()->Direct()->AddHandler([this](event::MouseEventArgs&) { - this->is_mouse_over_ = false; - this->OnMouseHoverChange(true); - }); -} - -Control::~Control() { - for (const auto child : children_) delete child; -} - -host::WindowHost* Control::GetWindowHost() const { return window_host_; } - -void Control::TraverseDescendants( - const std::function& predicate) { - predicate(this); - for (auto c : GetChildren()) c->TraverseDescendants(predicate); -} - -bool Control::HasFocus() { - auto host = GetWindowHost(); - if (host == nullptr) return false; - - return host->GetFocusControl() == this; -} - -bool Control::CaptureMouse() { - auto host = GetWindowHost(); - if (host == nullptr) return false; - - return host->CaptureMouseFor(this); -} - -void Control::SetFocus() { - auto host = GetWindowHost(); - if (host == nullptr) return; - - host->SetFocusControl(this); -} - -bool Control::ReleaseMouse() { - auto host = GetWindowHost(); - if (host == nullptr) return false; - - return host->CaptureMouseFor(nullptr); -} - -bool Control::IsMouseCaptured() { - auto host = GetWindowHost(); - if (host == nullptr) return false; - - return host->GetMouseCaptureControl() == this; -} - -std::shared_ptr Control::GetCursor() { return cursor_; } - -std::shared_ptr Control::GetInheritedCursor() { - Control* control = this; - while (control != nullptr) { - const auto cursor = control->GetCursor(); - if (cursor != nullptr) return cursor; - control = control->GetParent(); - } - return IUiApplication::GetInstance()->GetCursorManager()->GetSystemCursor( - SystemCursorType::Arrow); -} - -void Control::SetCursor(std::shared_ptr cursor) { - cursor_ = std::move(cursor); - const auto host = GetWindowHost(); - if (host != nullptr) { - host->UpdateCursor(); - } -} - -void Control::AddChild(Control* control, const Index position) { - Expects(control->GetParent() == - nullptr); // The control already has a parent. - Expects(position >= 0); - Expects(position <= static_cast( - children_.size())); // The position is out of range. - - children_.insert(children_.cbegin() + position, control); - - const auto old_parent = control->parent_; - control->parent_ = this; - - OnAddChild(control, position); - control->OnParentChanged(old_parent, this); - - if (window_host_) - control->TraverseDescendants([this](Control* control) { - control->window_host_ = window_host_; - control->OnAttachToHost(window_host_); - }); -} - -void Control::RemoveChild(const Index position) { - Expects(position >= 0); - Expects(position < static_cast( - children_.size())); // The position is out of range. - - const auto i = children_.cbegin() + position; - const auto control = *i; - - children_.erase(i); - control->parent_ = nullptr; - - OnRemoveChild(control, position); - control->OnParentChanged(this, nullptr); - - if (window_host_) - control->TraverseDescendants([this](Control* control) { - control->window_host_ = nullptr; - control->OnDetachFromHost(window_host_); - }); -} - -void Control::OnAddChild(Control* child, Index position) { - CRU_UNUSED(child) - CRU_UNUSED(position) -} -void Control::OnRemoveChild(Control* child, Index position) { - CRU_UNUSED(child) - CRU_UNUSED(position) -} - -void Control::OnParentChanged(Control* old_parent, Control* new_parent) { - CRU_UNUSED(old_parent) - CRU_UNUSED(new_parent) -} - -void Control::OnAttachToHost(host::WindowHost* host) { CRU_UNUSED(host) } - -void Control::OnDetachFromHost(host::WindowHost* host) { CRU_UNUSED(host) } -} // namespace cru::ui diff --git a/src/ui/LayoutControl.cpp b/src/ui/LayoutControl.cpp deleted file mode 100644 index 351026f9..00000000 --- a/src/ui/LayoutControl.cpp +++ /dev/null @@ -1,6 +0,0 @@ -#include "cru/ui/LayoutControl.hpp" - -#include "cru/ui/Window.hpp" - -namespace cru::ui { -} // namespace cru::ui diff --git a/src/ui/NoChildControl.cpp b/src/ui/NoChildControl.cpp deleted file mode 100644 index 8adbe3bc..00000000 --- a/src/ui/NoChildControl.cpp +++ /dev/null @@ -1,3 +0,0 @@ -#include "cru/ui/NoChildControl.hpp" - -namespace cru::ui {} diff --git a/src/ui/ShortcutHub.cpp b/src/ui/ShortcutHub.cpp deleted file mode 100644 index c9ce6cdd..00000000 --- a/src/ui/ShortcutHub.cpp +++ /dev/null @@ -1,120 +0,0 @@ -#include "cru/ui/ShortcutHub.hpp" - -#include "cru/common/Logger.hpp" -#include "cru/ui/Control.hpp" -#include "cru/ui/DebugFlags.hpp" - -#include -#include -#include -#include - -namespace cru::ui { -int ShortcutHub::RegisterShortcut(Shortcut shortcut) { - const int id = current_id_++; - map_[shortcut.key_bind].push_back({id, std::move(shortcut.name), - shortcut.key_bind, - std::move(shortcut.handler)}); - return id; -} - -void ShortcutHub::UnregisterShortcut(int id) { - if (id <= 0) return; - for (auto& pair : map_) { - auto& list = pair.second; - auto result = - std::find_if(list.cbegin(), list.cend(), - [id](const ShortcutInfo& info) { return info.id == id; }); - if (result != list.cend()) { - list.erase(result); - } - } -} - -std::vector ShortcutHub::GetAllShortcuts() const { - std::vector result; - - for (const auto& pair : map_) { - std::copy(pair.second.cbegin(), pair.second.cend(), - std::back_inserter(result)); - } - - return result; -} - -std::optional ShortcutHub::GetShortcut(int id) const { - for (auto& pair : map_) { - auto& list = pair.second; - auto result = - std::find_if(list.cbegin(), list.cend(), - [id](const ShortcutInfo& info) { return info.id == id; }); - if (result != list.cend()) { - return *result; - } - } - return std::nullopt; -} - -const std::vector& ShortcutHub::GetShortcutByKeyBind( - const ShortcutKeyBind& key_bind) const { - auto result = map_.find(key_bind); - if (result != map_.cend()) return result->second; - return empty_list_; -} - -void ShortcutHub::Install(Control* control) { - if (!event_guard_.IsEmpty()) { - log::Error(u"Shortcut hub is already installed. Failed to install."); - return; - } - - event_guard_ += control->KeyDownEvent()->Bubble()->AddHandler( - std::bind(&ShortcutHub::OnKeyDown, this, std::placeholders::_1)); -} - -void ShortcutHub::Uninstall() { - if (event_guard_.IsEmpty()) { - log::Warn(u"Shortcut hub is not installed. Failed to uninstall."); - return; - } - - event_guard_.Clear(); -} - -void ShortcutHub::OnKeyDown(event::KeyEventArgs& event) { - ShortcutKeyBind key_bind(event.GetKeyCode(), event.GetKeyModifier()); - const auto& shortcut_list = this->GetShortcutByKeyBind(key_bind); - - if constexpr (debug_flags::shortcut) { - if (shortcut_list.empty()) { - log::Debug(u"No shortcut for key bind {}.", key_bind.ToString()); - } - log::Debug(u"Begin to handle shortcut for key bind {}.", - key_bind.ToString()); - } - - for (const auto& shortcut : shortcut_list) { - auto is_handled = shortcut.handler(); - if (is_handled) { - if constexpr (debug_flags::shortcut) { - log::Debug(u"Handle {} handled it.", shortcut.name); - } - - event.SetHandled(); - - break; - } else { - if constexpr (debug_flags::shortcut) { - log::Debug(u"Handle {} disdn't handle it.", shortcut.name); - } - } - } - - if constexpr (debug_flags::shortcut) { - if (!shortcut_list.empty()) { - log::Debug(u"End handling shortcut for key bind {}.", - key_bind.ToString()); - } - } -} -} // namespace cru::ui diff --git a/src/ui/UiEvent.cpp b/src/ui/UiEvent.cpp deleted file mode 100644 index 74dd54dc..00000000 --- a/src/ui/UiEvent.cpp +++ /dev/null @@ -1,10 +0,0 @@ -#include "cru/ui/UiEvent.hpp" - -#include "cru/ui/render/RenderObject.hpp" - -namespace cru::ui::event { -Point MouseEventArgs::GetPointToContent( - render::RenderObject* render_object) const { - return render_object->FromRootToContent(GetPoint()); -} -} // namespace cru::ui::event diff --git a/src/ui/Window.cpp b/src/ui/Window.cpp deleted file mode 100644 index c49140a4..00000000 --- a/src/ui/Window.cpp +++ /dev/null @@ -1,32 +0,0 @@ -#include "cru/ui/Window.hpp" - -#include "cru/common/Base.hpp" -#include "cru/ui/host/WindowHost.hpp" -#include "cru/ui/render/Base.hpp" -#include "cru/ui/render/StackLayoutRenderObject.hpp" - -namespace cru::ui { -Window* Window::CreateOverlapped() { return new Window(); } - -Window::Window() : render_object_(new render::StackLayoutRenderObject()) { - render_object_->SetAttachedControl(this); - window_host_ = std::make_unique(this); -} - -Window::~Window() {} - -std::u16string_view Window::GetControlType() const { return control_type; } - -render::RenderObject* Window::GetRenderObject() const { - return render_object_.get(); -} - -void Window::OnAddChild(Control* child, Index position) { - render_object_->AddChild(child->GetRenderObject(), position); -} - -void Window::OnRemoveChild(Control* child, Index position) { - CRU_UNUSED(child); - render_object_->RemoveChild(position); -} -} // namespace cru::ui diff --git a/src/ui/controls/Button.cpp b/src/ui/controls/Button.cpp index 5f7ed143..b7407ec2 100644 --- a/src/ui/controls/Button.cpp +++ b/src/ui/controls/Button.cpp @@ -5,9 +5,9 @@ #include "cru/platform/graphics/Brush.hpp" #include "cru/platform/gui/Cursor.hpp" #include "cru/platform/gui/UiApplication.hpp" -#include "cru/ui/render/BorderRenderObject.hpp" #include "cru/ui/UiManager.hpp" -#include "cru/ui/Window.hpp" +#include "cru/ui/helper/ClickDetector.hpp" +#include "cru/ui/render/BorderRenderObject.hpp" namespace cru::ui::controls { using cru::platform::gui::SystemCursorType; @@ -21,8 +21,7 @@ void Set(render::BorderRenderObject* o, const ButtonStateStyle& s) { o->SetBackgroundBrush(s.background_brush); } -std::shared_ptr GetSystemCursor( - SystemCursorType type) { +std::shared_ptr GetSystemCursor(SystemCursorType type) { return GetUiApplication()->GetCursorManager()->GetSystemCursor(type); } } // namespace @@ -36,21 +35,21 @@ Button::Button() : click_detector_(this) { render_object_->SetBorderEnabled(true); click_detector_.StateChangeEvent()->AddHandler( - [this](const ClickState& state) { + [this](const helper::ClickState& state) { switch (state) { - case ClickState::None: + case helper::ClickState::None: Set(render_object_.get(), style_.normal); SetCursor(GetSystemCursor(SystemCursorType::Arrow)); break; - case ClickState::Hover: + case helper::ClickState::Hover: Set(render_object_.get(), style_.hover); SetCursor(GetSystemCursor(SystemCursorType::Hand)); break; - case ClickState::Press: + case helper::ClickState::Press: Set(render_object_.get(), style_.press); SetCursor(GetSystemCursor(SystemCursorType::Hand)); break; - case ClickState::PressInactive: + case helper::ClickState::PressInactive: Set(render_object_.get(), style_.press_cancel); SetCursor(GetSystemCursor(SystemCursorType::Arrow)); break; diff --git a/src/ui/controls/ContentControl.cpp b/src/ui/controls/ContentControl.cpp new file mode 100644 index 00000000..653882c0 --- /dev/null +++ b/src/ui/controls/ContentControl.cpp @@ -0,0 +1,25 @@ +#include "cru/ui/controls/ContentControl.hpp" + +namespace cru::ui::controls { +Control* ContentControl::GetChild() const { + if (GetChildren().empty()) return nullptr; + return GetChildren()[0]; +} + +void ContentControl::SetChild(Control* child) { + Control* old_child = nullptr; + if (!GetChildren().empty()) { + old_child = GetChildren()[0]; + this->RemoveChild(0); + } + if (child) { + this->AddChild(child, 0); + } + OnChildChanged(old_child, child); +} + +void ContentControl::OnChildChanged(Control* old_child, Control* new_child) { + CRU_UNUSED(old_child) + CRU_UNUSED(new_child) +} +} // namespace cru::ui::controls diff --git a/src/ui/controls/Control.cpp b/src/ui/controls/Control.cpp new file mode 100644 index 00000000..c1316a62 --- /dev/null +++ b/src/ui/controls/Control.cpp @@ -0,0 +1,157 @@ +#include "cru/ui/controls/Control.hpp" + +#include "cru/common/Base.hpp" +#include "cru/platform/gui/Cursor.hpp" +#include "cru/platform/gui/UiApplication.hpp" +#include "cru/ui/Base.hpp" +#include "cru/ui/host/WindowHost.hpp" +#include "cru/ui/render/RenderObject.hpp" + +#include + +namespace cru::ui::controls { +using platform::gui::ICursor; +using platform::gui::IUiApplication; +using platform::gui::SystemCursorType; + +Control::Control() { + MouseEnterEvent()->Direct()->AddHandler([this](event::MouseEventArgs&) { + this->is_mouse_over_ = true; + this->OnMouseHoverChange(true); + }); + + MouseLeaveEvent()->Direct()->AddHandler([this](event::MouseEventArgs&) { + this->is_mouse_over_ = false; + this->OnMouseHoverChange(true); + }); +} + +Control::~Control() { + for (const auto child : children_) delete child; +} + +host::WindowHost* Control::GetWindowHost() const { return window_host_; } + +void Control::TraverseDescendants( + const std::function& predicate) { + predicate(this); + for (auto c : GetChildren()) c->TraverseDescendants(predicate); +} + +bool Control::HasFocus() { + auto host = GetWindowHost(); + if (host == nullptr) return false; + + return host->GetFocusControl() == this; +} + +bool Control::CaptureMouse() { + auto host = GetWindowHost(); + if (host == nullptr) return false; + + return host->CaptureMouseFor(this); +} + +void Control::SetFocus() { + auto host = GetWindowHost(); + if (host == nullptr) return; + + host->SetFocusControl(this); +} + +bool Control::ReleaseMouse() { + auto host = GetWindowHost(); + if (host == nullptr) return false; + + return host->CaptureMouseFor(nullptr); +} + +bool Control::IsMouseCaptured() { + auto host = GetWindowHost(); + if (host == nullptr) return false; + + return host->GetMouseCaptureControl() == this; +} + +std::shared_ptr Control::GetCursor() { return cursor_; } + +std::shared_ptr Control::GetInheritedCursor() { + Control* control = this; + while (control != nullptr) { + const auto cursor = control->GetCursor(); + if (cursor != nullptr) return cursor; + control = control->GetParent(); + } + return IUiApplication::GetInstance()->GetCursorManager()->GetSystemCursor( + SystemCursorType::Arrow); +} + +void Control::SetCursor(std::shared_ptr cursor) { + cursor_ = std::move(cursor); + const auto host = GetWindowHost(); + if (host != nullptr) { + host->UpdateCursor(); + } +} + +void Control::AddChild(Control* control, const Index position) { + Expects(control->GetParent() == + nullptr); // The control already has a parent. + Expects(position >= 0); + Expects(position <= static_cast( + children_.size())); // The position is out of range. + + children_.insert(children_.cbegin() + position, control); + + const auto old_parent = control->parent_; + control->parent_ = this; + + OnAddChild(control, position); + control->OnParentChanged(old_parent, this); + + if (window_host_) + control->TraverseDescendants([this](Control* control) { + control->window_host_ = window_host_; + control->OnAttachToHost(window_host_); + }); +} + +void Control::RemoveChild(const Index position) { + Expects(position >= 0); + Expects(position < static_cast( + children_.size())); // The position is out of range. + + const auto i = children_.cbegin() + position; + const auto control = *i; + + children_.erase(i); + control->parent_ = nullptr; + + OnRemoveChild(control, position); + control->OnParentChanged(this, nullptr); + + if (window_host_) + control->TraverseDescendants([this](Control* control) { + control->window_host_ = nullptr; + control->OnDetachFromHost(window_host_); + }); +} + +void Control::OnAddChild(Control* child, Index position) { + CRU_UNUSED(child) + CRU_UNUSED(position) +} +void Control::OnRemoveChild(Control* child, Index position) { + CRU_UNUSED(child) + CRU_UNUSED(position) +} + +void Control::OnParentChanged(Control* old_parent, Control* new_parent) { + CRU_UNUSED(old_parent) + CRU_UNUSED(new_parent) +} + +void Control::OnAttachToHost(host::WindowHost* host) { CRU_UNUSED(host) } + +void Control::OnDetachFromHost(host::WindowHost* host) { CRU_UNUSED(host) } +} // namespace cru::ui::controls diff --git a/src/ui/controls/LayoutControl.cpp b/src/ui/controls/LayoutControl.cpp new file mode 100644 index 00000000..85417beb --- /dev/null +++ b/src/ui/controls/LayoutControl.cpp @@ -0,0 +1,3 @@ +#include "cru/ui/controls/LayoutControl.hpp" + +namespace cru::ui::controls {} diff --git a/src/ui/controls/NoChildControl.cpp b/src/ui/controls/NoChildControl.cpp new file mode 100644 index 00000000..c62c5819 --- /dev/null +++ b/src/ui/controls/NoChildControl.cpp @@ -0,0 +1,3 @@ +#include "cru/ui/controls/NoChildControl.hpp" + +namespace cru::ui::controls {} diff --git a/src/ui/controls/TextControlService.hpp b/src/ui/controls/TextControlService.hpp index a7e4e440..8ad95dec 100644 --- a/src/ui/controls/TextControlService.hpp +++ b/src/ui/controls/TextControlService.hpp @@ -8,10 +8,10 @@ #include "cru/platform/gui/UiApplication.hpp" #include "cru/platform/gui/Window.hpp" #include "cru/ui/Base.hpp" -#include "cru/ui/Control.hpp" #include "cru/ui/DebugFlags.hpp" -#include "cru/ui/ShortcutHub.hpp" -#include "cru/ui/UiEvent.hpp" +#include "cru/ui/controls/Control.hpp" +#include "cru/ui/events/UiEvent.hpp" +#include "cru/ui/helper/ShortcutHub.hpp" #include "cru/ui/host/WindowHost.hpp" #include "cru/ui/render/CanvasRenderObject.hpp" #include "cru/ui/render/ScrollRenderObject.hpp" @@ -472,7 +472,7 @@ class TextControlService : public Object { platform::gui::TimerAutoCanceler caret_timer_canceler_; int caret_blink_duration_ = k_default_caret_blink_duration; - ShortcutHub shortcut_hub_; + helper::ShortcutHub shortcut_hub_; // nullopt means not selecting std::optional select_down_button_; diff --git a/src/ui/controls/Window.cpp b/src/ui/controls/Window.cpp new file mode 100644 index 00000000..7ce40dfe --- /dev/null +++ b/src/ui/controls/Window.cpp @@ -0,0 +1,32 @@ +#include "cru/ui/controls/Window.hpp" + +#include "cru/common/Base.hpp" +#include "cru/ui/host/WindowHost.hpp" +#include "cru/ui/render/Base.hpp" +#include "cru/ui/render/StackLayoutRenderObject.hpp" + +namespace cru::ui::controls { +Window* Window::CreateOverlapped() { return new Window(); } + +Window::Window() : render_object_(new render::StackLayoutRenderObject()) { + render_object_->SetAttachedControl(this); + window_host_ = std::make_unique(this); +} + +Window::~Window() {} + +std::u16string_view Window::GetControlType() const { return control_type; } + +render::RenderObject* Window::GetRenderObject() const { + return render_object_.get(); +} + +void Window::OnAddChild(Control* child, Index position) { + render_object_->AddChild(child->GetRenderObject(), position); +} + +void Window::OnRemoveChild(Control* child, Index position) { + CRU_UNUSED(child); + render_object_->RemoveChild(position); +} +} // namespace cru::ui::controls diff --git a/src/ui/events/UiEvent.cpp b/src/ui/events/UiEvent.cpp new file mode 100644 index 00000000..b35f15a7 --- /dev/null +++ b/src/ui/events/UiEvent.cpp @@ -0,0 +1,10 @@ +#include "cru/ui/events/UiEvent.hpp" + +#include "cru/ui/render/RenderObject.hpp" + +namespace cru::ui::event { +Point MouseEventArgs::GetPointToContent( + render::RenderObject* render_object) const { + return render_object->FromRootToContent(GetPoint()); +} +} // namespace cru::ui::event diff --git a/src/ui/helper/ClickDetector.cpp b/src/ui/helper/ClickDetector.cpp new file mode 100644 index 00000000..4059f890 --- /dev/null +++ b/src/ui/helper/ClickDetector.cpp @@ -0,0 +1,131 @@ +#include "cru/ui/helper/ClickDetector.hpp" + +#include "cru/common/Logger.hpp" + +#include + +namespace cru::ui::helper { +ClickDetector::ClickDetector(controls::Control* control) { + Expects(control); + control_ = control; + + event_rovoker_guards_.push_back( + EventRevokerGuard(control->MouseEnterEvent()->Direct()->AddHandler( + [this](event::MouseEventArgs&) { + if (this->enable_) { + if (this->state_ == ClickState::PressInactive) { + if ((this->button_ & this->trigger_button_)) { + this->SetState(ClickState::Press); + } + } else { + this->SetState(ClickState::Hover); + } + } + }))); + + event_rovoker_guards_.push_back( + EventRevokerGuard(control->MouseLeaveEvent()->Direct()->AddHandler( + [this](event::MouseEventArgs&) { + if (this->enable_) { + if (this->state_ == ClickState::Press) { + if ((this->button_ & this->trigger_button_)) { + this->SetState(ClickState::PressInactive); + } + } else { + this->SetState(ClickState::None); + } + } + }))); + + event_rovoker_guards_.push_back( + EventRevokerGuard(control->MouseDownEvent()->Direct()->AddHandler( + [this](event::MouseButtonEventArgs& args) { + const auto button = args.GetButton(); + if (this->enable_ && (button & this->trigger_button_) && + this->state_ == ClickState::Hover) { + if (!this->control_->CaptureMouse()) { + log::TagDebug(log_tag, + u"Failed to capture mouse when begin click."); + return; + } + this->down_point_ = args.GetPoint(); + this->button_ = button; + this->SetState(ClickState::Press); + } + }))); + + event_rovoker_guards_.push_back( + EventRevokerGuard(control->MouseUpEvent()->Direct()->AddHandler( + [this](event::MouseButtonEventArgs& args) { + const auto button = args.GetButton(); + if (this->enable_ && (button & this->trigger_button_) && + button == button_) { + if (this->state_ == ClickState::Press) { + this->SetState(ClickState::Hover); + this->event_.Raise(ClickEventArgs{this->control_, + this->down_point_, + args.GetPoint(), button}); + this->control_->ReleaseMouse(); + } else if (this->state_ == ClickState::PressInactive) { + this->SetState(ClickState::None); + this->control_->ReleaseMouse(); + } + } + }))); +} // namespace cru::ui + +void ClickDetector::SetEnabled(bool enable) { + if (enable == enable_) { + return; + } + + enable_ = enable; + if (enable) { + SetState(control_->IsMouseOver() ? ClickState::Hover : ClickState::None); + } else { + if (state_ == ClickState::Press || state_ == ClickState::PressInactive) { + SetState(ClickState::None); + control_->ReleaseMouse(); + } else if (state_ == ClickState::Hover) { + SetState(ClickState::None); + } + } +} + +void ClickDetector::SetTriggerButton(MouseButton trigger_button) { + if (trigger_button == trigger_button_) { + return; + } + + trigger_button_ = trigger_button; + if ((state_ == ClickState::Press || state_ == ClickState::PressInactive) && + !(button_ & trigger_button)) { + SetState(control_->IsMouseOver() ? ClickState::Hover : ClickState::None); + control_->ReleaseMouse(); + } +} + +void ClickDetector::SetState(ClickState state) { +#ifdef CRU_DEBUG + auto to_string = [](ClickState state) -> std::u16string_view { + switch (state) { + case ClickState::None: + return u"None"; + case ClickState::Hover: + return u"Hover"; + case ClickState::Press: + return u"Press"; + case ClickState::PressInactive: + return u"PressInvactive"; + default: + UnreachableCode(); + } + }; + log::TagDebug(log_tag, u"Click state changed, new state: {}.", + to_string(state)); +#endif + + state_ = state; + state_change_event_.Raise(state); +} +} // namespace cru::ui::helper diff --git a/src/ui/helper/ShortcutHub.cpp b/src/ui/helper/ShortcutHub.cpp new file mode 100644 index 00000000..823072f2 --- /dev/null +++ b/src/ui/helper/ShortcutHub.cpp @@ -0,0 +1,120 @@ +#include "cru/ui/helper/ShortcutHub.hpp" + +#include "cru/common/Logger.hpp" +#include "cru/ui/DebugFlags.hpp" +#include "cru/ui/controls/Control.hpp" + +#include +#include +#include +#include + +namespace cru::ui::helper { +int ShortcutHub::RegisterShortcut(Shortcut shortcut) { + const int id = current_id_++; + map_[shortcut.key_bind].push_back({id, std::move(shortcut.name), + shortcut.key_bind, + std::move(shortcut.handler)}); + return id; +} + +void ShortcutHub::UnregisterShortcut(int id) { + if (id <= 0) return; + for (auto& pair : map_) { + auto& list = pair.second; + auto result = + std::find_if(list.cbegin(), list.cend(), + [id](const ShortcutInfo& info) { return info.id == id; }); + if (result != list.cend()) { + list.erase(result); + } + } +} + +std::vector ShortcutHub::GetAllShortcuts() const { + std::vector result; + + for (const auto& pair : map_) { + std::copy(pair.second.cbegin(), pair.second.cend(), + std::back_inserter(result)); + } + + return result; +} + +std::optional ShortcutHub::GetShortcut(int id) const { + for (auto& pair : map_) { + auto& list = pair.second; + auto result = + std::find_if(list.cbegin(), list.cend(), + [id](const ShortcutInfo& info) { return info.id == id; }); + if (result != list.cend()) { + return *result; + } + } + return std::nullopt; +} + +const std::vector& ShortcutHub::GetShortcutByKeyBind( + const ShortcutKeyBind& key_bind) const { + auto result = map_.find(key_bind); + if (result != map_.cend()) return result->second; + return empty_list_; +} + +void ShortcutHub::Install(controls::Control* control) { + if (!event_guard_.IsEmpty()) { + log::Error(u"Shortcut hub is already installed. Failed to install."); + return; + } + + event_guard_ += control->KeyDownEvent()->Bubble()->AddHandler( + std::bind(&ShortcutHub::OnKeyDown, this, std::placeholders::_1)); +} + +void ShortcutHub::Uninstall() { + if (event_guard_.IsEmpty()) { + log::Warn(u"Shortcut hub is not installed. Failed to uninstall."); + return; + } + + event_guard_.Clear(); +} + +void ShortcutHub::OnKeyDown(event::KeyEventArgs& event) { + ShortcutKeyBind key_bind(event.GetKeyCode(), event.GetKeyModifier()); + const auto& shortcut_list = this->GetShortcutByKeyBind(key_bind); + + if constexpr (debug_flags::shortcut) { + if (shortcut_list.empty()) { + log::Debug(u"No shortcut for key bind {}.", key_bind.ToString()); + } + log::Debug(u"Begin to handle shortcut for key bind {}.", + key_bind.ToString()); + } + + for (const auto& shortcut : shortcut_list) { + auto is_handled = shortcut.handler(); + if (is_handled) { + if constexpr (debug_flags::shortcut) { + log::Debug(u"Handle {} handled it.", shortcut.name); + } + + event.SetHandled(); + + break; + } else { + if constexpr (debug_flags::shortcut) { + log::Debug(u"Handle {} disdn't handle it.", shortcut.name); + } + } + } + + if constexpr (debug_flags::shortcut) { + if (!shortcut_list.empty()) { + log::Debug(u"End handling shortcut for key bind {}.", + key_bind.ToString()); + } + } +} +} // namespace cru::ui::helper diff --git a/src/ui/host/RoutedEventDispatch.hpp b/src/ui/host/RoutedEventDispatch.hpp index de94a598..52507fc7 100644 --- a/src/ui/host/RoutedEventDispatch.hpp +++ b/src/ui/host/RoutedEventDispatch.hpp @@ -1,8 +1,7 @@ #pragma once -#include "cru/ui/Control.hpp" - #include "cru/common/Logger.hpp" #include "cru/ui/DebugFlags.hpp" +#include "cru/ui/controls/Control.hpp" #include @@ -21,10 +20,11 @@ namespace cru::ui { // "original_sender", which is unchanged. And "args" will be perfectly forwarded // as the rest arguments. template -void DispatchEvent(const std::u16string_view& event_name, - Control* const original_sender, - event::RoutedEvent* (Control::*event_ptr)(), - Control* const last_receiver, Args&&... args) { +void DispatchEvent( + const std::u16string_view& event_name, + controls::Control* const original_sender, + event::RoutedEvent* (controls::Control::*event_ptr)(), + controls::Control* const last_receiver, Args&&... args) { CRU_UNUSED(event_name) if (original_sender == last_receiver) { @@ -36,7 +36,7 @@ void DispatchEvent(const std::u16string_view& event_name, return; } - std::vector receive_list; + std::vector receive_list; auto parent = original_sender; while (parent != last_receiver) { diff --git a/src/ui/host/WindowHost.cpp b/src/ui/host/WindowHost.cpp index e04fdf31..1702c4ed 100644 --- a/src/ui/host/WindowHost.cpp +++ b/src/ui/host/WindowHost.cpp @@ -8,7 +8,7 @@ #include "cru/platform/gui/UiApplication.hpp" #include "cru/platform/gui/Window.hpp" #include "cru/ui/DebugFlags.hpp" -#include "cru/ui/Window.hpp" +#include "cru/ui/controls/Window.hpp" #include "cru/ui/host/LayoutPaintCycler.hpp" #include "cru/ui/render/MeasureRequirement.hpp" #include "cru/ui/render/RenderObject.hpp" @@ -43,7 +43,7 @@ CRU_DEFINE_EVENT_NAME(KeyUp) } // namespace event_names namespace { -bool IsAncestor(Control* control, Control* ancestor) { +bool IsAncestor(controls::Control* control, controls::Control* ancestor) { while (control != nullptr) { if (control == ancestor) return true; control = control->GetParent(); @@ -52,8 +52,8 @@ bool IsAncestor(Control* control, Control* ancestor) { } // Ancestor at last. -std::vector GetAncestorList(Control* control) { - std::vector l; +std::vector GetAncestorList(controls::Control* control) { + std::vector l; while (control != nullptr) { l.push_back(control); control = control->GetParent(); @@ -61,7 +61,8 @@ std::vector GetAncestorList(Control* control) { return l; } -Control* FindLowestCommonAncestor(Control* left, Control* right) { +controls::Control* FindLowestCommonAncestor(controls::Control* left, + controls::Control* right) { if (left == nullptr || right == nullptr) return nullptr; auto&& left_list = GetAncestorList(left); @@ -102,13 +103,13 @@ inline void BindNativeEvent( } } // namespace -WindowHost::WindowHost(Control* root_control) +WindowHost::WindowHost(controls::Control* root_control) : root_control_(root_control), focus_control_(root_control) { const auto ui_application = IUiApplication::GetInstance(); auto native_window = ui_application->CreateWindow(nullptr); native_window_ = native_window; - root_control_->TraverseDescendants([this](Control* control) { + root_control_->TraverseDescendants([this](controls::Control* control) { control->window_host_ = this; control->OnAttachToHost(this); }); @@ -195,9 +196,9 @@ void WindowHost::Repaint() { painter->EndDraw(); } -Control* WindowHost::GetFocusControl() { return focus_control_; } +controls::Control* WindowHost::GetFocusControl() { return focus_control_; } -void WindowHost::SetFocusControl(Control* control) { +void WindowHost::SetFocusControl(controls::Control* control) { if (focus_control_ == control) return; if (control == nullptr) control = root_control_; @@ -206,13 +207,13 @@ void WindowHost::SetFocusControl(Control* control) { focus_control_ = control; DispatchEvent(event_names::LoseFocus, old_focus_control, - &Control::LoseFocusEvent, nullptr, false); + &controls::Control::LoseFocusEvent, nullptr, false); - DispatchEvent(event_names::GainFocus, control, &Control::GainFocusEvent, - nullptr, false); + DispatchEvent(event_names::GainFocus, control, + &controls::Control::GainFocusEvent, nullptr, false); } -bool WindowHost::CaptureMouseFor(Control* control) { +bool WindowHost::CaptureMouseFor(controls::Control* control) { if (!native_window_) return false; if (control == mouse_captured_control_) return true; @@ -240,7 +241,7 @@ bool WindowHost::CaptureMouseFor(Control* control) { return true; } -Control* WindowHost::GetMouseCaptureControl() { +controls::Control* WindowHost::GetMouseCaptureControl() { return mouse_captured_control_; } @@ -275,9 +276,9 @@ void WindowHost::OnNativeFocus(INativeWindow* window, focus == platform::gui::FocusChangeType::Gain ? DispatchEvent(event_names::GainFocus, focus_control_, - &Control::GainFocusEvent, nullptr, true) + &controls::Control::GainFocusEvent, nullptr, true) : DispatchEvent(event_names::LoseFocus, focus_control_, - &Control::LoseFocusEvent, nullptr, true); + &controls::Control::LoseFocusEvent, nullptr, true); } void WindowHost::OnNativeMouseEnterLeave( @@ -286,7 +287,7 @@ void WindowHost::OnNativeMouseEnterLeave( if (type == platform::gui::MouseEnterLeaveType::Leave) { DispatchEvent(event_names::MouseLeave, mouse_hover_control_, - &Control::MouseLeaveEvent, nullptr); + &controls::Control::MouseLeaveEvent, nullptr); mouse_hover_control_ = nullptr; } } @@ -306,13 +307,14 @@ void WindowHost::OnNativeMouseMove(INativeWindow* window, const Point& point) { mouse_captured_control_); bool a = IsAncestor(o, n); if (a) { - DispatchEvent(event_names::MouseLeave, o, &Control::MouseLeaveEvent, n); + DispatchEvent(event_names::MouseLeave, o, + &controls::Control::MouseLeaveEvent, n); } else { - DispatchEvent(event_names::MouseEnter, n, &Control::MouseEnterEvent, o, - point); + DispatchEvent(event_names::MouseEnter, n, + &controls::Control::MouseEnterEvent, o, point); } DispatchEvent(event_names::MouseMove, mouse_captured_control_, - &Control::MouseMoveEvent, nullptr, point); + &controls::Control::MouseMoveEvent, nullptr, point); UpdateCursor(); return; } @@ -320,7 +322,7 @@ void WindowHost::OnNativeMouseMove(INativeWindow* window, const Point& point) { DispatchMouseHoverControlChangeEvent( old_mouse_hover_control, new_mouse_hover_control, point, false, false); DispatchEvent(event_names::MouseMove, new_mouse_hover_control, - &Control::MouseMoveEvent, nullptr, point); + &controls::Control::MouseMoveEvent, nullptr, point); UpdateCursor(); } @@ -329,10 +331,11 @@ void WindowHost::OnNativeMouseDown( const platform::gui::NativeMouseButtonEventArgs& args) { CRU_UNUSED(window) - Control* control = + controls::Control* control = mouse_captured_control_ ? mouse_captured_control_ : HitTest(args.point); - DispatchEvent(event_names::MouseDown, control, &Control::MouseDownEvent, - nullptr, args.point, args.button, args.modifier); + DispatchEvent(event_names::MouseDown, control, + &controls::Control::MouseDownEvent, nullptr, args.point, + args.button, args.modifier); } void WindowHost::OnNativeMouseUp( @@ -340,44 +343,44 @@ void WindowHost::OnNativeMouseUp( const platform::gui::NativeMouseButtonEventArgs& args) { CRU_UNUSED(window) - Control* control = + controls::Control* control = mouse_captured_control_ ? mouse_captured_control_ : HitTest(args.point); - DispatchEvent(event_names::MouseUp, control, &Control::MouseUpEvent, nullptr, - args.point, args.button, args.modifier); + DispatchEvent(event_names::MouseUp, control, &controls::Control::MouseUpEvent, + nullptr, args.point, args.button, args.modifier); } void WindowHost::OnNativeKeyDown( INativeWindow* window, const platform::gui::NativeKeyEventArgs& args) { CRU_UNUSED(window) - DispatchEvent(event_names::KeyDown, focus_control_, &Control::KeyDownEvent, - nullptr, args.key, args.modifier); + DispatchEvent(event_names::KeyDown, focus_control_, + &controls::Control::KeyDownEvent, nullptr, args.key, + args.modifier); } void WindowHost::OnNativeKeyUp(INativeWindow* window, const platform::gui::NativeKeyEventArgs& args) { CRU_UNUSED(window) - DispatchEvent(event_names::KeyUp, focus_control_, &Control::KeyUpEvent, - nullptr, args.key, args.modifier); + DispatchEvent(event_names::KeyUp, focus_control_, + &controls::Control::KeyUpEvent, nullptr, args.key, + args.modifier); } -void WindowHost::DispatchMouseHoverControlChangeEvent(Control* old_control, - Control* new_control, - const Point& point, - bool no_leave, - bool no_enter) { +void WindowHost::DispatchMouseHoverControlChangeEvent( + controls::Control* old_control, controls::Control* new_control, + const Point& point, bool no_leave, bool no_enter) { if (new_control != old_control) // if the mouse-hover-on control changed { const auto lowest_common_ancestor = FindLowestCommonAncestor(old_control, new_control); if (!no_leave && old_control != nullptr) DispatchEvent(event_names::MouseLeave, old_control, - &Control::MouseLeaveEvent, + &controls::Control::MouseLeaveEvent, lowest_common_ancestor); // dispatch mouse leave event. if (!no_enter && new_control != nullptr) { DispatchEvent(event_names::MouseEnter, new_control, - &Control::MouseEnterEvent, lowest_common_ancestor, + &controls::Control::MouseEnterEvent, lowest_common_ancestor, point); // dispatch mouse enter event. } } @@ -391,7 +394,7 @@ void WindowHost::UpdateCursor() { } } -Control* WindowHost::HitTest(const Point& point) { +controls::Control* WindowHost::HitTest(const Point& point) { const auto render_object = root_render_object_->HitTest(point); if (render_object) { const auto control = render_object->GetAttachedControl(); diff --git a/src/win/gui/UiApplication.cpp b/src/win/gui/UiApplication.cpp index 5041a6c0..f4541dd0 100644 --- a/src/win/gui/UiApplication.cpp +++ b/src/win/gui/UiApplication.cpp @@ -36,8 +36,8 @@ WinUiApplication::WinUiApplication() { log::Logger::GetInstance()->AddSource( std::make_unique<::cru::platform::win::WinStdOutLoggerSource>()); - graph_factory_ = - std::make_unique(); + graph_factory_ = std::make_unique< + cru::platform::graphics::win::direct::DirectGraphFactory>(); god_window_ = std::make_unique(this); timer_manager_ = std::make_unique(god_window_.get()); @@ -99,13 +99,17 @@ std::vector WinUiApplication::GetAllWindow() { return result; } -INativeWindow* WinUiApplication::CreateWindow(INativeWindow* parent) { +INativeWindow* WinUiApplication::CreateWindow(INativeWindow* parent, + CreateWindowFlag flag) { WinNativeWindow* p = nullptr; if (parent != nullptr) { p = CheckPlatform(parent, GetPlatformId()); } return new WinNativeWindow(this, window_manager_->GetGeneralWindowClass(), - WS_OVERLAPPEDWINDOW, p); + flag & CreateWindowFlags::NoCaptionAndBorder + ? WS_POPUP + : WS_OVERLAPPEDWINDOW, + p); } cru::platform::graphics::IGraphFactory* WinUiApplication::GetGraphFactory() { -- cgit v1.2.3 From 349b26d350d46fd6c48c6895ee9d8ef81add1315 Mon Sep 17 00:00:00 2001 From: crupest Date: Mon, 9 Nov 2020 16:18:20 +0800 Subject: ... --- include/cru/ui/controls/LayoutControl.hpp | 18 +++++++++++++++++- include/cru/ui/controls/Popup.hpp | 23 +++++++++++++++++++++++ src/ui/CMakeLists.txt | 2 ++ src/ui/controls/LayoutControl.cpp | 17 ++++++++++++++++- src/ui/controls/Popup.cpp | 20 ++++++++++++++++++++ 5 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 include/cru/ui/controls/Popup.hpp create mode 100644 src/ui/controls/Popup.cpp (limited to 'include/cru/ui/controls') diff --git a/include/cru/ui/controls/LayoutControl.hpp b/include/cru/ui/controls/LayoutControl.hpp index cbdb8aa2..106dd94d 100644 --- a/include/cru/ui/controls/LayoutControl.hpp +++ b/include/cru/ui/controls/LayoutControl.hpp @@ -5,6 +5,8 @@ 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; @@ -15,5 +17,19 @@ class LayoutControl : public Control { 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 +} // namespace cru::ui::controls diff --git a/include/cru/ui/controls/Popup.hpp b/include/cru/ui/controls/Popup.hpp new file mode 100644 index 00000000..f17cd1b2 --- /dev/null +++ b/include/cru/ui/controls/Popup.hpp @@ -0,0 +1,23 @@ +#pragma once +#include "LayoutControl.hpp" + +#include + +namespace cru::ui::controls { +class Popup : public LayoutControl { + public: + explicit Popup(Control* attached_control = nullptr); + + CRU_DELETE_COPY(Popup) + CRU_DELETE_MOVE(Popup) + + ~Popup() override; + + private: + std::unique_ptr window_host_; + + std::unique_ptr render_object_; + + Control* attached_control_; +}; +} // namespace cru::ui::controls diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt index 2f0eb10d..15ad1258 100644 --- a/src/ui/CMakeLists.txt +++ b/src/ui/CMakeLists.txt @@ -13,6 +13,7 @@ add_library(cru_ui STATIC controls/FlexLayout.cpp controls/LayoutControl.cpp controls/NoChildControl.cpp + controls/Popup.cpp controls/StackLayout.cpp controls/TextBlock.cpp controls/TextBox.cpp @@ -44,6 +45,7 @@ target_sources(cru_ui PUBLIC ${CRU_UI_INCLUDE_DIR}/controls/FlexLayout.hpp ${CRU_UI_INCLUDE_DIR}/controls/LayoutControl.hpp ${CRU_UI_INCLUDE_DIR}/controls/NoChildControl.hpp + ${CRU_UI_INCLUDE_DIR}/controls/Popup.hpp ${CRU_UI_INCLUDE_DIR}/controls/StackLayout.hpp ${CRU_UI_INCLUDE_DIR}/controls/TextBox.hpp ${CRU_UI_INCLUDE_DIR}/controls/TextBlock.hpp diff --git a/src/ui/controls/LayoutControl.cpp b/src/ui/controls/LayoutControl.cpp index 85417beb..5954853e 100644 --- a/src/ui/controls/LayoutControl.cpp +++ b/src/ui/controls/LayoutControl.cpp @@ -1,3 +1,18 @@ #include "cru/ui/controls/LayoutControl.hpp" -namespace cru::ui::controls {} +#include "cru/ui/render/RenderObject.hpp" + +namespace cru::ui::controls { +void LayoutControl::OnAddChild(Control* child, Index position) { + if (container_render_object_ != nullptr) { + container_render_object_->AddChild(child->GetRenderObject(), position); + } +} + +void LayoutControl::OnRemoveChild(Control* child, Index position) { + CRU_UNUSED(child) + if (container_render_object_ != nullptr) { + container_render_object_->RemoveChild(position); + } +} +} // namespace cru::ui::controls diff --git a/src/ui/controls/Popup.cpp b/src/ui/controls/Popup.cpp new file mode 100644 index 00000000..f51f2b3b --- /dev/null +++ b/src/ui/controls/Popup.cpp @@ -0,0 +1,20 @@ +#include "cru/ui/controls/Popup.hpp" + +#include "cru/platform/gui/UiApplication.hpp" +#include "cru/ui/host/WindowHost.hpp" +#include "cru/ui/render/StackLayoutRenderObject.hpp" + +#include + +namespace cru::ui::controls { +Popup::Popup(Control* attached_control) : attached_control_(attached_control) { + render_object_ = std::make_unique(); + SetContainerRenderObject(render_object_.get()); + + window_host_ = std::make_unique( + this, host::CreateWindowParams( + nullptr, platform::gui::CreateWindowFlags::NoCaptionAndBorder)); +} + +Popup::~Popup() = default; +} // namespace cru::ui::controls -- cgit v1.2.3 From ddc6d6478f849ef10b832bc8b1d8ab7fe9454601 Mon Sep 17 00:00:00 2001 From: crupest Date: Mon, 9 Nov 2020 18:28:33 +0800 Subject: ... --- demos/main/main.cpp | 2 +- include/cru/ui/controls/Window.hpp | 8 ++++---- include/cru/ui/host/WindowHost.hpp | 13 +++++++++++-- src/ui/controls/Popup.cpp | 5 ++--- src/ui/controls/Window.cpp | 15 ++++++++------- src/ui/host/WindowHost.cpp | 31 ++++++++++++++++++++++--------- 6 files changed, 48 insertions(+), 26 deletions(-) (limited to 'include/cru/ui/controls') diff --git a/demos/main/main.cpp b/demos/main/main.cpp index dd3e31e9..2d5939f1 100644 --- a/demos/main/main.cpp +++ b/demos/main/main.cpp @@ -45,7 +45,7 @@ int main() { const auto text_box = TextBox::Create(); flex_layout->AddChild(text_box, 2); - window->GetWindowHost()->GetNativeWindow()->SetVisible(true); + window->Show(); return application->Run(); } diff --git a/include/cru/ui/controls/Window.hpp b/include/cru/ui/controls/Window.hpp index 616e2ee7..996bc75e 100644 --- a/include/cru/ui/controls/Window.hpp +++ b/include/cru/ui/controls/Window.hpp @@ -24,13 +24,13 @@ class Window final : public LayoutControl { render::RenderObject* GetRenderObject() const override; - protected: - void OnAddChild(Control* child, Index position) override; - void OnRemoveChild(Control* child, Index position) override; + // If create is false and native window is not create, it will not be created + // and shown. + void Show(bool create = true); private: std::unique_ptr window_host_; std::unique_ptr render_object_; }; -} // namespace cru::ui +} // namespace cru::ui::controls diff --git a/include/cru/ui/host/WindowHost.hpp b/include/cru/ui/host/WindowHost.hpp index 6d338df1..9fc24eb2 100644 --- a/include/cru/ui/host/WindowHost.hpp +++ b/include/cru/ui/host/WindowHost.hpp @@ -28,8 +28,7 @@ class WindowHost : public Object { CRU_DEFINE_CLASS_LOG_TAG(u"cru::ui::host::WindowHost") public: - WindowHost(controls::Control* root_control, - CreateWindowParams create_window_params = {}); + WindowHost(controls::Control* root_control); CRU_DELETE_COPY(WindowHost) CRU_DELETE_MOVE(WindowHost) @@ -39,6 +38,10 @@ class WindowHost : public Object { public: platform::gui::INativeWindow* GetNativeWindow() { return native_window_; } + // Do nothing if native window is already created. + gsl::not_null 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. @@ -102,6 +105,10 @@ class WindowHost : public Object { void UpdateCursor(); + IEvent* NativeWindowChangeEvent() { + return &native_window_change_event_; + } + private: //*************** region: native messages *************** void OnNativeDestroy(platform::gui::INativeWindow* window, std::nullptr_t); @@ -152,5 +159,7 @@ class WindowHost : public Object { controls::Control* mouse_captured_control_ = nullptr; bool layout_prefer_to_fill_window_ = true; + + Event native_window_change_event_; }; } // namespace cru::ui::host diff --git a/src/ui/controls/Popup.cpp b/src/ui/controls/Popup.cpp index f51f2b3b..982aee38 100644 --- a/src/ui/controls/Popup.cpp +++ b/src/ui/controls/Popup.cpp @@ -9,11 +9,10 @@ namespace cru::ui::controls { Popup::Popup(Control* attached_control) : attached_control_(attached_control) { render_object_ = std::make_unique(); + render_object_->SetAttachedControl(this); SetContainerRenderObject(render_object_.get()); - window_host_ = std::make_unique( - this, host::CreateWindowParams( - nullptr, platform::gui::CreateWindowFlags::NoCaptionAndBorder)); + window_host_ = std::make_unique(this); } Popup::~Popup() = default; diff --git a/src/ui/controls/Window.cpp b/src/ui/controls/Window.cpp index 7ce40dfe..b302cb62 100644 --- a/src/ui/controls/Window.cpp +++ b/src/ui/controls/Window.cpp @@ -1,6 +1,7 @@ #include "cru/ui/controls/Window.hpp" #include "cru/common/Base.hpp" +#include "cru/platform/gui/Base.hpp" #include "cru/ui/host/WindowHost.hpp" #include "cru/ui/render/Base.hpp" #include "cru/ui/render/StackLayoutRenderObject.hpp" @@ -10,6 +11,7 @@ Window* Window::CreateOverlapped() { return new Window(); } Window::Window() : render_object_(new render::StackLayoutRenderObject()) { render_object_->SetAttachedControl(this); + SetContainerRenderObject(render_object_.get()); window_host_ = std::make_unique(this); } @@ -21,12 +23,11 @@ render::RenderObject* Window::GetRenderObject() const { return render_object_.get(); } -void Window::OnAddChild(Control* child, Index position) { - render_object_->AddChild(child->GetRenderObject(), position); -} - -void Window::OnRemoveChild(Control* child, Index position) { - CRU_UNUSED(child); - render_object_->RemoveChild(position); +void Window::Show(bool create) { + platform::gui::INativeWindow* native_window = + create ? window_host_->CreateNativeWindow().get() + : window_host_->GetNativeWindow(); + if (!native_window) return; + native_window->SetVisible(true); } } // namespace cru::ui::controls diff --git a/src/ui/host/WindowHost.cpp b/src/ui/host/WindowHost.cpp index 95de51c2..4bd981c2 100644 --- a/src/ui/host/WindowHost.cpp +++ b/src/ui/host/WindowHost.cpp @@ -103,14 +103,8 @@ inline void BindNativeEvent( } } // namespace -WindowHost::WindowHost(controls::Control* root_control, - CreateWindowParams create_window_params) +WindowHost::WindowHost(controls::Control* root_control) : root_control_(root_control), focus_control_(root_control) { - const auto ui_application = IUiApplication::GetInstance(); - auto native_window = ui_application->CreateWindow(create_window_params.parent, - create_window_params.flag); - native_window_ = native_window; - root_control_->TraverseDescendants([this](controls::Control* control) { control->window_host_ = this; control->OnAttachToHost(this); @@ -120,6 +114,20 @@ WindowHost::WindowHost(controls::Control* root_control, root_render_object_->SetWindowHostRecursive(this); this->layout_paint_cycler_ = std::make_unique(this); +} + +WindowHost::~WindowHost() {} + +gsl::not_null WindowHost::CreateNativeWindow( + CreateWindowParams create_window_params) { + if (native_window_ != nullptr) return native_window_; + + const auto ui_application = IUiApplication::GetInstance(); + + auto native_window = ui_application->CreateWindow(create_window_params.parent, + create_window_params.flag); + + native_window_ = native_window; BindNativeEvent(this, native_window, native_window->DestroyEvent(), &WindowHost::OnNativeDestroy, event_revoker_guards_); @@ -141,9 +149,11 @@ WindowHost::WindowHost(controls::Control* root_control, &WindowHost::OnNativeKeyDown, event_revoker_guards_); BindNativeEvent(this, native_window, native_window->KeyUpEvent(), &WindowHost::OnNativeKeyUp, event_revoker_guards_); -} -WindowHost::~WindowHost() {} + native_window_change_event_.Raise(native_window); + + return native_window_; +} void WindowHost::InvalidatePaint() { layout_paint_cycler_->InvalidatePaint(); } @@ -254,6 +264,9 @@ void WindowHost::RunAfterLayoutStable(std::function action) { void WindowHost::OnNativeDestroy(INativeWindow* window, std::nullptr_t) { CRU_UNUSED(window) this->native_window_ = nullptr; + event_revoker_guards_.clear(); + + native_window_change_event_.Raise(nullptr); } void WindowHost::OnNativePaint(INativeWindow* window, std::nullptr_t) { -- cgit v1.2.3 From 68fc33443981fcd499dfe263c228787e213ae943 Mon Sep 17 00:00:00 2001 From: crupest Date: Mon, 9 Nov 2020 20:06:04 +0800 Subject: ... --- demos/main/main.cpp | 2 +- include/cru/ui/controls/Popup.hpp | 17 +++++++------ include/cru/ui/controls/RootControl.hpp | 40 +++++++++++++++++++++++++++++ include/cru/ui/controls/Window.hpp | 34 +++++++++++-------------- src/ui/CMakeLists.txt | 2 ++ src/ui/controls/Popup.cpp | 15 ++++++----- src/ui/controls/RootControl.cpp | 45 +++++++++++++++++++++++++++++++++ src/ui/controls/Window.cpp | 27 +++++++------------- 8 files changed, 130 insertions(+), 52 deletions(-) create mode 100644 include/cru/ui/controls/RootControl.hpp create mode 100644 src/ui/controls/RootControl.cpp (limited to 'include/cru/ui/controls') diff --git a/demos/main/main.cpp b/demos/main/main.cpp index 2d5939f1..66354289 100644 --- a/demos/main/main.cpp +++ b/demos/main/main.cpp @@ -23,7 +23,7 @@ int main() { auto application = CreateUiApplication(); - const auto window = Window::CreateOverlapped(); + const auto window = Window::Create(); const auto flex_layout = FlexLayout::Create(); flex_layout->SetFlexDirection(cru::ui::FlexDirection::Vertical); diff --git a/include/cru/ui/controls/Popup.hpp b/include/cru/ui/controls/Popup.hpp index f17cd1b2..d76e1211 100644 --- a/include/cru/ui/controls/Popup.hpp +++ b/include/cru/ui/controls/Popup.hpp @@ -1,10 +1,13 @@ #pragma once -#include "LayoutControl.hpp" +#include "RootControl.hpp" + +#include "cru/ui/Base.hpp" +#include "cru/platform/gui/Base.hpp" #include namespace cru::ui::controls { -class Popup : public LayoutControl { +class Popup : public RootControl { public: explicit Popup(Control* attached_control = nullptr); @@ -13,11 +16,9 @@ class Popup : public LayoutControl { ~Popup() override; - private: - std::unique_ptr window_host_; - - std::unique_ptr render_object_; - - Control* attached_control_; + protected: + gsl::not_null CreateNativeWindow( + gsl::not_null 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..ff1b545a --- /dev/null +++ b/include/cru/ui/controls/RootControl.hpp @@ -0,0 +1,40 @@ +#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; + + // If create is false and native window is not create, it will not be created + // and shown. + void Show(bool create = true); + + protected: + virtual gsl::not_null CreateNativeWindow( + gsl::not_null host, + platform::gui::INativeWindow* parent) = 0; + + private: + platform::gui::INativeWindow* GetNativeWindow(bool create); + + private: + std::unique_ptr window_host_; + + std::unique_ptr render_object_; + + Control* attached_control_; +}; +} // namespace cru::ui::controls diff --git a/include/cru/ui/controls/Window.hpp b/include/cru/ui/controls/Window.hpp index 996bc75e..cca56b64 100644 --- a/include/cru/ui/controls/Window.hpp +++ b/include/cru/ui/controls/Window.hpp @@ -1,36 +1,32 @@ #pragma once -#include "LayoutControl.hpp" +#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 LayoutControl { +class Window final : public RootControl { public: static constexpr std::u16string_view control_type = u"Window"; public: - static Window* CreateOverlapped(); + static Window* Create(Control* attached_control = nullptr); private: - Window(); + explicit Window(Control* attached_control); public: - Window(const Window& other) = delete; - Window(Window&& other) = delete; - Window& operator=(const Window& other) = delete; - Window& operator=(Window&& other) = delete; + CRU_DELETE_COPY(Window) + CRU_DELETE_MOVE(Window) + ~Window() override; public: - std::u16string_view GetControlType() const final; - - render::RenderObject* GetRenderObject() const override; - - // If create is false and native window is not create, it will not be created - // and shown. - void Show(bool create = true); - - private: - std::unique_ptr window_host_; + std::u16string_view GetControlType() const final { return control_type; } - std::unique_ptr render_object_; + protected: + gsl::not_null CreateNativeWindow( + gsl::not_null host, + platform::gui::INativeWindow* parent) override; }; } // namespace cru::ui::controls diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt index 15ad1258..d9edf49e 100644 --- a/src/ui/CMakeLists.txt +++ b/src/ui/CMakeLists.txt @@ -14,6 +14,7 @@ add_library(cru_ui STATIC controls/LayoutControl.cpp controls/NoChildControl.cpp controls/Popup.cpp + controls/RootControl.cpp controls/StackLayout.cpp controls/TextBlock.cpp controls/TextBox.cpp @@ -46,6 +47,7 @@ target_sources(cru_ui PUBLIC ${CRU_UI_INCLUDE_DIR}/controls/LayoutControl.hpp ${CRU_UI_INCLUDE_DIR}/controls/NoChildControl.hpp ${CRU_UI_INCLUDE_DIR}/controls/Popup.hpp + ${CRU_UI_INCLUDE_DIR}/controls/RootControl.hpp ${CRU_UI_INCLUDE_DIR}/controls/StackLayout.hpp ${CRU_UI_INCLUDE_DIR}/controls/TextBox.hpp ${CRU_UI_INCLUDE_DIR}/controls/TextBlock.hpp diff --git a/src/ui/controls/Popup.cpp b/src/ui/controls/Popup.cpp index 982aee38..bc217bf5 100644 --- a/src/ui/controls/Popup.cpp +++ b/src/ui/controls/Popup.cpp @@ -1,19 +1,22 @@ #include "cru/ui/controls/Popup.hpp" #include "cru/platform/gui/UiApplication.hpp" +#include "cru/ui/controls/RootControl.hpp" #include "cru/ui/host/WindowHost.hpp" #include "cru/ui/render/StackLayoutRenderObject.hpp" #include namespace cru::ui::controls { -Popup::Popup(Control* attached_control) : attached_control_(attached_control) { - render_object_ = std::make_unique(); - render_object_->SetAttachedControl(this); - SetContainerRenderObject(render_object_.get()); +Popup::Popup(Control* attached_control) : RootControl(attached_control) {} - window_host_ = std::make_unique(this); +Popup::~Popup() = default; + +gsl::not_null Popup::CreateNativeWindow( + gsl::not_null host, + platform::gui::INativeWindow* parent) { + return host->CreateNativeWindow( + {parent, platform::gui::CreateWindowFlags::NoCaptionAndBorder}); } -Popup::~Popup() = default; } // namespace cru::ui::controls diff --git a/src/ui/controls/RootControl.cpp b/src/ui/controls/RootControl.cpp new file mode 100644 index 00000000..61d272f8 --- /dev/null +++ b/src/ui/controls/RootControl.cpp @@ -0,0 +1,45 @@ +#include "cru/ui/controls/RootControl.hpp" + +#include "cru/common/Base.hpp" +#include "cru/platform/gui/Base.hpp" +#include "cru/ui/host/WindowHost.hpp" +#include "cru/ui/render/Base.hpp" +#include "cru/ui/render/StackLayoutRenderObject.hpp" +#include "gsl/pointers" + +#include + +namespace cru::ui::controls { +RootControl::RootControl(Control* attached_control) + : attached_control_(attached_control) { + render_object_ = std::make_unique(); + render_object_->SetAttachedControl(this); + SetContainerRenderObject(render_object_.get()); + window_host_ = std::make_unique(this); +} + +RootControl::~RootControl() {} + +render::RenderObject* RootControl::GetRenderObject() const { + return render_object_.get(); +} + +void RootControl::Show(bool create) { + platform::gui::INativeWindow* native_window = GetNativeWindow(create); + if (!native_window) return; + native_window->SetVisible(true); +} + +platform::gui::INativeWindow* RootControl::GetNativeWindow(bool create) { + const auto host = GetWindowHost(); + platform::gui::INativeWindow* native_window = host->GetNativeWindow(); + if (!create) return native_window; + if (!native_window) { + native_window = this->CreateNativeWindow( + host, attached_control_ + ? attached_control_->GetWindowHost()->GetNativeWindow() + : nullptr); + } + return native_window; +} +} // namespace cru::ui::controls diff --git a/src/ui/controls/Window.cpp b/src/ui/controls/Window.cpp index b302cb62..ba66f42e 100644 --- a/src/ui/controls/Window.cpp +++ b/src/ui/controls/Window.cpp @@ -2,32 +2,23 @@ #include "cru/common/Base.hpp" #include "cru/platform/gui/Base.hpp" +#include "cru/ui/controls/RootControl.hpp" #include "cru/ui/host/WindowHost.hpp" #include "cru/ui/render/Base.hpp" #include "cru/ui/render/StackLayoutRenderObject.hpp" namespace cru::ui::controls { -Window* Window::CreateOverlapped() { return new Window(); } - -Window::Window() : render_object_(new render::StackLayoutRenderObject()) { - render_object_->SetAttachedControl(this); - SetContainerRenderObject(render_object_.get()); - window_host_ = std::make_unique(this); +Window* Window::Create(Control* attached_control) { + return new Window(attached_control); } -Window::~Window() {} - -std::u16string_view Window::GetControlType() const { return control_type; } +Window::Window(Control* attached_control) : RootControl(attached_control) {} -render::RenderObject* Window::GetRenderObject() const { - return render_object_.get(); -} +Window::~Window() {} -void Window::Show(bool create) { - platform::gui::INativeWindow* native_window = - create ? window_host_->CreateNativeWindow().get() - : window_host_->GetNativeWindow(); - if (!native_window) return; - native_window->SetVisible(true); +gsl::not_null Window::CreateNativeWindow( + gsl::not_null host, + platform::gui::INativeWindow* parent) { + return host->CreateNativeWindow({parent}); } } // namespace cru::ui::controls -- cgit v1.2.3 From 014aaf773fd95a7f0dd1a766023a21fb6b54a1a1 Mon Sep 17 00:00:00 2001 From: crupest Date: Mon, 9 Nov 2020 20:28:39 +0800 Subject: ... --- include/cru/ui/controls/RootControl.hpp | 7 +++++++ src/ui/controls/RootControl.cpp | 9 +++++++++ 2 files changed, 16 insertions(+) (limited to 'include/cru/ui/controls') diff --git a/include/cru/ui/controls/RootControl.hpp b/include/cru/ui/controls/RootControl.hpp index ff1b545a..d41f02d8 100644 --- a/include/cru/ui/controls/RootControl.hpp +++ b/include/cru/ui/controls/RootControl.hpp @@ -18,10 +18,17 @@ class RootControl : public LayoutControl { 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); + // If native window does not exist, nothing will be done. It will not save it + // and use it when creating window. So call this after ensuring window + // created. + void SetRect(const Rect& rect); + protected: virtual gsl::not_null CreateNativeWindow( gsl::not_null host, diff --git a/src/ui/controls/RootControl.cpp b/src/ui/controls/RootControl.cpp index 61d272f8..c6d9a577 100644 --- a/src/ui/controls/RootControl.cpp +++ b/src/ui/controls/RootControl.cpp @@ -24,6 +24,15 @@ render::RenderObject* RootControl::GetRenderObject() const { return render_object_.get(); } +void RootControl::EnsureWindowCreated() { this->GetNativeWindow(true); } + +void RootControl::SetRect(const Rect& rect) { + auto native_window = GetNativeWindow(false); + if (!native_window) return; + + native_window->SetWindowRect(rect); +} + void RootControl::Show(bool create) { platform::gui::INativeWindow* native_window = GetNativeWindow(create); if (!native_window) return; -- cgit v1.2.3 From bed29872d2a7befff9d4cb7313cb40060326a95e Mon Sep 17 00:00:00 2001 From: crupest Date: Mon, 9 Nov 2020 20:45:27 +0800 Subject: ... --- include/cru/ui/controls/RootControl.hpp | 3 --- include/cru/ui/host/WindowHost.hpp | 11 +++++++++++ src/ui/controls/RootControl.cpp | 5 +---- src/ui/host/WindowHost.cpp | 21 +++++++++++++++++++++ 4 files changed, 33 insertions(+), 7 deletions(-) (limited to 'include/cru/ui/controls') diff --git a/include/cru/ui/controls/RootControl.hpp b/include/cru/ui/controls/RootControl.hpp index d41f02d8..b3fa6101 100644 --- a/include/cru/ui/controls/RootControl.hpp +++ b/include/cru/ui/controls/RootControl.hpp @@ -24,9 +24,6 @@ class RootControl : public LayoutControl { // and shown. void Show(bool create = true); - // If native window does not exist, nothing will be done. It will not save it - // and use it when creating window. So call this after ensuring window - // created. void SetRect(const Rect& rect); protected: diff --git a/include/cru/ui/host/WindowHost.hpp b/include/cru/ui/host/WindowHost.hpp index 9fc24eb2..bd2f7c16 100644 --- a/include/cru/ui/host/WindowHost.hpp +++ b/include/cru/ui/host/WindowHost.hpp @@ -8,6 +8,7 @@ #include #include +#include namespace cru::ui::host { class LayoutPaintCycler; @@ -109,6 +110,14 @@ class WindowHost : public Object { 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); + + void SetWindowRect(const Rect& rect); + private: //*************** region: native messages *************** void OnNativeDestroy(platform::gui::INativeWindow* window, std::nullptr_t); @@ -161,5 +170,7 @@ class WindowHost : public Object { bool layout_prefer_to_fill_window_ = true; Event native_window_change_event_; + + std::optional saved_rect_; }; } // namespace cru::ui::host diff --git a/src/ui/controls/RootControl.cpp b/src/ui/controls/RootControl.cpp index c6d9a577..1ebcb6e7 100644 --- a/src/ui/controls/RootControl.cpp +++ b/src/ui/controls/RootControl.cpp @@ -27,10 +27,7 @@ render::RenderObject* RootControl::GetRenderObject() const { void RootControl::EnsureWindowCreated() { this->GetNativeWindow(true); } void RootControl::SetRect(const Rect& rect) { - auto native_window = GetNativeWindow(false); - if (!native_window) return; - - native_window->SetWindowRect(rect); + window_host_->SetWindowRect(rect); } void RootControl::Show(bool create) { diff --git a/src/ui/host/WindowHost.cpp b/src/ui/host/WindowHost.cpp index 1550361b..5e107733 100644 --- a/src/ui/host/WindowHost.cpp +++ b/src/ui/host/WindowHost.cpp @@ -150,6 +150,10 @@ gsl::not_null WindowHost::CreateNativeWindow( BindNativeEvent(this, native_window, native_window->KeyUpEvent(), &WindowHost::OnNativeKeyUp, event_revoker_guards_); + if (saved_rect_) { + native_window->SetWindowRect(saved_rect_.value()); + } + native_window_change_event_.Raise(native_window); return native_window_; @@ -263,8 +267,25 @@ void WindowHost::RunAfterLayoutStable(std::function action) { } } +Rect WindowHost::GetWindowRect() { + if (native_window_) return native_window_->GetWindowRect(); + return saved_rect_.value_or(Rect{}); +} + +void WindowHost::SetSavedWindowRect(std::optional rect) { + saved_rect_ = std::move(rect); +} + +void WindowHost::SetWindowRect(const Rect& rect) { + SetSavedWindowRect(rect); + if (native_window_) native_window_->SetWindowRect(rect); +} + void WindowHost::OnNativeDestroy(INativeWindow* window, std::nullptr_t) { CRU_UNUSED(window) + + saved_rect_ = this->native_window_->GetWindowRect(); + this->native_window_ = nullptr; event_revoker_guards_.clear(); -- cgit v1.2.3 From c5fe6fc72e9bca222e6d7b94fd47523089083fdd Mon Sep 17 00:00:00 2001 From: crupest Date: Mon, 9 Nov 2020 20:48:13 +0800 Subject: ... --- include/cru/ui/controls/RootControl.hpp | 1 + src/ui/controls/RootControl.cpp | 2 ++ 2 files changed, 3 insertions(+) (limited to 'include/cru/ui/controls') diff --git a/include/cru/ui/controls/RootControl.hpp b/include/cru/ui/controls/RootControl.hpp index b3fa6101..53e69e7c 100644 --- a/include/cru/ui/controls/RootControl.hpp +++ b/include/cru/ui/controls/RootControl.hpp @@ -24,6 +24,7 @@ class RootControl : public LayoutControl { // and shown. void Show(bool create = true); + Rect GetRect(); void SetRect(const Rect& rect); protected: diff --git a/src/ui/controls/RootControl.cpp b/src/ui/controls/RootControl.cpp index 1ebcb6e7..015703c3 100644 --- a/src/ui/controls/RootControl.cpp +++ b/src/ui/controls/RootControl.cpp @@ -26,6 +26,8 @@ render::RenderObject* RootControl::GetRenderObject() const { void RootControl::EnsureWindowCreated() { this->GetNativeWindow(true); } +Rect RootControl::GetRect() { return window_host_->GetWindowRect(); } + void RootControl::SetRect(const Rect& rect) { window_host_->SetWindowRect(rect); } -- cgit v1.2.3 From 460a45df8be4613053c6a097d9c699c70dbe1a2c Mon Sep 17 00:00:00 2001 From: crupest Date: Tue, 10 Nov 2020 15:03:54 +0800 Subject: ... --- include/cru/ui/controls/Button.hpp | 3 --- include/cru/ui/controls/Container.hpp | 3 --- include/cru/ui/controls/ContentControl.hpp | 12 ++++++++++++ include/cru/ui/controls/FlexLayout.hpp | 4 ---- include/cru/ui/controls/StackLayout.hpp | 4 ---- src/ui/controls/Button.cpp | 9 ++------- src/ui/controls/Container.cpp | 8 +++++--- src/ui/controls/ContentControl.cpp | 10 ++++++++-- src/ui/controls/FlexLayout.cpp | 11 +---------- src/ui/controls/StackLayout.cpp | 15 ++++----------- 10 files changed, 32 insertions(+), 47 deletions(-) (limited to 'include/cru/ui/controls') diff --git a/include/cru/ui/controls/Button.hpp b/include/cru/ui/controls/Button.hpp index e8285507..5619ec89 100644 --- a/include/cru/ui/controls/Button.hpp +++ b/include/cru/ui/controls/Button.hpp @@ -28,9 +28,6 @@ class Button : public ContentControl { const ButtonStyle& GetStyle() const { return style_; } void SetStyle(ButtonStyle style); - protected: - void OnChildChanged(Control* old_child, Control* new_child) override; - private: std::unique_ptr render_object_{}; diff --git a/include/cru/ui/controls/Container.hpp b/include/cru/ui/controls/Container.hpp index d9cb8aec..18958837 100644 --- a/include/cru/ui/controls/Container.hpp +++ b/include/cru/ui/controls/Container.hpp @@ -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_object_; }; diff --git a/include/cru/ui/controls/ContentControl.hpp b/include/cru/ui/controls/ContentControl.hpp index 47720a87..1bdaf7e4 100644 --- a/include/cru/ui/controls/ContentControl.hpp +++ b/include/cru/ui/controls/ContentControl.hpp @@ -1,6 +1,8 @@ #pragma once #include "Control.hpp" +#include "cru/ui/render/RenderObject.hpp" + namespace cru::ui::controls { class ContentControl : public Control { protected: @@ -19,8 +21,18 @@ class ContentControl : public Control { 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/controls/FlexLayout.hpp b/include/cru/ui/controls/FlexLayout.hpp index a6c6a40c..4f6abfdb 100644 --- a/include/cru/ui/controls/FlexLayout.hpp +++ b/include/cru/ui/controls/FlexLayout.hpp @@ -34,10 +34,6 @@ class FlexLayout : public LayoutControl { 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_object_; }; diff --git a/include/cru/ui/controls/StackLayout.hpp b/include/cru/ui/controls/StackLayout.hpp index 373b4681..aa9440c2 100644 --- a/include/cru/ui/controls/StackLayout.hpp +++ b/include/cru/ui/controls/StackLayout.hpp @@ -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_object_; }; diff --git a/src/ui/controls/Button.cpp b/src/ui/controls/Button.cpp index b7407ec2..39c4b961 100644 --- a/src/ui/controls/Button.cpp +++ b/src/ui/controls/Button.cpp @@ -31,6 +31,8 @@ Button::Button() : click_detector_(this) { render_object_ = std::make_unique(); render_object_->SetAttachedControl(this); + SetContainerRenderObject(render_object_.get()); + Set(render_object_.get(), style_.normal); render_object_->SetBorderEnabled(true); @@ -62,11 +64,4 @@ Button::~Button() = default; render::RenderObject* Button::GetRenderObject() const { return render_object_.get(); } - -void Button::OnChildChanged(Control* old_child, Control* new_child) { - if (old_child != nullptr) render_object_->RemoveChild(0); - if (new_child != nullptr) - render_object_->AddChild(new_child->GetRenderObject(), 0); -} - } // namespace cru::ui::controls diff --git a/src/ui/controls/Container.cpp b/src/ui/controls/Container.cpp index 8b15c566..30129f64 100644 --- a/src/ui/controls/Container.cpp +++ b/src/ui/controls/Container.cpp @@ -2,17 +2,19 @@ #include "cru/platform/graphics/Factory.hpp" #include "cru/ui/render/BorderRenderObject.hpp" +#include "cru/ui/render/RenderObject.hpp" namespace cru::ui::controls { Container::Container() { render_object_ = std::make_unique(); render_object_->SetBorderEnabled(false); + render_object_->SetAttachedControl(this); + SetContainerRenderObject(render_object_.get()); } Container::~Container() = default; -void Container::OnChildChanged(Control*, Control* new_child) { - render_object_->RemoveChild(0); - render_object_->AddChild(new_child->GetRenderObject(), 0); +render::RenderObject* Container::GetRenderObject() const { + return render_object_.get(); } } // namespace cru::ui::controls diff --git a/src/ui/controls/ContentControl.cpp b/src/ui/controls/ContentControl.cpp index 653882c0..8c6f0b00 100644 --- a/src/ui/controls/ContentControl.cpp +++ b/src/ui/controls/ContentControl.cpp @@ -19,7 +19,13 @@ void ContentControl::SetChild(Control* child) { } void ContentControl::OnChildChanged(Control* old_child, Control* new_child) { - CRU_UNUSED(old_child) - CRU_UNUSED(new_child) + if (container_render_object_) { + if (old_child) { + container_render_object_->RemoveChild(0); + } + if (new_child) { + container_render_object_->AddChild(new_child->GetRenderObject(), 0); + } + } } } // namespace cru::ui::controls diff --git a/src/ui/controls/FlexLayout.cpp b/src/ui/controls/FlexLayout.cpp index 05f6999f..e390241f 100644 --- a/src/ui/controls/FlexLayout.cpp +++ b/src/ui/controls/FlexLayout.cpp @@ -8,6 +8,7 @@ using render::FlexLayoutRenderObject; FlexLayout::FlexLayout() { render_object_.reset(new FlexLayoutRenderObject()); render_object_->SetAttachedControl(this); + SetContainerRenderObject(render_object_.get()); } FlexLayout::~FlexLayout() = default; @@ -68,14 +69,4 @@ void FlexLayout::SetItemCrossAlign(FlexCrossAlignment alignment) { if (alignment == GetItemCrossAlign()) return; render_object_->SetItemCrossAlign(alignment); } - -void FlexLayout::OnAddChild(Control* child, const Index position) { - render_object_->AddChild(child->GetRenderObject(), position); -} - -void FlexLayout::OnRemoveChild(Control* child, const Index position) { - CRU_UNUSED(child) - - render_object_->RemoveChild(position); -} } // namespace cru::ui::controls diff --git a/src/ui/controls/StackLayout.cpp b/src/ui/controls/StackLayout.cpp index ce500b79..89968571 100644 --- a/src/ui/controls/StackLayout.cpp +++ b/src/ui/controls/StackLayout.cpp @@ -1,12 +1,15 @@ #include "cru/ui/controls/StackLayout.hpp" +#include #include "cru/ui/render/StackLayoutRenderObject.hpp" namespace cru::ui::controls { using render::StackLayoutRenderObject; -StackLayout::StackLayout() : render_object_(new StackLayoutRenderObject()) { +StackLayout::StackLayout() { + render_object_ = std::make_unique(); render_object_->SetAttachedControl(this); + SetContainerRenderObject(render_object_.get()); } StackLayout::~StackLayout() = default; @@ -14,14 +17,4 @@ StackLayout::~StackLayout() = default; render::RenderObject* StackLayout::GetRenderObject() const { return render_object_.get(); } - -void StackLayout::OnAddChild(Control* child, const Index position) { - render_object_->AddChild(child->GetRenderObject(), position); -} - -void StackLayout::OnRemoveChild(Control* child, const Index position) { - CRU_UNUSED(child) - - render_object_->RemoveChild(position); -} } // namespace cru::ui::controls -- cgit v1.2.3 From 02ed6999e9db0c20c3f55ab9c695f939aacb110c Mon Sep 17 00:00:00 2001 From: crupest Date: Tue, 10 Nov 2020 15:12:19 +0800 Subject: ... --- demos/main/main.cpp | 6 ++---- include/cru/ui/controls/TextBlock.hpp | 6 +++++- src/ui/controls/TextBlock.cpp | 19 ++++++++++++++++--- 3 files changed, 23 insertions(+), 8 deletions(-) (limited to 'include/cru/ui/controls') diff --git a/demos/main/main.cpp b/demos/main/main.cpp index 66354289..541a2320 100644 --- a/demos/main/main.cpp +++ b/demos/main/main.cpp @@ -32,12 +32,10 @@ int main() { window->AddChild(flex_layout, 0); - const auto text_block = TextBlock::Create(); - text_block->SetText(u"Hello World from CruUI!"); + const auto text_block = TextBlock::Create(u"Hello World from CruUI!", true); flex_layout->AddChild(text_block, 0); - const auto button_text_block = TextBlock::Create(); - button_text_block->SetText(u"OK"); + const auto button_text_block = TextBlock::Create(u"OK"); const auto button = Button::Create(); button->SetChild(button_text_block); flex_layout->AddChild(button, 1); diff --git a/include/cru/ui/controls/TextBlock.hpp b/include/cru/ui/controls/TextBlock.hpp index fdfdb2fa..66ebe476 100644 --- a/include/cru/ui/controls/TextBlock.hpp +++ b/include/cru/ui/controls/TextBlock.hpp @@ -9,7 +9,8 @@ class TextBlock : public NoChildControl { 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,6 +29,9 @@ class TextBlock : public NoChildControl { std::u16string GetText() const; void SetText(std::u16string text); + bool IsSelectable() const; + void SetSelectable(bool value); + gsl::not_null GetTextRenderObject(); render::ScrollRenderObject* GetScrollRenderObject() { return nullptr; } diff --git a/src/ui/controls/TextBlock.cpp b/src/ui/controls/TextBlock.cpp index 9ce99ab6..1a432582 100644 --- a/src/ui/controls/TextBlock.cpp +++ b/src/ui/controls/TextBlock.cpp @@ -7,10 +7,17 @@ #include "cru/ui/render/TextRenderObject.hpp" namespace cru::ui::controls { -using render::CanvasRenderObject; -using render::StackLayoutRenderObject; using render::TextRenderObject; +TextBlock* TextBlock::Create() { return new TextBlock(); } + +TextBlock* TextBlock::Create(std::u16string text, bool selectable) { + auto c = new TextBlock(); + c->SetText(text); + c->SetSelectable(selectable); + return c; +} + TextBlock::TextBlock() { const auto theme_resources = UiManager::GetInstance()->GetThemeResources(); @@ -21,7 +28,9 @@ TextBlock::TextBlock() { text_render_object_->SetAttachedControl(this); service_ = std::make_unique>(this); - service_->SetEnabled(true); + + service_->SetEnabled(false); + service_->SetEditable(false); } TextBlock::~TextBlock() = default; @@ -36,6 +45,10 @@ void TextBlock::SetText(std::u16string text) { service_->SetText(std::move(text)); } +bool TextBlock::IsSelectable() const { return service_->IsEnabled(); } + +void TextBlock::SetSelectable(bool value) { service_->SetEnabled(value); } + gsl::not_null TextBlock::GetTextRenderObject() { return text_render_object_.get(); } -- cgit v1.2.3 From 0fb7a0e0b2b9e04ca414b1e47c69cc854c79831b Mon Sep 17 00:00:00 2001 From: crupest Date: Tue, 1 Dec 2020 23:20:01 +0800 Subject: ... --- include/cru/common/HandlerRegistry.hpp | 86 +++++++++++++++++++++++++++ include/cru/ui/controls/Button.hpp | 12 +++- include/cru/ui/controls/IClickableControl.hpp | 12 ++++ include/cru/ui/helper/Styler.hpp | 47 --------------- include/cru/ui/style/Styler.hpp | 0 include/cru/ui/style/Trigger.hpp | 75 +++++++++++++++++++++++ src/ui/CMakeLists.txt | 4 +- src/ui/helper/Styler.cpp | 27 --------- src/ui/style/Styler.cpp | 0 src/ui/style/Trigger.cpp | 49 +++++++++++++++ test/common/HandlerRegistryTest.cpp | 36 +++++++++++ 11 files changed, 271 insertions(+), 77 deletions(-) create mode 100644 include/cru/common/HandlerRegistry.hpp create mode 100644 include/cru/ui/controls/IClickableControl.hpp delete mode 100644 include/cru/ui/helper/Styler.hpp create mode 100644 include/cru/ui/style/Styler.hpp create mode 100644 include/cru/ui/style/Trigger.hpp delete mode 100644 src/ui/helper/Styler.cpp create mode 100644 src/ui/style/Styler.cpp create mode 100644 src/ui/style/Trigger.cpp create mode 100644 test/common/HandlerRegistryTest.cpp (limited to 'include/cru/ui/controls') diff --git a/include/cru/common/HandlerRegistry.hpp b/include/cru/common/HandlerRegistry.hpp new file mode 100644 index 00000000..bd74a9e0 --- /dev/null +++ b/include/cru/common/HandlerRegistry.hpp @@ -0,0 +1,86 @@ +#pragma once +#include "Base.hpp" + +#include +#include +#include +#include + +namespace cru { + +template +class HandlerRegistryIterator { + public: + using RawIterator = + typename std::vector>>::const_iterator; + + explicit HandlerRegistryIterator(RawIterator raw) : raw_(std::move(raw)) {} + + CRU_DELETE_COPY(HandlerRegistryIterator) + CRU_DELETE_MOVE(HandlerRegistryIterator) + + ~HandlerRegistryIterator() = default; + + const std::function& operator*() const { return raw_->second; } + const std::function* operator->() const { return &raw_->second; } + + HandlerRegistryIterator& operator++() { + ++raw_; + return *this; + } + + HandlerRegistryIterator operator++(int) { + auto c = *this; + this->operator++(); + return c; + } + + bool operator==(const HandlerRegistryIterator& other) const { + return this->raw_ == other.raw_; + } + + bool operator!=(const HandlerRegistryIterator& other) const { + return !this->operator==(other); + } + + private: + RawIterator raw_; +}; + +template +class HandlerRegistry final { + public: + HandlerRegistry() = default; + CRU_DEFAULT_COPY(HandlerRegistry) + CRU_DEFAULT_MOVE(HandlerRegistry) + ~HandlerRegistry() = default; + + public: + int AddHandler(std::function handler) { + auto id = current_id_++; + handler_list_.push_back({id, std::move(handler)}); + } + + void RemoveHandler(int id) { + auto result = std::find_if(handler_list_.cbegin(), handler_list_.cend(), + [id](const std::pair>& d) { + return d.first == id; + }); + if (result != handler_list_.cend()) { + handler_list_.erase(result); + } + } + + HandlerRegistryIterator begin() const { + return HandlerRegistryIterator(handler_list_.begin()); + } + + HandlerRegistryIterator end() const { + return HandlerRegistryIterator(handler_list_.begin()); + } + + private: + int current_id_ = 1; + std::vector>> handler_list_; +}; +} // namespace cru diff --git a/include/cru/ui/controls/Button.hpp b/include/cru/ui/controls/Button.hpp index 5619ec89..0d2f4898 100644 --- a/include/cru/ui/controls/Button.hpp +++ b/include/cru/ui/controls/Button.hpp @@ -2,9 +2,11 @@ #include "ContentControl.hpp" #include "../helper/ClickDetector.hpp" +#include "IClickableControl.hpp" +#include "cru/common/Event.hpp" namespace cru::ui::controls { -class Button : public ContentControl { +class Button : public ContentControl, public virtual IClickableControl { public: static constexpr std::u16string_view control_type = u"Button"; @@ -25,6 +27,14 @@ class Button : public ContentControl { render::RenderObject* GetRenderObject() const override; public: + helper::ClickState GetClickState() override { + return click_detector_.GetState(); + } + + IEvent* ClickStateChangeEvent() override { + return click_detector_.StateChangeEvent(); + } + const ButtonStyle& GetStyle() const { return style_; } void SetStyle(ButtonStyle style); 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* ClickStateChangeEvent() = 0; +}; +} // namespace cru::ui::controls diff --git a/include/cru/ui/helper/Styler.hpp b/include/cru/ui/helper/Styler.hpp deleted file mode 100644 index ed8bfbdc..00000000 --- a/include/cru/ui/helper/Styler.hpp +++ /dev/null @@ -1,47 +0,0 @@ -#pragma once -#include "cru/common/Base.hpp" -#include "cru/common/Event.hpp" -#include "cru/ui/Base.hpp" -#include "cru/ui/helper/ClickDetector.hpp" -#include "gsl/pointers" - -#include - -namespace cru::ui::helper { -struct ControlStyleState { - ClickState click_state; - bool focus; -}; - -class Styler : public Object { - public: - // You could provide your click detector. Otherwise a new one will be created. - explicit Styler(gsl::not_null control, - ClickDetector* click_detector = nullptr); - - CRU_DELETE_COPY(Styler) - CRU_DELETE_MOVE(Styler) - - ~Styler(); - - public: - gsl::not_null GetControl() const { return control_; } - gsl::not_null GetClickDetector() const { - return click_detector_; - } - - IEvent* StateChangeEvent() { return &state_change_event_; } - - private: - void RaiseStateChangeEvent(); - - private: - gsl::not_null control_; - std::unique_ptr managed_click_detector_; - gsl::not_null click_detector_; - - Event state_change_event_; - - EventRevokerListGuard event_guard_; -}; -} // namespace cru::ui::helper diff --git a/include/cru/ui/style/Styler.hpp b/include/cru/ui/style/Styler.hpp new file mode 100644 index 00000000..e69de29b diff --git a/include/cru/ui/style/Trigger.hpp b/include/cru/ui/style/Trigger.hpp new file mode 100644 index 00000000..ab012a3e --- /dev/null +++ b/include/cru/ui/style/Trigger.hpp @@ -0,0 +1,75 @@ +#pragma once +#include "../Base.hpp" +#include "cru/common/Base.hpp" +#include "cru/common/Event.hpp" +#include "cru/ui/controls/IClickableControl.hpp" +#include "cru/ui/helper/ClickDetector.hpp" + +#include +#include + +namespace cru::ui::style { +class Trigger { + public: + virtual ~Trigger() = default; + + bool GetState() const { return current_; } + IEvent* ChangeEvent() { return &change_event_; } + + protected: + void Raise(bool value); + + private: + bool current_ = false; + Event change_event_; + + protected: + EventRevokerListGuard guard_; +}; + +class CompoundTrigger : public Trigger { + public: + explicit CompoundTrigger(std::vector triggers); + + const std::vector& GetTriggers() const { return triggers_; } + + protected: + virtual bool CalculateState(const std::vector& triggers) const = 0; + + private: + std::vector triggers_; +}; + +class AndTrigger : public CompoundTrigger { + public: + using CompoundTrigger::CompoundTrigger; + + protected: + bool CalculateState(const std::vector& triggers) const override; +}; + +class OrTrigger : public CompoundTrigger { + public: + using CompoundTrigger::CompoundTrigger; + + protected: + bool CalculateState(const std::vector& triggers) const override; +}; + +class FocusTrigger : public Trigger { + public: + FocusTrigger(controls::Control* control, bool has_focus); + + private: + bool has_focus_; +}; + +class ClickStateTrigger : public Trigger { + public: + ClickStateTrigger(controls::IClickableControl* control, + helper::ClickState click_state); + + private: + helper::ClickState click_state_; +}; +} // namespace cru::ui::style diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt index 974a3959..5cb50ce3 100644 --- a/src/ui/CMakeLists.txt +++ b/src/ui/CMakeLists.txt @@ -24,7 +24,6 @@ add_library(cru_ui STATIC helper/BorderStyle.cpp helper/ClickDetector.cpp helper/ShortcutHub.cpp - helper/Styler.cpp host/LayoutPaintCycler.cpp host/WindowHost.cpp render/BorderRenderObject.cpp @@ -35,6 +34,7 @@ add_library(cru_ui STATIC render/ScrollRenderObject.cpp render/StackLayoutRenderObject.cpp render/TextRenderObject.cpp + style/Trigger.cpp ) target_sources(cru_ui PUBLIC ${CRU_UI_INCLUDE_DIR}/Base.hpp @@ -58,7 +58,6 @@ target_sources(cru_ui PUBLIC ${CRU_UI_INCLUDE_DIR}/helper/BorderStyle.hpp ${CRU_UI_INCLUDE_DIR}/helper/ClickDetector.hpp ${CRU_UI_INCLUDE_DIR}/helper/ShortcutHub.hpp - ${CRU_UI_INCLUDE_DIR}/helper/Styler.hpp ${CRU_UI_INCLUDE_DIR}/host/LayoutPaintCycler.hpp ${CRU_UI_INCLUDE_DIR}/host/WindowHost.hpp ${CRU_UI_INCLUDE_DIR}/render/Base.hpp @@ -72,5 +71,6 @@ target_sources(cru_ui PUBLIC ${CRU_UI_INCLUDE_DIR}/render/ScrollRenderObject.hpp ${CRU_UI_INCLUDE_DIR}/render/StackLayoutRenderObject.hpp ${CRU_UI_INCLUDE_DIR}/render/TextRenderObject.hpp + ${CRU_UI_INCLUDE_DIR}/style/Trigger.hpp ) target_link_libraries(cru_ui PUBLIC cru_platform_gui) diff --git a/src/ui/helper/Styler.cpp b/src/ui/helper/Styler.cpp deleted file mode 100644 index 6500a3f7..00000000 --- a/src/ui/helper/Styler.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#include "cru/ui/helper/Styler.hpp" -#include "cru/ui/helper/ClickDetector.hpp" -#include "gsl/pointers" - -namespace cru::ui::helper { -Styler::Styler(gsl::not_null control, - ClickDetector* click_detector) - : control_(control), - managed_click_detector_(click_detector ? nullptr - : new ClickDetector(control)), - click_detector_(click_detector ? click_detector - : managed_click_detector_.get()) { - event_guard_ += control_->GainFocusEvent()->Direct()->AddHandler( - [this](auto) { this->RaiseStateChangeEvent(); }); - event_guard_ += control_->LoseFocusEvent()->Direct()->AddHandler( - [this](auto) { this->RaiseStateChangeEvent(); }); - event_guard_ += click_detector_->StateChangeEvent()->AddHandler( - [this](auto) { this->RaiseStateChangeEvent(); }); -} - -Styler::~Styler() = default; - -void Styler::RaiseStateChangeEvent() { - this->state_change_event_.Raise(ControlStyleState{ - this->click_detector_->GetState(), this->control_->HasFocus()}); -} -} // namespace cru::ui::helper diff --git a/src/ui/style/Styler.cpp b/src/ui/style/Styler.cpp new file mode 100644 index 00000000..e69de29b diff --git a/src/ui/style/Trigger.cpp b/src/ui/style/Trigger.cpp new file mode 100644 index 00000000..b7292ce3 --- /dev/null +++ b/src/ui/style/Trigger.cpp @@ -0,0 +1,49 @@ +#include "cru/ui/style/Trigger.hpp" + +#include "cru/ui/controls/Control.hpp" +#include "cru/ui/helper/ClickDetector.hpp" + +namespace cru::ui::style { +void Trigger::Raise(bool value) { + if (value == current_) return; + current_ = value; + change_event_.Raise(value); +} + +CompoundTrigger::CompoundTrigger(std::vector triggers) + : triggers_(std::move(triggers)) { + for (auto trigger : triggers_) { + guard_ += trigger->ChangeEvent()->AddHandler( + [this](bool) { Raise(this->CalculateState(triggers_)); }); + } +} + +bool AndTrigger::CalculateState(const std::vector& triggers) const { + for (auto trigger : triggers) { + if (!trigger->GetState()) return false; + } + return true; +} + +bool OrTrigger::CalculateState(const std::vector& triggers) const { + for (auto trigger : triggers) { + if (trigger->GetState()) return true; + } + return false; +} + +FocusTrigger::FocusTrigger(controls::Control* control, bool has_focus) + : has_focus_(has_focus) { + guard_ += control->GainFocusEvent()->Direct()->AddHandler( + [this](auto) { Raise(has_focus_); }); + guard_ += control->LoseFocusEvent()->Direct()->AddHandler( + [this](auto) { Raise(!has_focus_); }); +} + +ClickStateTrigger::ClickStateTrigger(controls::IClickableControl* control, + helper::ClickState click_state) + : click_state_(click_state) { + guard_ += control->ClickStateChangeEvent()->AddHandler( + [this](helper::ClickState cs) { Raise(cs == click_state_); }); +} +} // namespace cru::ui::style diff --git a/test/common/HandlerRegistryTest.cpp b/test/common/HandlerRegistryTest.cpp new file mode 100644 index 00000000..d1792c7c --- /dev/null +++ b/test/common/HandlerRegistryTest.cpp @@ -0,0 +1,36 @@ +#include "cru/common/HandlerRegistry.hpp" + +#include +#include + +TEST(HandlerRegistry, Work) { + using namespace cru; + HandlerRegistry registry; + + int counter = 1; + + auto tag1 = registry.AddHandler([&counter] { counter++; }); + auto tag2 = registry.AddHandler([&counter] { counter++; }); + + for (const auto& handler : registry) { + handler(); + } + + ASSERT_EQ(counter, 3); + + registry.RemoveHandler(tag1); + + for (const auto& handler : registry) { + handler(); + } + + ASSERT_EQ(counter, 4); + + registry.RemoveHandler(tag2); + + for (const auto& handler : registry) { + handler(); + } + + ASSERT_EQ(counter, 4); +} -- cgit v1.2.3 From d7dca1be0dd0814e30fa63924a20af3d924e974c Mon Sep 17 00:00:00 2001 From: crupest Date: Wed, 2 Dec 2020 19:38:25 +0800 Subject: ... --- include/cru/ui/controls/Button.hpp | 8 +++++++- include/cru/ui/controls/IBorderControl.hpp | 10 ++++++++++ include/cru/ui/render/BorderRenderObject.hpp | 4 ++++ include/cru/ui/style/ApplyBorderStyleInfo.hpp | 12 ++++++++++++ include/cru/ui/style/Condition.hpp | 4 +--- include/cru/ui/style/Styler.hpp | 13 +++++++++++++ src/ui/CMakeLists.txt | 5 +++++ src/ui/controls/Button.cpp | 4 ++++ src/ui/render/BorderRenderObject.cpp | 18 +++++++++++++++--- 9 files changed, 71 insertions(+), 7 deletions(-) create mode 100644 include/cru/ui/controls/IBorderControl.hpp create mode 100644 include/cru/ui/style/ApplyBorderStyleInfo.hpp (limited to 'include/cru/ui/controls') diff --git a/include/cru/ui/controls/Button.hpp b/include/cru/ui/controls/Button.hpp index 0d2f4898..7299c146 100644 --- a/include/cru/ui/controls/Button.hpp +++ b/include/cru/ui/controls/Button.hpp @@ -2,11 +2,15 @@ #include "ContentControl.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, public virtual IClickableControl { +class Button : public ContentControl, + public virtual IClickableControl, + public virtual IBorderControl { public: static constexpr std::u16string_view control_type = u"Button"; @@ -35,6 +39,8 @@ class Button : public ContentControl, public virtual IClickableControl { return click_detector_.StateChangeEvent(); } + void ApplyBorderStyle(const style::ApplyBorderStyleInfo& style) override; + const ButtonStyle& GetStyle() const { return style_; } void SetStyle(ButtonStyle style); 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/render/BorderRenderObject.hpp b/include/cru/ui/render/BorderRenderObject.hpp index f1b957cf..ec0bd52b 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 { @@ -64,6 +66,8 @@ class BorderRenderObject : public RenderObject { void SetBorderStyle(const BorderStyle& style); + void ApplyBorderStyle(const style::ApplyBorderStyleInfo& style); + RenderObject* HitTest(const Point& point) override; protected: diff --git a/include/cru/ui/style/ApplyBorderStyleInfo.hpp b/include/cru/ui/style/ApplyBorderStyleInfo.hpp new file mode 100644 index 00000000..e9c4ca44 --- /dev/null +++ b/include/cru/ui/style/ApplyBorderStyleInfo.hpp @@ -0,0 +1,12 @@ +#pragma once +#include "../Base.hpp" + +namespace cru::ui::style { +struct ApplyBorderStyleInfo { + std::shared_ptr border_brush; + std::optional border_thickness; + std::optional border_radius; + std::shared_ptr foreground_brush; + std::shared_ptr background_brush; +}; +} // namespace cru::ui::style diff --git a/include/cru/ui/style/Condition.hpp b/include/cru/ui/style/Condition.hpp index b88a338f..97d29287 100644 --- a/include/cru/ui/style/Condition.hpp +++ b/include/cru/ui/style/Condition.hpp @@ -10,10 +10,8 @@ #include namespace cru::ui::style { -class Condition { +class Condition : public Object { public: - virtual ~Condition() = default; - virtual std::vector ChangeOn( controls::Control* control) const = 0; virtual bool Judge(controls::Control* control) const = 0; diff --git a/include/cru/ui/style/Styler.hpp b/include/cru/ui/style/Styler.hpp index e69de29b..0b48f1ce 100644 --- a/include/cru/ui/style/Styler.hpp +++ b/include/cru/ui/style/Styler.hpp @@ -0,0 +1,13 @@ +#pragma once +#include "../Base.hpp" +#include "cru/common/Base.hpp" + +#include + +namespace cru::ui::style { +class Styler : public Object { + public: + virtual void Apply(controls::Control* control) const; +}; + +} // namespace cru::ui::style diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt index 85c87f5e..03297988 100644 --- a/src/ui/CMakeLists.txt +++ b/src/ui/CMakeLists.txt @@ -35,6 +35,7 @@ add_library(cru_ui STATIC render/StackLayoutRenderObject.cpp render/TextRenderObject.cpp style/Condition.cpp + style/Styler.cpp ) target_sources(cru_ui PUBLIC ${CRU_UI_INCLUDE_DIR}/Base.hpp @@ -46,6 +47,8 @@ target_sources(cru_ui PUBLIC ${CRU_UI_INCLUDE_DIR}/controls/ContentControl.hpp ${CRU_UI_INCLUDE_DIR}/controls/Control.hpp ${CRU_UI_INCLUDE_DIR}/controls/FlexLayout.hpp + ${CRU_UI_INCLUDE_DIR}/controls/IBorderControl.hpp + ${CRU_UI_INCLUDE_DIR}/controls/IClickableControl.hpp ${CRU_UI_INCLUDE_DIR}/controls/LayoutControl.hpp ${CRU_UI_INCLUDE_DIR}/controls/NoChildControl.hpp ${CRU_UI_INCLUDE_DIR}/controls/Popup.hpp @@ -71,6 +74,8 @@ target_sources(cru_ui PUBLIC ${CRU_UI_INCLUDE_DIR}/render/ScrollRenderObject.hpp ${CRU_UI_INCLUDE_DIR}/render/StackLayoutRenderObject.hpp ${CRU_UI_INCLUDE_DIR}/render/TextRenderObject.hpp + ${CRU_UI_INCLUDE_DIR}/style/ApplyBorderStyleInfo.hpp ${CRU_UI_INCLUDE_DIR}/style/Condition.hpp + ${CRU_UI_INCLUDE_DIR}/style/Styler.hpp ) target_link_libraries(cru_ui PUBLIC cru_platform_gui) diff --git a/src/ui/controls/Button.cpp b/src/ui/controls/Button.cpp index 39c4b961..6f19e6b9 100644 --- a/src/ui/controls/Button.cpp +++ b/src/ui/controls/Button.cpp @@ -64,4 +64,8 @@ Button::~Button() = default; render::RenderObject* Button::GetRenderObject() const { return render_object_.get(); } + +void Button::ApplyBorderStyle(const style::ApplyBorderStyleInfo& style) { + render_object_->ApplyBorderStyle(style); +} } // namespace cru::ui::controls diff --git a/src/ui/render/BorderRenderObject.cpp b/src/ui/render/BorderRenderObject.cpp index 8e16d8cb..5abc7832 100644 --- a/src/ui/render/BorderRenderObject.cpp +++ b/src/ui/render/BorderRenderObject.cpp @@ -5,6 +5,7 @@ #include "cru/platform/graphics/Factory.hpp" #include "cru/platform/graphics/Geometry.hpp" #include "cru/platform/graphics/util/Painter.hpp" +#include "cru/ui/style/ApplyBorderStyleInfo.hpp" #include @@ -25,6 +26,16 @@ void BorderRenderObject::SetBorderStyle(const BorderStyle& style) { InvalidateLayout(); } +void BorderRenderObject::ApplyBorderStyle( + const style::ApplyBorderStyleInfo& style) { + if (style.border_brush != nullptr) border_brush_ = style.border_brush; + if (style.border_thickness) border_thickness_ = *style.border_thickness; + if (style.border_radius) border_radius_ = *style.border_radius; + if (style.foreground_brush) foreground_brush_ = style.foreground_brush; + if (style.background_brush) background_brush_ = style.background_brush; + InvalidateLayout(); +} + RenderObject* BorderRenderObject::HitTest(const Point& point) { if (const auto child = GetSingleChild()) { auto offset = child->GetOffset(); @@ -109,9 +120,10 @@ Size BorderRenderObject::OnMeasureCore(const MeasureRequirement& requirement, if (!requirement.max.height.IsNotSpecified()) { const auto max_height = requirement.max.height.GetLengthOrMax(); if (coerced_space_size.height > max_height) { - log::TagWarn(log_tag, - u"(Measure) Vertical length of padding, border and margin is " - u"bigger than required max length."); + log::TagWarn( + log_tag, + u"(Measure) Vertical length of padding, border and margin is " + u"bigger than required max length."); coerced_space_size.height = max_height; } content_requirement.max.height = max_height - coerced_space_size.height; -- cgit v1.2.3 From b29fb11be2f043a3438a50d8942b4ad7d2af0034 Mon Sep 17 00:00:00 2001 From: crupest Date: Thu, 3 Dec 2020 22:44:57 +0800 Subject: ... --- include/cru/common/ClonablePtr.hpp | 7 ++- include/cru/common/Event.hpp | 4 +- include/cru/ui/Base.hpp | 12 ++-- include/cru/ui/UiManager.hpp | 5 +- include/cru/ui/controls/Base.hpp | 22 +------ include/cru/ui/controls/Button.hpp | 5 -- include/cru/ui/controls/Control.hpp | 5 ++ include/cru/ui/controls/TextBox.hpp | 14 +---- include/cru/ui/helper/ClickDetector.hpp | 2 +- include/cru/ui/render/BorderRenderObject.hpp | 2 - include/cru/ui/style/Condition.hpp | 42 +++++++++++++ include/cru/ui/style/StyleRuleSet.hpp | 55 +++++++++++++++++ include/cru/ui/style/Styler.hpp | 7 ++- src/ui/UiManager.cpp | 91 ++++++++++++++++------------ src/ui/controls/Button.cpp | 19 +----- src/ui/controls/Control.cpp | 7 +++ src/ui/controls/TextBox.cpp | 29 +-------- src/ui/render/BorderRenderObject.cpp | 9 --- src/ui/style/Condition.cpp | 10 +++ src/ui/style/StyleRuleSet.cpp | 58 ++++++++++++++++++ src/win/gui/Window.cpp | 2 +- 21 files changed, 262 insertions(+), 145 deletions(-) (limited to 'include/cru/ui/controls') diff --git a/include/cru/common/ClonablePtr.hpp b/include/cru/common/ClonablePtr.hpp index 47a1d3bd..5e4b80c9 100644 --- a/include/cru/common/ClonablePtr.hpp +++ b/include/cru/common/ClonablePtr.hpp @@ -8,13 +8,16 @@ namespace cru { template class ClonablePtr { + template + friend class ClonablePtr; + public: using element_type = typename std::unique_ptr::element_type; using pointer = typename std::unique_ptr::pointer; ClonablePtr() = default; ClonablePtr(std::nullptr_t) noexcept : ptr_(nullptr) {} - ClonablePtr(pointer p) noexcept : ptr_(p) {} + explicit ClonablePtr(pointer p) noexcept : ptr_(p) {} ClonablePtr(std::unique_ptr&& p) noexcept : ptr_(std::move(p)) {} template (other.ptr->Clone()); + ptr_ = std::unique_ptr(other.ptr_->Clone()); } return *this; } diff --git a/include/cru/common/Event.hpp b/include/cru/common/Event.hpp index 59502527..7f7b4dd4 100644 --- a/include/cru/common/Event.hpp +++ b/include/cru/common/Event.hpp @@ -98,7 +98,7 @@ struct IBaseEvent { using SpyOnlyHandler = std::function; public: - virtual EventRevoker AddHandler(SpyOnlyHandler handler) = 0; + virtual EventRevoker AddSpyOnlyHandler(SpyOnlyHandler handler) = 0; }; // Provides an interface of event. @@ -147,7 +147,7 @@ class Event : public details::EventBase, public IEvent { CRU_DEFAULT_MOVE(Event) ~Event() = default; - EventRevoker AddHandler(SpyOnlyHandler handler) override { + EventRevoker AddSpyOnlyHandler(SpyOnlyHandler handler) override { const auto token = current_token_++; this->handler_data_list_.emplace_back(token, std::move(handler)); return CreateRevoker(token); diff --git a/include/cru/ui/Base.hpp b/include/cru/ui/Base.hpp index 8595258d..57beb723 100644 --- a/include/cru/ui/Base.hpp +++ b/include/cru/ui/Base.hpp @@ -40,6 +40,10 @@ namespace render { class RenderObject; } +namespace style { +class StyleRuleSet; +} + //-------------------- region: basic types -------------------- namespace internal { constexpr int align_start = 0; @@ -87,14 +91,6 @@ inline bool operator!=(const CornerRadius& left, const CornerRadius& right) { return !(left == right); } -struct BorderStyle { - std::shared_ptr border_brush; - Thickness border_thickness; - CornerRadius border_radius; - std::shared_ptr foreground_brush; - std::shared_ptr background_brush; -}; - class CanvasPaintEventArgs { public: CanvasPaintEventArgs(platform::graphics::IPainter* painter, diff --git a/include/cru/ui/UiManager.hpp b/include/cru/ui/UiManager.hpp index 64599d99..e747fcd2 100644 --- a/include/cru/ui/UiManager.hpp +++ b/include/cru/ui/UiManager.hpp @@ -2,6 +2,7 @@ #include "Base.hpp" #include "controls/Base.hpp" +#include "style/StyleRuleSet.hpp" #include #include @@ -13,8 +14,8 @@ struct ThemeResources { std::shared_ptr text_brush; std::shared_ptr text_selection_brush; std::shared_ptr caret_brush; - controls::ButtonStyle button_style; - controls::TextBoxBorderStyle text_box_border_style; + style::StyleRuleSet button_style; + style::StyleRuleSet text_box_style; }; class UiManager : public Object { diff --git a/include/cru/ui/controls/Base.hpp b/include/cru/ui/controls/Base.hpp index 82c31d1e..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 = ui::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 { - ui::BorderStyle normal; - ui::BorderStyle hover; - ui::BorderStyle focus; - ui::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 7299c146..1c9b1216 100644 --- a/include/cru/ui/controls/Button.hpp +++ b/include/cru/ui/controls/Button.hpp @@ -41,14 +41,9 @@ class Button : public ContentControl, void ApplyBorderStyle(const style::ApplyBorderStyleInfo& style) override; - const ButtonStyle& GetStyle() const { return style_; } - void SetStyle(ButtonStyle style); - private: std::unique_ptr render_object_{}; - ButtonStyle style_; - helper::ClickDetector click_detector_; }; } // namespace cru::ui::controls diff --git a/include/cru/ui/controls/Control.hpp b/include/cru/ui/controls/Control.hpp index 96aad2bd..0d34bc63 100644 --- a/include/cru/ui/controls/Control.hpp +++ b/include/cru/ui/controls/Control.hpp @@ -66,6 +66,9 @@ class Control : public Object { // null to unset void SetCursor(std::shared_ptr cursor); + public: + style::StyleRuleSet* GetStyleRuleSet(); + //*************** region: events *************** public: // Raised when mouse enter the control. Even when the control itself captures @@ -147,5 +150,7 @@ class Control : public Object { bool is_mouse_over_ = false; std::shared_ptr cursor_ = nullptr; + + std::unique_ptr style_rule_set_; }; } // namespace cru::ui::controls diff --git a/include/cru/ui/controls/TextBox.hpp b/include/cru/ui/controls/TextBox.hpp index 91d38c61..75e7cb65 100644 --- a/include/cru/ui/controls/TextBox.hpp +++ b/include/cru/ui/controls/TextBox.hpp @@ -1,5 +1,6 @@ #pragma once #include "NoChildControl.hpp" +#include "IBorderControl.hpp" #include @@ -7,7 +8,7 @@ namespace cru::ui::controls { template class TextControlService; -class TextBox : public NoChildControl { +class TextBox : public NoChildControl, public IBorderControl { public: static constexpr std::u16string_view control_type = u"TextBox"; @@ -29,22 +30,13 @@ class TextBox : public NoChildControl { gsl::not_null GetTextRenderObject(); render::ScrollRenderObject* GetScrollRenderObject(); - const TextBoxBorderStyle& GetBorderStyle(); - void SetBorderStyle(TextBoxBorderStyle border_style); - - protected: - void OnMouseHoverChange(bool newHover) override; - - private: - void UpdateBorderStyle(); + void ApplyBorderStyle(const style::ApplyBorderStyleInfo& style) override; private: std::unique_ptr border_render_object_; std::unique_ptr scroll_render_object_; std::unique_ptr text_render_object_; - TextBoxBorderStyle border_style_; - std::unique_ptr> service_; }; } // namespace cru::ui::controls diff --git a/include/cru/ui/helper/ClickDetector.hpp b/include/cru/ui/helper/ClickDetector.hpp index 0df77c60..b58297b1 100644 --- a/include/cru/ui/helper/ClickDetector.hpp +++ b/include/cru/ui/helper/ClickDetector.hpp @@ -71,7 +71,7 @@ class ClickDetector : public Object { private: controls::Control* control_; - ClickState state_; + ClickState state_ = ClickState::None; bool enable_ = true; MouseButton trigger_button_ = mouse_buttons::left | mouse_buttons::right; diff --git a/include/cru/ui/render/BorderRenderObject.hpp b/include/cru/ui/render/BorderRenderObject.hpp index ec0bd52b..3d4f4dad 100644 --- a/include/cru/ui/render/BorderRenderObject.hpp +++ b/include/cru/ui/render/BorderRenderObject.hpp @@ -64,8 +64,6 @@ class BorderRenderObject : public RenderObject { InvalidatePaint(); } - void SetBorderStyle(const BorderStyle& style); - void ApplyBorderStyle(const style::ApplyBorderStyleInfo& style); RenderObject* HitTest(const Point& point) override; diff --git a/include/cru/ui/style/Condition.hpp b/include/cru/ui/style/Condition.hpp index 13ab7764..d5cf16f2 100644 --- a/include/cru/ui/style/Condition.hpp +++ b/include/cru/ui/style/Condition.hpp @@ -21,6 +21,21 @@ class Condition : public Object { virtual Condition* Clone() const = 0; }; +class NoCondition : public Condition { + public: + static ClonablePtr Create() { + return ClonablePtr(new NoCondition); + }; + + std::vector 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> conditions); @@ -51,6 +66,10 @@ class OrCondition : public CompoundCondition { class FocusCondition : public Condition { public: + static ClonablePtr Create(bool has_focus) { + return ClonablePtr(new FocusCondition(has_focus)); + } + explicit FocusCondition(bool has_focus); std::vector ChangeOn(controls::Control* control) const override; @@ -64,8 +83,31 @@ class FocusCondition : public Condition { bool has_focus_; }; +class HoverCondition : public Condition { + public: + static ClonablePtr Create(bool hover) { + return ClonablePtr(new HoverCondition(hover)); + } + + explicit HoverCondition(bool hover) : hover_(hover) {} + + std::vector 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 Create( + helper::ClickState click_state) { + return ClonablePtr( + new ClickStateCondition(click_state)); + } + explicit ClickStateCondition(helper::ClickState click_state); std::vector ChangeOn(controls::Control* control) const override; diff --git a/include/cru/ui/style/StyleRuleSet.hpp b/include/cru/ui/style/StyleRuleSet.hpp index e69de29b..3ec71730 100644 --- a/include/cru/ui/style/StyleRuleSet.hpp +++ b/include/cru/ui/style/StyleRuleSet.hpp @@ -0,0 +1,55 @@ +#pragma once +#include "StyleRule.hpp" +#include "cru/common/Base.hpp" +#include "cru/common/Event.hpp" +#include "gsl/gsl_assert" + +namespace cru::ui::style { +class StyleRuleSet : public Object { + public: + StyleRuleSet() : control_(nullptr) {} + explicit StyleRuleSet(controls::Control* control) : control_(control) {} + + CRU_DELETE_COPY(StyleRuleSet) + CRU_DELETE_MOVE(StyleRuleSet) + + ~StyleRuleSet() override = default; + + public: + gsl::index GetSize() const { return static_cast(rules_.size()); } + const std::vector& GetRules() const { return rules_; } + + void AddStyleRule(StyleRule rule) { + AddStyleRule(std::move(rule), GetSize()); + } + + void AddStyleRule(StyleRule rule, gsl::index index); + + template + 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); + + const StyleRule& operator[](gsl::index index) const { return rules_[index]; } + + private: + void UpdateChangeListener(); + void UpdateStyle(); + + private: + controls::Control* control_; + + std::vector rules_; + + EventRevokerListGuard guard_; +}; +} // namespace cru::ui::style diff --git a/include/cru/ui/style/Styler.hpp b/include/cru/ui/style/Styler.hpp index 2aece114..10b169b1 100644 --- a/include/cru/ui/style/Styler.hpp +++ b/include/cru/ui/style/Styler.hpp @@ -2,19 +2,24 @@ #include "../Base.hpp" #include "ApplyBorderStyleInfo.hpp" #include "cru/common/Base.hpp" +#include "cru/common/ClonablePtr.hpp" #include namespace cru::ui::style { class Styler : public Object { public: - virtual void Apply(controls::Control* control) const; + virtual void Apply(controls::Control* control) const = 0; virtual Styler* Clone() const = 0; }; class BorderStyler : public Styler { public: + static ClonablePtr Create(ApplyBorderStyleInfo style) { + return ClonablePtr(new BorderStyler(std::move(style))); + } + explicit BorderStyler(ApplyBorderStyleInfo style); void Apply(controls::Control* control) const override; diff --git a/src/ui/UiManager.cpp b/src/ui/UiManager.cpp index 62995f86..bb7f5841 100644 --- a/src/ui/UiManager.cpp +++ b/src/ui/UiManager.cpp @@ -5,9 +5,14 @@ #include "cru/platform/graphics/Factory.hpp" #include "cru/platform/graphics/Font.hpp" #include "cru/platform/gui/UiApplication.hpp" +#include "cru/ui/style/ApplyBorderStyleInfo.hpp" +#include "cru/ui/style/Condition.hpp" +#include "cru/ui/style/Styler.hpp" namespace cru::ui { using namespace cru::platform::graphics; +using namespace cru::ui::style; +using namespace cru::ui::helper; namespace { std::unique_ptr CreateSolidColorBrush(IGraphFactory* factory, @@ -35,49 +40,59 @@ UiManager::UiManager() { theme_resource_.default_font = factory->CreateFont(theme_resource_.default_font_family, 24.0f); - const auto black_brush = std::shared_ptr( - CreateSolidColorBrush(factory, colors::black)); + const auto black_brush = + std::shared_ptr( + CreateSolidColorBrush(factory, colors::black)); theme_resource_.text_brush = black_brush; theme_resource_.text_selection_brush = CreateSolidColorBrush(factory, colors::skyblue); theme_resource_.caret_brush = black_brush; - theme_resource_.button_style.normal.border_brush = - CreateSolidColorBrush(factory, Color::FromHex(0x00bfff)); - theme_resource_.button_style.hover.border_brush = - CreateSolidColorBrush(factory, Color::FromHex(0x47d1ff)); - theme_resource_.button_style.press.border_brush = - CreateSolidColorBrush(factory, Color::FromHex(0x91e4ff)); - theme_resource_.button_style.press_cancel.border_brush = - CreateSolidColorBrush(factory, Color::FromHex(0x91e4ff)); - - theme_resource_.button_style.normal.border_thickness = - theme_resource_.button_style.hover.border_thickness = - theme_resource_.button_style.press.border_thickness = - theme_resource_.button_style.press_cancel.border_thickness = - Thickness(3); - - theme_resource_.button_style.normal.border_radius = - theme_resource_.button_style.hover.border_radius = - theme_resource_.button_style.press.border_radius = - theme_resource_.button_style.press_cancel.border_radius = - CornerRadius({5, 5}); - - theme_resource_.text_box_border_style.normal.border_brush = - CreateSolidColorBrush(factory, Color::FromHex(0xced4da)); - theme_resource_.text_box_border_style.normal.border_radius = CornerRadius(5); - theme_resource_.text_box_border_style.normal.border_thickness = Thickness(1); - - theme_resource_.text_box_border_style.hover = - theme_resource_.text_box_border_style.normal; - - theme_resource_.text_box_border_style.focus.border_brush = - CreateSolidColorBrush(factory, Color::FromHex(0x495057)); - theme_resource_.text_box_border_style.focus.border_radius = CornerRadius(5); - theme_resource_.text_box_border_style.focus.border_thickness = Thickness(1); - - theme_resource_.text_box_border_style.focus_hover = - theme_resource_.text_box_border_style.focus; + theme_resource_.button_style.AddStyleRule( + {ClickStateCondition::Create(ClickState::None), + BorderStyler::Create(ApplyBorderStyleInfo{ + CreateSolidColorBrush(factory, Color::FromHex(0x00bfff)), + Thickness(3), CornerRadius(5), nullptr, nullptr}), + u"DefaultButtonNormal"}); + theme_resource_.button_style.AddStyleRule( + {ClickStateCondition::Create(ClickState::Hover), + BorderStyler::Create(ApplyBorderStyleInfo{ + CreateSolidColorBrush(factory, Color::FromHex(0x47d1ff)), + Thickness(3), CornerRadius(5), nullptr, nullptr}), + u"DefaultButtonHover"}); + theme_resource_.button_style.AddStyleRule( + {ClickStateCondition::Create(ClickState::Press), + BorderStyler::Create(ApplyBorderStyleInfo{ + CreateSolidColorBrush(factory, Color::FromHex(0x91e4ff)), + Thickness(3), CornerRadius(5), nullptr, nullptr}), + u"DefaultButtonPress"}); + theme_resource_.button_style.AddStyleRule( + {ClickStateCondition::Create(ClickState::PressInactive), + BorderStyler::Create(ApplyBorderStyleInfo{ + CreateSolidColorBrush(factory, Color::FromHex(0x91e4ff)), + Thickness(3), CornerRadius(5), nullptr, nullptr}), + u"DefaultButtonPressInactive"}); + + theme_resource_.text_box_style.AddStyleRule( + {HoverCondition::Create(false), + BorderStyler::Create(ApplyBorderStyleInfo{ + CreateSolidColorBrush(factory, Color::FromHex(0xced4da)), + Thickness(1), CornerRadius(5), nullptr, nullptr}), + u"DefaultTextBoxNormal"}); + + theme_resource_.text_box_style.AddStyleRule( + {HoverCondition::Create(true), + BorderStyler::Create(ApplyBorderStyleInfo{ + CreateSolidColorBrush(factory, Color::FromHex(0xced4da)), + Thickness(1), CornerRadius(5), nullptr, nullptr}), + u"DefaultTextBoxHover"}); + + theme_resource_.text_box_style.AddStyleRule( + {FocusCondition::Create(true), + BorderStyler::Create(ApplyBorderStyleInfo{ + CreateSolidColorBrush(factory, Color::FromHex(0x495057)), + Thickness(1), CornerRadius(5), nullptr, nullptr}), + u"DefaultTextBoxHover"}); } UiManager::~UiManager() = default; diff --git a/src/ui/controls/Button.cpp b/src/ui/controls/Button.cpp index 6f19e6b9..7858eadb 100644 --- a/src/ui/controls/Button.cpp +++ b/src/ui/controls/Button.cpp @@ -13,50 +13,37 @@ namespace cru::ui::controls { using cru::platform::gui::SystemCursorType; namespace { -void Set(render::BorderRenderObject* o, const ButtonStateStyle& s) { - o->SetBorderBrush(s.border_brush); - o->SetBorderThickness(s.border_thickness); - o->SetBorderRadius(s.border_radius); - o->SetForegroundBrush(s.foreground_brush); - o->SetBackgroundBrush(s.background_brush); -} - std::shared_ptr GetSystemCursor(SystemCursorType type) { return GetUiApplication()->GetCursorManager()->GetSystemCursor(type); } } // namespace Button::Button() : click_detector_(this) { - style_ = UiManager::GetInstance()->GetThemeResources()->button_style; - render_object_ = std::make_unique(); render_object_->SetAttachedControl(this); SetContainerRenderObject(render_object_.get()); - - Set(render_object_.get(), style_.normal); render_object_->SetBorderEnabled(true); click_detector_.StateChangeEvent()->AddHandler( [this](const helper::ClickState& state) { switch (state) { case helper::ClickState::None: - Set(render_object_.get(), style_.normal); SetCursor(GetSystemCursor(SystemCursorType::Arrow)); break; case helper::ClickState::Hover: - Set(render_object_.get(), style_.hover); SetCursor(GetSystemCursor(SystemCursorType::Hand)); break; case helper::ClickState::Press: - Set(render_object_.get(), style_.press); SetCursor(GetSystemCursor(SystemCursorType::Hand)); break; case helper::ClickState::PressInactive: - Set(render_object_.get(), style_.press_cancel); SetCursor(GetSystemCursor(SystemCursorType::Arrow)); break; } }); + + GetStyleRuleSet()->Set( + UiManager::GetInstance()->GetThemeResources()->button_style); } Button::~Button() = default; diff --git a/src/ui/controls/Control.cpp b/src/ui/controls/Control.cpp index c1316a62..1c4ffe51 100644 --- a/src/ui/controls/Control.cpp +++ b/src/ui/controls/Control.cpp @@ -6,6 +6,7 @@ #include "cru/ui/Base.hpp" #include "cru/ui/host/WindowHost.hpp" #include "cru/ui/render/RenderObject.hpp" +#include "cru/ui/style/StyleRuleSet.hpp" #include @@ -15,6 +16,8 @@ using platform::gui::IUiApplication; using platform::gui::SystemCursorType; Control::Control() { + style_rule_set_ = std::make_unique(this); + MouseEnterEvent()->Direct()->AddHandler([this](event::MouseEventArgs&) { this->is_mouse_over_ = true; this->OnMouseHoverChange(true); @@ -94,6 +97,10 @@ void Control::SetCursor(std::shared_ptr cursor) { } } +style::StyleRuleSet* Control::GetStyleRuleSet() { + return style_rule_set_.get(); +} + void Control::AddChild(Control* control, const Index position) { Expects(control->GetParent() == nullptr); // The control already has a parent. diff --git a/src/ui/controls/TextBox.cpp b/src/ui/controls/TextBox.cpp index 6ba6ecb2..031894c0 100644 --- a/src/ui/controls/TextBox.cpp +++ b/src/ui/controls/TextBox.cpp @@ -18,8 +18,6 @@ TextBox::TextBox() scroll_render_object_(new ScrollRenderObject()) { const auto theme_resources = UiManager::GetInstance()->GetThemeResources(); - border_style_ = theme_resources->text_box_border_style; - text_render_object_ = std::make_unique( theme_resources->text_brush, theme_resources->default_font, theme_resources->text_selection_brush, theme_resources->caret_brush); @@ -38,17 +36,8 @@ TextBox::TextBox() service_->SetEditable(true); border_render_object_->SetBorderEnabled(true); - border_render_object_->SetBorderStyle(border_style_.normal); - - GainFocusEvent()->Direct()->AddHandler([this](event::FocusChangeEventArgs&) { - this->service_->SetCaretVisible(true); - this->UpdateBorderStyle(); - }); - LoseFocusEvent()->Direct()->AddHandler([this](event::FocusChangeEventArgs&) { - this->service_->SetCaretVisible(false); - this->UpdateBorderStyle(); - }); + GetStyleRuleSet()->Set(theme_resources->text_box_style); } TextBox::~TextBox() {} @@ -65,19 +54,7 @@ render::ScrollRenderObject* TextBox::GetScrollRenderObject() { return scroll_render_object_.get(); } -const TextBoxBorderStyle& TextBox::GetBorderStyle() { return border_style_; } - -void TextBox::SetBorderStyle(TextBoxBorderStyle border_style) { - border_style_ = std::move(border_style); -} - -void TextBox::OnMouseHoverChange(bool) { UpdateBorderStyle(); } - -void TextBox::UpdateBorderStyle() { - const auto focus = HasFocus(); - const auto hover = IsMouseOver(); - border_render_object_->SetBorderStyle( - focus ? (hover ? border_style_.focus_hover : border_style_.focus) - : (hover ? border_style_.hover : border_style_.normal)); +void TextBox::ApplyBorderStyle(const style::ApplyBorderStyleInfo& style) { + border_render_object_->ApplyBorderStyle(style); } } // namespace cru::ui::controls diff --git a/src/ui/render/BorderRenderObject.cpp b/src/ui/render/BorderRenderObject.cpp index 5abc7832..c176e760 100644 --- a/src/ui/render/BorderRenderObject.cpp +++ b/src/ui/render/BorderRenderObject.cpp @@ -17,15 +17,6 @@ BorderRenderObject::BorderRenderObject() { BorderRenderObject::~BorderRenderObject() {} -void BorderRenderObject::SetBorderStyle(const BorderStyle& style) { - border_brush_ = style.border_brush; - border_thickness_ = style.border_thickness; - border_radius_ = style.border_radius; - foreground_brush_ = style.foreground_brush; - background_brush_ = style.background_brush; - InvalidateLayout(); -} - void BorderRenderObject::ApplyBorderStyle( const style::ApplyBorderStyleInfo& style) { if (style.border_brush != nullptr) border_brush_ = style.border_brush; diff --git a/src/ui/style/Condition.cpp b/src/ui/style/Condition.cpp index 891eb062..f4866c04 100644 --- a/src/ui/style/Condition.cpp +++ b/src/ui/style/Condition.cpp @@ -51,6 +51,16 @@ bool FocusCondition::Judge(controls::Control* control) const { return control->HasFocus() == has_focus_; } +std::vector HoverCondition::ChangeOn( + controls::Control* control) const { + return {control->MouseEnterEvent()->Direct(), + control->MouseLeaveEvent()->Direct()}; +} + +bool HoverCondition::Judge(controls::Control* control) const { + return control->IsMouseOver() == hover_; +} + ClickStateCondition::ClickStateCondition(helper::ClickState click_state) : click_state_(click_state) {} diff --git a/src/ui/style/StyleRuleSet.cpp b/src/ui/style/StyleRuleSet.cpp index e69de29b..403fe114 100644 --- a/src/ui/style/StyleRuleSet.cpp +++ b/src/ui/style/StyleRuleSet.cpp @@ -0,0 +1,58 @@ +#include "cru/ui/style/StyleRuleSet.hpp" +#include "cru/common/Event.hpp" +#include "gsl/gsl_assert" + +#include + +namespace cru::ui::style { +void StyleRuleSet::AddStyleRule(StyleRule rule, gsl::index index) { + Expects(index >= 0 && index <= GetSize()); + + rules_.insert(rules_.cbegin() + index, std::move(rule)); + + UpdateChangeListener(); + UpdateStyle(); +} + +void StyleRuleSet::RemoveStyleRule(gsl::index index, gsl::index count) { + Expects(index >= 0); + Expects(count >= 0 && index + count <= GetSize()); + + rules_.erase(rules_.cbegin() + index, rules_.cbegin() + index + count); + + UpdateChangeListener(); + UpdateStyle(); +} + +void StyleRuleSet::Set(const StyleRuleSet& other) { + rules_ = other.rules_; + UpdateChangeListener(); + UpdateStyle(); +} + +void StyleRuleSet::UpdateChangeListener() { + if (control_ == nullptr) return; + + guard_.Clear(); + + std::unordered_set events; + for (const auto& rule : rules_) { + auto e = rule.GetCondition()->ChangeOn(control_); + events.insert(e.cbegin(), e.cend()); + } + + for (auto e : events) { + guard_ += e->AddSpyOnlyHandler([this] { this->UpdateStyle(); }); + } +} + +void StyleRuleSet::UpdateStyle() { + if (control_ == nullptr) return; + + for (const auto& rule : rules_) { + if (rule.GetCondition()->Judge(control_)) { + rule.GetStyler()->Apply(control_); + } + } +} +} // namespace cru::ui::style diff --git a/src/win/gui/Window.cpp b/src/win/gui/Window.cpp index 174b8931..efd3bfcc 100644 --- a/src/win/gui/Window.cpp +++ b/src/win/gui/Window.cpp @@ -367,9 +367,9 @@ RECT WinNativeWindow::GetClientRectPixel() { } void WinNativeWindow::OnDestroyInternal() { + destroy_event_.Raise(nullptr); application_->GetWindowManager()->UnregisterWindow(hwnd_); hwnd_ = nullptr; - destroy_event_.Raise(nullptr); if (!sync_flag_) { sync_flag_ = true; delete this; -- cgit v1.2.3 From 9fcebe16b5ad4acc8b2e206dbe163e58b1f75cdf Mon Sep 17 00:00:00 2001 From: crupest Date: Wed, 9 Dec 2020 17:21:44 +0800 Subject: ... --- include/cru/common/Event.hpp | 2 + include/cru/ui/Base.hpp | 1 + include/cru/ui/controls/Control.hpp | 1 + include/cru/ui/style/StyleRuleSet.hpp | 40 +++++++++++++++++-- src/ui/controls/Button.cpp | 4 +- src/ui/controls/Control.cpp | 4 +- src/ui/controls/TextBox.cpp | 2 +- src/ui/style/StyleRuleSet.cpp | 74 ++++++++++++++++++++++++++--------- 8 files changed, 102 insertions(+), 26 deletions(-) (limited to 'include/cru/ui/controls') diff --git a/include/cru/common/Event.hpp b/include/cru/common/Event.hpp index 7f7b4dd4..aa8fadbb 100644 --- a/include/cru/common/Event.hpp +++ b/include/cru/common/Event.hpp @@ -233,6 +233,8 @@ class EventRevokerGuard { EventRevoker Release() { return std::move(*revoker_.release()); } + void Reset() { revoker_.reset(); } + void Reset(EventRevoker&& revoker) { revoker_.reset(new EventRevoker(std::move(revoker))); } diff --git a/include/cru/ui/Base.hpp b/include/cru/ui/Base.hpp index 57beb723..b2939a0b 100644 --- a/include/cru/ui/Base.hpp +++ b/include/cru/ui/Base.hpp @@ -42,6 +42,7 @@ class RenderObject; namespace style { class StyleRuleSet; +class StyleRuleSetBind; } //-------------------- region: basic types -------------------- diff --git a/include/cru/ui/controls/Control.hpp b/include/cru/ui/controls/Control.hpp index 0d34bc63..341b6ef2 100644 --- a/include/cru/ui/controls/Control.hpp +++ b/include/cru/ui/controls/Control.hpp @@ -152,5 +152,6 @@ class Control : public Object { std::shared_ptr cursor_ = nullptr; std::unique_ptr style_rule_set_; + std::unique_ptr style_rule_set_bind_; }; } // namespace cru::ui::controls diff --git a/include/cru/ui/style/StyleRuleSet.hpp b/include/cru/ui/style/StyleRuleSet.hpp index 3ec71730..ba3f8b4c 100644 --- a/include/cru/ui/style/StyleRuleSet.hpp +++ b/include/cru/ui/style/StyleRuleSet.hpp @@ -2,13 +2,14 @@ #include "StyleRule.hpp" #include "cru/common/Base.hpp" #include "cru/common/Event.hpp" -#include "gsl/gsl_assert" + +#include namespace cru::ui::style { class StyleRuleSet : public Object { public: - StyleRuleSet() : control_(nullptr) {} - explicit StyleRuleSet(controls::Control* control) : control_(control) {} + StyleRuleSet() = default; + explicit StyleRuleSet(StyleRuleSet* parent); CRU_DELETE_COPY(StyleRuleSet) CRU_DELETE_MOVE(StyleRuleSet) @@ -16,6 +17,9 @@ class StyleRuleSet : public Object { ~StyleRuleSet() override = default; public: + StyleRuleSet* GetParent() const { return parent_; } + void SetParent(StyleRuleSet* parent); + gsl::index GetSize() const { return static_cast(rules_.size()); } const std::vector& GetRules() const { return rules_; } @@ -41,14 +45,42 @@ class StyleRuleSet : public Object { 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* ChangeEvent() { return &change_event_; } + + private: + void RaiseChangeEvent() { change_event_.Raise(nullptr); } + + private: + Event change_event_; + + StyleRuleSet* parent_ = nullptr; + EventRevokerGuard parent_change_event_guard_; + + std::vector 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_; - std::vector rules_; + // child first, parent last. + std::vector ruleset_chain_cache_; EventRevokerListGuard guard_; }; diff --git a/src/ui/controls/Button.cpp b/src/ui/controls/Button.cpp index 7858eadb..8bd9f93f 100644 --- a/src/ui/controls/Button.cpp +++ b/src/ui/controls/Button.cpp @@ -42,8 +42,8 @@ Button::Button() : click_detector_(this) { } }); - GetStyleRuleSet()->Set( - UiManager::GetInstance()->GetThemeResources()->button_style); + GetStyleRuleSet()->SetParent( + &UiManager::GetInstance()->GetThemeResources()->button_style); } Button::~Button() = default; diff --git a/src/ui/controls/Control.cpp b/src/ui/controls/Control.cpp index 1c4ffe51..29c2c46a 100644 --- a/src/ui/controls/Control.cpp +++ b/src/ui/controls/Control.cpp @@ -16,7 +16,9 @@ using platform::gui::IUiApplication; using platform::gui::SystemCursorType; Control::Control() { - style_rule_set_ = std::make_unique(this); + style_rule_set_ = std::make_unique(); + style_rule_set_bind_ = + std::make_unique(this, style_rule_set_.get()); MouseEnterEvent()->Direct()->AddHandler([this](event::MouseEventArgs&) { this->is_mouse_over_ = true; diff --git a/src/ui/controls/TextBox.cpp b/src/ui/controls/TextBox.cpp index 0d14dbcf..d8317a8c 100644 --- a/src/ui/controls/TextBox.cpp +++ b/src/ui/controls/TextBox.cpp @@ -36,7 +36,7 @@ TextBox::TextBox() border_render_object_->SetBorderEnabled(true); - GetStyleRuleSet()->Set(theme_resources->text_box_style); + GetStyleRuleSet()->SetParent(&theme_resources->text_box_style); } TextBox::~TextBox() {} diff --git a/src/ui/style/StyleRuleSet.cpp b/src/ui/style/StyleRuleSet.cpp index 403fe114..24b88af9 100644 --- a/src/ui/style/StyleRuleSet.cpp +++ b/src/ui/style/StyleRuleSet.cpp @@ -1,17 +1,30 @@ #include "cru/ui/style/StyleRuleSet.hpp" #include "cru/common/Event.hpp" +#include "cru/ui/controls/Control.hpp" #include "gsl/gsl_assert" #include namespace cru::ui::style { +StyleRuleSet::StyleRuleSet(StyleRuleSet* parent) { SetParent(parent); } + +void StyleRuleSet::SetParent(StyleRuleSet* parent) { + if (parent == parent_) return; + parent_change_event_guard_.Reset(); + parent_ = parent; + if (parent != nullptr) { + parent_change_event_guard_.Reset(parent->ChangeEvent()->AddSpyOnlyHandler( + [this] { this->RaiseChangeEvent(); })); + } + RaiseChangeEvent(); +} + void StyleRuleSet::AddStyleRule(StyleRule rule, gsl::index index) { Expects(index >= 0 && index <= GetSize()); rules_.insert(rules_.cbegin() + index, std::move(rule)); - UpdateChangeListener(); - UpdateStyle(); + RaiseChangeEvent(); } void StyleRuleSet::RemoveStyleRule(gsl::index index, gsl::index count) { @@ -20,25 +33,48 @@ void StyleRuleSet::RemoveStyleRule(gsl::index index, gsl::index count) { rules_.erase(rules_.cbegin() + index, rules_.cbegin() + index + count); - UpdateChangeListener(); - UpdateStyle(); + RaiseChangeEvent(); } void StyleRuleSet::Set(const StyleRuleSet& other) { rules_ = other.rules_; - UpdateChangeListener(); - UpdateStyle(); + + RaiseChangeEvent(); +} + +StyleRuleSetBind::StyleRuleSetBind(controls::Control* control, + StyleRuleSet* ruleset) + : control_(control), ruleset_(ruleset) { + Expects(control); + Expects(ruleset); + + ruleset->ChangeEvent()->AddSpyOnlyHandler([this] { + UpdateRuleSetChainCache(); + UpdateChangeListener(); + UpdateStyle(); + }); } -void StyleRuleSet::UpdateChangeListener() { - if (control_ == nullptr) return; +void StyleRuleSetBind::UpdateRuleSetChainCache() { + ruleset_chain_cache_.clear(); + auto parent = ruleset_; + while (parent != nullptr) { + ruleset_chain_cache_.push_back(parent); + parent = parent->GetParent(); + } +} +void StyleRuleSetBind::UpdateChangeListener() { guard_.Clear(); std::unordered_set events; - for (const auto& rule : rules_) { - auto e = rule.GetCondition()->ChangeOn(control_); - events.insert(e.cbegin(), e.cend()); + + // ruleset order does not matter + for (auto ruleset : ruleset_chain_cache_) { + for (const auto& rule : ruleset->GetRules()) { + auto e = rule.GetCondition()->ChangeOn(control_); + events.insert(e.cbegin(), e.cend()); + } } for (auto e : events) { @@ -46,13 +82,15 @@ void StyleRuleSet::UpdateChangeListener() { } } -void StyleRuleSet::UpdateStyle() { - if (control_ == nullptr) return; - - for (const auto& rule : rules_) { - if (rule.GetCondition()->Judge(control_)) { - rule.GetStyler()->Apply(control_); - } +void StyleRuleSetBind::UpdateStyle() { + // cache is parent last, but when calculate style, parent first, so iterate + // reverse. + for (auto iter = ruleset_chain_cache_.crbegin(); + iter != ruleset_chain_cache_.crend(); ++iter) { + for (const auto& rule : (*iter)->GetRules()) + if (rule.GetCondition()->Judge(control_)) { + rule.GetStyler()->Apply(control_); + } } } } // namespace cru::ui::style -- cgit v1.2.3 From da7ad0ff5c5b158be69c6cf9a2c8e9fc9ef2b3cb Mon Sep 17 00:00:00 2001 From: crupest Date: Fri, 25 Dec 2020 15:38:18 +0800 Subject: ... --- include/cru/platform/GraphBase.hpp | 2 +- include/cru/ui/controls/TextBlock.hpp | 15 +- include/cru/ui/controls/TextBox.hpp | 12 +- include/cru/ui/controls/TextHostControlService.hpp | 141 ++++++ src/ui/CMakeLists.txt | 5 +- src/ui/controls/TextBlock.cpp | 3 +- src/ui/controls/TextBox.cpp | 3 +- src/ui/controls/TextControlService.hpp | 511 --------------------- src/ui/controls/TextHostControlService.cpp | 458 ++++++++++++++++++ 9 files changed, 621 insertions(+), 529 deletions(-) create mode 100644 include/cru/ui/controls/TextHostControlService.hpp delete mode 100644 src/ui/controls/TextControlService.hpp create mode 100644 src/ui/controls/TextHostControlService.cpp (limited to 'include/cru/ui/controls') diff --git a/include/cru/platform/GraphBase.hpp b/include/cru/platform/GraphBase.hpp index 2b40898e..b580ad31 100644 --- a/include/cru/platform/GraphBase.hpp +++ b/include/cru/platform/GraphBase.hpp @@ -277,7 +277,7 @@ struct TextRange final { gsl::index GetStart() const { return position; } gsl::index GetEnd() const { return position + count; } - void AdjustEnd(gsl::index new_end) { count = new_end - position; } + void ChangeEnd(gsl::index new_end) { count = new_end - position; } TextRange Normalize() const { auto result = *this; diff --git a/include/cru/ui/controls/TextBlock.hpp b/include/cru/ui/controls/TextBlock.hpp index 66ebe476..be31816c 100644 --- a/include/cru/ui/controls/TextBlock.hpp +++ b/include/cru/ui/controls/TextBlock.hpp @@ -1,11 +1,10 @@ #pragma once #include "NoChildControl.hpp" -namespace cru::ui::controls { -template -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"; @@ -32,12 +31,14 @@ class TextBlock : public NoChildControl { bool IsSelectable() const; void SetSelectable(bool value); - gsl::not_null GetTextRenderObject(); - render::ScrollRenderObject* GetScrollRenderObject() { return nullptr; } + gsl::not_null GetTextRenderObject() override; + render::ScrollRenderObject* GetScrollRenderObject() override { + return nullptr; + } private: std::unique_ptr text_render_object_; - std::unique_ptr> service_; + std::unique_ptr service_; }; } // namespace cru::ui::controls diff --git a/include/cru/ui/controls/TextBox.hpp b/include/cru/ui/controls/TextBox.hpp index 75e7cb65..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 "IBorderControl.hpp" +#include "TextHostControlService.hpp" #include @@ -8,7 +10,9 @@ namespace cru::ui::controls { template class TextControlService; -class TextBox : public NoChildControl, public IBorderControl { +class TextBox : public NoChildControl, + public virtual IBorderControl, + public virtual ITextHostControl { public: static constexpr std::u16string_view control_type = u"TextBox"; @@ -27,8 +31,8 @@ class TextBox : public NoChildControl, public IBorderControl { render::RenderObject* GetRenderObject() const override; - gsl::not_null GetTextRenderObject(); - render::ScrollRenderObject* GetScrollRenderObject(); + gsl::not_null GetTextRenderObject() override; + render::ScrollRenderObject* GetScrollRenderObject() override; void ApplyBorderStyle(const style::ApplyBorderStyleInfo& style) override; @@ -37,6 +41,6 @@ class TextBox : public NoChildControl, public IBorderControl { std::unique_ptr scroll_render_object_; std::unique_ptr text_render_object_; - std::unique_ptr> service_; + std::unique_ptr 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..0bea52c8 --- /dev/null +++ b/include/cru/ui/controls/TextHostControlService.hpp @@ -0,0 +1,141 @@ +#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 +#include + +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 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); + + 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 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 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 GetTextRenderObject(); + render::ScrollRenderObject* GetScrollRenderObject(); + + // May return nullptr. + platform::gui::IInputMethodContext* GetInputMethodContext(); + + void CoerceSelection(); + + void SetupCaret(); + void TearDownCaret(); + + void SyncTextRenderObject(); + + void StartSelection(Index start); + void UpdateSelection(Index new_end); + void AbortSelection(); + + void UpdateInputMethodPosition(); + + template + void SetupOneHandler(event::RoutedEvent* (Control::*event)(), + void (TextHostControlService::*handler)( + typename event::RoutedEvent::EventArgs)) { + this->event_guard_ += (this->control_->*event)()->Direct()->AddHandler( + std::bind(handler, this, std::placeholders::_1)); + } + + void SetUpHandlers(); + void TearDownHandlers(); + + void MouseMoveHandler(event::MouseEventArgs& args); + void MouseDownHandler(event::MouseButtonEventArgs& args); + void MouseUpHandler(event::MouseButtonEventArgs& args); + void KeyDownHandler(event::KeyEventArgs& args); + void GainFocusHandler(event::FocusChangeEventArgs& args); + void LoseFocusHandler(event::FocusChangeEventArgs& args); + + private: + gsl::not_null control_; + gsl::not_null 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_; +}; +} // namespace cru::ui::controls diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt index 28200eb5..d1c1e830 100644 --- a/src/ui/CMakeLists.txt +++ b/src/ui/CMakeLists.txt @@ -20,7 +20,7 @@ add_library(cru_ui STATIC controls/StackLayout.cpp controls/TextBlock.cpp controls/TextBox.cpp - controls/TextControlService.hpp + controls/TextHostControlService.cpp controls/Window.cpp events/UiEvent.cpp helper/BorderStyle.cpp @@ -60,8 +60,9 @@ target_sources(cru_ui PUBLIC ${CRU_UI_INCLUDE_DIR}/controls/Popup.hpp ${CRU_UI_INCLUDE_DIR}/controls/RootControl.hpp ${CRU_UI_INCLUDE_DIR}/controls/StackLayout.hpp - ${CRU_UI_INCLUDE_DIR}/controls/TextBox.hpp ${CRU_UI_INCLUDE_DIR}/controls/TextBlock.hpp + ${CRU_UI_INCLUDE_DIR}/controls/TextBox.hpp + ${CRU_UI_INCLUDE_DIR}/controls/TextHostControlService.hpp ${CRU_UI_INCLUDE_DIR}/controls/Window.hpp ${CRU_UI_INCLUDE_DIR}/events/UiEvent.hpp ${CRU_UI_INCLUDE_DIR}/helper/ClickDetector.hpp diff --git a/src/ui/controls/TextBlock.cpp b/src/ui/controls/TextBlock.cpp index 1a432582..0724edcf 100644 --- a/src/ui/controls/TextBlock.cpp +++ b/src/ui/controls/TextBlock.cpp @@ -1,6 +1,5 @@ #include "cru/ui/controls/TextBlock.hpp" -#include "TextControlService.hpp" #include "cru/ui/UiManager.hpp" #include "cru/ui/render/CanvasRenderObject.hpp" #include "cru/ui/render/StackLayoutRenderObject.hpp" @@ -27,7 +26,7 @@ TextBlock::TextBlock() { text_render_object_->SetAttachedControl(this); - service_ = std::make_unique>(this); + service_ = std::make_unique(this); service_->SetEnabled(false); service_->SetEditable(false); diff --git a/src/ui/controls/TextBox.cpp b/src/ui/controls/TextBox.cpp index d8317a8c..e1acaee0 100644 --- a/src/ui/controls/TextBox.cpp +++ b/src/ui/controls/TextBox.cpp @@ -1,6 +1,5 @@ #include "cru/ui/controls/TextBox.hpp" -#include "TextControlService.hpp" #include "cru/ui/UiManager.hpp" #include "cru/ui/render/BorderRenderObject.hpp" #include "cru/ui/render/CanvasRenderObject.hpp" @@ -30,7 +29,7 @@ TextBox::TextBox() text_render_object_->SetAttachedControl(this); text_render_object_->SetMinSize(Size{100, 24}); - service_ = std::make_unique>(this); + service_ = std::make_unique(this); service_->SetEnabled(true); service_->SetEditable(true); diff --git a/src/ui/controls/TextControlService.hpp b/src/ui/controls/TextControlService.hpp deleted file mode 100644 index c535512f..00000000 --- a/src/ui/controls/TextControlService.hpp +++ /dev/null @@ -1,511 +0,0 @@ -#pragma once -#include "../Helper.hpp" -#include "cru/common/Logger.hpp" -#include "cru/common/StringUtil.hpp" -#include "cru/platform/graphics/Font.hpp" -#include "cru/platform/graphics/Painter.hpp" -#include "cru/platform/gui/Cursor.hpp" -#include "cru/platform/gui/InputMethod.hpp" -#include "cru/platform/gui/UiApplication.hpp" -#include "cru/platform/gui/Window.hpp" -#include "cru/ui/Base.hpp" -#include "cru/ui/DebugFlags.hpp" -#include "cru/ui/controls/Control.hpp" -#include "cru/ui/events/UiEvent.hpp" -#include "cru/ui/helper/ShortcutHub.hpp" -#include "cru/ui/host/WindowHost.hpp" -#include "cru/ui/render/CanvasRenderObject.hpp" -#include "cru/ui/render/ScrollRenderObject.hpp" -#include "cru/ui/render/TextRenderObject.hpp" - -#include - -namespace cru::ui::controls { -constexpr int k_default_caret_blink_duration = 500; - -// TControl should inherits `Control` and has following methods: -// ``` -// gsl::not_null GetTextRenderObject(); -// render::ScrollRenderObject* GetScrollRenderObject(); -// ``` -template -class TextControlService : public Object { - CRU_DEFINE_CLASS_LOG_TAG(u"cru::ui::controls::TextControlService") - - public: - TextControlService(gsl::not_null control) : control_(control) {} - - CRU_DELETE_COPY(TextControlService) - CRU_DELETE_MOVE(TextControlService) - - ~TextControlService() = default; - - public: - bool IsEnabled() { return enable_; } - - void SetEnabled(bool enable) { - if (enable == this->enable_) return; - this->enable_ = enable; - if (enable) { - this->SetUpHandlers(); - if (this->caret_visible_) { - this->SetupCaret(); - } - this->control_->SetCursor( - GetUiApplication()->GetCursorManager()->GetSystemCursor( - platform::gui::SystemCursorType::IBeam)); - } else { - this->AbortSelection(); - this->TearDownHandlers(); - this->TearDownCaret(); - this->control_->SetCursor(nullptr); - } - } - - bool IsEditable() { return this->editable_; } - - void SetEditable(bool editable) { - this->editable_ = editable; - if (!editable) CancelComposition(); - } - - std::u16string GetText() { return this->text_; } - std::u16string_view GetTextView() { return this->text_; } - void SetText(std::u16string text, bool stop_composition = false) { - this->text_ = std::move(text); - CoerceSelection(); - if (stop_composition) { - CancelComposition(); - } - SyncTextRenderObject(); - } - - void InsertText(gsl::index position, std::u16string_view text, - bool stop_composition = false) { - if (!Utf16IsValidInsertPosition(this->text_, position)) { - log::TagError(log_tag, u"Invalid text insert position."); - return; - } - this->text_.insert(this->text_.cbegin() + position, text.begin(), - text.end()); - if (stop_composition) { - CancelComposition(); - } - SyncTextRenderObject(); - } - - void DeleteChar(gsl::index position, bool stop_composition = false) { - if (!Utf16IsValidInsertPosition(this->text_, position)) { - log::TagError(log_tag, u"Invalid text delete position."); - return; - } - if (position == static_cast(this->text_.size())) return; - Index next; - Utf16NextCodePoint(this->text_, position, &next); - this->DeleteText(TextRange::FromTwoSides(position, next), stop_composition); - } - - // Return the position of deleted character. - gsl::index DeleteCharPrevious(gsl::index position, - bool stop_composition = false) { - if (!Utf16IsValidInsertPosition(this->text_, position)) { - log::TagError(log_tag, u"Invalid text delete position."); - return 0; - } - if (position == 0) return 0; - Index previous; - Utf16PreviousCodePoint(this->text_, position, &previous); - this->DeleteText(TextRange::FromTwoSides(previous, position), - stop_composition); - return previous; - } - - void DeleteText(TextRange range, bool stop_composition = false) { - if (range.count == 0) return; - range = range.Normalize(); - if (!Utf16IsValidInsertPosition(this->text_, range.GetStart())) { - log::TagError(log_tag, u"Invalid text delete start position."); - return; - } - if (!Utf16IsValidInsertPosition(this->text_, range.GetStart())) { - log::TagError(log_tag, u"Invalid text delete end position."); - return; - } - this->text_.erase(this->text_.cbegin() + range.GetStart(), - this->text_.cbegin() + range.GetEnd()); - this->CoerceSelection(); - if (stop_composition) { - CancelComposition(); - } - this->SyncTextRenderObject(); - } - - platform::gui::IInputMethodContext* GetInputMethodContext() { - host::WindowHost* host = this->control_->GetWindowHost(); - if (!host) return nullptr; - platform::gui::INativeWindow* native_window = host->GetNativeWindow(); - if (!native_window) return nullptr; - return native_window->GetInputMethodContext(); - } - - void CancelComposition() { - auto input_method_context = GetInputMethodContext(); - if (input_method_context == nullptr) return; - input_method_context->CancelComposition(); - } - - std::optional GetCompositionInfo() { - auto input_method_context = GetInputMethodContext(); - if (input_method_context == nullptr) return std::nullopt; - auto composition_info = input_method_context->GetCompositionText(); - if (composition_info.text.empty()) return std::nullopt; - return composition_info; - } - - bool IsCaretVisible() { return caret_visible_; } - - void SetCaretVisible(bool visible) { - if (visible == this->caret_visible_) return; - - this->caret_visible_ = visible; - - if (this->enable_) { - if (visible) { - this->SetupCaret(); - } else { - this->TearDownCaret(); - } - } - } - - int GetCaretBlinkDuration() { return caret_blink_duration_; } - - void SetCaretBlinkDuration(int milliseconds) { - if (this->caret_blink_duration_ == milliseconds) return; - - if (this->enable_ && this->caret_visible_) { - this->TearDownCaret(); - this->SetupCaret(); - } - } - - gsl::not_null GetTextRenderObject() { - return this->control_->GetTextRenderObject(); - } - - render::ScrollRenderObject* GetScrollRenderObject() { - return this->control_->GetScrollRenderObject(); - } - - gsl::index GetCaretPosition() { return selection_.GetEnd(); } - - TextRange GetSelection() { return selection_; } - - void SetSelection(gsl::index caret_position) { - this->SetSelection(TextRange{caret_position, 0}); - } - - void SetSelection(TextRange selection, bool scroll_to_caret = true) { - this->selection_ = selection; - CoerceSelection(); - SyncTextRenderObject(); - if (scroll_to_caret) { - this->ScrollToCaret(); - } - } - - void DeleteSelectedText() { - this->DeleteText(GetSelection()); - SetSelection(GetSelection().Normalize().GetStart()); - } - - // If some text is selected, then they are deleted first. Then insert text - // into caret position. - void ReplaceSelectedText(std::u16string_view text) { - DeleteSelectedText(); - InsertText(GetSelection().GetStart(), text); - SetSelection(GetSelection().GetStart() + text.size()); - } - - void ScrollToCaret() { - if (const auto scroll_render_object = this->GetScrollRenderObject()) { - this->control_->GetWindowHost()->RunAfterLayoutStable( - [this, scroll_render_object]() { - const auto caret_rect = this->GetTextRenderObject()->GetCaretRect(); - scroll_render_object->ScrollToContain(caret_rect, Thickness{5.f}); - }); - } - } - - private: - void CoerceSelection() { - this->selection_ = this->selection_.CoerceInto(0, text_.size()); - } - - void AbortSelection() { - if (this->select_down_button_.has_value()) { - this->control_->ReleaseMouse(); - this->select_down_button_ = std::nullopt; - } - this->GetTextRenderObject()->SetSelectionRange(std::nullopt); - } - - void SetupCaret() { - const auto application = GetUiApplication(); - this->GetTextRenderObject()->SetDrawCaret(true); - this->caret_timer_canceler_.Reset(application->SetInterval( - std::chrono::milliseconds(this->caret_blink_duration_), - [this] { this->GetTextRenderObject()->ToggleDrawCaret(); })); - } - - void TearDownCaret() { - this->caret_timer_canceler_.Reset(); - this->GetTextRenderObject()->SetDrawCaret(false); - } - - void SyncTextRenderObject() { - const auto text_render_object = this->GetTextRenderObject(); - const auto composition_info = this->GetCompositionInfo(); - if (composition_info) { - const auto caret_position = GetCaretPosition(); - auto text = this->text_; - text.insert(caret_position, composition_info->text); - text_render_object->SetText(text); - text_render_object->SetCaretPosition( - caret_position + composition_info->selection.GetEnd()); - auto selection = composition_info->selection; - selection.position += caret_position; - text_render_object->SetSelectionRange(selection); - } else { - text_render_object->SetText(this->text_); - text_render_object->SetCaretPosition(this->GetCaretPosition()); - text_render_object->SetSelectionRange(this->GetSelection()); - } - } - - void StartSelection(Index start) { - SetSelection(start); - if constexpr (debug_flags::text_service) - log::TagDebug(log_tag, u"Text selection started, position: {}.", start); - } - - void UpdateSelection(Index new_end) { - auto selection = GetSelection(); - selection.AdjustEnd(new_end); - this->SetSelection(selection); - if constexpr (debug_flags::text_service) - log::TagDebug(log_tag, u"Text selection updated, range: {}, {}.", - selection.GetStart(), selection.GetEnd()); - } - - void UpdateInputMethodPosition() { - if (auto input_method_context = this->GetInputMethodContext()) { - Point right_bottom = - this->GetTextRenderObject()->GetTotalOffset() + - this->GetTextRenderObject()->GetCaretRect().GetRightBottom(); - right_bottom.x += 5; - right_bottom.y += 5; - - if constexpr (debug_flags::text_service) { - log::TagDebug(log_tag, - u"Calculate input method candidate window position: {}.", - right_bottom.ToDebugString()); - } - - input_method_context->SetCandidateWindowPosition(right_bottom); - } - } - - template - void SetupOneHandler(event::RoutedEvent* (Control::*event)(), - void (TextControlService::*handler)( - typename event::RoutedEvent::EventArgs)) { - this->event_guard_ += (this->control_->*event)()->Direct()->AddHandler( - std::bind(handler, this, std::placeholders::_1)); - } - - void SetUpHandlers() { - Expects(event_guard_.IsEmpty()); - - SetupOneHandler(&Control::MouseMoveEvent, - &TextControlService::MouseMoveHandler); - SetupOneHandler(&Control::MouseDownEvent, - &TextControlService::MouseDownHandler); - SetupOneHandler(&Control::MouseUpEvent, - &TextControlService::MouseUpHandler); - SetupOneHandler(&Control::KeyDownEvent, - &TextControlService::KeyDownHandler); - SetupOneHandler(&Control::GainFocusEvent, - &TextControlService::GainFocusHandler); - SetupOneHandler(&Control::LoseFocusEvent, - &TextControlService::LoseFocusHandler); - - shortcut_hub_.Install(control_); - } - - void TearDownHandlers() { - event_guard_.Clear(); - shortcut_hub_.Uninstall(); - } - - void MouseMoveHandler(event::MouseEventArgs& args) { - if (this->select_down_button_.has_value()) { - const auto text_render_object = this->GetTextRenderObject(); - const auto result = text_render_object->TextHitTest( - args.GetPointToContent(text_render_object)); - const auto position = result.position + (result.trailing ? 1 : 0); - UpdateSelection(position); - } - } - - void MouseDownHandler(event::MouseButtonEventArgs& args) { - if (this->select_down_button_.has_value()) { - return; - } else { - this->control_->SetFocus(); - if (!this->control_->CaptureMouse()) return; - const auto text_render_object = this->GetTextRenderObject(); - this->select_down_button_ = args.GetButton(); - const auto result = text_render_object->TextHitTest( - args.GetPointToContent(text_render_object)); - const auto position = result.position + (result.trailing ? 1 : 0); - StartSelection(position); - } - } - - void MouseUpHandler(event::MouseButtonEventArgs& args) { - if (this->select_down_button_.has_value() && - this->select_down_button_.value() == args.GetButton()) { - this->control_->ReleaseMouse(); - this->select_down_button_ = std::nullopt; - } - } - - void KeyDownHandler(event::KeyEventArgs& args) { - const auto key_code = args.GetKeyCode(); - using cru::platform::gui::KeyCode; - using cru::platform::gui::KeyModifiers; - - switch (key_code) { - case KeyCode::Backspace: { - if (!IsEditable()) return; - const auto selection = GetSelection(); - if (selection.count == 0) { - SetSelection(DeleteCharPrevious(GetCaretPosition())); - } else { - this->DeleteSelectedText(); - } - } break; - case KeyCode::Delete: { - if (!IsEditable()) return; - const auto selection = GetSelection(); - if (selection.count == 0) { - DeleteChar(GetCaretPosition()); - } else { - this->DeleteSelectedText(); - } - } break; - case KeyCode::Left: { - const auto key_modifier = args.GetKeyModifier(); - const bool shift = key_modifier & KeyModifiers::shift; - auto text = this->GetTextView(); - if (shift) { - auto selection = this->GetSelection(); - gsl::index new_position; - Utf16PreviousCodePoint(text, selection.GetEnd(), &new_position); - selection.AdjustEnd(new_position); - this->SetSelection(selection); - } else { - const auto caret = this->GetCaretPosition(); - gsl::index new_position; - Utf16PreviousCodePoint(text, caret, &new_position); - this->SetSelection(new_position); - } - } break; - case KeyCode::Right: { - const auto key_modifier = args.GetKeyModifier(); - const bool shift = key_modifier & KeyModifiers::shift; - auto text = this->GetTextView(); - if (shift) { - auto selection = this->GetSelection(); - gsl::index new_position; - Utf16NextCodePoint(text, selection.GetEnd(), &new_position); - selection.AdjustEnd(new_position); - this->SetSelection(selection); - } else { - const auto caret = this->GetCaretPosition(); - gsl::index new_position; - Utf16NextCodePoint(text, caret, &new_position); - this->SetSelection(new_position); - } - } break; - default: - break; - } - } - - void GainFocusHandler(event::FocusChangeEventArgs& args) { - CRU_UNUSED(args); - if (editable_) { - auto input_method_context = GetInputMethodContext(); - if (input_method_context == nullptr) return; - input_method_context->EnableIME(); - auto sync = [this](std::nullptr_t) { - this->SyncTextRenderObject(); - ScrollToCaret(); - }; - input_method_context_event_guard_ += - input_method_context->CompositionStartEvent()->AddHandler( - [this](std::nullptr_t) { this->DeleteSelectedText(); }); - input_method_context_event_guard_ += - input_method_context->CompositionEvent()->AddHandler(sync); - input_method_context_event_guard_ += - input_method_context->CompositionEndEvent()->AddHandler(sync); - input_method_context_event_guard_ += - input_method_context->TextEvent()->AddHandler( - [this](const std::u16string_view& text) { - if (text == u"\b") return; - this->ReplaceSelectedText(text); - }); - - host::WindowHost* window_host = control_->GetWindowHost(); - if (window_host) - input_method_context_event_guard_ += - window_host->AfterLayoutEvent()->AddHandler( - [this](auto) { this->UpdateInputMethodPosition(); }); - SetCaretVisible(true); - } - } - - void LoseFocusHandler(event::FocusChangeEventArgs& args) { - if (!args.IsWindow()) this->AbortSelection(); - input_method_context_event_guard_.Clear(); - auto input_method_context = GetInputMethodContext(); - if (input_method_context) { - input_method_context->DisableIME(); - } - SetCaretVisible(false); - SyncTextRenderObject(); - } - - private: - gsl::not_null 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_; - - // nullopt means not selecting - std::optional select_down_button_; -}; -} // namespace cru::ui::controls diff --git a/src/ui/controls/TextHostControlService.cpp b/src/ui/controls/TextHostControlService.cpp new file mode 100644 index 00000000..1ce3f642 --- /dev/null +++ b/src/ui/controls/TextHostControlService.cpp @@ -0,0 +1,458 @@ +#include "cru/ui/controls/TextHostControlService.hpp" + +#include "../Helper.hpp" +#include "cru/common/Logger.hpp" +#include "cru/common/StringUtil.hpp" +#include "cru/platform/graphics/Font.hpp" +#include "cru/platform/graphics/Painter.hpp" +#include "cru/platform/gui/Cursor.hpp" +#include "cru/platform/gui/InputMethod.hpp" +#include "cru/platform/gui/UiApplication.hpp" +#include "cru/platform/gui/Window.hpp" +#include "cru/ui/Base.hpp" +#include "cru/ui/DebugFlags.hpp" +#include "cru/ui/controls/Control.hpp" +#include "cru/ui/events/UiEvent.hpp" +#include "cru/ui/helper/ShortcutHub.hpp" +#include "cru/ui/host/WindowHost.hpp" +#include "cru/ui/render/CanvasRenderObject.hpp" +#include "cru/ui/render/ScrollRenderObject.hpp" +#include "cru/ui/render/TextRenderObject.hpp" +#include "gsl/gsl_assert" +#include "gsl/pointers" + +namespace cru::ui::controls { +TextHostControlService::TextHostControlService(gsl::not_null control) + : control_(control), + text_host_control_(dynamic_cast(control.get())) {} + +void TextHostControlService::SetEnabled(bool enable) { + if (enable == this->enable_) return; + this->enable_ = enable; + if (enable) { + this->SetUpHandlers(); + if (this->caret_visible_) { + this->SetupCaret(); + } + this->control_->SetCursor( + GetUiApplication()->GetCursorManager()->GetSystemCursor( + platform::gui::SystemCursorType::IBeam)); + } else { + this->AbortSelection(); + this->TearDownHandlers(); + this->TearDownCaret(); + this->control_->SetCursor(nullptr); + } +} + +void TextHostControlService::SetEditable(bool editable) { + this->editable_ = editable; + if (!editable) CancelComposition(); +} + +void TextHostControlService::SetText(std::u16string text, + bool stop_composition) { + this->text_ = std::move(text); + CoerceSelection(); + if (stop_composition) { + CancelComposition(); + } + SyncTextRenderObject(); +} + +void TextHostControlService::InsertText(gsl::index position, + std::u16string_view text, + bool stop_composition) { + if (!Utf16IsValidInsertPosition(this->text_, position)) { + log::TagError(log_tag, u"Invalid text insert position."); + return; + } + this->text_.insert(this->text_.cbegin() + position, text.begin(), text.end()); + if (stop_composition) { + CancelComposition(); + } + SyncTextRenderObject(); +} + +void TextHostControlService::DeleteChar(gsl::index position, + bool stop_composition) { + if (!Utf16IsValidInsertPosition(this->text_, position)) { + log::TagError(log_tag, u"Invalid text delete position."); + return; + } + if (position == static_cast(this->text_.size())) return; + Index next; + Utf16NextCodePoint(this->text_, position, &next); + this->DeleteText(TextRange::FromTwoSides(position, next), stop_composition); +} + +// Return the position of deleted character. +gsl::index TextHostControlService::DeleteCharPrevious(gsl::index position, + bool stop_composition) { + if (!Utf16IsValidInsertPosition(this->text_, position)) { + log::TagError(log_tag, u"Invalid text delete position."); + return 0; + } + if (position == 0) return 0; + Index previous; + Utf16PreviousCodePoint(this->text_, position, &previous); + this->DeleteText(TextRange::FromTwoSides(previous, position), + stop_composition); + return previous; +} + +void TextHostControlService::DeleteText(TextRange range, + bool stop_composition) { + if (range.count == 0) return; + range = range.Normalize(); + if (!Utf16IsValidInsertPosition(this->text_, range.GetStart())) { + log::TagError(log_tag, u"Invalid text delete start position."); + return; + } + if (!Utf16IsValidInsertPosition(this->text_, range.GetStart())) { + log::TagError(log_tag, u"Invalid text delete end position."); + return; + } + this->text_.erase(this->text_.cbegin() + range.GetStart(), + this->text_.cbegin() + range.GetEnd()); + this->CoerceSelection(); + if (stop_composition) { + CancelComposition(); + } + this->SyncTextRenderObject(); +} + +platform::gui::IInputMethodContext* +TextHostControlService ::GetInputMethodContext() { + host::WindowHost* host = this->control_->GetWindowHost(); + if (!host) return nullptr; + platform::gui::INativeWindow* native_window = host->GetNativeWindow(); + if (!native_window) return nullptr; + return native_window->GetInputMethodContext(); +} + +void TextHostControlService::CancelComposition() { + auto input_method_context = GetInputMethodContext(); + if (input_method_context == nullptr) return; + input_method_context->CancelComposition(); +} + +std::optional +TextHostControlService::GetCompositionInfo() { + auto input_method_context = GetInputMethodContext(); + if (input_method_context == nullptr) return std::nullopt; + auto composition_info = input_method_context->GetCompositionText(); + if (composition_info.text.empty()) return std::nullopt; + return composition_info; +} + +void TextHostControlService::SetCaretVisible(bool visible) { + if (visible == this->caret_visible_) return; + + this->caret_visible_ = visible; + + if (this->enable_) { + if (visible) { + this->SetupCaret(); + } else { + this->TearDownCaret(); + } + } +} + +void TextHostControlService::SetCaretBlinkDuration(int milliseconds) { + if (this->caret_blink_duration_ == milliseconds) return; + + if (this->enable_ && this->caret_visible_) { + this->TearDownCaret(); + this->SetupCaret(); + } +} + +void TextHostControlService::ScrollToCaret() { + if (const auto scroll_render_object = this->GetScrollRenderObject()) { + this->control_->GetWindowHost()->RunAfterLayoutStable( + [this, scroll_render_object]() { + const auto caret_rect = this->GetTextRenderObject()->GetCaretRect(); + scroll_render_object->ScrollToContain(caret_rect, Thickness{5.f}); + }); + } +} + +gsl::not_null +TextHostControlService::GetTextRenderObject() { + return this->text_host_control_->GetTextRenderObject(); +} + +render::ScrollRenderObject* TextHostControlService::GetScrollRenderObject() { + return this->text_host_control_->GetScrollRenderObject(); +} + +void TextHostControlService::SetSelection(gsl::index caret_position) { + this->SetSelection(TextRange{caret_position, 0}); +} + +void TextHostControlService::SetSelection(TextRange selection, + bool scroll_to_caret) { + this->selection_ = selection; + CoerceSelection(); + SyncTextRenderObject(); + if (scroll_to_caret) { + this->ScrollToCaret(); + } +} + +void TextHostControlService::ReplaceSelectedText(std::u16string_view text) { + DeleteSelectedText(); + InsertText(GetSelection().GetStart(), text); + SetSelection(GetSelection().GetStart() + text.size()); +} + +void TextHostControlService::DeleteSelectedText() { + this->DeleteText(GetSelection()); + SetSelection(GetSelection().Normalize().GetStart()); +} + +void TextHostControlService::SetupCaret() { + const auto application = GetUiApplication(); + this->GetTextRenderObject()->SetDrawCaret(true); + this->caret_timer_canceler_.Reset(application->SetInterval( + std::chrono::milliseconds(this->caret_blink_duration_), + [this] { this->GetTextRenderObject()->ToggleDrawCaret(); })); +} + +void TextHostControlService::TearDownCaret() { + this->caret_timer_canceler_.Reset(); + this->GetTextRenderObject()->SetDrawCaret(false); +} + +void TextHostControlService::CoerceSelection() { + this->selection_ = this->selection_.CoerceInto(0, text_.size()); +} + +void TextHostControlService::StartSelection(Index start) { + SetSelection(start); + if constexpr (debug_flags::text_service) + log::TagDebug(log_tag, u"Text selection started, position: {}.", start); +} + +void TextHostControlService::UpdateSelection(Index new_end) { + auto selection = GetSelection(); + selection.ChangeEnd(new_end); + this->SetSelection(selection); + if constexpr (debug_flags::text_service) + log::TagDebug(log_tag, u"Text selection updated, range: {}, {}.", + selection.GetStart(), selection.GetEnd()); +} + +void TextHostControlService::SyncTextRenderObject() { + const auto text_render_object = this->GetTextRenderObject(); + const auto composition_info = this->GetCompositionInfo(); + if (composition_info) { + const auto caret_position = GetCaretPosition(); + auto text = this->text_; + text.insert(caret_position, composition_info->text); + text_render_object->SetText(text); + text_render_object->SetCaretPosition(caret_position + + composition_info->selection.GetEnd()); + auto selection = composition_info->selection; + selection.position += caret_position; + text_render_object->SetSelectionRange(selection); + } else { + text_render_object->SetText(this->text_); + text_render_object->SetCaretPosition(this->GetCaretPosition()); + text_render_object->SetSelectionRange(this->GetSelection()); + } +} + +void TextHostControlService::AbortSelection() { + if (this->mouse_move_selecting_) { + this->control_->ReleaseMouse(); + this->mouse_move_selecting_ = false; + } + this->GetTextRenderObject()->SetSelectionRange(std::nullopt); +} + +void TextHostControlService::UpdateInputMethodPosition() { + if (auto input_method_context = this->GetInputMethodContext()) { + Point right_bottom = + this->GetTextRenderObject()->GetTotalOffset() + + this->GetTextRenderObject()->GetCaretRect().GetRightBottom(); + right_bottom.x += 5; + right_bottom.y += 5; + + if constexpr (debug_flags::text_service) { + log::TagDebug(log_tag, + u"Calculate input method candidate window position: {}.", + right_bottom.ToDebugString()); + } + + input_method_context->SetCandidateWindowPosition(right_bottom); + } +} + +void TextHostControlService::TearDownHandlers() { + event_guard_.Clear(); + shortcut_hub_.Uninstall(); +} +void TextHostControlService::SetUpHandlers() { + Expects(event_guard_.IsEmpty()); + + SetupOneHandler(&Control::MouseMoveEvent, + &TextHostControlService::MouseMoveHandler); + SetupOneHandler(&Control::MouseDownEvent, + &TextHostControlService::MouseDownHandler); + SetupOneHandler(&Control::MouseUpEvent, + &TextHostControlService::MouseUpHandler); + SetupOneHandler(&Control::KeyDownEvent, + &TextHostControlService::KeyDownHandler); + SetupOneHandler(&Control::GainFocusEvent, + &TextHostControlService::GainFocusHandler); + SetupOneHandler(&Control::LoseFocusEvent, + &TextHostControlService::LoseFocusHandler); + + shortcut_hub_.Install(control_); +} + +void TextHostControlService::MouseDownHandler( + event::MouseButtonEventArgs& args) { + if (this->mouse_move_selecting_) { + return; + } else { + this->control_->SetFocus(); + if (!this->control_->CaptureMouse()) return; + this->mouse_move_selecting_ = true; + const auto text_render_object = this->GetTextRenderObject(); + const auto result = text_render_object->TextHitTest( + args.GetPointToContent(text_render_object)); + const auto position = result.position + (result.trailing ? 1 : 0); + StartSelection(position); + } +} + +void TextHostControlService::MouseUpHandler(event::MouseButtonEventArgs&) { + if (mouse_move_selecting_) { + this->control_->ReleaseMouse(); + this->mouse_move_selecting_ = false; + } +} + +void TextHostControlService::MouseMoveHandler(event::MouseEventArgs& args) { + if (this->mouse_move_selecting_) { + const auto text_render_object = this->GetTextRenderObject(); + const auto result = text_render_object->TextHitTest( + args.GetPointToContent(text_render_object)); + const auto position = result.position + (result.trailing ? 1 : 0); + UpdateSelection(position); + } +} + +void TextHostControlService::KeyDownHandler(event::KeyEventArgs& args) { + const auto key_code = args.GetKeyCode(); + using cru::platform::gui::KeyCode; + using cru::platform::gui::KeyModifiers; + + switch (key_code) { + case KeyCode::Backspace: { + if (!IsEditable()) return; + const auto selection = GetSelection(); + if (selection.count == 0) { + SetSelection(DeleteCharPrevious(GetCaretPosition())); + } else { + this->DeleteSelectedText(); + } + } break; + case KeyCode::Delete: { + if (!IsEditable()) return; + const auto selection = GetSelection(); + if (selection.count == 0) { + DeleteChar(GetCaretPosition()); + } else { + this->DeleteSelectedText(); + } + } break; + case KeyCode::Left: { + const auto key_modifier = args.GetKeyModifier(); + const bool shift = key_modifier & KeyModifiers::shift; + auto text = this->GetTextView(); + if (shift) { + auto selection = this->GetSelection(); + gsl::index new_position; + Utf16PreviousCodePoint(text, selection.GetEnd(), &new_position); + selection.ChangeEnd(new_position); + this->SetSelection(selection); + } else { + const auto caret = this->GetCaretPosition(); + gsl::index new_position; + Utf16PreviousCodePoint(text, caret, &new_position); + this->SetSelection(new_position); + } + } break; + case KeyCode::Right: { + const auto key_modifier = args.GetKeyModifier(); + const bool shift = key_modifier & KeyModifiers::shift; + auto text = this->GetTextView(); + if (shift) { + auto selection = this->GetSelection(); + gsl::index new_position; + Utf16NextCodePoint(text, selection.GetEnd(), &new_position); + selection.ChangeEnd(new_position); + this->SetSelection(selection); + } else { + const auto caret = this->GetCaretPosition(); + gsl::index new_position; + Utf16NextCodePoint(text, caret, &new_position); + this->SetSelection(new_position); + } + } break; + default: + break; + } +} + +void TextHostControlService::GainFocusHandler( + event::FocusChangeEventArgs& args) { + CRU_UNUSED(args); + if (editable_) { + auto input_method_context = GetInputMethodContext(); + if (input_method_context == nullptr) return; + input_method_context->EnableIME(); + auto sync = [this](std::nullptr_t) { + this->SyncTextRenderObject(); + ScrollToCaret(); + }; + input_method_context_event_guard_ += + input_method_context->CompositionStartEvent()->AddHandler( + [this](std::nullptr_t) { this->DeleteSelectedText(); }); + input_method_context_event_guard_ += + input_method_context->CompositionEvent()->AddHandler(sync); + input_method_context_event_guard_ += + input_method_context->CompositionEndEvent()->AddHandler(sync); + input_method_context_event_guard_ += + input_method_context->TextEvent()->AddHandler( + [this](const std::u16string_view& text) { + if (text == u"\b") return; + this->ReplaceSelectedText(text); + }); + + host::WindowHost* window_host = control_->GetWindowHost(); + if (window_host) + input_method_context_event_guard_ += + window_host->AfterLayoutEvent()->AddHandler( + [this](auto) { this->UpdateInputMethodPosition(); }); + SetCaretVisible(true); + } +} + +void TextHostControlService::LoseFocusHandler( + event::FocusChangeEventArgs& args) { + if (!args.IsWindow()) this->AbortSelection(); + input_method_context_event_guard_.Clear(); + auto input_method_context = GetInputMethodContext(); + if (input_method_context) { + input_method_context->DisableIME(); + } + SetCaretVisible(false); + SyncTextRenderObject(); +} +} // namespace cru::ui::controls -- cgit v1.2.3 From 715be3c81b96fcf87c7650501d71480a8743a984 Mon Sep 17 00:00:00 2001 From: crupest Date: Sat, 26 Dec 2020 19:39:24 +0800 Subject: ... --- include/cru/platform/gui/Keyboard.hpp | 1 + include/cru/ui/controls/TextHostControlService.hpp | 16 +- include/cru/ui/helper/ShortcutHub.hpp | 9 +- src/ui/controls/TextHostControlService.cpp | 242 ++++++++++----------- src/ui/helper/ShortcutHub.cpp | 13 +- 5 files changed, 143 insertions(+), 138 deletions(-) (limited to 'include/cru/ui/controls') diff --git a/include/cru/platform/gui/Keyboard.hpp b/include/cru/platform/gui/Keyboard.hpp index e12cccda..6c29239b 100644 --- a/include/cru/platform/gui/Keyboard.hpp +++ b/include/cru/platform/gui/Keyboard.hpp @@ -116,6 +116,7 @@ struct TagKeyModifier {}; using KeyModifier = Bitmask; struct KeyModifiers { + static constexpr KeyModifier none{0}; static constexpr KeyModifier shift{0b1}; static constexpr KeyModifier ctrl{0b10}; static constexpr KeyModifier alt{0b100}; diff --git a/include/cru/ui/controls/TextHostControlService.hpp b/include/cru/ui/controls/TextHostControlService.hpp index 0bea52c8..9e6a08bc 100644 --- a/include/cru/ui/controls/TextHostControlService.hpp +++ b/include/cru/ui/controls/TextHostControlService.hpp @@ -70,8 +70,10 @@ class TextHostControlService : public Object { void SetSelection(gsl::index caret_position); void SetSelection(TextRange selection, bool scroll_to_caret = true); - void DeleteSelectedText(); + 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); @@ -92,10 +94,6 @@ class TextHostControlService : public Object { void SyncTextRenderObject(); - void StartSelection(Index start); - void UpdateSelection(Index new_end); - void AbortSelection(); - void UpdateInputMethodPosition(); template @@ -106,16 +104,14 @@ class TextHostControlService : public Object { std::bind(handler, this, std::placeholders::_1)); } - void SetUpHandlers(); - void TearDownHandlers(); - void MouseMoveHandler(event::MouseEventArgs& args); void MouseDownHandler(event::MouseButtonEventArgs& args); void MouseUpHandler(event::MouseButtonEventArgs& args); - void KeyDownHandler(event::KeyEventArgs& args); void GainFocusHandler(event::FocusChangeEventArgs& args); void LoseFocusHandler(event::FocusChangeEventArgs& args); + void SetUpShortcuts(); + private: gsl::not_null control_; gsl::not_null text_host_control_; @@ -136,6 +132,6 @@ class TextHostControlService : public Object { helper::ShortcutHub shortcut_hub_; // true if left mouse is down and selecting - bool mouse_move_selecting_; + bool mouse_move_selecting_ = false; }; } // namespace cru::ui::controls diff --git a/include/cru/ui/helper/ShortcutHub.hpp b/include/cru/ui/helper/ShortcutHub.hpp index a4ff2da2..fe3414fe 100644 --- a/include/cru/ui/helper/ShortcutHub.hpp +++ b/include/cru/ui/helper/ShortcutHub.hpp @@ -19,8 +19,9 @@ namespace cru::ui::helper { class ShortcutKeyBind { public: - ShortcutKeyBind(platform::gui::KeyCode key, - platform::gui::KeyModifier modifier) + ShortcutKeyBind( + platform::gui::KeyCode key, + platform::gui::KeyModifier modifier = platform::gui::KeyModifiers::none) : key_(key), modifier_(modifier) {} CRU_DEFAULT_COPY(ShortcutKeyBind) @@ -111,6 +112,8 @@ class ShortcutHub : public Object { const std::vector& GetShortcutByKeyBind( const ShortcutKeyBind& key_bind) const; + IEvent* FallbackKeyEvent() { return &fallback_event_; } + void Install(controls::Control* control); void Uninstall(); @@ -124,6 +127,8 @@ class ShortcutHub : public Object { int current_id_ = 1; + Event fallback_event_; + EventRevokerListGuard event_guard_; }; } // namespace cru::ui::helper diff --git a/src/ui/controls/TextHostControlService.cpp b/src/ui/controls/TextHostControlService.cpp index 7126f7b7..91ec53ff 100644 --- a/src/ui/controls/TextHostControlService.cpp +++ b/src/ui/controls/TextHostControlService.cpp @@ -3,11 +3,15 @@ #include "../Helper.hpp" #include "cru/common/Logger.hpp" #include "cru/common/StringUtil.hpp" +#include "cru/platform/gui/Base.hpp" #include "cru/platform/gui/Cursor.hpp" #include "cru/platform/gui/InputMethod.hpp" +#include "cru/platform/gui/Keyboard.hpp" #include "cru/platform/gui/UiApplication.hpp" #include "cru/platform/gui/Window.hpp" +#include "cru/ui/Base.hpp" #include "cru/ui/DebugFlags.hpp" +#include "cru/ui/events/UiEvent.hpp" #include "cru/ui/helper/ShortcutHub.hpp" #include "cru/ui/host/WindowHost.hpp" #include "cru/ui/render/ScrollRenderObject.hpp" @@ -16,13 +20,27 @@ namespace cru::ui::controls { TextHostControlService::TextHostControlService(gsl::not_null control) : control_(control), - text_host_control_(dynamic_cast(control.get())) {} + text_host_control_(dynamic_cast(control.get())) { + SetUpShortcuts(); + + SetupOneHandler(&Control::MouseMoveEvent, + &TextHostControlService::MouseMoveHandler); + SetupOneHandler(&Control::MouseDownEvent, + &TextHostControlService::MouseDownHandler); + SetupOneHandler(&Control::MouseUpEvent, + &TextHostControlService::MouseUpHandler); + SetupOneHandler(&Control::GainFocusEvent, + &TextHostControlService::GainFocusHandler); + SetupOneHandler(&Control::LoseFocusEvent, + &TextHostControlService::LoseFocusHandler); + + shortcut_hub_.Install(control_); +} void TextHostControlService::SetEnabled(bool enable) { if (enable == this->enable_) return; this->enable_ = enable; if (enable) { - this->SetUpHandlers(); if (this->caret_visible_) { this->SetupCaret(); } @@ -31,7 +49,6 @@ void TextHostControlService::SetEnabled(bool enable) { platform::gui::SystemCursorType::IBeam)); } else { this->AbortSelection(); - this->TearDownHandlers(); this->TearDownCaret(); this->control_->SetCursor(nullptr); } @@ -194,6 +211,20 @@ void TextHostControlService::SetSelection(TextRange selection, } } +void TextHostControlService::ChangeSelectionEnd(Index new_end) { + auto selection = GetSelection(); + selection.ChangeEnd(new_end); + this->SetSelection(selection); +} + +void TextHostControlService::AbortSelection() { + if (this->mouse_move_selecting_) { + this->control_->ReleaseMouse(); + this->mouse_move_selecting_ = false; + } + SetSelection(GetCaretPosition()); +} + void TextHostControlService::ReplaceSelectedText(std::u16string_view text) { DeleteSelectedText(); InsertText(GetSelection().GetStart(), text); @@ -222,21 +253,6 @@ void TextHostControlService::CoerceSelection() { this->selection_ = this->selection_.CoerceInto(0, text_.size()); } -void TextHostControlService::StartSelection(Index start) { - SetSelection(start); - if constexpr (debug_flags::text_service) - log::TagDebug(log_tag, u"Text selection started, position: {}.", start); -} - -void TextHostControlService::UpdateSelection(Index new_end) { - auto selection = GetSelection(); - selection.ChangeEnd(new_end); - this->SetSelection(selection); - if constexpr (debug_flags::text_service) - log::TagDebug(log_tag, u"Text selection updated, range: {}, {}.", - selection.GetStart(), selection.GetEnd()); -} - void TextHostControlService::SyncTextRenderObject() { const auto text_render_object = this->GetTextRenderObject(); const auto composition_info = this->GetCompositionInfo(); @@ -257,14 +273,6 @@ void TextHostControlService::SyncTextRenderObject() { } } -void TextHostControlService::AbortSelection() { - if (this->mouse_move_selecting_) { - this->control_->ReleaseMouse(); - this->mouse_move_selecting_ = false; - } - this->GetTextRenderObject()->SetSelectionRange(std::nullopt); -} - void TextHostControlService::UpdateInputMethodPosition() { if (auto input_method_context = this->GetInputMethodContext()) { Point right_bottom = @@ -283,47 +291,25 @@ void TextHostControlService::UpdateInputMethodPosition() { } } -void TextHostControlService::TearDownHandlers() { - event_guard_.Clear(); - shortcut_hub_.Uninstall(); -} -void TextHostControlService::SetUpHandlers() { - Expects(event_guard_.IsEmpty()); - - SetupOneHandler(&Control::MouseMoveEvent, - &TextHostControlService::MouseMoveHandler); - SetupOneHandler(&Control::MouseDownEvent, - &TextHostControlService::MouseDownHandler); - SetupOneHandler(&Control::MouseUpEvent, - &TextHostControlService::MouseUpHandler); - SetupOneHandler(&Control::KeyDownEvent, - &TextHostControlService::KeyDownHandler); - SetupOneHandler(&Control::GainFocusEvent, - &TextHostControlService::GainFocusHandler); - SetupOneHandler(&Control::LoseFocusEvent, - &TextHostControlService::LoseFocusHandler); - - shortcut_hub_.Install(control_); -} - void TextHostControlService::MouseDownHandler( event::MouseButtonEventArgs& args) { - if (this->mouse_move_selecting_) { - return; - } else { + if (IsEnabled()) { this->control_->SetFocus(); - if (!this->control_->CaptureMouse()) return; - this->mouse_move_selecting_ = true; - const auto text_render_object = this->GetTextRenderObject(); - const auto result = text_render_object->TextHitTest( - args.GetPointToContent(text_render_object)); - const auto position = result.position + (result.trailing ? 1 : 0); - StartSelection(position); + if (args.GetButton() == mouse_buttons::left && + !this->mouse_move_selecting_) { + if (!this->control_->CaptureMouse()) return; + this->mouse_move_selecting_ = true; + const auto text_render_object = this->GetTextRenderObject(); + const auto result = text_render_object->TextHitTest( + args.GetPointToContent(text_render_object)); + const auto position = result.position + (result.trailing ? 1 : 0); + SetSelection(position); + } } } -void TextHostControlService::MouseUpHandler(event::MouseButtonEventArgs&) { - if (mouse_move_selecting_) { +void TextHostControlService::MouseUpHandler(event::MouseButtonEventArgs& args) { + if (args.GetButton() == mouse_buttons::left && mouse_move_selecting_) { this->control_->ReleaseMouse(); this->mouse_move_selecting_ = false; } @@ -335,70 +321,7 @@ void TextHostControlService::MouseMoveHandler(event::MouseEventArgs& args) { const auto result = text_render_object->TextHitTest( args.GetPointToContent(text_render_object)); const auto position = result.position + (result.trailing ? 1 : 0); - UpdateSelection(position); - } -} - -void TextHostControlService::KeyDownHandler(event::KeyEventArgs& args) { - const auto key_code = args.GetKeyCode(); - using cru::platform::gui::KeyCode; - using cru::platform::gui::KeyModifiers; - - switch (key_code) { - case KeyCode::Backspace: { - if (!IsEditable()) return; - const auto selection = GetSelection(); - if (selection.count == 0) { - SetSelection(DeleteCharPrevious(GetCaretPosition())); - } else { - this->DeleteSelectedText(); - } - } break; - case KeyCode::Delete: { - if (!IsEditable()) return; - const auto selection = GetSelection(); - if (selection.count == 0) { - DeleteChar(GetCaretPosition()); - } else { - this->DeleteSelectedText(); - } - } break; - case KeyCode::Left: { - const auto key_modifier = args.GetKeyModifier(); - const bool shift = key_modifier & KeyModifiers::shift; - auto text = this->GetTextView(); - if (shift) { - auto selection = this->GetSelection(); - gsl::index new_position; - Utf16PreviousCodePoint(text, selection.GetEnd(), &new_position); - selection.ChangeEnd(new_position); - this->SetSelection(selection); - } else { - const auto caret = this->GetCaretPosition(); - gsl::index new_position; - Utf16PreviousCodePoint(text, caret, &new_position); - this->SetSelection(new_position); - } - } break; - case KeyCode::Right: { - const auto key_modifier = args.GetKeyModifier(); - const bool shift = key_modifier & KeyModifiers::shift; - auto text = this->GetTextView(); - if (shift) { - auto selection = this->GetSelection(); - gsl::index new_position; - Utf16NextCodePoint(text, selection.GetEnd(), &new_position); - selection.ChangeEnd(new_position); - this->SetSelection(selection); - } else { - const auto caret = this->GetCaretPosition(); - gsl::index new_position; - Utf16NextCodePoint(text, caret, &new_position); - this->SetSelection(new_position); - } - } break; - default: - break; + ChangeSelectionEnd(position); } } @@ -447,4 +370,73 @@ void TextHostControlService::LoseFocusHandler( SetCaretVisible(false); SyncTextRenderObject(); } + +void TextHostControlService::SetUpShortcuts() { + using platform::gui::KeyCode; + using platform::gui::KeyModifiers; + + shortcut_hub_.RegisterShortcut(u"Backspace", KeyCode::Backspace, [this] { + if (!IsEnabled()) return false; + if (!IsEditable()) return false; + const auto selection = GetSelection(); + if (selection.count == 0) { + SetSelection(DeleteCharPrevious(GetCaretPosition())); + } else { + this->DeleteSelectedText(); + } + return true; + }); + + shortcut_hub_.RegisterShortcut(u"Delete", KeyCode::Delete, [this] { + if (!IsEnabled()) return false; + if (!IsEditable()) return false; + const auto selection = GetSelection(); + if (selection.count == 0) { + DeleteChar(GetCaretPosition()); + } else { + this->DeleteSelectedText(); + } + return true; + }); + + shortcut_hub_.RegisterShortcut(u"Left", KeyCode::Left, [this] { + auto text = this->GetTextView(); + const auto caret = this->GetCaretPosition(); + gsl::index new_position; + Utf16PreviousCodePoint(text, caret, &new_position); + this->SetSelection(new_position); + return true; + }); + + shortcut_hub_.RegisterShortcut( + u"ShiftLeft", {KeyCode::Left, KeyModifiers::shift}, [this] { + auto text = this->GetTextView(); + auto selection = this->GetSelection(); + gsl::index new_position; + Utf16PreviousCodePoint(text, selection.GetEnd(), &new_position); + selection.ChangeEnd(new_position); + this->SetSelection(selection); + return true; + }); + + shortcut_hub_.RegisterShortcut(u"Right", KeyCode::Right, [this] { + auto text = this->GetTextView(); + const auto caret = this->GetCaretPosition(); + gsl::index new_position; + Utf16NextCodePoint(text, caret, &new_position); + this->SetSelection(new_position); + return true; + }); + + shortcut_hub_.RegisterShortcut( + u"ShiftRight", {KeyCode::Right, KeyModifiers::shift}, [this] { + auto text = this->GetTextView(); + auto selection = this->GetSelection(); + gsl::index new_position; + Utf16NextCodePoint(text, selection.GetEnd(), &new_position); + selection.ChangeEnd(new_position); + this->SetSelection(selection); + return true; + }); +} } // namespace cru::ui::controls diff --git a/src/ui/helper/ShortcutHub.cpp b/src/ui/helper/ShortcutHub.cpp index 823072f2..f35ad0ef 100644 --- a/src/ui/helper/ShortcutHub.cpp +++ b/src/ui/helper/ShortcutHub.cpp @@ -85,6 +85,8 @@ void ShortcutHub::OnKeyDown(event::KeyEventArgs& event) { ShortcutKeyBind key_bind(event.GetKeyCode(), event.GetKeyModifier()); const auto& shortcut_list = this->GetShortcutByKeyBind(key_bind); + bool handled = false; + if constexpr (debug_flags::shortcut) { if (shortcut_list.empty()) { log::Debug(u"No shortcut for key bind {}.", key_bind.ToString()); @@ -100,12 +102,13 @@ void ShortcutHub::OnKeyDown(event::KeyEventArgs& event) { log::Debug(u"Handle {} handled it.", shortcut.name); } + handled = true; event.SetHandled(); break; } else { if constexpr (debug_flags::shortcut) { - log::Debug(u"Handle {} disdn't handle it.", shortcut.name); + log::Debug(u"Handle {} didn't handle it.", shortcut.name); } } } @@ -116,5 +119,13 @@ void ShortcutHub::OnKeyDown(event::KeyEventArgs& event) { key_bind.ToString()); } } + + if (!handled) { + if constexpr (debug_flags::shortcut) { + log::Debug(u"Raise fallback event for unhandled shortcut of key bind {}.", + key_bind.ToString()); + } + fallback_event_.Raise(event); + } } } // namespace cru::ui::helper -- cgit v1.2.3 From 4b78e0b74f70bca2e24dc89b4fdca4dc9222c8b9 Mon Sep 17 00:00:00 2001 From: crupest Date: Sun, 28 Feb 2021 00:22:34 +0800 Subject: ... --- include/cru/platform/graphics/Factory.hpp | 8 +- include/cru/ui/controls/TextHostControlService.hpp | 2 +- include/cru/ui/events/UiEvent.hpp | 1 + include/cru/ui/render/ScrollBar.hpp | 93 ++++- include/cru/ui/render/ScrollRenderObject.hpp | 15 + src/ui/events/UiEvent.cpp | 4 + src/ui/render/ScrollBar.cpp | 446 ++++++++++++++++++--- src/ui/render/ScrollRenderObject.cpp | 33 ++ 8 files changed, 532 insertions(+), 70 deletions(-) (limited to 'include/cru/ui/controls') diff --git a/include/cru/platform/graphics/Factory.hpp b/include/cru/platform/graphics/Factory.hpp index d1b37783..f9018e13 100644 --- a/include/cru/platform/graphics/Factory.hpp +++ b/include/cru/platform/graphics/Factory.hpp @@ -21,5 +21,11 @@ struct IGraphFactory : virtual INativeResource { virtual std::unique_ptr CreateTextLayout( std::shared_ptr font, std::u16string text) = 0; + + std::unique_ptr CreateSolidColorBrush(const Color& color) { + std::unique_ptr brush = CreateSolidColorBrush(); + brush->SetColor(color); + return brush; + } }; -} // namespace cru::platform::graph +} // namespace cru::platform::graphics diff --git a/include/cru/ui/controls/TextHostControlService.hpp b/include/cru/ui/controls/TextHostControlService.hpp index 9e6a08bc..340228fe 100644 --- a/include/cru/ui/controls/TextHostControlService.hpp +++ b/include/cru/ui/controls/TextHostControlService.hpp @@ -100,7 +100,7 @@ class TextHostControlService : public Object { void SetupOneHandler(event::RoutedEvent* (Control::*event)(), void (TextHostControlService::*handler)( typename event::RoutedEvent::EventArgs)) { - this->event_guard_ += (this->control_->*event)()->Direct()->AddHandler( + this->event_guard_ += (this->control_->*event)()->Bubble()->AddHandler( std::bind(handler, this, std::placeholders::_1)); } diff --git a/include/cru/ui/events/UiEvent.hpp b/include/cru/ui/events/UiEvent.hpp index 660b33f5..22ad0150 100644 --- a/include/cru/ui/events/UiEvent.hpp +++ b/include/cru/ui/events/UiEvent.hpp @@ -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: diff --git a/include/cru/ui/render/ScrollBar.hpp b/include/cru/ui/render/ScrollBar.hpp index a9be49a3..e3dabb57 100644 --- a/include/cru/ui/render/ScrollBar.hpp +++ b/include/cru/ui/render/ScrollBar.hpp @@ -15,25 +15,27 @@ namespace cru::ui::render { class ScrollRenderObject; -enum class ScrollKind { Absolute, Page, Line }; +enum class ScrollKind { Absolute, Relative, Page, Line }; struct Scroll { Direction direction; ScrollKind kind; - float offset; + // For absolute, the new scroll position. Otherwise, offset. + float value; }; enum class ScrollBarAreaKind { UpArrow, // Line up DownArrow, // Line down - UpThumb, // Page up - DownThumb, // Page down + UpSlot, // Page up + DownSlot, // Page down Thumb }; class ScrollBar : public Object { public: - explicit ScrollBar(gsl::not_null render_object); + ScrollBar(gsl::not_null render_object, + Direction direction); CRU_DELETE_COPY(ScrollBar) CRU_DELETE_MOVE(ScrollBar) @@ -41,12 +43,15 @@ class ScrollBar : public Object { ~ScrollBar() override = default; public: + Direction GetDirection() const { return direction_; } + bool IsEnabled() const { return is_enabled_; } void SetEnabled(bool value); - void Draw(platform::graphics::IPainter* painter); + bool IsExpanded() const { return is_expanded_; } + void SetExpanded(bool value); - virtual std::optional HitTest(const Point& point) = 0; + void Draw(platform::graphics::IPainter* painter); IEvent* ScrollAttemptEvent() { return &scroll_attempt_event_; } @@ -54,20 +59,54 @@ class ScrollBar : public Object { void UninstallHandlers() { InstallHandlers(nullptr); } gsl::not_null> - GetCollapseThumbBrush() const; + GetCollapsedThumbBrush() const; + gsl::not_null> + GetExpandedThumbBrush() const; + gsl::not_null> + GetExpandedSlotBrush() const; + gsl::not_null> + GetExpandedArrowBrush() const; + gsl::not_null> + GetExpandedArrowBackgroundBrush() const; protected: - virtual void OnDraw(platform::graphics::IPainter* painter, bool expand) = 0; + 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 ExpandedHitTest(const Point& point); + + virtual bool IsShowBar() = 0; + + virtual std::optional GetExpandedAreaRect( + ScrollBarAreaKind area_kind) = 0; + virtual std::optional GetCollapsedTriggerExpandAreaRect() = 0; + virtual std::optional GetCollapsedThumbRect() = 0; + + virtual float CalculateNewScrollPosition(const Rect& thumb_original_rect, + const Point& mouse_offset) = 0; protected: gsl::not_null render_object_; private: + Direction direction_; + bool is_enabled_ = true; bool is_expanded_ = false; - std::shared_ptr collapse_thumb_brush_; + std::shared_ptr collapsed_thumb_brush_; + std::shared_ptr expanded_thumb_brush_; + std::shared_ptr expanded_slot_brush_; + std::shared_ptr expanded_arrow_brush_; + std::shared_ptr expanded_arrow_background_brush_; + + Rect move_thumb_thumb_original_rect_; + std::optional move_thumb_start_; EventRevokerListGuard event_guard_; @@ -84,11 +123,20 @@ class HorizontalScrollBar : public ScrollBar { ~HorizontalScrollBar() override = default; - public: - std::optional HitTest(const Point& point) override; - protected: - void OnDraw(platform::graphics::IPainter* painter, bool expand) override; + 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 GetExpandedAreaRect(ScrollBarAreaKind area_kind) override; + std::optional GetCollapsedTriggerExpandAreaRect() override; + std::optional GetCollapsedThumbRect() override; + + float CalculateNewScrollPosition(const Rect& thumb_original_rect, + const Point& mouse_offset) override; }; class VerticalScrollBar : public ScrollBar { @@ -100,11 +148,20 @@ class VerticalScrollBar : public ScrollBar { ~VerticalScrollBar() override = default; - public: - std::optional HitTest(const Point& point) override; - protected: - void OnDraw(platform::graphics::IPainter* painter, bool expand) override; + 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 GetExpandedAreaRect(ScrollBarAreaKind area_kind) override; + std::optional GetCollapsedTriggerExpandAreaRect() override; + std::optional GetCollapsedThumbRect() override; + + float CalculateNewScrollPosition(const Rect& thumb_original_rect, + const Point& mouse_offset) override; }; // A delegate to draw scrollbar and register related events. diff --git a/include/cru/ui/render/ScrollRenderObject.hpp b/include/cru/ui/render/ScrollRenderObject.hpp index 6a6ef198..aed25f8e 100644 --- a/include/cru/ui/render/ScrollRenderObject.hpp +++ b/include/cru/ui/render/ScrollRenderObject.hpp @@ -2,6 +2,7 @@ #include "RenderObject.hpp" #include "cru/platform/graphics/util/Painter.hpp" +#include "cru/ui/Base.hpp" #include "cru/ui/render/ScrollBar.hpp" #include @@ -29,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 x, std::optional y); + void SetScrollOffset(Direction direction, std::optional 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. diff --git a/src/ui/events/UiEvent.cpp b/src/ui/events/UiEvent.cpp index b35f15a7..4c75f690 100644 --- a/src/ui/events/UiEvent.cpp +++ b/src/ui/events/UiEvent.cpp @@ -3,6 +3,10 @@ #include "cru/ui/render/RenderObject.hpp" namespace cru::ui::event { +Point MouseEventArgs::GetPoint(render::RenderObject* render_object) const { + return GetPoint() - render_object->GetTotalOffset(); +} + Point MouseEventArgs::GetPointToContent( render::RenderObject* render_object) const { return render_object->FromRootToContent(GetPoint()); diff --git a/src/ui/render/ScrollBar.cpp b/src/ui/render/ScrollBar.cpp index 487f0b91..6096ab63 100644 --- a/src/ui/render/ScrollBar.cpp +++ b/src/ui/render/ScrollBar.cpp @@ -5,23 +5,44 @@ #include "cru/platform/GraphBase.hpp" #include "cru/platform/graphics/Factory.hpp" #include "cru/platform/graphics/Painter.hpp" +#include "cru/platform/gui/Base.hpp" +#include "cru/ui/Base.hpp" +#include "cru/ui/events/UiEvent.hpp" #include "cru/ui/render/ScrollRenderObject.hpp" +#include "gsl/gsl_assert" +#include +#include #include #include +#include namespace cru::ui::render { constexpr float kScrollBarCollapseThumbWidth = 2; +constexpr float kScrollBarCollapsedTriggerExpandAreaWidth = 5; +constexpr float kScrollBarExpandWidth = 10; -ScrollBar::ScrollBar(gsl::not_null render_object) - : render_object_(render_object) { +constexpr std::array kScrollBarAreaKindList{ + ScrollBarAreaKind::UpArrow, ScrollBarAreaKind::DownArrow, + ScrollBarAreaKind::UpSlot, ScrollBarAreaKind::DownSlot, + ScrollBarAreaKind::Thumb}; + +ScrollBar::ScrollBar(gsl::not_null render_object, + Direction direction) + : render_object_(render_object), direction_(direction) { // TODO: Use theme resource and delete this. - auto collapse_thumb_brush = GetUiApplication() - ->GetInstance() - ->GetGraphFactory() - ->CreateSolidColorBrush(); - collapse_thumb_brush->SetColor(colors::gray.WithAlpha(128)); - collapse_thumb_brush_ = std::move(collapse_thumb_brush); + + auto graphics_factory = GetUiApplication()->GetInstance()->GetGraphFactory(); + + collapsed_thumb_brush_ = + graphics_factory->CreateSolidColorBrush(colors::gray.WithAlpha(128)); + expanded_thumb_brush_ = graphics_factory->CreateSolidColorBrush(colors::gray); + expanded_slot_brush_ = + graphics_factory->CreateSolidColorBrush(colors::seashell); + expanded_arrow_brush_ = + graphics_factory->CreateSolidColorBrush(colors::white); + expanded_arrow_background_brush_ = + graphics_factory->CreateSolidColorBrush(colors::black); } void ScrollBar::SetEnabled(bool value) { @@ -29,6 +50,12 @@ void ScrollBar::SetEnabled(bool value) { // TODO: Implement this. } +void ScrollBar::SetExpanded(bool value) { + if (is_expanded_ == value) return; + is_expanded_ = value; + render_object_->InvalidatePaint(); +} + void ScrollBar::Draw(platform::graphics::IPainter* painter) { if (is_enabled_) { OnDraw(painter, is_expanded_); @@ -36,88 +63,407 @@ void ScrollBar::Draw(platform::graphics::IPainter* painter) { } void ScrollBar::InstallHandlers(controls::Control* control) { - CRU_UNUSED(control); - // TODO: Implement this. + event_guard_.Clear(); + if (control != nullptr) { + event_guard_ += control->MouseDownEvent()->Bubble()->AddHandler( + [control, this](event::MouseButtonEventArgs& event) { + if (event.GetButton() == mouse_buttons::left && IsEnabled() && + IsExpanded()) { + auto hit_test_result = + ExpandedHitTest(event.GetPoint(render_object_)); + if (!hit_test_result) return; + + switch (*hit_test_result) { + case ScrollBarAreaKind::UpArrow: + this->scroll_attempt_event_.Raise( + {GetDirection(), ScrollKind::Line, -1}); + event.SetHandled(); + break; + case ScrollBarAreaKind::DownArrow: + this->scroll_attempt_event_.Raise( + {GetDirection(), ScrollKind::Line, 1}); + event.SetHandled(); + break; + case ScrollBarAreaKind::UpSlot: + this->scroll_attempt_event_.Raise( + {GetDirection(), ScrollKind::Page, -1}); + event.SetHandled(); + break; + case ScrollBarAreaKind::DownSlot: + this->scroll_attempt_event_.Raise( + {GetDirection(), ScrollKind::Page, 1}); + event.SetHandled(); + break; + case ScrollBarAreaKind::Thumb: { + auto thumb_rect = GetExpandedAreaRect(ScrollBarAreaKind::Thumb); + assert(thumb_rect); + + if (!control->CaptureMouse()) break; + move_thumb_thumb_original_rect_ = *thumb_rect; + move_thumb_start_ = event.GetPoint(); + event.SetHandled(); + break; + } + default: + break; + } + } + }); + + event_guard_ += control->MouseUpEvent()->Bubble()->AddHandler( + [control, this](event::MouseButtonEventArgs& event) { + if (event.GetButton() == mouse_buttons::left && move_thumb_start_) { + move_thumb_start_ = std::nullopt; + control->ReleaseMouse(); + event.SetHandled(); + } + }); + + event_guard_ += control->MouseMoveEvent()->Bubble()->AddHandler( + [this](event::MouseEventArgs& event) { + if (move_thumb_start_) { + auto new_scroll_position = CalculateNewScrollPosition( + move_thumb_thumb_original_rect_, + event.GetPoint() - *move_thumb_start_); + + this->scroll_attempt_event_.Raise( + {GetDirection(), ScrollKind::Absolute, new_scroll_position}); + event.SetHandled(); + return; + } + + if (IsEnabled() && !IsExpanded()) { + auto trigger_expand_area = GetCollapsedTriggerExpandAreaRect(); + if (trigger_expand_area && + trigger_expand_area->IsPointInside( + event.GetPoint(this->render_object_))) + SetExpanded(true); + event.SetHandled(); + } + }); + } } gsl::not_null> -ScrollBar::GetCollapseThumbBrush() const { +ScrollBar::GetCollapsedThumbBrush() const { // TODO: Read theme resource. - return collapse_thumb_brush_; + return collapsed_thumb_brush_; } -HorizontalScrollBar::HorizontalScrollBar( - gsl::not_null render_object) - : ScrollBar(render_object) {} +gsl::not_null> +ScrollBar::GetExpandedThumbBrush() const { + // TODO: Read theme resource. + return expanded_thumb_brush_; +} + +gsl::not_null> +ScrollBar::GetExpandedSlotBrush() const { + // TODO: Read theme resource. + return expanded_slot_brush_; +} + +gsl::not_null> +ScrollBar::GetExpandedArrowBrush() const { + // TODO: Read theme resource. + return expanded_arrow_brush_; +} + +gsl::not_null> +ScrollBar::GetExpandedArrowBackgroundBrush() const { + // TODO: Read theme resource. + return expanded_arrow_brush_; +} + +void ScrollBar::OnDraw(platform::graphics::IPainter* painter, + bool is_expanded) { + if (is_expanded) { + auto thumb_rect = GetExpandedAreaRect(ScrollBarAreaKind::Thumb); + if (thumb_rect) + painter->FillRectangle(*thumb_rect, GetExpandedThumbBrush().get().get()); + + auto slot_brush = GetExpandedSlotBrush().get().get(); + + auto up_slot_rect = GetExpandedAreaRect(ScrollBarAreaKind::UpSlot); + if (up_slot_rect) painter->FillRectangle(*up_slot_rect, slot_brush); + + auto down_slot_rect = GetExpandedAreaRect(ScrollBarAreaKind::DownSlot); + if (down_slot_rect) painter->FillRectangle(*down_slot_rect, slot_brush); + + auto up_arrow = GetExpandedAreaRect(ScrollBarAreaKind::UpArrow); + if (up_arrow) this->DrawUpArrow(painter, *up_arrow); -std::optional HorizontalScrollBar::HitTest( + auto down_arrow = GetExpandedAreaRect(ScrollBarAreaKind::DownArrow); + if (down_arrow) this->DrawUpArrow(painter, *down_arrow); + } else { + auto optional_rect = GetCollapsedThumbRect(); + if (optional_rect) { + painter->FillRectangle(*optional_rect, + GetCollapsedThumbBrush().get().get()); + } + } +} + +std::optional ScrollBar::ExpandedHitTest( const Point& point) { - // TODO: Implement this. - CRU_UNUSED(point); + for (auto kind : kScrollBarAreaKindList) { + auto rect = this->GetExpandedAreaRect(kind); + if (rect) { + if (rect->IsPointInside(point)) return kind; + } + } return std::nullopt; } -void HorizontalScrollBar::OnDraw(platform::graphics::IPainter* painter, - bool expand) { +HorizontalScrollBar::HorizontalScrollBar( + gsl::not_null render_object) + : ScrollBar(render_object, Direction::Horizontal) {} + +void HorizontalScrollBar::DrawUpArrow(platform::graphics::IPainter* painter, + const Rect& area) { + // TODO: Do what you must! + painter->FillRectangle(area, GetExpandedArrowBackgroundBrush().get().get()); +} + +void HorizontalScrollBar::DrawDownArrow(platform::graphics::IPainter* painter, + const Rect& area) { + // TODO: Do what you must! + painter->FillRectangle(area, GetExpandedArrowBackgroundBrush().get().get()); +} + +bool HorizontalScrollBar::IsShowBar() { const auto child = render_object_->GetFirstChild(); - if (child == nullptr) return; + if (child == nullptr) return false; const auto view_rect = render_object_->GetViewRect(); + const auto child_size = child->GetSize(); + + if (view_rect.width >= child_size.width) return false; + + return true; +} + +std::optional HorizontalScrollBar::GetExpandedAreaRect( + ScrollBarAreaKind area_kind) { + auto show = IsShowBar(); + if (!show) return std::nullopt; + const auto padding_rect = render_object_->GetPaddingRect(); + + const auto child = render_object_->GetFirstChild(); + + const auto view_rect = render_object_->GetViewRect(); const auto child_size = child->GetSize(); - if (view_rect.width >= child_size.width) return; + const float start_percentage = view_rect.left / child_size.width; + const float length_percentage = view_rect.width / child_size.width; + const float end_percentage = start_percentage + length_percentage; + + const float top = padding_rect.GetBottom() - kScrollBarExpandWidth; + const float height = kScrollBarExpandWidth; + + // Without arrow. + const float bar_area_length = padding_rect.width - 3 * kScrollBarExpandWidth; + const float bar_area_start = padding_rect.left + kScrollBarExpandWidth; + + switch (area_kind) { + case ScrollBarAreaKind::UpArrow: + return Rect{padding_rect.left, top, kScrollBarExpandWidth, height}; + case ScrollBarAreaKind::DownArrow: + return Rect{padding_rect.GetRight() - 2 * kScrollBarExpandWidth, top, + kScrollBarExpandWidth, height}; + case ScrollBarAreaKind::UpSlot: + return Rect{bar_area_start, top, bar_area_length * start_percentage, + height}; + case ScrollBarAreaKind::DownSlot: + return Rect{bar_area_start + bar_area_length * end_percentage, top, + bar_area_length * (1 - end_percentage), height}; + case ScrollBarAreaKind::Thumb: + return Rect{bar_area_start + bar_area_length * start_percentage, top, + bar_area_length * length_percentage, height}; + default: + throw std::invalid_argument("Unsupported scroll area kind."); + } +} + +std::optional HorizontalScrollBar::GetCollapsedTriggerExpandAreaRect() { + auto show = IsShowBar(); + if (!show) return std::nullopt; + + const auto padding_rect = render_object_->GetPaddingRect(); + + return Rect{ + padding_rect.left, + padding_rect.GetBottom() - kScrollBarCollapsedTriggerExpandAreaWidth, + padding_rect.width, kScrollBarCollapseThumbWidth}; +} + +std::optional HorizontalScrollBar::GetCollapsedThumbRect() { + auto show = IsShowBar(); + if (!show) return std::nullopt; + + const auto child = render_object_->GetFirstChild(); + + const auto view_rect = render_object_->GetViewRect(); + const auto child_size = child->GetSize(); const float start_percentage = view_rect.left / child_size.width; const float length_percentage = view_rect.width / child_size.width; // const float end_percentage = start_percentage + length_percentage; - if (expand) { - // TODO: Implement this. - } else { - Rect thumb_rect{padding_rect.left + padding_rect.width * start_percentage, - padding_rect.GetBottom() - kScrollBarCollapseThumbWidth, - padding_rect.width * length_percentage, - kScrollBarCollapseThumbWidth}; - painter->FillRectangle(thumb_rect, GetCollapseThumbBrush().get().get()); - } + const auto padding_rect = render_object_->GetPaddingRect(); + + return Rect{padding_rect.left + padding_rect.width * start_percentage, + padding_rect.GetBottom() - kScrollBarCollapseThumbWidth, + padding_rect.width * length_percentage, + kScrollBarCollapseThumbWidth}; +} + +float HorizontalScrollBar::CalculateNewScrollPosition( + const Rect& thumb_original_rect, const Point& mouse_offset) { + auto new_thumb_start = thumb_original_rect.left + mouse_offset.x; + + const auto padding_rect = render_object_->GetPaddingRect(); + + auto scroll_area_start = padding_rect.left + kScrollBarExpandWidth; + auto scroll_area_end = padding_rect.GetRight() - 2 * kScrollBarExpandWidth; + + auto thumb_head_end = scroll_area_end - thumb_original_rect.width; + + const auto child = render_object_->GetFirstChild(); + const auto child_size = child->GetSize(); + + new_thumb_start = + std::clamp(new_thumb_start, scroll_area_start, thumb_head_end); + + auto offset = new_thumb_start / (scroll_area_end - scroll_area_start) * + child_size.width; + + return offset; } VerticalScrollBar::VerticalScrollBar( gsl::not_null render_object) - : ScrollBar(render_object) {} + : ScrollBar(render_object, Direction::Vertical) {} -std::optional VerticalScrollBar::HitTest( - const Point& point) { - // TODO: Implement this. - CRU_UNUSED(point); - return std::nullopt; +void VerticalScrollBar::DrawUpArrow(platform::graphics::IPainter* painter, + const Rect& area) { + // TODO: Do what you must! + painter->FillRectangle(area, GetExpandedArrowBackgroundBrush().get().get()); +} + +void VerticalScrollBar::DrawDownArrow(platform::graphics::IPainter* painter, + const Rect& area) { + // TODO: Do what you must! + painter->FillRectangle(area, GetExpandedArrowBackgroundBrush().get().get()); +} + +bool VerticalScrollBar::IsShowBar() { + const auto child = render_object_->GetFirstChild(); + if (child == nullptr) return false; + + const auto view_rect = render_object_->GetViewRect(); + const auto child_size = child->GetSize(); + + if (view_rect.height >= child_size.height) return false; + + return true; } -void VerticalScrollBar::OnDraw(platform::graphics::IPainter* painter, - bool expand) { +std::optional VerticalScrollBar::GetExpandedAreaRect( + ScrollBarAreaKind area_kind) { + auto show = IsShowBar(); + if (!show) return std::nullopt; + + const auto padding_rect = render_object_->GetPaddingRect(); + const auto child = render_object_->GetFirstChild(); - if (child == nullptr) return; + + const auto view_rect = render_object_->GetViewRect(); + const auto child_size = child->GetSize(); + + const float start_percentage = view_rect.top / child_size.height; + const float length_percentage = view_rect.height / child_size.height; + const float end_percentage = start_percentage + length_percentage; + + const float left = padding_rect.GetRight() - kScrollBarExpandWidth; + const float width = kScrollBarExpandWidth; + + // Without arrow. + const float bar_area_length = padding_rect.height - 3 * kScrollBarExpandWidth; + const float bar_area_start = padding_rect.top + kScrollBarExpandWidth; + + switch (area_kind) { + case ScrollBarAreaKind::UpArrow: + return Rect{left, padding_rect.top, width, kScrollBarExpandWidth}; + case ScrollBarAreaKind::DownArrow: + return Rect{left, padding_rect.GetBottom() - 2 * kScrollBarExpandWidth, + width, kScrollBarExpandWidth}; + case ScrollBarAreaKind::UpSlot: + return Rect{left, bar_area_start, width, + bar_area_length * start_percentage}; + case ScrollBarAreaKind::DownSlot: + return Rect{left, bar_area_start + bar_area_length * end_percentage, + width, bar_area_length * (1 - end_percentage)}; + case ScrollBarAreaKind::Thumb: + return Rect{left, bar_area_start + bar_area_length * start_percentage, + width, bar_area_length * length_percentage}; + default: + throw std::invalid_argument("Unsupported scroll area kind."); + } +} + +std::optional VerticalScrollBar::GetCollapsedTriggerExpandAreaRect() { + auto show = IsShowBar(); + if (!show) return std::nullopt; + + const auto padding_rect = render_object_->GetPaddingRect(); + + return Rect{ + padding_rect.GetRight() - kScrollBarCollapsedTriggerExpandAreaWidth, + padding_rect.top, kScrollBarCollapseThumbWidth, padding_rect.height}; +} + +std::optional VerticalScrollBar::GetCollapsedThumbRect() { + const auto child = render_object_->GetFirstChild(); + if (child == nullptr) return std::nullopt; const auto view_rect = render_object_->GetViewRect(); const auto padding_rect = render_object_->GetPaddingRect(); const auto child_size = child->GetSize(); - if (view_rect.height >= child_size.height) return; + if (view_rect.height >= child_size.height) return std::nullopt; const float start_percentage = view_rect.top / child_size.height; const float length_percentage = view_rect.height / child_size.height; // const float end_percentage = start_percentage + length_percentage; - if (expand) { - // TODO: Implement this. - } else { - Rect thumb_rect{padding_rect.GetRight() - kScrollBarCollapseThumbWidth, - padding_rect.top + padding_rect.height * start_percentage, - kScrollBarCollapseThumbWidth, - padding_rect.height * length_percentage}; - painter->FillRectangle(thumb_rect, GetCollapseThumbBrush().get().get()); - } + return Rect{padding_rect.GetRight() - kScrollBarCollapseThumbWidth, + padding_rect.top + padding_rect.height * start_percentage, + kScrollBarCollapseThumbWidth, + padding_rect.height * length_percentage}; +} + +float VerticalScrollBar::CalculateNewScrollPosition( + const Rect& thumb_original_rect, const Point& mouse_offset) { + auto new_thumb_start = thumb_original_rect.top + mouse_offset.y; + + const auto padding_rect = render_object_->GetPaddingRect(); + + auto scroll_area_start = padding_rect.top + kScrollBarExpandWidth; + auto scroll_area_end = padding_rect.GetBottom() - 2 * kScrollBarExpandWidth; + + auto thumb_head_end = scroll_area_end - thumb_original_rect.height; + + const auto child = render_object_->GetFirstChild(); + const auto child_size = child->GetSize(); + + new_thumb_start = + std::clamp(new_thumb_start, scroll_area_start, thumb_head_end); + + auto offset = new_thumb_start / (scroll_area_end - scroll_area_start) * + child_size.height; + + return offset; } ScrollBarDelegate::ScrollBarDelegate( diff --git a/src/ui/render/ScrollRenderObject.cpp b/src/ui/render/ScrollRenderObject.cpp index a9ec729d..0a81eaab 100644 --- a/src/ui/render/ScrollRenderObject.cpp +++ b/src/ui/render/ScrollRenderObject.cpp @@ -2,13 +2,17 @@ #include "cru/platform/graphics/Painter.hpp" #include "cru/platform/graphics/util/Painter.hpp" +#include "cru/ui/Base.hpp" #include "cru/ui/controls/Control.hpp" #include "cru/ui/render/ScrollBar.hpp" #include #include +#include namespace cru::ui::render { +constexpr float kLineHeight = 16; + namespace { // This method assumes margin offset is already considered. // It promises that it won't return negetive value. @@ -36,6 +40,35 @@ Point CoerceScroll(const Point& scroll_offset, const Size& content_size, ScrollRenderObject::ScrollRenderObject() : RenderObject(ChildMode::Single) { scroll_bar_delegate_ = std::make_unique(this); + scroll_bar_delegate_->ScrollAttemptEvent()->AddHandler( + [this](const struct Scroll& scroll) { this->Scroll(scroll); }); +} + +void ScrollRenderObject::Scroll(const struct Scroll& scroll) { + auto direction = scroll.direction; + + switch (scroll.kind) { + case ScrollKind::Absolute: + SetScrollOffset(direction, scroll.value); + break; + case ScrollKind::Relative: + SetScrollOffset(direction, + GetScrollOffset(scroll.direction) + scroll.value); + break; + case ScrollKind::Page: + SetScrollOffset(direction, GetScrollOffset(direction) + + (direction == Direction::Horizontal + ? GetViewRect().width + : GetViewRect().height) * + scroll.value); + break; + case ScrollKind::Line: + SetScrollOffset(direction, + GetScrollOffset(direction) + kLineHeight * scroll.value); + break; + default: + break; + } } RenderObject* ScrollRenderObject::HitTest(const Point& point) { -- cgit v1.2.3