diff options
-rw-r--r-- | include/cru/ui/base.hpp | 14 | ||||
-rw-r--r-- | include/cru/ui/controls/flex_layout.hpp | 5 | ||||
-rw-r--r-- | include/cru/ui/controls/stack_layout.hpp | 37 | ||||
-rw-r--r-- | include/cru/ui/controls/text_box.hpp | 20 | ||||
-rw-r--r-- | include/cru/ui/render/flex_layout_render_object.hpp | 25 | ||||
-rw-r--r-- | include/cru/ui/render/layout_render_object.hpp | 91 | ||||
-rw-r--r-- | include/cru/ui/render/render_object.hpp | 16 | ||||
-rw-r--r-- | include/cru/ui/render/stack_layout_render_object.hpp | 22 | ||||
-rw-r--r-- | src/main.cpp | 11 | ||||
-rw-r--r-- | src/ui/CMakeLists.txt | 7 | ||||
-rw-r--r-- | src/ui/controls/flex_layout.cpp | 6 | ||||
-rw-r--r-- | src/ui/controls/stack_layout.cpp | 27 | ||||
-rw-r--r-- | src/ui/controls/text_box.cpp | 1 | ||||
-rw-r--r-- | src/ui/render/flex_layout_render_object.cpp | 66 | ||||
-rw-r--r-- | src/ui/render/stack_layout_render_object.cpp | 49 |
15 files changed, 305 insertions, 92 deletions
diff --git a/include/cru/ui/base.hpp b/include/cru/ui/base.hpp index 90a3f746..703d61fc 100644 --- a/include/cru/ui/base.hpp +++ b/include/cru/ui/base.hpp @@ -27,4 +27,18 @@ using cru::platform::colors::black; using cru::platform::colors::skyblue; using cru::platform::colors::white; } // namespace colors + +namespace internal { +constexpr int align_start = 0; +constexpr int align_end = align_start + 1; +constexpr int align_center = align_end + 1; +constexpr int align_stretch = align_center + 1; +} // namespace internal + +enum class Alignment { + Start = internal::align_start, + End = internal::align_end, + Center = internal::align_center, + Stretch = internal::align_stretch +}; } // namespace cru::ui diff --git a/include/cru/ui/controls/flex_layout.hpp b/include/cru/ui/controls/flex_layout.hpp index ff8ec53b..7861534a 100644 --- a/include/cru/ui/controls/flex_layout.hpp +++ b/include/cru/ui/controls/flex_layout.hpp @@ -2,7 +2,6 @@ #include "../layout_control.hpp" #include "../render/flex_layout_render_object.hpp" -#include "../window.hpp" #include <memory> @@ -29,9 +28,7 @@ class FlexLayout : public LayoutControl { FlexLayout& operator=(FlexLayout&& other) = delete; ~FlexLayout() override = default; - std::string_view GetControlType() const final { - return control_type; - } + std::string_view GetControlType() const final { return control_type; } render::RenderObject* GetRenderObject() const override; diff --git a/include/cru/ui/controls/stack_layout.hpp b/include/cru/ui/controls/stack_layout.hpp new file mode 100644 index 00000000..298de089 --- /dev/null +++ b/include/cru/ui/controls/stack_layout.hpp @@ -0,0 +1,37 @@ +#pragma once +#include "../layout_control.hpp" + +#include <memory> + +namespace cru::ui::render { +class StackLayoutRenderObject; +} + +namespace cru::ui::controls { +class StackLayout : public LayoutControl { + public: + static constexpr std::string_view control_type = "StackLayout"; + + static StackLayout* Create() { return new StackLayout(); } + + protected: + StackLayout(); + + public: + CRU_DELETE_COPY(StackLayout) + CRU_DELETE_MOVE(StackLayout) + + ~StackLayout() override; + + std::string_view GetControlType() const final { return control_type; } + + render::RenderObject* GetRenderObject() const override; + + protected: + void OnAddChild(Control* child, int position) override; + void OnRemoveChild(Control* child, int position) override; + + private: + std::shared_ptr<render::StackLayoutRenderObject> render_object_; +}; +} // namespace cru::ui::controls diff --git a/include/cru/ui/controls/text_box.hpp b/include/cru/ui/controls/text_box.hpp new file mode 100644 index 00000000..48168e9d --- /dev/null +++ b/include/cru/ui/controls/text_box.hpp @@ -0,0 +1,20 @@ +#pragma once +#include "../no_child_control.hpp" + +namespace cru::ui::controls { +class TextBox : public NoChildControl { + public: + static constexpr std::string_view control_type = "TextBox"; + + protected: + TextBox(); + + public: + CRU_DELETE_COPY(TextBox) + CRU_DELETE_MOVE(TextBox) + + ~TextBox() override; + + std::string_view GetControlType() const final { return control_type; } +}; +} // namespace cru::ui::controls diff --git a/include/cru/ui/render/flex_layout_render_object.hpp b/include/cru/ui/render/flex_layout_render_object.hpp index ab6265d6..c2bc5fd1 100644 --- a/include/cru/ui/render/flex_layout_render_object.hpp +++ b/include/cru/ui/render/flex_layout_render_object.hpp @@ -1,5 +1,5 @@ #pragma once -#include "render_object.hpp" +#include "layout_render_object.hpp" #include <optional> @@ -39,9 +39,9 @@ struct FlexChildLayoutData { std::optional<FlexCrossAlignment> cross_alignment = std::nullopt; }; -class FlexLayoutRenderObject : public RenderObject { +class FlexLayoutRenderObject : public LayoutRenderObject<FlexChildLayoutData> { public: - FlexLayoutRenderObject(); + FlexLayoutRenderObject() = default; FlexLayoutRenderObject(const FlexLayoutRenderObject& other) = delete; FlexLayoutRenderObject& operator=(const FlexLayoutRenderObject& other) = delete; @@ -67,25 +67,7 @@ class FlexLayoutRenderObject : public RenderObject { InvalidateLayout(); } - FlexChildLayoutData GetChildLayoutData(int position) { - assert(position >= 0 && position < static_cast<int>(child_layout_data_.size())); - return child_layout_data_[position]; - } - - void SetChildLayoutData(int position, const FlexChildLayoutData& data) { - assert(position >= 0 && position < static_cast<int>(child_layout_data_.size())); - child_layout_data_[position] = data; - InvalidateLayout(); - } - - void Draw(platform::graph::IPainter* 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; @@ -93,6 +75,5 @@ class FlexLayoutRenderObject : public RenderObject { FlexDirection direction_ = FlexDirection::Horizontal; FlexMainAlignment content_main_align_ = FlexMainAlignment::Start; FlexCrossAlignment item_cross_align_ = FlexCrossAlignment::Center; - std::vector<FlexChildLayoutData> child_layout_data_{}; }; } // namespace cru::ui::render diff --git a/include/cru/ui/render/layout_render_object.hpp b/include/cru/ui/render/layout_render_object.hpp new file mode 100644 index 00000000..db6daba9 --- /dev/null +++ b/include/cru/ui/render/layout_render_object.hpp @@ -0,0 +1,91 @@ +#pragma once +#include "render_object.hpp" + +#include "cru/platform/graph/util/painter.hpp" + +#include <cassert> +#include <functional> + +namespace cru::ui::render { +template <typename TChildLayoutData> +class LayoutRenderObject : public RenderObject { + public: + using ChildLayoutData = TChildLayoutData; + + protected: + LayoutRenderObject() : RenderObject(ChildMode::Multiple) {} + + public: + CRU_DELETE_COPY(LayoutRenderObject) + CRU_DELETE_MOVE(LayoutRenderObject) + + ~LayoutRenderObject() override = default; + + ChildLayoutData* GetChildLayoutData(int position) { + assert(position >= 0 && + position < static_cast<int>(child_layout_data_.size())); + return &child_layout_data_[position]; + } + + void Draw(platform::graph::IPainter* 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; + + private: + std::vector<ChildLayoutData> child_layout_data_{}; +}; + +template <typename TChildLayoutData> +void LayoutRenderObject<TChildLayoutData>::Draw( + platform::graph::IPainter* painter) { + for (const auto child : GetChildren()) { + auto offset = child->GetOffset(); + platform::graph::util::WithTransform( + painter, platform::Matrix::Translation(offset.x, offset.y), + [child](auto p) { child->Draw(p); }); + } +} + +template <typename TChildLayoutData> +RenderObject* LayoutRenderObject<TChildLayoutData>::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(p); + if (result != nullptr) { + return result; + } + } + + const auto margin = GetMargin(); + const auto size = GetSize(); + return Rect{margin.left, margin.top, + std::max(size.width - margin.GetHorizontalTotal(), 0.0f), + std::max(size.height - margin.GetVerticalTotal(), 0.0f)} + .IsPointInside(point) + ? this + : nullptr; +} // namespace cru::ui::render + +template <typename TChildLayoutData> +void LayoutRenderObject<TChildLayoutData>::OnAddChild(RenderObject* new_child, + int position) { + CRU_UNUSED(new_child) + + child_layout_data_.emplace(child_layout_data_.cbegin() + position); +} + +template <typename TChildLayoutData> +void LayoutRenderObject<TChildLayoutData>::OnRemoveChild( + RenderObject* removed_child, int position) { + CRU_UNUSED(removed_child) + + child_layout_data_.erase(child_layout_data_.cbegin() + position); +} +} // namespace cru::ui::render diff --git a/include/cru/ui/render/render_object.hpp b/include/cru/ui/render/render_object.hpp index 3e351d34..73543aa3 100644 --- a/include/cru/ui/render/render_object.hpp +++ b/include/cru/ui/render/render_object.hpp @@ -34,7 +34,16 @@ struct IRenderHost : Interface { class RenderObject : public Object { protected: + enum class ChildMode { + None, + Single, + Multiple, + }; + RenderObject() = default; + RenderObject(ChildMode child_mode) : RenderObject() { + SetChildMode(child_mode); + } public: RenderObject(const RenderObject& other) = delete; @@ -52,6 +61,7 @@ class RenderObject : public Object { RenderObject* GetParent() const { return parent_; } const std::vector<RenderObject*>& GetChildren() const { return children_; } + int GetChildCount() const { return static_cast<int>(children_.size()); } void AddChild(RenderObject* render_object, int position); void RemoveChild(int position); @@ -79,12 +89,6 @@ class RenderObject : public Object { virtual RenderObject* HitTest(const Point& point) = 0; protected: - enum class ChildMode { - None, - Single, - Multiple, - }; - void SetChildMode(ChildMode mode) { child_mode_ = mode; } void InvalidateLayout() const { diff --git a/include/cru/ui/render/stack_layout_render_object.hpp b/include/cru/ui/render/stack_layout_render_object.hpp new file mode 100644 index 00000000..0d33e7e3 --- /dev/null +++ b/include/cru/ui/render/stack_layout_render_object.hpp @@ -0,0 +1,22 @@ +#pragma once +#include "layout_render_object.hpp" + +namespace cru::ui::render { +struct StackChildLayoutData { + Alignment horizontal = Alignment::Start; + Alignment vertical = Alignment::Start; +}; + +class StackLayoutRenderObject + : public LayoutRenderObject<StackChildLayoutData> { + public: + StackLayoutRenderObject() = default; + CRU_DELETE_COPY(StackLayoutRenderObject) + CRU_DELETE_MOVE(StackLayoutRenderObject) + ~StackLayoutRenderObject() = default; + + protected: + Size OnMeasureContent(const Size& available_size) override; + void OnLayoutContent(const Rect& content_rect) override; +}; +} // namespace cru::ui::render diff --git a/src/main.cpp b/src/main.cpp index ece75367..e2a766dc 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,6 +2,7 @@ #include "cru/platform/native/window.hpp" #include "cru/ui/controls/button.hpp" #include "cru/ui/controls/flex_layout.hpp" +#include "cru/ui/controls/stack_layout.hpp" #include "cru/ui/controls/text_block.hpp" #include "cru/ui/window.hpp" #include "cru/win/native/ui_application.hpp" @@ -12,6 +13,7 @@ using cru::ui::Thickness; using cru::ui::Window; using cru::ui::controls::Button; using cru::ui::controls::FlexLayout; +using cru::ui::controls::StackLayout; using cru::ui::controls::TextBlock; int main() { @@ -36,7 +38,14 @@ int main() { const auto text_block2 = TextBlock::Create(); text_block2->SetText("Hello World!"); - flex_layout->AddChild(text_block2, 1); + + const auto text_block3 = TextBlock::Create(); + text_block3->SetText("Overlapped text"); + + const auto stack_layout = StackLayout::Create(); + stack_layout->AddChild(text_block2, 0); + stack_layout->AddChild(text_block3, 1); + flex_layout->AddChild(stack_layout, 1); window->ResolveNativeWindow()->SetVisible(true); diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt index 111e3e33..64799b7a 100644 --- a/src/ui/CMakeLists.txt +++ b/src/ui/CMakeLists.txt @@ -15,10 +15,13 @@ add_library(cru_ui STATIC controls/button.cpp controls/container.cpp controls/flex_layout.cpp + controls/stack_layout.cpp controls/text_block.cpp + controls/text_box.cpp render/border_render_object.cpp render/flex_layout_render_object.cpp render/render_object.cpp + render/stack_layout_render_object.cpp render/text_render_object.cpp render/window_render_object.cpp ) @@ -35,10 +38,14 @@ target_sources(cru_ui PUBLIC ${CRU_UI_INCLUDE_DIR}/controls/button.hpp ${CRU_UI_INCLUDE_DIR}/controls/container.hpp ${CRU_UI_INCLUDE_DIR}/controls/flex_layout.hpp + ${CRU_UI_INCLUDE_DIR}/controls/stack_layout.hpp + ${CRU_UI_INCLUDE_DIR}/controls/text_box.hpp ${CRU_UI_INCLUDE_DIR}/controls/text_block.hpp ${CRU_UI_INCLUDE_DIR}/render/border_render_object.hpp ${CRU_UI_INCLUDE_DIR}/render/flex_layout_render_object.hpp + ${CRU_UI_INCLUDE_DIR}/render/layout_render_object.hpp ${CRU_UI_INCLUDE_DIR}/render/render_object.hpp + ${CRU_UI_INCLUDE_DIR}/render/stack_layout_render_object.hpp ${CRU_UI_INCLUDE_DIR}/render/text_render_object.hpp ${CRU_UI_INCLUDE_DIR}/render/window_render_object.hpp ) diff --git a/src/ui/controls/flex_layout.cpp b/src/ui/controls/flex_layout.cpp index 6491b180..6ea26d92 100644 --- a/src/ui/controls/flex_layout.cpp +++ b/src/ui/controls/flex_layout.cpp @@ -28,15 +28,15 @@ int FindPosition(render::RenderObject* parent, render::RenderObject* child) { FlexChildLayoutData FlexLayout::GetChildLayoutData(Control* control) { assert(control); - return render_object_->GetChildLayoutData( + return *render_object_->GetChildLayoutData( FindPosition(render_object_.get(), control->GetRenderObject())); } void FlexLayout::SetChildLayoutData(Control* control, const FlexChildLayoutData& data) { assert(control); - render_object_->SetChildLayoutData( - FindPosition(render_object_.get(), control->GetRenderObject()), data); + *render_object_->GetChildLayoutData( + FindPosition(render_object_.get(), control->GetRenderObject())) = data; } void FlexLayout::OnAddChild(Control* child, int position) { diff --git a/src/ui/controls/stack_layout.cpp b/src/ui/controls/stack_layout.cpp new file mode 100644 index 00000000..b9abb510 --- /dev/null +++ b/src/ui/controls/stack_layout.cpp @@ -0,0 +1,27 @@ +#include "cru/ui/controls/stack_layout.hpp" + +#include "cru/ui/render/stack_layout_render_object.hpp" + +namespace cru::ui::controls { +using render::StackLayoutRenderObject; + +StackLayout::StackLayout() : render_object_(new StackLayoutRenderObject()) { + render_object_->SetAttachedControl(this); +} + +StackLayout::~StackLayout() = default; + +render::RenderObject* StackLayout::GetRenderObject() const { + return render_object_.get(); +} + +void StackLayout::OnAddChild(Control* child, int position) { + render_object_->AddChild(child->GetRenderObject(), position); +} + +void StackLayout::OnRemoveChild(Control* child, int position) { + CRU_UNUSED(child) + + render_object_->RemoveChild(position); +} +} // namespace cru::ui::controls diff --git a/src/ui/controls/text_box.cpp b/src/ui/controls/text_box.cpp new file mode 100644 index 00000000..7fde06d9 --- /dev/null +++ b/src/ui/controls/text_box.cpp @@ -0,0 +1 @@ +#include "cru/ui/controls/text_box.hpp" diff --git a/src/ui/render/flex_layout_render_object.cpp b/src/ui/render/flex_layout_render_object.cpp index 1cac6899..791e705a 100644 --- a/src/ui/render/flex_layout_render_object.cpp +++ b/src/ui/render/flex_layout_render_object.cpp @@ -7,60 +7,14 @@ #include <functional> namespace cru::ui::render { -FlexLayoutRenderObject::FlexLayoutRenderObject() { - SetChildMode(ChildMode::Multiple); -} - -void FlexLayoutRenderObject::Draw(platform::graph::IPainter* painter) { - for (const auto child : GetChildren()) { - auto offset = child->GetOffset(); - platform::graph::util::WithTransform( - painter, platform::Matrix::Translation(offset.x, offset.y), - [child](auto p) { child->Draw(p); }); - } -} - -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(p); - if (result != nullptr) { - return result; - } - } - - const auto margin = GetMargin(); - const auto size = GetSize(); - return Rect{margin.left, margin.top, - std::max(size.width - margin.GetHorizontalTotal(), 0.0f), - std::max(size.height - margin.GetVerticalTotal(), 0.0f)} - .IsPointInside(point) - ? this - : nullptr; -} // namespace cru::ui::render - -void FlexLayoutRenderObject::OnAddChild(RenderObject* new_child, int position) { - CRU_UNUSED(new_child) - - child_layout_data_.emplace(child_layout_data_.cbegin() + position); -} - -void FlexLayoutRenderObject::OnRemoveChild(RenderObject* removed_child, - int position) { - CRU_UNUSED(removed_child) - - 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 < static_cast<int>(child_layout_data_.size()); i++) { - const auto& layout_data = child_layout_data_[i]; + const auto child_count = GetChildCount(); + for (int i = 0; i < child_count; i++) { + const auto& layout_data = *GetChildLayoutData(i); if (layout_data.flex_basis.has_value()) has_basis_children.push_back(i); else @@ -91,7 +45,7 @@ Size FlexLayoutRenderObject::OnMeasureContent(const Size& available_size) { for (const int i : has_basis_children) { const auto child = children[i]; - const float basis = child_layout_data_[i].flex_basis.value(); + const float basis = GetChildLayoutData(i)->flex_basis.value(); child->Measure(create_size(basis, get_cross_length(available_size))); remain_main_length -= basis; const float child_preferred_cross_length = @@ -112,11 +66,11 @@ Size FlexLayoutRenderObject::OnMeasureContent(const Size& available_size) { if (remain_main_length > 0) { float total_grow = 0; for (const int i : grow_children) - total_grow += child_layout_data_[i].flex_grow; + total_grow += GetChildLayoutData(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); + remain_main_length * (GetChildLayoutData(i)->flex_grow / total_grow); const auto child = children[i]; const float new_main_length = get_main_length(child->GetPreferredSize()) + distributed_grow_length; @@ -134,12 +88,12 @@ Size FlexLayoutRenderObject::OnMeasureContent(const Size& available_size) { if (remain_main_length < 0) { float total_shrink = 0; for (const int i : shrink_chilren) - total_shrink += child_layout_data_[i].flex_shrink; + total_shrink += GetChildLayoutData(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); + (GetChildLayoutData(i)->flex_shrink / total_shrink); const auto child = children[i]; float new_main_length = get_main_length(child->GetPreferredSize()) + distributed_shrink_length; @@ -201,7 +155,7 @@ void FlexLayoutRenderObject::OnLayoutContent(const Rect& content_rect) { child->Layout(Rect{ real_anchor_x, calculate_anchor( - static_cast<int>(child_layout_data_[i].cross_alignment.value_or( + static_cast<int>(GetChildLayoutData(i)->cross_alignment.value_or( this->item_cross_align_)), content_rect.top, content_rect.height, size.height), size.width, size.height}); @@ -232,7 +186,7 @@ void FlexLayoutRenderObject::OnLayoutContent(const Rect& content_rect) { child->Layout(Rect{ real_anchor_y, calculate_anchor( - static_cast<int>(child_layout_data_[i].cross_alignment.value_or( + static_cast<int>(GetChildLayoutData(i)->cross_alignment.value_or( this->item_cross_align_)), content_rect.left, content_rect.width, size.width), size.width, size.height}); diff --git a/src/ui/render/stack_layout_render_object.cpp b/src/ui/render/stack_layout_render_object.cpp new file mode 100644 index 00000000..1cb31252 --- /dev/null +++ b/src/ui/render/stack_layout_render_object.cpp @@ -0,0 +1,49 @@ +#include "cru/ui/render/stack_layout_render_object.hpp" + +#include <algorithm> + +namespace cru::ui::render { +Size StackLayoutRenderObject::OnMeasureContent(const Size& available_size) { + auto size = Size{}; + for (const auto child : GetChildren()) { + child->Measure(available_size); + const auto& preferred_size = child->GetPreferredSize(); + size.width = std::max(size.width, preferred_size.width); + size.height = std::max(size.height, preferred_size.height); + } + return size; +} + +void StackLayoutRenderObject::OnLayoutContent(const Rect& rect) { + auto calculate_anchor = [](int alignment, float start_point, + float total_length, + float content_length) -> float { + switch (alignment) { + case internal::align_start: + return start_point; + case internal::align_center: + return start_point + (total_length - content_length) / 2.0f; + case internal::align_end: + return start_point + total_length - content_length; + default: + return 0; + } + }; + + const auto count = GetChildCount(); + const auto& children = GetChildren(); + + for (int i = 0; i < count; i++) { + const auto layout_data = GetChildLayoutData(i); + const auto child = children[i]; + const auto& size = child->GetPreferredSize(); + child->Layout( + Rect{calculate_anchor(static_cast<int>(layout_data->horizontal), + rect.left, rect.width, size.width), + calculate_anchor(static_cast<int>(layout_data->vertical), rect.top, + rect.height, size.height), + size.width, size.height}); + } +} + +} // namespace cru::ui::render |