aboutsummaryrefslogtreecommitdiff
path: root/include/cru/ui/controls
diff options
context:
space:
mode:
Diffstat (limited to 'include/cru/ui/controls')
-rw-r--r--include/cru/ui/controls/Base.hpp22
-rw-r--r--include/cru/ui/controls/Button.hpp29
-rw-r--r--include/cru/ui/controls/Container.hpp5
-rw-r--r--include/cru/ui/controls/ContentControl.hpp38
-rw-r--r--include/cru/ui/controls/Control.hpp157
-rw-r--r--include/cru/ui/controls/FlexLayout.hpp9
-rw-r--r--include/cru/ui/controls/IBorderControl.hpp10
-rw-r--r--include/cru/ui/controls/IClickableControl.hpp12
-rw-r--r--include/cru/ui/controls/LayoutControl.hpp35
-rw-r--r--include/cru/ui/controls/NoChildControl.hpp20
-rw-r--r--include/cru/ui/controls/Popup.hpp24
-rw-r--r--include/cru/ui/controls/RootControl.hpp45
-rw-r--r--include/cru/ui/controls/StackLayout.hpp6
-rw-r--r--include/cru/ui/controls/TextBlock.hpp23
-rw-r--r--include/cru/ui/controls/TextBox.hpp27
-rw-r--r--include/cru/ui/controls/TextHostControlService.hpp137
-rw-r--r--include/cru/ui/controls/Window.hpp32
17 files changed, 560 insertions, 71 deletions
diff --git a/include/cru/ui/controls/Base.hpp b/include/cru/ui/controls/Base.hpp
index b550601b..7c85cdb2 100644
--- a/include/cru/ui/controls/Base.hpp
+++ b/include/cru/ui/controls/Base.hpp
@@ -1,24 +1,4 @@
#pragma once
#include "../Base.hpp"
-namespace cru::ui::controls {
-using ButtonStateStyle = BorderStyle;
-
-struct ButtonStyle {
- // corresponds to ClickState::None
- ButtonStateStyle normal;
- // corresponds to ClickState::Hover
- ButtonStateStyle hover;
- // corresponds to ClickState::Press
- ButtonStateStyle press;
- // corresponds to ClickState::PressInactive
- ButtonStateStyle press_cancel;
-};
-
-struct TextBoxBorderStyle {
- BorderStyle normal;
- BorderStyle hover;
- BorderStyle focus;
- BorderStyle focus_hover;
-};
-} // namespace cru::ui::controls
+namespace cru::ui::controls {} // namespace cru::ui::controls
diff --git a/include/cru/ui/controls/Button.hpp b/include/cru/ui/controls/Button.hpp
index a4f727d6..1c9b1216 100644
--- a/include/cru/ui/controls/Button.hpp
+++ b/include/cru/ui/controls/Button.hpp
@@ -1,11 +1,16 @@
#pragma once
-#include "../ContentControl.hpp"
-#include "Base.hpp"
+#include "ContentControl.hpp"
-#include "../ClickDetector.hpp"
+#include "../helper/ClickDetector.hpp"
+#include "IBorderControl.hpp"
+#include "IClickableControl.hpp"
+#include "cru/common/Event.hpp"
+#include "cru/ui/style/ApplyBorderStyleInfo.hpp"
namespace cru::ui::controls {
-class Button : public ContentControl {
+class Button : public ContentControl,
+ public virtual IClickableControl,
+ public virtual IBorderControl {
public:
static constexpr std::u16string_view control_type = u"Button";
@@ -26,17 +31,19 @@ class Button : public ContentControl {
render::RenderObject* GetRenderObject() const override;
public:
- const ButtonStyle& GetStyle() const { return style_; }
- void SetStyle(ButtonStyle style);
+ helper::ClickState GetClickState() override {
+ return click_detector_.GetState();
+ }
- protected:
- void OnChildChanged(Control* old_child, Control* new_child) override;
+ IEvent<helper::ClickState>* ClickStateChangeEvent() override {
+ return click_detector_.StateChangeEvent();
+ }
+
+ void ApplyBorderStyle(const style::ApplyBorderStyleInfo& style) override;
private:
std::unique_ptr<render::BorderRenderObject> render_object_{};
- ButtonStyle style_;
-
- ClickDetector click_detector_;
+ helper::ClickDetector click_detector_;
};
} // namespace cru::ui::controls
diff --git a/include/cru/ui/controls/Container.hpp b/include/cru/ui/controls/Container.hpp
index 304d402c..18958837 100644
--- a/include/cru/ui/controls/Container.hpp
+++ b/include/cru/ui/controls/Container.hpp
@@ -1,5 +1,5 @@
#pragma once
-#include "../ContentControl.hpp"
+#include "ContentControl.hpp"
namespace cru::ui::controls {
class Container : public ContentControl {
@@ -19,9 +19,6 @@ class Container : public ContentControl {
render::RenderObject* GetRenderObject() const override;
- protected:
- void OnChildChanged(Control* old_child, Control* new_child) override;
-
private:
std::unique_ptr<render::BorderRenderObject> render_object_;
};
diff --git a/include/cru/ui/controls/ContentControl.hpp b/include/cru/ui/controls/ContentControl.hpp
new file mode 100644
index 00000000..1bdaf7e4
--- /dev/null
+++ b/include/cru/ui/controls/ContentControl.hpp
@@ -0,0 +1,38 @@
+#pragma once
+#include "Control.hpp"
+
+#include "cru/ui/render/RenderObject.hpp"
+
+namespace cru::ui::controls {
+class ContentControl : public Control {
+ protected:
+ ContentControl() = default;
+
+ public:
+ ContentControl(const ContentControl& other) = delete;
+ ContentControl(ContentControl&& other) = delete;
+ ContentControl& operator=(const ContentControl& other) = delete;
+ ContentControl& operator=(ContentControl&& other) = delete;
+ ~ContentControl() override = default;
+
+ Control* GetChild() const;
+ void SetChild(Control* child);
+
+ protected:
+ virtual void OnChildChanged(Control* old_child, Control* new_child);
+
+ render::RenderObject* GetContainerRenderObject() const {
+ return container_render_object_;
+ }
+ void SetContainerRenderObject(render::RenderObject* ro) {
+ container_render_object_ = ro;
+ }
+
+ private:
+ using Control::AddChild;
+ using Control::RemoveChild;
+
+ private:
+ render::RenderObject* container_render_object_ = nullptr;
+};
+} // namespace cru::ui::controls
diff --git a/include/cru/ui/controls/Control.hpp b/include/cru/ui/controls/Control.hpp
new file mode 100644
index 00000000..341b6ef2
--- /dev/null
+++ b/include/cru/ui/controls/Control.hpp
@@ -0,0 +1,157 @@
+#pragma once
+#include "Base.hpp"
+
+#include "../events/UiEvent.hpp"
+#include "../render/Base.hpp"
+#include "cru/common/Event.hpp"
+
+#include <string_view>
+
+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<Control*>& GetChildren() const { return children_; }
+
+ // Traverse the tree rooted the control including itself.
+ void TraverseDescendants(const std::function<void(Control*)>& 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<platform::gui::ICursor> GetCursor();
+
+ // will not return nullptr
+ std::shared_ptr<platform::gui::ICursor> GetInheritedCursor();
+
+ // null to unset
+ void SetCursor(std::shared_ptr<platform::gui::ICursor> cursor);
+
+ public:
+ style::StyleRuleSet* GetStyleRuleSet();
+
+ //*************** 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<event::MouseEventArgs>* 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<event::MouseEventArgs>* MouseLeaveEvent() {
+ return &mouse_leave_event_;
+ }
+ // Raised when mouse is move in the control.
+ event::RoutedEvent<event::MouseEventArgs>* MouseMoveEvent() {
+ return &mouse_move_event_;
+ }
+ // Raised when a mouse button is pressed in the control.
+ event::RoutedEvent<event::MouseButtonEventArgs>* MouseDownEvent() {
+ return &mouse_down_event_;
+ }
+ // Raised when a mouse button is released in the control.
+ event::RoutedEvent<event::MouseButtonEventArgs>* MouseUpEvent() {
+ return &mouse_up_event_;
+ }
+ event::RoutedEvent<event::MouseWheelEventArgs>* MouseWheelEvent() {
+ return &mouse_wheel_event_;
+ }
+ event::RoutedEvent<event::KeyEventArgs>* KeyDownEvent() {
+ return &key_down_event_;
+ }
+ event::RoutedEvent<event::KeyEventArgs>* KeyUpEvent() {
+ return &key_up_event_;
+ }
+ event::RoutedEvent<event::FocusChangeEventArgs>* GainFocusEvent() {
+ return &gain_focus_event_;
+ }
+ event::RoutedEvent<event::FocusChangeEventArgs>* LoseFocusEvent() {
+ return &lose_focus_event_;
+ }
+
+ private:
+ event::RoutedEvent<event::MouseEventArgs> mouse_enter_event_;
+ event::RoutedEvent<event::MouseEventArgs> mouse_leave_event_;
+ event::RoutedEvent<event::MouseEventArgs> mouse_move_event_;
+ event::RoutedEvent<event::MouseButtonEventArgs> mouse_down_event_;
+ event::RoutedEvent<event::MouseButtonEventArgs> mouse_up_event_;
+ event::RoutedEvent<event::MouseWheelEventArgs> mouse_wheel_event_;
+
+ event::RoutedEvent<event::KeyEventArgs> key_down_event_;
+ event::RoutedEvent<event::KeyEventArgs> key_up_event_;
+
+ event::RoutedEvent<event::FocusChangeEventArgs> gain_focus_event_;
+ event::RoutedEvent<event::FocusChangeEventArgs> 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<Control*> children_;
+
+ host::WindowHost* window_host_ = nullptr;
+
+ private:
+ bool is_mouse_over_ = false;
+
+ std::shared_ptr<platform::gui::ICursor> cursor_ = nullptr;
+
+ std::unique_ptr<style::StyleRuleSet> style_rule_set_;
+ std::unique_ptr<style::StyleRuleSetBind> style_rule_set_bind_;
+};
+} // namespace cru::ui::controls
diff --git a/include/cru/ui/controls/FlexLayout.hpp b/include/cru/ui/controls/FlexLayout.hpp
index 87162569..4f6abfdb 100644
--- a/include/cru/ui/controls/FlexLayout.hpp
+++ b/include/cru/ui/controls/FlexLayout.hpp
@@ -1,5 +1,5 @@
#pragma once
-#include "../LayoutControl.hpp"
+#include "LayoutControl.hpp"
namespace cru::ui::controls {
class FlexLayout : public LayoutControl {
@@ -28,13 +28,12 @@ class FlexLayout : public LayoutControl {
FlexDirection GetFlexDirection() const;
void SetFlexDirection(FlexDirection direction);
+ FlexCrossAlignment GetItemCrossAlign() const;
+ void SetItemCrossAlign(FlexCrossAlignment alignment);
+
FlexChildLayoutData GetChildLayoutData(Control* control);
void SetChildLayoutData(Control* control, FlexChildLayoutData data);
- protected:
- void OnAddChild(Control* child, Index position) override;
- void OnRemoveChild(Control* child, Index position) override;
-
private:
std::shared_ptr<render::FlexLayoutRenderObject> render_object_;
};
diff --git a/include/cru/ui/controls/IBorderControl.hpp b/include/cru/ui/controls/IBorderControl.hpp
new file mode 100644
index 00000000..817305ef
--- /dev/null
+++ b/include/cru/ui/controls/IBorderControl.hpp
@@ -0,0 +1,10 @@
+#pragma once
+#include "../style/ApplyBorderStyleInfo.hpp"
+#include "Base.hpp"
+#include "cru/common/Base.hpp"
+
+namespace cru::ui::controls {
+struct IBorderControl : virtual Interface {
+ virtual void ApplyBorderStyle(const style::ApplyBorderStyleInfo& style) = 0;
+};
+} // namespace cru::ui::controls
diff --git a/include/cru/ui/controls/IClickableControl.hpp b/include/cru/ui/controls/IClickableControl.hpp
new file mode 100644
index 00000000..aa7f13ab
--- /dev/null
+++ b/include/cru/ui/controls/IClickableControl.hpp
@@ -0,0 +1,12 @@
+#pragma once
+#include "Base.hpp"
+
+#include "cru/common/Event.hpp"
+#include "cru/ui/helper/ClickDetector.hpp"
+
+namespace cru::ui::controls {
+struct IClickableControl : virtual Interface {
+ virtual helper::ClickState GetClickState() = 0;
+ virtual IEvent<helper::ClickState>* ClickStateChangeEvent() = 0;
+};
+} // namespace cru::ui::controls
diff --git a/include/cru/ui/controls/LayoutControl.hpp b/include/cru/ui/controls/LayoutControl.hpp
new file mode 100644
index 00000000..106dd94d
--- /dev/null
+++ b/include/cru/ui/controls/LayoutControl.hpp
@@ -0,0 +1,35 @@
+#pragma once
+#include "Control.hpp"
+
+namespace cru::ui::controls {
+class LayoutControl : public Control {
+ protected:
+ LayoutControl() = default;
+ explicit LayoutControl(render::RenderObject* container_render_object)
+ : container_render_object_(container_render_object) {}
+
+ public:
+ LayoutControl(const LayoutControl& other) = delete;
+ LayoutControl(LayoutControl&& other) = delete;
+ LayoutControl& operator=(const LayoutControl& other) = delete;
+ LayoutControl& operator=(LayoutControl&& other) = delete;
+ ~LayoutControl() override = default;
+
+ using Control::AddChild;
+ using Control::RemoveChild;
+
+ protected:
+ // If container render object is not null. Render object of added or removed
+ // child control will automatically sync to the container render object.
+ render::RenderObject* GetContainerRenderObject() const;
+ void SetContainerRenderObject(render::RenderObject* ro) {
+ container_render_object_ = ro;
+ }
+
+ void OnAddChild(Control* child, Index position) override;
+ void OnRemoveChild(Control* child, Index position) override;
+
+ private:
+ render::RenderObject* container_render_object_ = nullptr;
+};
+} // namespace cru::ui::controls
diff --git a/include/cru/ui/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/Popup.hpp b/include/cru/ui/controls/Popup.hpp
new file mode 100644
index 00000000..d76e1211
--- /dev/null
+++ b/include/cru/ui/controls/Popup.hpp
@@ -0,0 +1,24 @@
+#pragma once
+#include "RootControl.hpp"
+
+#include "cru/ui/Base.hpp"
+#include "cru/platform/gui/Base.hpp"
+
+#include <memory>
+
+namespace cru::ui::controls {
+class Popup : public RootControl {
+ public:
+ explicit Popup(Control* attached_control = nullptr);
+
+ CRU_DELETE_COPY(Popup)
+ CRU_DELETE_MOVE(Popup)
+
+ ~Popup() override;
+
+ protected:
+ gsl::not_null<platform::gui::INativeWindow*> CreateNativeWindow(
+ gsl::not_null<host::WindowHost*> host,
+ platform::gui::INativeWindow* parent) override;
+};
+} // namespace cru::ui::controls
diff --git a/include/cru/ui/controls/RootControl.hpp b/include/cru/ui/controls/RootControl.hpp
new file mode 100644
index 00000000..53e69e7c
--- /dev/null
+++ b/include/cru/ui/controls/RootControl.hpp
@@ -0,0 +1,45 @@
+#pragma once
+#include "LayoutControl.hpp"
+
+#include "cru/common/Base.hpp"
+#include "cru/platform/gui/Base.hpp"
+#include "cru/ui/Base.hpp"
+
+namespace cru::ui::controls {
+class RootControl : public LayoutControl {
+ protected:
+ explicit RootControl(Control* attached_control);
+
+ public:
+ CRU_DELETE_COPY(RootControl)
+ CRU_DELETE_MOVE(RootControl)
+ ~RootControl() override;
+
+ public:
+ render::RenderObject* GetRenderObject() const override;
+
+ void EnsureWindowCreated();
+
+ // If create is false and native window is not create, it will not be created
+ // and shown.
+ void Show(bool create = true);
+
+ Rect GetRect();
+ void SetRect(const Rect& rect);
+
+ protected:
+ virtual gsl::not_null<platform::gui::INativeWindow*> CreateNativeWindow(
+ gsl::not_null<host::WindowHost*> host,
+ platform::gui::INativeWindow* parent) = 0;
+
+ private:
+ platform::gui::INativeWindow* GetNativeWindow(bool create);
+
+ private:
+ std::unique_ptr<host::WindowHost> window_host_;
+
+ std::unique_ptr<render::StackLayoutRenderObject> render_object_;
+
+ Control* attached_control_;
+};
+} // namespace cru::ui::controls
diff --git a/include/cru/ui/controls/StackLayout.hpp b/include/cru/ui/controls/StackLayout.hpp
index c0b95044..aa9440c2 100644
--- a/include/cru/ui/controls/StackLayout.hpp
+++ b/include/cru/ui/controls/StackLayout.hpp
@@ -1,5 +1,5 @@
#pragma once
-#include "../LayoutControl.hpp"
+#include "LayoutControl.hpp"
namespace cru::ui::controls {
class StackLayout : public LayoutControl {
@@ -21,10 +21,6 @@ class StackLayout : public LayoutControl {
render::RenderObject* GetRenderObject() const override;
- protected:
- void OnAddChild(Control* child, Index position) override;
- void OnRemoveChild(Control* child, Index position) override;
-
private:
std::shared_ptr<render::StackLayoutRenderObject> render_object_;
};
diff --git a/include/cru/ui/controls/TextBlock.hpp b/include/cru/ui/controls/TextBlock.hpp
index 8a9a3bff..be31816c 100644
--- a/include/cru/ui/controls/TextBlock.hpp
+++ b/include/cru/ui/controls/TextBlock.hpp
@@ -1,15 +1,15 @@
#pragma once
-#include "../NoChildControl.hpp"
+#include "NoChildControl.hpp"
-namespace cru::ui::controls {
-template <typename TControl>
-class TextControlService;
+#include "TextHostControlService.hpp"
-class TextBlock : public NoChildControl {
+namespace cru::ui::controls {
+class TextBlock : public NoChildControl, public virtual ITextHostControl {
public:
static constexpr std::u16string_view control_type = u"TextBlock";
- static TextBlock* Create() { return new TextBlock(); }
+ static TextBlock* Create();
+ static TextBlock* Create(std::u16string text, bool selectable = false);
protected:
TextBlock();
@@ -28,12 +28,17 @@ class TextBlock : public NoChildControl {
std::u16string GetText() const;
void SetText(std::u16string text);
- gsl::not_null<render::TextRenderObject*> GetTextRenderObject();
- render::ScrollRenderObject* GetScrollRenderObject() { return nullptr; }
+ bool IsSelectable() const;
+ void SetSelectable(bool value);
+
+ gsl::not_null<render::TextRenderObject*> GetTextRenderObject() override;
+ render::ScrollRenderObject* GetScrollRenderObject() override {
+ return nullptr;
+ }
private:
std::unique_ptr<render::TextRenderObject> text_render_object_;
- std::unique_ptr<TextControlService<TextBlock>> service_;
+ std::unique_ptr<TextHostControlService> service_;
};
} // namespace cru::ui::controls
diff --git a/include/cru/ui/controls/TextBox.hpp b/include/cru/ui/controls/TextBox.hpp
index 5976f6da..5693b315 100644
--- a/include/cru/ui/controls/TextBox.hpp
+++ b/include/cru/ui/controls/TextBox.hpp
@@ -1,6 +1,8 @@
#pragma once
-#include "../NoChildControl.hpp"
-#include "Base.hpp"
+#include "NoChildControl.hpp"
+
+#include "IBorderControl.hpp"
+#include "TextHostControlService.hpp"
#include <memory>
@@ -8,7 +10,9 @@ namespace cru::ui::controls {
template <typename TControl>
class TextControlService;
-class TextBox : public NoChildControl {
+class TextBox : public NoChildControl,
+ public virtual IBorderControl,
+ public virtual ITextHostControl {
public:
static constexpr std::u16string_view control_type = u"TextBox";
@@ -27,25 +31,16 @@ class TextBox : public NoChildControl {
render::RenderObject* GetRenderObject() const override;
- gsl::not_null<render::TextRenderObject*> GetTextRenderObject();
- render::ScrollRenderObject* GetScrollRenderObject();
-
- const TextBoxBorderStyle& GetBorderStyle();
- void SetBorderStyle(TextBoxBorderStyle border_style);
-
- protected:
- void OnMouseHoverChange(bool newHover) override;
+ gsl::not_null<render::TextRenderObject*> GetTextRenderObject() override;
+ render::ScrollRenderObject* GetScrollRenderObject() override;
- private:
- void UpdateBorderStyle();
+ void ApplyBorderStyle(const style::ApplyBorderStyleInfo& style) override;
private:
std::unique_ptr<render::BorderRenderObject> border_render_object_;
std::unique_ptr<render::ScrollRenderObject> scroll_render_object_;
std::unique_ptr<render::TextRenderObject> text_render_object_;
- TextBoxBorderStyle border_style_;
-
- std::unique_ptr<TextControlService<TextBox>> service_;
+ std::unique_ptr<TextHostControlService> service_;
};
} // namespace cru::ui::controls
diff --git a/include/cru/ui/controls/TextHostControlService.hpp b/include/cru/ui/controls/TextHostControlService.hpp
new file mode 100644
index 00000000..340228fe
--- /dev/null
+++ b/include/cru/ui/controls/TextHostControlService.hpp
@@ -0,0 +1,137 @@
+#pragma once
+#include "Base.hpp"
+
+#include "cru/platform/gui/InputMethod.hpp"
+#include "cru/platform/gui/UiApplication.hpp"
+#include "cru/ui/controls/Control.hpp"
+#include "cru/ui/helper/ShortcutHub.hpp"
+
+#include <functional>
+#include <string>
+
+namespace cru::ui::render {
+class TextRenderObject;
+class ScrollRenderObject;
+} // namespace cru::ui::render
+
+namespace cru::ui::controls {
+constexpr int k_default_caret_blink_duration = 500;
+
+struct ITextHostControl : virtual Interface {
+ virtual gsl::not_null<render::TextRenderObject*> GetTextRenderObject() = 0;
+ // May return nullptr.
+ virtual render::ScrollRenderObject* GetScrollRenderObject() = 0;
+};
+
+class TextHostControlService : public Object {
+ CRU_DEFINE_CLASS_LOG_TAG(u"cru::ui::controls::TextControlService")
+
+ public:
+ TextHostControlService(gsl::not_null<Control*> control);
+
+ CRU_DELETE_COPY(TextHostControlService)
+ CRU_DELETE_MOVE(TextHostControlService)
+
+ ~TextHostControlService() = default;
+
+ public:
+ bool IsEnabled() { return enable_; }
+ void SetEnabled(bool enable);
+
+ bool IsEditable() { return this->editable_; }
+ void SetEditable(bool editable);
+
+ std::u16string GetText() { return this->text_; }
+ std::u16string_view GetTextView() { return this->text_; }
+ void SetText(std::u16string text, bool stop_composition = false);
+
+ void InsertText(gsl::index position, std::u16string_view text,
+ bool stop_composition = false);
+ void DeleteChar(gsl::index position, bool stop_composition = false);
+
+ // Return the position of deleted character.
+ gsl::index DeleteCharPrevious(gsl::index position,
+ bool stop_composition = false);
+ void DeleteText(TextRange range, bool stop_composition = false);
+
+ void CancelComposition();
+
+ std::optional<platform::gui::CompositionText> GetCompositionInfo();
+
+ bool IsCaretVisible() { return caret_visible_; }
+ void SetCaretVisible(bool visible);
+
+ int GetCaretBlinkDuration() { return caret_blink_duration_; }
+ void SetCaretBlinkDuration(int milliseconds);
+
+ gsl::index GetCaretPosition() { return selection_.GetEnd(); }
+ TextRange GetSelection() { return selection_; }
+
+ void SetSelection(gsl::index caret_position);
+ void SetSelection(TextRange selection, bool scroll_to_caret = true);
+
+ void ChangeSelectionEnd(gsl::index new_end);
+ void AbortSelection();
+
+ void DeleteSelectedText();
+ // If some text is selected, then they are deleted first. Then insert text
+ // into caret position.
+ void ReplaceSelectedText(std::u16string_view text);
+
+ void ScrollToCaret();
+
+ private:
+ gsl::not_null<render::TextRenderObject*> GetTextRenderObject();
+ render::ScrollRenderObject* GetScrollRenderObject();
+
+ // May return nullptr.
+ platform::gui::IInputMethodContext* GetInputMethodContext();
+
+ void CoerceSelection();
+
+ void SetupCaret();
+ void TearDownCaret();
+
+ void SyncTextRenderObject();
+
+ void UpdateInputMethodPosition();
+
+ template <typename TArgs>
+ void SetupOneHandler(event::RoutedEvent<TArgs>* (Control::*event)(),
+ void (TextHostControlService::*handler)(
+ typename event::RoutedEvent<TArgs>::EventArgs)) {
+ this->event_guard_ += (this->control_->*event)()->Bubble()->AddHandler(
+ std::bind(handler, this, std::placeholders::_1));
+ }
+
+ void MouseMoveHandler(event::MouseEventArgs& args);
+ void MouseDownHandler(event::MouseButtonEventArgs& args);
+ void MouseUpHandler(event::MouseButtonEventArgs& args);
+ void GainFocusHandler(event::FocusChangeEventArgs& args);
+ void LoseFocusHandler(event::FocusChangeEventArgs& args);
+
+ void SetUpShortcuts();
+
+ private:
+ gsl::not_null<Control*> control_;
+ gsl::not_null<ITextHostControl*> text_host_control_;
+
+ EventRevokerListGuard event_guard_;
+ EventRevokerListGuard input_method_context_event_guard_;
+
+ std::u16string text_;
+ TextRange selection_;
+
+ bool enable_ = false;
+ bool editable_ = false;
+
+ bool caret_visible_ = false;
+ platform::gui::TimerAutoCanceler caret_timer_canceler_;
+ int caret_blink_duration_ = k_default_caret_blink_duration;
+
+ helper::ShortcutHub shortcut_hub_;
+
+ // true if left mouse is down and selecting
+ bool mouse_move_selecting_ = false;
+};
+} // namespace cru::ui::controls
diff --git a/include/cru/ui/controls/Window.hpp b/include/cru/ui/controls/Window.hpp
new file mode 100644
index 00000000..cca56b64
--- /dev/null
+++ b/include/cru/ui/controls/Window.hpp
@@ -0,0 +1,32 @@
+#pragma once
+#include "cru/platform/gui/Base.hpp"
+#include "cru/ui/controls/RootControl.hpp"
+
+#include "cru/common/Base.hpp"
+
+namespace cru::ui::controls {
+class Window final : public RootControl {
+ public:
+ static constexpr std::u16string_view control_type = u"Window";
+
+ public:
+ static Window* Create(Control* attached_control = nullptr);
+
+ private:
+ explicit Window(Control* attached_control);
+
+ public:
+ CRU_DELETE_COPY(Window)
+ CRU_DELETE_MOVE(Window)
+
+ ~Window() override;
+
+ public:
+ std::u16string_view GetControlType() const final { return control_type; }
+
+ protected:
+ gsl::not_null<platform::gui::INativeWindow*> CreateNativeWindow(
+ gsl::not_null<host::WindowHost*> host,
+ platform::gui::INativeWindow* parent) override;
+};
+} // namespace cru::ui::controls