aboutsummaryrefslogtreecommitdiff
path: root/src/ui/render
diff options
context:
space:
mode:
Diffstat (limited to 'src/ui/render')
-rw-r--r--src/ui/render/flex_layout_render_object.cpp212
-rw-r--r--src/ui/render/flex_layout_render_object.hpp55
-rw-r--r--src/ui/render/linear_layout_render_object.cpp0
-rw-r--r--src/ui/render/linear_layout_render_object.hpp1
-rw-r--r--src/ui/render/render_object.cpp158
-rw-r--r--src/ui/render/render_object.hpp133
6 files changed, 426 insertions, 133 deletions
diff --git a/src/ui/render/flex_layout_render_object.cpp b/src/ui/render/flex_layout_render_object.cpp
new file mode 100644
index 00000000..4e5171a2
--- /dev/null
+++ b/src/ui/render/flex_layout_render_object.cpp
@@ -0,0 +1,212 @@
+#include "flex_layout_render_object.hpp"
+
+#include <algorithm>
+#include <functional>
+
+#include "cru_debug.hpp"
+#include "graph/graph.hpp"
+
+namespace cru::ui::render {
+FlexChildLayoutData* FlexLayoutRenderObject::GetChildLayoutData(int position) {
+ if (position < 0 || position >= child_layout_data_.size())
+ throw std::invalid_argument("Position out of bound.");
+
+ return &child_layout_data_[position];
+}
+
+void FlexLayoutRenderObject::Draw(ID2D1RenderTarget* render_target) {
+ for (const auto child : GetChildren()) {
+ auto offset = child->GetOffset();
+ graph::WithTransform(render_target,
+ D2D1::Matrix3x2F::Translation(offset.x, offset.y),
+ [child](auto rt) { child->Draw(rt); });
+ }
+}
+
+RenderObject* FlexLayoutRenderObject::HitTest(const Point& point) {
+ const auto& children = GetChildren();
+ for (auto i = children.crbegin(); i != children.crend(); ++i) {
+ auto offset = (*i)->GetOffset();
+ Point p{point.x - offset.x, point.y - offset.y};
+ const auto result = (*i)->HitTest(point);
+ if (result != nullptr) {
+ return result;
+ }
+ }
+ return Rect{Point::Zero(), GetSize()}.IsPointInside(point) ? this : nullptr;
+}
+
+void FlexLayoutRenderObject::OnAddChild(RenderObject* new_child, int position) {
+ child_layout_data_.emplace(child_layout_data_.cbegin() + position);
+}
+
+void FlexLayoutRenderObject::OnRemoveChild(RenderObject* removed_child,
+ int position) {
+ child_layout_data_.erase(child_layout_data_.cbegin() + position);
+}
+
+Size FlexLayoutRenderObject::OnMeasureContent(const Size& available_size) {
+ std::vector<int> has_basis_children;
+ std::vector<int> no_basis_children;
+ std::vector<int> grow_children;
+ std::vector<int> shrink_chilren;
+ for (int i = 0; i < child_layout_data_.size(); i++) {
+ const auto& layout_data = child_layout_data_[i];
+ if (layout_data.flex_basis.has_value())
+ has_basis_children.push_back(i);
+ else
+ no_basis_children.push_back(i);
+ if (layout_data.flex_grow > 0) grow_children.push_back(i);
+ if (layout_data.flex_shrink > 0) shrink_chilren.push_back(i);
+ }
+
+ std::function<float(const Size&)> get_main_length;
+ std::function<float(const Size&)> get_cross_length;
+ std::function<Size(float main, float cross)> create_size;
+
+ if (direction_ == FlexDirection::Horizontal ||
+ direction_ == FlexDirection::HorizontalReverse) {
+ get_main_length = [](const Size& size) { return size.width; };
+ get_cross_length = [](const Size& size) { return size.height; };
+ create_size = [](float main, float cross) { return Size(main, cross); };
+ } else {
+ get_main_length = [](const Size& size) { return size.height; };
+ get_cross_length = [](const Size& size) { return size.width; };
+ create_size = [](float main, float cross) { return Size(cross, main); };
+ }
+
+ const auto& children = GetChildren();
+
+ float remain_main_length = get_main_length(available_size);
+ float max_cross_length = 0;
+
+ for (const int i : has_basis_children) {
+ const auto child = children[i];
+ const float basis = child_layout_data_[i].flex_basis.value();
+ child->Measure(create_size(basis, get_cross_length(available_size)));
+ remain_main_length -= basis;
+ const float child_preferred_cross_length =
+ get_cross_length(child->GetPreferredSize());
+ child->SetPreferredSize(create_size(basis, child_preferred_cross_length));
+ max_cross_length = std::max(max_cross_length, child_preferred_cross_length);
+ }
+
+ for (const int i : no_basis_children) {
+ const auto child = children[i];
+ child->Measure(create_size(remain_main_length > 0 ? remain_main_length : 0,
+ get_cross_length(available_size)));
+ remain_main_length -= get_main_length(child->GetPreferredSize());
+ max_cross_length =
+ std::max(max_cross_length, get_cross_length(child->GetPreferredSize()));
+ }
+
+ if (remain_main_length > 0) {
+ float total_grow = 0;
+ for (const int i : grow_children)
+ total_grow += child_layout_data_[i].flex_grow;
+
+ for (const int i : grow_children) {
+ const float distributed_grow_length =
+ remain_main_length * (child_layout_data_[i].flex_grow / total_grow);
+ const auto child = children[i];
+ const float new_main_length =
+ get_main_length(child->GetPreferredSize()) + distributed_grow_length;
+ child->Measure(
+ create_size(new_main_length, get_cross_length(available_size)));
+ const float new_child_preferred_cross_length =
+ get_cross_length(child->GetPreferredSize());
+ child->SetPreferredSize(
+ create_size(new_main_length, new_child_preferred_cross_length));
+ max_cross_length =
+ std::max(max_cross_length, new_child_preferred_cross_length);
+ }
+ }
+
+ if (remain_main_length < 0) {
+ float total_shrink = 0;
+ for (const int i : shrink_chilren)
+ total_shrink += child_layout_data_[i].flex_shrink;
+
+ for (const int i : shrink_chilren) {
+ const float distributed_shrink_length = // negative
+ remain_main_length *
+ (child_layout_data_[i].flex_shrink / total_shrink);
+ const auto child = children[i];
+ float new_main_length = get_main_length(child->GetPreferredSize()) +
+ distributed_shrink_length;
+ new_main_length = new_main_length > 0 ? new_main_length : 0;
+ child->Measure(
+ create_size(new_main_length, get_cross_length(available_size)));
+ const float new_child_preferred_cross_length =
+ get_cross_length(child->GetPreferredSize());
+ child->SetPreferredSize(
+ create_size(new_main_length, new_child_preferred_cross_length));
+ max_cross_length =
+ std::max(max_cross_length, new_child_preferred_cross_length);
+ }
+ }
+
+ return create_size(get_main_length(available_size) -
+ (remain_main_length > 0 ? remain_main_length : 0),
+ max_cross_length);
+}
+
+void FlexLayoutRenderObject::OnLayoutContent(const Rect& content_rect) {
+ auto calculate_cross_anchor = [](Alignment alignment, float start_point,
+ float content_length,
+ float preferred_length) -> float {
+ switch (alignment) {
+ case Alignment::Start:
+ return start_point;
+ case Alignment::Center:
+ return start_point + (content_length - preferred_length) / 2.0f;
+ case Alignment::End:
+ return start_point + content_length - preferred_length;
+ }
+ };
+
+ const auto& children = GetChildren();
+ if (direction_ == FlexDirection::Horizontal ||
+ direction_ == FlexDirection::HorizontalReverse) {
+ auto anchor_x = 0;
+ for (int i = 0; i < children.size(); i++) {
+ const auto child = children[i];
+ const auto size = child->GetPreferredSize();
+
+ float real_anchor_x;
+ if (direction_ == FlexDirection::Horizontal) {
+ real_anchor_x = anchor_x + content_rect.left;
+ } else {
+ real_anchor_x = content_rect.GetRight() - anchor_x;
+ }
+ child->Layout(Rect{real_anchor_x,
+ calculate_cross_anchor(
+ child_layout_data_[i].alignment, content_rect.top,
+ content_rect.height, size.height),
+ size.width, size.height});
+
+ anchor_x += size.width;
+ }
+ } else {
+ auto anchor_y = 0;
+ for (int i = 0; i < children.size(); i++) {
+ const auto child = children[i];
+ const auto size = child->GetPreferredSize();
+
+ float real_anchor_y;
+ if (direction_ == FlexDirection::Vertical) {
+ real_anchor_y = anchor_y + content_rect.top;
+ } else {
+ real_anchor_y = content_rect.GetBottom() - anchor_y;
+ }
+ child->Layout(Rect{real_anchor_y,
+ calculate_cross_anchor(child_layout_data_[i].alignment,
+ content_rect.left,
+ content_rect.width, size.width),
+ size.width, size.height});
+
+ anchor_y += size.height;
+ }
+ }
+}
+} // namespace cru::ui::render
diff --git a/src/ui/render/flex_layout_render_object.hpp b/src/ui/render/flex_layout_render_object.hpp
new file mode 100644
index 00000000..7172f0c0
--- /dev/null
+++ b/src/ui/render/flex_layout_render_object.hpp
@@ -0,0 +1,55 @@
+#pragma once
+#include "pre.hpp"
+
+#include <optional>
+
+#include "render_object.hpp"
+
+namespace cru::ui::render {
+enum class FlexDirection {
+ Horizontal,
+ HorizontalReverse,
+ Vertical,
+ VertivalReverse
+};
+
+enum class Alignment { Start, End, Center };
+
+struct FlexChildLayoutData {
+ std::optional<float> flex_basis;
+ 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; }
+
+ FlexChildLayoutData* GetChildLayoutData(int position);
+
+ void Draw(ID2D1RenderTarget* render_target) 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;
+ std::vector<FlexChildLayoutData> child_layout_data_{};
+};
+} // namespace cru::ui::render
diff --git a/src/ui/render/linear_layout_render_object.cpp b/src/ui/render/linear_layout_render_object.cpp
deleted file mode 100644
index e69de29b..00000000
--- a/src/ui/render/linear_layout_render_object.cpp
+++ /dev/null
diff --git a/src/ui/render/linear_layout_render_object.hpp b/src/ui/render/linear_layout_render_object.hpp
deleted file mode 100644
index 6f70f09b..00000000
--- a/src/ui/render/linear_layout_render_object.hpp
+++ /dev/null
@@ -1 +0,0 @@
-#pragma once
diff --git a/src/ui/render/render_object.cpp b/src/ui/render/render_object.cpp
index c2aaeb62..0035d1be 100644
--- a/src/ui/render/render_object.cpp
+++ b/src/ui/render/render_object.cpp
@@ -1,90 +1,130 @@
#include "render_object.hpp"
-#include <utility>
-namespace cru::ui::render
-{
-void RenderObject::SetRenderHost(IRenderHost* new_render_host)
-{
- if (new_render_host == render_host_) return;
+#include "cru_debug.hpp"
- const auto old = render_host_;
- render_host_ = new_render_host;
- OnRenderHostChanged(old, new_render_host);
+namespace cru::ui::render {
+void RenderObject::SetRenderHost(IRenderHost* new_render_host) {
+ if (new_render_host == render_host_) return;
+
+ const auto old = render_host_;
+ render_host_ = new_render_host;
+ OnRenderHostChanged(old, new_render_host);
}
-void RenderObject::AddChild(RenderObject* render_object, const int position)
-{
- if (render_object->GetParent() != nullptr)
- throw std::invalid_argument("Render object already has a parent.");
+void RenderObject::AddChild(RenderObject* render_object, const int position) {
+ if (render_object->GetParent() != nullptr)
+ throw std::invalid_argument("Render object already has a parent.");
- if (position < 0)
- throw std::invalid_argument("Position index is less than 0.");
+ if (position < 0)
+ throw std::invalid_argument("Position index is less than 0.");
- if (static_cast<std::vector<RenderObject*>::size_type>(position) >
- children_.size())
- throw std::invalid_argument("Position index is out of bound.");
+ if (static_cast<std::vector<RenderObject*>::size_type>(position) >
+ children_.size())
+ throw std::invalid_argument("Position index is out of bound.");
- children_.insert(children_.cbegin() + position, render_object);
- render_object->SetParent(this);
- OnAddChild(render_object, position);
+ children_.insert(children_.cbegin() + position, render_object);
+ render_object->SetParent(this);
+ OnAddChild(render_object, position);
}
-void RenderObject::RemoveChild(const int position)
-{
- if (position < 0)
- throw std::invalid_argument("Position index is less than 0.");
+void RenderObject::RemoveChild(const int position) {
+ if (position < 0)
+ throw std::invalid_argument("Position index is less than 0.");
- if (static_cast<std::vector<RenderObject*>::size_type>(position) >=
- children_.size())
- throw std::invalid_argument("Position index is out of bound.");
+ if (static_cast<std::vector<RenderObject*>::size_type>(position) >=
+ children_.size())
+ throw std::invalid_argument("Position index is out of bound.");
- const auto i = children_.cbegin() + position;
- const auto removed_child = *i;
- children_.erase(i);
- removed_child->SetParent(nullptr);
- OnRemoveChild(removed_child, position);
+ const auto i = children_.cbegin() + position;
+ const auto removed_child = *i;
+ children_.erase(i);
+ removed_child->SetParent(nullptr);
+ OnRemoveChild(removed_child, position);
}
+void RenderObject::Measure(const Size& available_size) {
+ OnMeasureCore(available_size);
+}
-void RenderObject::OnRenderHostChanged(IRenderHost* old_render_host,
- IRenderHost* new_render_host)
-{
+void RenderObject::Layout(const Rect& rect) {
+ SetOffset(rect.GetLeftTop());
+ SetSize(rect.GetSize());
+ OnLayoutCore(Rect{Point::Zero(), rect.GetSize()});
}
-void RenderObject::InvalidateRenderHostPaint() const
-{
- if (render_host_ != nullptr) render_host_->InvalidatePaint();
+void RenderObject::OnRenderHostChanged(IRenderHost* old_render_host,
+ IRenderHost* new_render_host) {}
+
+void RenderObject::InvalidateRenderHostPaint() const {
+ if (render_host_ != nullptr) render_host_->InvalidatePaint();
}
-void RenderObject::InvalidateRenderHostLayout() const
-{
- if (render_host_ != nullptr) render_host_->InvalidateLayout();
+void RenderObject::InvalidateRenderHostLayout() const {
+ if (render_host_ != nullptr) render_host_->InvalidateLayout();
}
void RenderObject::OnParentChanged(RenderObject* old_parent,
- RenderObject* new_parent)
-{
-}
+ RenderObject* new_parent) {}
+void RenderObject::OnAddChild(RenderObject* new_child, int position) {}
-void RenderObject::OnAddChild(RenderObject* new_child, int position)
-{
-}
+void RenderObject::OnRemoveChild(RenderObject* removed_child, int position) {}
-void RenderObject::OnRemoveChild(RenderObject* removed_child, int position)
-{
+void RenderObject::SetParent(RenderObject* new_parent) {
+ const auto old_parent = parent_;
+ parent_ = new_parent;
+ OnParentChanged(old_parent, new_parent);
}
-void RenderObject::SetParent(RenderObject* new_parent)
-{
- const auto old_parent = parent_;
- parent_ = new_parent;
- OnParentChanged(old_parent, new_parent);
+void RenderObject::OnMeasureCore(const Size& available_size) {
+ Size margin_padding_size{
+ margin_.GetHorizontalTotal() + padding_.GetHorizontalTotal(),
+ margin_.GetVerticalTotal() + padding_.GetVerticalTotal()};
+ const auto content_available_size = available_size - margin_padding_size;
+ auto coerced_content_available_size = content_available_size;
+
+ if (coerced_content_available_size.width < 0) {
+ debug::DebugMessage(
+ L"Measure: horizontal length of padding and margin is bigger than "
+ L"available length.");
+ coerced_content_available_size.width = 0;
+ }
+ if (coerced_content_available_size.height < 0) {
+ debug::DebugMessage(
+ L"Measure: vertical length of padding and margin is bigger than "
+ L"available length.");
+ coerced_content_available_size.height = 0;
+ }
+
+ const auto actual_content_size =
+ OnMeasureContent(coerced_content_available_size);
+
+ SetPreferredSize(margin_padding_size + content_available_size +
+ actual_content_size);
}
-
-void LinearLayoutRenderObject::Measure(const MeasureConstraint& constraint)
-{
-
+void RenderObject::OnLayoutCore(const Rect& rect) {
+ Size margin_padding_size{
+ margin_.GetHorizontalTotal() + padding_.GetHorizontalTotal(),
+ margin_.GetVerticalTotal() + padding_.GetVerticalTotal()};
+ const auto content_available_size = rect.GetSize() - margin_padding_size;
+ auto coerced_content_available_size = content_available_size;
+
+ if (coerced_content_available_size.width < 0) {
+ debug::DebugMessage(
+ L"Layout: horizontal length of padding and margin is bigger than "
+ L"available length.");
+ coerced_content_available_size.width = 0;
+ }
+ if (coerced_content_available_size.height < 0) {
+ debug::DebugMessage(
+ L"Layout: vertical length of padding and margin is bigger than "
+ L"available length.");
+ coerced_content_available_size.height = 0;
+ }
+
+ OnLayoutContent(Rect{margin_.left + padding_.left, margin_.top + padding_.top,
+ coerced_content_available_size.width,
+ coerced_content_available_size.height});
}
} // namespace cru::ui::render
diff --git a/src/ui/render/render_object.hpp b/src/ui/render/render_object.hpp
index 00f761d1..aeba1457 100644
--- a/src/ui/render/render_object.hpp
+++ b/src/ui/render/render_object.hpp
@@ -1,5 +1,4 @@
#pragma once
-
#include "pre.hpp"
#include <optional>
@@ -9,101 +8,89 @@
#include "base.hpp"
#include "ui/ui_base.hpp"
-namespace cru::ui::render
-{
-struct MeasureConstraint
-{
- std::optional<float> min_width;
- std::optional<float> min_height;
- std::optional<float> max_width;
- std::optional<float> max_height;
+namespace cru::ui::render {
+struct IRenderHost : Interface {
+ virtual void InvalidatePaint() = 0;
+ virtual void InvalidateLayout() = 0;
};
+class RenderObject : public Object {
+ protected:
+ RenderObject() = default;
-struct LayoutConstraint
-{
- float preferred_width;
- float preferred_height;
-};
+ public:
+ RenderObject(const RenderObject& other) = delete;
+ RenderObject(RenderObject&& other) = delete;
+ RenderObject& operator=(const RenderObject& other) = delete;
+ RenderObject& operator=(RenderObject&& other) = delete;
+ ~RenderObject() override = default;
+ IRenderHost* GetRenderHost() const { return render_host_; }
+ void SetRenderHost(IRenderHost* new_render_host);
-struct IRenderHost : Interface
-{
- virtual void InvalidatePaint() = 0;
- virtual void InvalidateLayout() = 0;
-};
+ RenderObject* GetParent() const { return parent_; }
+ const std::vector<RenderObject*>& GetChildren() const { return children_; }
+ void AddChild(RenderObject* render_object, int position);
+ void RemoveChild(int position);
-// features:
-// 1. tree
-// 2. layout
-// 3. paint
-// 3. hit test
-class RenderObject : public Object
-{
-protected:
- RenderObject() = default;
+ Point GetOffset() const { return offset_; }
+ void SetOffset(const Point& offset) { offset_ = offset; }
+ Size GetSize() const { return size_; }
+ void SetSize(const Size& size) { size_ = size; }
-public:
- RenderObject(const RenderObject& other) = delete;
- RenderObject(RenderObject&& other) = delete;
- RenderObject& operator=(const RenderObject& other) = delete;
- RenderObject& operator=(RenderObject&& other) = delete;
- ~RenderObject() override = default;
+ Thickness GetMargin() const { return margin_; }
+ void SetMargin(const Thickness& margin) { margin_ = margin; }
- IRenderHost* GetRenderHost() const { return render_host_; }
- void SetRenderHost(IRenderHost* new_render_host);
+ Thickness GetPadding() const { return padding_; }
+ void SetPadding(const Thickness& padding) { padding_ = padding; }
- RenderObject* GetParent() const { return parent_; }
+ Size GetPreferredSize() const { return preferred_size_; }
+ void SetPreferredSize(const Size& preferred_size) {
+ preferred_size_ = preferred_size;
+ }
- const std::vector<RenderObject*>& GetChildren() const { return children_; }
- void AddChild(RenderObject* render_object, int position);
- void RemoveChild(int position);
+ void Measure(const Size& available_size);
+ void Layout(const Rect& rect);
- virtual void Measure(const MeasureConstraint& constraint) = 0;
- virtual void Layout(const LayoutConstraint& constraint) = 0;
+ virtual void Draw(ID2D1RenderTarget* render_target) = 0;
- virtual void Draw(ID2D1RenderTarget* render_target) = 0;
+ virtual RenderObject* HitTest(const Point& point) = 0;
- virtual void HitTest(const Point& point) = 0;
+ protected:
+ virtual void OnRenderHostChanged(IRenderHost* old_render_host,
+ IRenderHost* new_render_host);
-protected:
- virtual void OnRenderHostChanged(IRenderHost* old_render_host,
- IRenderHost* new_render_host);
+ void InvalidateRenderHostPaint() const;
+ void InvalidateRenderHostLayout() const;
- void InvalidateRenderHostPaint() const;
- void InvalidateRenderHostLayout() const;
+ virtual void OnParentChanged(RenderObject* old_parent,
+ RenderObject* new_parent);
- 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 OnAddChild(RenderObject* new_child, int position);
- virtual void OnRemoveChild(RenderObject* removed_child, int position);
+ virtual Size OnMeasureContent(const Size& available_size) = 0;
+ virtual void OnLayoutContent(const Rect& content_rect) = 0;
-private:
- void SetParent(RenderObject* new_parent);
+ private:
+ void SetParent(RenderObject* new_parent);
-private:
- IRenderHost* render_host_ = nullptr;
- RenderObject* parent_ = nullptr;
- std::vector<RenderObject*> children_;
-};
+ void OnMeasureCore(const Size& available_size);
+ void OnLayoutCore(const Rect& rect);
+
+ private:
+ IRenderHost* render_host_ = nullptr;
+ RenderObject* parent_ = nullptr;
+ std::vector<RenderObject*> children_{};
-class LinearLayoutRenderObject : public RenderObject
-{
-public:
- LinearLayoutRenderObject() = default;
- LinearLayoutRenderObject(const LinearLayoutRenderObject& other) = delete;
- LinearLayoutRenderObject& operator=(const LinearLayoutRenderObject& other) =
- delete;
- LinearLayoutRenderObject(LinearLayoutRenderObject&& other) = delete;
- LinearLayoutRenderObject& operator=(LinearLayoutRenderObject&& other) =
- delete;
- ~LinearLayoutRenderObject() = default;
+ Point offset_ = Point::Zero();
+ Size size_ = Size::Zero();
- void Measure(const MeasureConstraint& constraint) override;
+ Thickness margin_ = Thickness::Zero();
+ Thickness padding_ = Thickness::Zero();
-private:
+ Size preferred_size_ = Size::Zero();
};
} // namespace cru::ui::render