diff options
Diffstat (limited to 'src/ui/render')
-rw-r--r-- | src/ui/render/flex_layout_render_object.cpp | 212 | ||||
-rw-r--r-- | src/ui/render/flex_layout_render_object.hpp | 55 | ||||
-rw-r--r-- | src/ui/render/linear_layout_render_object.cpp | 0 | ||||
-rw-r--r-- | src/ui/render/linear_layout_render_object.hpp | 1 | ||||
-rw-r--r-- | src/ui/render/render_object.cpp | 158 | ||||
-rw-r--r-- | src/ui/render/render_object.hpp | 133 |
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 |