aboutsummaryrefslogtreecommitdiff
path: root/include
diff options
context:
space:
mode:
authorcrupest <crupest@outlook.com>2019-04-01 18:08:58 +0800
committercrupest <crupest@outlook.com>2019-04-01 18:08:58 +0800
commitde00126c6aeba189a50296df455dd516e21e4176 (patch)
tree3d89a8a36a3603096d4371230c2d071f91e9e986 /include
parent055a3cde0cd19c896f3e498b774078654555c065 (diff)
downloadcru-de00126c6aeba189a50296df455dd516e21e4176.tar.gz
cru-de00126c6aeba189a50296df455dd516e21e4176.tar.bz2
cru-de00126c6aeba189a50296df455dd516e21e4176.zip
...
Diffstat (limited to 'include')
-rw-r--r--include/cru/platform/matrix.hpp84
-rw-r--r--include/cru/platform/painter.hpp3
-rw-r--r--include/cru/platform/painter_util.hpp14
-rw-r--r--include/cru/platform/win/d2d_util.hpp17
-rw-r--r--include/cru/platform/win/win_painter.hpp2
-rw-r--r--include/cru/ui/event/ui_event.hpp200
-rw-r--r--include/cru/ui/render/border_render_object.hpp88
-rw-r--r--include/cru/ui/render/flex_layout_render_object.hpp57
-rw-r--r--include/cru/ui/render/render_object.hpp98
-rw-r--r--include/cru/ui/render/text_render_object.hpp69
-rw-r--r--include/cru/ui/render/window_render_object.hpp40
11 files changed, 672 insertions, 0 deletions
diff --git a/include/cru/platform/matrix.hpp b/include/cru/platform/matrix.hpp
new file mode 100644
index 00000000..e5e5cf42
--- /dev/null
+++ b/include/cru/platform/matrix.hpp
@@ -0,0 +1,84 @@
+#include "cru/common/pre_config.hpp"
+
+#include "cru/common/ui_base.hpp"
+
+#include <cmath>
+
+namespace cru::platform {
+struct Matrix {
+ float m11;
+ float m12;
+ float m21;
+ float m22;
+ float m31;
+ float m32;
+
+ Matrix() {}
+
+ Matrix(float m11, float m12, float m21, float m22, float m31, float m32) {
+ this->m11 = m11;
+ this->m12 = m12;
+ this->m21 = m21;
+ this->m22 = m22;
+ this->m31 = m31;
+ this->m32 = m32;
+ }
+
+ bool IsIdentity() const {
+ return m11 == 1.0f && m12 == 0.0f && m21 == 0.0f && m22 == 1.0f &&
+ m31 == 0.0f && m32 == 0.0f;
+ }
+
+ Matrix& operator*=(const Matrix& matrix) const {
+ *this = Product(*this, matrix);
+ return *this;
+ }
+
+ Matrix operator*(const Matrix& matrix) const { return Product(this, matrix); }
+
+ ui::Point TransformPoint(const ui::Point& point) const {
+ return ui::Point{point.x * m11 + point.y * m21 + m31,
+ point.x * m12 + point.y * m22 + m32};
+ }
+
+ static Matrix Identity() {
+ return Matrix{1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f};
+ }
+
+ static Matrix Translation(float x, float y) {
+ return Matrix{1.0f, 0.0f, 0.0f, 1.0f, x, y};
+ }
+
+ static Matrix Scale(float sx, float sy) { return Scale(sx, sy, 0.0f, 0.0f); }
+
+ static Matrix Scale(float sx, float sy, float cx, float cy) {
+ return Matrix{sx, 0.0f, 0.0f, sy, cx - sx * cx, cy - sy * cy};
+ }
+
+ static Matrix Rotation(float angle) {
+ float r = AngleToRadian(angle);
+ float s = std::sinf(r);
+ float c = std::cosf(r);
+
+ return Matrix{c, s, -s, c, 0.0f, 0.0f};
+ }
+
+ static Matrix Skew(float sx, float sy) {
+ return Matrix{1.0f, sx, sy, 1.0f, 0.0f, 0.0f};
+ }
+
+ static Matrix Product(const Matrix& a, const Matrix& b) {
+ return Matrix{a.m11 * b.m11 + a.m12 * b.m21,
+ a.m11 * b.m12 + a.m12 * b.m22,
+ a.m21 * b.m11 + a.m22 * b.m21,
+ a.m21 * b.m12 + a.m22 * b.m22,
+ a.m31 * b.m11 + a.m32 * b.m21 + b.m31,
+ a.m31 * b.m12 + a.m32 * b.m22 + b.m32};
+ }
+
+ private:
+ static constexpr float PI = 3.1415926535f;
+
+ static float AngleToRadian(float angle) { return angle / 180.f * PI; }
+};
+} // namespace cru::platform
diff --git a/include/cru/platform/painter.hpp b/include/cru/platform/painter.hpp
index 77ca5098..2e979184 100644
--- a/include/cru/platform/painter.hpp
+++ b/include/cru/platform/painter.hpp
@@ -2,12 +2,15 @@
#include "cru/common/base.hpp"
#include "cru/common/ui_base.hpp"
+#include "matrix.hpp"
namespace cru::platform {
struct Brush;
struct Geometry;
struct Painter : virtual Interface {
+ virtual Matrix GetTransform() = 0;
+ virtual void SetTransform(const Matrix& matrix) = 0;
virtual void StrokeGeometry(Geometry* geometry, Brush* brush,
float width) = 0;
virtual void FillGeometry(Geometry* geometry, Brush* brush) = 0;
diff --git a/include/cru/platform/painter_util.hpp b/include/cru/platform/painter_util.hpp
new file mode 100644
index 00000000..fed0c487
--- /dev/null
+++ b/include/cru/platform/painter_util.hpp
@@ -0,0 +1,14 @@
+#pragma once
+#include "painter.hpp"
+
+#include <functional>
+
+namespace cru::platform::util {
+inline void WithTransform(Painter* painter, const Matrix& matrix,
+ const std::function<void(Painter*)>& action) {
+ const auto old = painter->GetTransform();
+ painter->SetTransform(old * matrix);
+ action(painter);
+ painter->SetTransform(old);
+}
+}
diff --git a/include/cru/platform/win/d2d_util.hpp b/include/cru/platform/win/d2d_util.hpp
index 9ff6556a..51aea4f8 100644
--- a/include/cru/platform/win/d2d_util.hpp
+++ b/include/cru/platform/win/d2d_util.hpp
@@ -1,9 +1,21 @@
#pragma once
#include "win_pre_config.hpp"
+#include "../matrix.hpp"
#include "cru/common/ui_base.hpp"
namespace cru::platform::win::util {
+inline D2D1_MATRIX_3X2_F Convert(const Matrix& matrix) {
+ D2D1_MATRIX_3X2_F m;
+ m._11 = matrix.m11;
+ m._12 = matrix.m12;
+ m._21 = matrix.m21;
+ m._22 = matrix.m22;
+ m._31 = matrix.m31;
+ m._32 = matrix.m32;
+ return m;
+}
+
inline D2D1_COLOR_F Convert(const ui::Color& color) {
return D2D1::ColorF(color.red / 255.0f, color.green / 255.0f,
color.blue / 255.0f, color.alpha / 255.0f);
@@ -28,6 +40,11 @@ inline D2D1_ELLIPSE Convert(const ui::Ellipse& ellipse) {
ellipse.radius_y);
}
+inline Matrix Convert(const D2D1_MATRIX_3X2_F& matrix) {
+ return Matrix{matrix._11, matrix._12, matrix._21,
+ matrix._22, matrix._31, matrix._32};
+}
+
inline ui::Color Convert(const D2D1_COLOR_F& color) {
auto floor = [](float n) { return static_cast<std::uint8_t>(n + 0.5f); };
return ui::Color{floor(color.r * 255.0f), floor(color.g * 255.0f),
diff --git a/include/cru/platform/win/win_painter.hpp b/include/cru/platform/win/win_painter.hpp
index 7b71ccbd..1e449d85 100644
--- a/include/cru/platform/win/win_painter.hpp
+++ b/include/cru/platform/win/win_painter.hpp
@@ -15,6 +15,8 @@ class WinPainter : public Object, public virtual Painter {
WinPainter& operator=(WinPainter&& other) = delete;
~WinPainter() override;
+ Matrix GetTransform() override;
+ void SetTransform(const Matrix& matrix) override;
void StrokeGeometry(Geometry* geometry, Brush* brush, float width) override;
void FillGeometry(Geometry* geometry, Brush* brush) override;
void EndDraw() override;
diff --git a/include/cru/ui/event/ui_event.hpp b/include/cru/ui/event/ui_event.hpp
new file mode 100644
index 00000000..ba8940cb
--- /dev/null
+++ b/include/cru/ui/event/ui_event.hpp
@@ -0,0 +1,200 @@
+#pragma once
+#include "cru/common/base.hpp"
+
+#include "cru/common/event.hpp"
+#include "cru/common/ui_base.hpp"
+#include "cru/platform/basic_types.hpp"
+
+#include <optional>
+
+namespace cru::platform {
+struct Painter;
+}
+
+namespace cru::ui {
+class Control;
+}
+
+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* 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_;
+};
+
+template <typename TEventArgs>
+class RoutedEvent {
+ public:
+ static_assert(std::is_base_of_v<UiEventArgs, TEventArgs>,
+ "TEventArgs must be subclass of UiEventArgs.");
+
+ using EventArgs = TEventArgs;
+
+ RoutedEvent() = default;
+ RoutedEvent(const RoutedEvent& other) = delete;
+ RoutedEvent(RoutedEvent&& other) = delete;
+ RoutedEvent& operator=(const RoutedEvent& other) = delete;
+ RoutedEvent& operator=(RoutedEvent&& other) = delete;
+ ~RoutedEvent() = default;
+
+ Event<TEventArgs> direct;
+ Event<TEventArgs> bubble;
+ Event<TEventArgs> tunnel;
+};
+
+class MouseEventArgs : public UiEventArgs {
+ public:
+ MouseEventArgs(Object* sender, Object* original_sender,
+ const std::optional<Point>& 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;
+
+ Point GetPoint() const { return point_.value_or(Point{}); }
+
+ private:
+ std::optional<Point> point_;
+};
+
+class MouseButtonEventArgs : public MouseEventArgs {
+ public:
+ MouseButtonEventArgs(Object* sender, Object* original_sender,
+ const Point& point, const platform::MouseButton button)
+ : MouseEventArgs(sender, original_sender, point), button_(button) {}
+ MouseButtonEventArgs(const MouseButtonEventArgs& other) = default;
+ MouseButtonEventArgs(MouseButtonEventArgs&& other) = default;
+ MouseButtonEventArgs& operator=(const MouseButtonEventArgs& other) = default;
+ MouseButtonEventArgs& operator=(MouseButtonEventArgs&& other) = default;
+ ~MouseButtonEventArgs() override = default;
+
+ platform::MouseButton GetMouseButton() const { return button_; }
+
+ private:
+ platform::MouseButton button_;
+};
+
+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::Painter* 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::Painter* GetPainter() const { return painter_; }
+
+ private:
+ platform::Painter* 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, int virtual_code)
+ : UiEventArgs(sender, original_sender), virtual_code_(virtual_code) {}
+ KeyEventArgs(const KeyEventArgs& other) = default;
+ KeyEventArgs(KeyEventArgs&& other) = default;
+ KeyEventArgs& operator=(const KeyEventArgs& other) = default;
+ KeyEventArgs& operator=(KeyEventArgs&& other) = default;
+ ~KeyEventArgs() override = default;
+
+ int GetVirtualCode() const { return virtual_code_; }
+
+ private:
+ int virtual_code_;
+};
+
+/*
+class CharEventArgs : public UiEventArgs {
+ public:
+ CharEventArgs(Object* sender, Object* original_sender, wchar_t c)
+ : UiEventArgs(sender, original_sender), c_(c) {}
+ CharEventArgs(const CharEventArgs& other) = default;
+ CharEventArgs(CharEventArgs&& other) = default;
+ CharEventArgs& operator=(const CharEventArgs& other) = default;
+ CharEventArgs& operator=(CharEventArgs&& other) = default;
+ ~CharEventArgs() override = default;
+
+ wchar_t GetChar() const { return c_; }
+
+ private:
+ wchar_t c_;
+};
+*/
+} // namespace cru::ui::events
diff --git a/include/cru/ui/render/border_render_object.hpp b/include/cru/ui/render/border_render_object.hpp
new file mode 100644
index 00000000..a96ccf99
--- /dev/null
+++ b/include/cru/ui/render/border_render_object.hpp
@@ -0,0 +1,88 @@
+#pragma once
+#include "render_object.hpp"
+
+namespace cru::platform {
+struct Brush;
+}
+
+namespace cru::ui::render {
+struct CornerRadius {
+ constexpr CornerRadius()
+ : left_top(), right_top(), left_bottom(), right_bottom() {}
+ constexpr CornerRadius(const Point& value)
+ : left_top(value),
+ right_top(value),
+ left_bottom(value),
+ right_bottom(value) {}
+ constexpr CornerRadius(Point left_top, Point right_top, Point left_bottom,
+ Point right_bottom)
+ : left_top(left_top),
+ right_top(right_top),
+ left_bottom(left_bottom),
+ right_bottom(right_bottom) {}
+
+ Point left_top;
+ Point right_top;
+ Point left_bottom;
+ Point right_bottom;
+};
+
+class BorderRenderObject : public RenderObject {
+ public:
+ explicit BorderRenderObject(platform::Brush* brush);
+ BorderRenderObject(const BorderRenderObject& other) = delete;
+ BorderRenderObject(BorderRenderObject&& other) = delete;
+ BorderRenderObject& operator=(const BorderRenderObject& other) = delete;
+ BorderRenderObject& operator=(BorderRenderObject&& other) = delete;
+ ~BorderRenderObject() override;
+
+ bool IsEnabled() const { return is_enabled_; }
+ void SetEnabled(bool enabled) { is_enabled_ = enabled; }
+
+ ID2D1Brush* GetBrush() const { return border_brush_; }
+ void SetBrush(ID2D1Brush* new_brush);
+
+ Thickness GetBorderWidth() const { return border_thickness_; }
+ void SetBorderWidth(const Thickness& thickness) {
+ border_thickness_ = thickness;
+ }
+
+ CornerRadius GetCornerRadius() const { return corner_radius_; }
+ void SetCornerRadius(const CornerRadius& new_corner_radius) {
+ corner_radius_ = new_corner_radius;
+ }
+
+ void Refresh() { RecreateGeometry(); }
+
+ void Draw(ID2D1RenderTarget* render_target) override;
+
+ RenderObject* HitTest(const Point& point) override;
+
+ protected:
+ void OnAddChild(RenderObject* new_child, int position) override;
+
+ void OnSizeChanged(const Size& old_size, const Size& new_size) override;
+
+ void OnMeasureCore(const Size& available_size) override;
+ void OnLayoutCore(const Rect& rect) override;
+ Size OnMeasureContent(const Size& available_size) override;
+ void OnLayoutContent(const Rect& content_rect) override;
+
+ private:
+ RenderObject* GetChild() const {
+ return GetChildren().empty() ? nullptr : GetChildren()[0];
+ }
+
+ void RecreateGeometry();
+
+ private:
+ bool is_enabled_ = false;
+
+ ID2D1Brush* border_brush_ = nullptr;
+ Thickness border_thickness_ = Thickness::Zero();
+ CornerRadius corner_radius_{};
+
+ ID2D1Geometry* geometry_ = nullptr;
+ ID2D1Geometry* border_outer_geometry_ = nullptr;
+};
+} // namespace cru::ui::render
diff --git a/include/cru/ui/render/flex_layout_render_object.hpp b/include/cru/ui/render/flex_layout_render_object.hpp
new file mode 100644
index 00000000..278bf8c2
--- /dev/null
+++ b/include/cru/ui/render/flex_layout_render_object.hpp
@@ -0,0 +1,57 @@
+#pragma once
+#include "render_object.hpp"
+
+#include <optional>
+
+namespace cru::ui::render {
+enum class FlexDirection {
+ Horizontal,
+ HorizontalReverse,
+ Vertical,
+ VertivalReverse
+};
+
+enum class Alignment { Start, End, Center };
+
+struct FlexChildLayoutData {
+ std::optional<float> flex_basis; // nullopt stands for content
+ float flex_grow = 0;
+ float flex_shrink = 0;
+ Alignment alignment = Alignment::Center;
+};
+
+class FlexLayoutRenderObject : public RenderObject {
+ public:
+ FlexLayoutRenderObject() = default;
+ FlexLayoutRenderObject(const FlexLayoutRenderObject& other) = delete;
+ FlexLayoutRenderObject& operator=(const FlexLayoutRenderObject& other) =
+ delete;
+ FlexLayoutRenderObject(FlexLayoutRenderObject&& other) = delete;
+ FlexLayoutRenderObject& operator=(FlexLayoutRenderObject&& other) = delete;
+ ~FlexLayoutRenderObject() override = default;
+
+ FlexDirection GetFlexDirection() const { return direction_; }
+ void SetFlexDirection(FlexDirection direction) { direction_ = direction; }
+
+ Alignment GetContentMainAlign() const { return content_main_align_; }
+ void SetContentMainAlign(Alignment align) { content_main_align_ = align; }
+
+ FlexChildLayoutData* GetChildLayoutData(int position);
+
+ void Draw(platform::Painter* painter) override;
+
+ RenderObject* HitTest(const Point& point) override;
+
+ protected:
+ void OnAddChild(RenderObject* new_child, int position) override;
+ void OnRemoveChild(RenderObject* removed_child, int position) override;
+
+ Size OnMeasureContent(const Size& available_size) override;
+ void OnLayoutContent(const Rect& content_rect) override;
+
+ private:
+ FlexDirection direction_ = FlexDirection::Horizontal;
+ Alignment content_main_align_ = Alignment::Start;
+ std::vector<FlexChildLayoutData> child_layout_data_{};
+};
+} // namespace cru::ui::render
diff --git a/include/cru/ui/render/render_object.hpp b/include/cru/ui/render/render_object.hpp
new file mode 100644
index 00000000..3cd14a6a
--- /dev/null
+++ b/include/cru/ui/render/render_object.hpp
@@ -0,0 +1,98 @@
+#pragma once
+#include "cru/common/base.hpp"
+
+#include "cru/common/ui_base.hpp"
+
+#include <vector>
+
+// forward declarations
+namespace cru::ui {
+class Control;
+}
+
+namespace cru::platform {
+struct Painter;
+}
+
+namespace cru::ui::render {
+class RenderObject : public Object {
+ protected:
+ RenderObject() = default;
+
+ public:
+ RenderObject(const RenderObject& other) = delete;
+ RenderObject(RenderObject&& other) = delete;
+ RenderObject& operator=(const RenderObject& other) = delete;
+ RenderObject& operator=(RenderObject&& other) = delete;
+ ~RenderObject() override = default;
+
+ Control* GetAttachedControl() const { return control_; }
+ void SetAttachedControl(Control* new_control) { control_ = new_control; }
+
+ RenderObject* GetParent() const { return parent_; }
+
+ const std::vector<RenderObject*>& GetChildren() const { return children_; }
+ void AddChild(RenderObject* render_object, int position);
+ void RemoveChild(int position);
+
+ Point GetOffset() const { return offset_; }
+ void SetOffset(const Point& offset) { offset_ = offset; }
+ Size GetSize() const { return size_; }
+ void SetSize(const Size& size) {
+ const auto old_size = size_;
+ size_ = size;
+ OnSizeChanged(old_size, size);
+ }
+
+ Thickness GetMargin() const { return margin_; }
+ void SetMargin(const Thickness& margin) { margin_ = margin; }
+
+ Thickness GetPadding() const { return padding_; }
+ void SetPadding(const Thickness& padding) { padding_ = padding; }
+
+ Size GetPreferredSize() const { return preferred_size_; }
+ void SetPreferredSize(const Size& preferred_size) {
+ preferred_size_ = preferred_size;
+ }
+
+ void Measure(const Size& available_size);
+ void Layout(const Rect& rect);
+
+ virtual void Draw(platform::Painter* render_target) = 0;
+
+ virtual RenderObject* HitTest(const Point& point) = 0;
+
+ protected:
+ virtual void OnParentChanged(RenderObject* old_parent,
+ RenderObject* new_parent);
+
+ virtual void OnAddChild(RenderObject* new_child, int position);
+ virtual void OnRemoveChild(RenderObject* removed_child, int position);
+
+ virtual void OnSizeChanged(const Size& old_size, const Size& new_size);
+
+ virtual void OnMeasureCore(const Size& available_size);
+ virtual void OnLayoutCore(const Rect& rect);
+ virtual Size OnMeasureContent(const Size& available_size) = 0;
+ virtual void OnLayoutContent(const Rect& content_rect) = 0;
+
+ Rect GetContentRect() const;
+
+ private:
+ void SetParent(RenderObject* new_parent);
+
+ private:
+ Control* control_ = nullptr;
+
+ RenderObject* parent_ = nullptr;
+ std::vector<RenderObject*> children_{};
+
+ Point offset_{};
+ Size size_{};
+
+ Thickness margin_{};
+ Thickness padding_{};
+
+ Size preferred_size_{};
+};
+} // namespace cru::ui::render
diff --git a/include/cru/ui/render/text_render_object.hpp b/include/cru/ui/render/text_render_object.hpp
new file mode 100644
index 00000000..7827f994
--- /dev/null
+++ b/include/cru/ui/render/text_render_object.hpp
@@ -0,0 +1,69 @@
+#pragma once
+#include "pre.hpp"
+
+#include "render_object.hpp"
+
+// forward declarations
+struct ID2D1Brush;
+struct IDWriteTextFormat;
+struct IDWriteTextLayout;
+
+namespace cru::ui::render {
+class TextRenderObject : public RenderObject {
+ public:
+ TextRenderObject(ID2D1Brush* brush, IDWriteTextFormat* format,
+ ID2D1Brush* selection_brush);
+ TextRenderObject(const TextRenderObject& other) = delete;
+ TextRenderObject(TextRenderObject&& other) = delete;
+ TextRenderObject& operator=(const TextRenderObject& other) = delete;
+ TextRenderObject& operator=(TextRenderObject&& other) = delete;
+ ~TextRenderObject() override;
+
+ String GetText() const { return text_; }
+ void SetText(String new_text) {
+ text_ = std::move(new_text);
+ RecreateTextLayout();
+ }
+
+ ID2D1Brush* GetBrush() const { return brush_; }
+ void SetBrush(ID2D1Brush* new_brush);
+
+ IDWriteTextFormat* GetTextFormat() const { return text_format_; }
+ void SetTextFormat(IDWriteTextFormat* new_text_format);
+
+ std::optional<TextRange> GetSelectionRange() const {
+ return selection_range_;
+ }
+ void SetSelectionRange(std::optional<TextRange> new_range) {
+ selection_range_ = std::move(new_range);
+ }
+
+ ID2D1Brush* GetSelectionBrush() const { return selection_brush_; }
+ void SetSelectionBrush(ID2D1Brush* new_brush);
+
+ void Refresh() { RecreateTextLayout(); }
+
+ void Draw(ID2D1RenderTarget* render_target) override;
+
+ RenderObject* HitTest(const Point& point) override;
+
+ protected:
+ void OnSizeChanged(const Size& old_size, const Size& new_size) override;
+
+ Size OnMeasureContent(const Size& available_size) override;
+ void OnLayoutContent(const Rect& content_rect) override;
+
+ private:
+ void RecreateTextLayout();
+
+ private:
+ String text_;
+
+ ID2D1Brush* brush_ = nullptr;
+ IDWriteTextFormat* text_format_ = nullptr;
+ IDWriteTextLayout* text_layout_ = nullptr;
+
+ std::optional<TextRange> selection_range_ = std::nullopt;
+ ID2D1Brush* selection_brush_ = nullptr;
+};
+} // namespace cru::ui::render
diff --git a/include/cru/ui/render/window_render_object.hpp b/include/cru/ui/render/window_render_object.hpp
new file mode 100644
index 00000000..63eb8588
--- /dev/null
+++ b/include/cru/ui/render/window_render_object.hpp
@@ -0,0 +1,40 @@
+#pragma once
+#include "pre.hpp"
+
+#include "render_object.hpp"
+
+namespace cru::ui {
+class Window;
+}
+
+namespace cru::ui::render {
+class WindowRenderObject : public RenderObject {
+ public:
+ WindowRenderObject(Window* window) : window_(window) {}
+ WindowRenderObject(const WindowRenderObject& other) = delete;
+ WindowRenderObject(WindowRenderObject&& other) = delete;
+ WindowRenderObject& operator=(const WindowRenderObject& other) = delete;
+ WindowRenderObject& operator=(WindowRenderObject&& other) = delete;
+ ~WindowRenderObject() override = default;
+
+ void MeasureAndLayout();
+
+ void Draw(ID2D1RenderTarget* render_target) override;
+
+ RenderObject* HitTest(const Point& point) override;
+
+ protected:
+ void OnAddChild(RenderObject* new_child, int position) override;
+
+ Size OnMeasureContent(const Size& available_size) override;
+ void OnLayoutContent(const Rect& content_rect) override;
+
+ private:
+ RenderObject* GetChild() const {
+ return GetChildren().empty() ? nullptr : GetChildren()[0];
+ }
+
+ private:
+ Window* window_;
+};
+} // namespace cru::ui::render