diff options
23 files changed, 481 insertions, 228 deletions
diff --git a/demos/input_method/main.cpp b/demos/input_method/main.cpp index 45924c96..b109f5e7 100644 --- a/demos/input_method/main.cpp +++ b/demos/input_method/main.cpp @@ -135,7 +135,7 @@ int main() { }); input_method_context->CompositionEndEvent()->AddHandler( - [window, &input_method_context, &optional_composition_text](auto) { + [window, &optional_composition_text](auto) { optional_composition_text = std::nullopt; window->RequestRepaint(); }); diff --git a/include/cru/platform/GraphBase.hpp b/include/cru/platform/GraphBase.hpp index af61eba3..0d4effd4 100644 --- a/include/cru/platform/GraphBase.hpp +++ b/include/cru/platform/GraphBase.hpp @@ -2,13 +2,17 @@ #include "cru/common/Base.hpp" #include <cstdint> +#include <limits> #include <optional> #include <utility> namespace cru::platform { +struct Size; + struct Point final { constexpr Point() = default; constexpr Point(const float x, const float y) : x(x), y(y) {} + explicit constexpr Point(const Size& size); float x = 0; float y = 0; @@ -34,11 +38,20 @@ struct Size final { constexpr Size() = default; constexpr Size(const float width, const float height) : width(width), height(height) {} + explicit constexpr Size(const Point& point) + : width(point.x), height(point.y) {} + + constexpr static Size Infinate() { + return Size{std::numeric_limits<float>::max(), + std::numeric_limits<float>::max()}; + } float width = 0; float height = 0; }; +constexpr Point::Point(const Size& size) : x(size.width), y(size.height) {} + constexpr Size operator+(const Size& left, const Size& right) { return Size(left.width + right.width, left.height + right.height); } @@ -88,6 +101,15 @@ struct Thickness final { float bottom; }; +constexpr Size operator+(const Size& size, const Thickness& thickness) { + return {size.width + thickness.left + thickness.right, + size.height + thickness.top + thickness.bottom}; +} + +constexpr Size operator+(const Thickness& thickness, const Size& size) { + return operator+(size, thickness); +} + constexpr bool operator==(const Thickness& left, const Thickness& right) { return left.left == right.left && left.top == right.top && left.right == right.right && left.bottom == right.bottom; diff --git a/include/cru/ui/base.hpp b/include/cru/ui/base.hpp index 97b0dbff..dacf2d6f 100644 --- a/include/cru/ui/base.hpp +++ b/include/cru/ui/base.hpp @@ -270,10 +270,8 @@ using FlexMainAlignment = Alignment; using FlexCrossAlignment = Alignment; struct FlexChildLayoutData { - // nullopt stands for looking at my content - std::optional<float> flex_basis = std::nullopt; - float flex_grow = 0; - float flex_shrink = 0; + float expand_factor = 0; + float shrink_factor = 1; // nullopt stands for looking at parent's setting std::optional<FlexCrossAlignment> cross_alignment = std::nullopt; }; diff --git a/include/cru/ui/render/BorderRenderObject.hpp b/include/cru/ui/render/BorderRenderObject.hpp index c3031f59..45df89f9 100644 --- a/include/cru/ui/render/BorderRenderObject.hpp +++ b/include/cru/ui/render/BorderRenderObject.hpp @@ -67,9 +67,9 @@ class BorderRenderObject : public RenderObject { RenderObject* HitTest(const Point& point) override; protected: - void OnMeasureCore(const Size& available_size) override; - void OnLayoutCore(const Rect& rect) override; - Size OnMeasureContent(const Size& available_size) override; + Size OnMeasureCore(const MeasureRequirement& requirement) override; + void OnLayoutCore(const Size& size) override; + Size OnMeasureContent(const MeasureRequirement& requirement) override; void OnLayoutContent(const Rect& content_rect) override; void OnAfterLayout() override; @@ -85,7 +85,7 @@ class BorderRenderObject : public RenderObject { bool is_border_enabled_ = false; std::shared_ptr<platform::graph::IBrush> border_brush_; - platform::Thickness border_thickness_; + Thickness border_thickness_; CornerRadius border_radius_; std::shared_ptr<platform::graph::IBrush> foreground_brush_; diff --git a/include/cru/ui/render/CanvasRenderObject.hpp b/include/cru/ui/render/CanvasRenderObject.hpp index ba50a985..402302cb 100644 --- a/include/cru/ui/render/CanvasRenderObject.hpp +++ b/include/cru/ui/render/CanvasRenderObject.hpp @@ -28,7 +28,7 @@ class CanvasRenderObject : public RenderObject { IEvent<CanvasPaintEventArgs>* PaintEvent() { return &paint_event_; } protected: - Size OnMeasureContent(const Size& available_size) override; + Size OnMeasureContent(const MeasureRequirement& requirement) override; void OnLayoutContent(const Rect& content_rect) override; private: diff --git a/include/cru/ui/render/FlexLayoutRenderObject.hpp b/include/cru/ui/render/FlexLayoutRenderObject.hpp index bc43141d..217ca6cb 100644 --- a/include/cru/ui/render/FlexLayoutRenderObject.hpp +++ b/include/cru/ui/render/FlexLayoutRenderObject.hpp @@ -31,7 +31,7 @@ class FlexLayoutRenderObject : public LayoutRenderObject<FlexChildLayoutData> { } protected: - Size OnMeasureContent(const Size& available_size) override; + Size OnMeasureContent(const MeasureRequirement& requirement) override; void OnLayoutContent(const Rect& content_rect) override; private: diff --git a/include/cru/ui/render/MeasureRequirement.hpp b/include/cru/ui/render/MeasureRequirement.hpp new file mode 100644 index 00000000..83de255d --- /dev/null +++ b/include/cru/ui/render/MeasureRequirement.hpp @@ -0,0 +1,79 @@ +#pragma once +#include "Base.hpp" + +#include <limits> + +namespace cru::ui::render { +class MeasureLength { + public: + struct tag_infinate_t {}; + constexpr static tag_infinate_t tag_infinate{}; + + constexpr MeasureLength() : MeasureLength(0) {} + constexpr MeasureLength(tag_infinate_t) : length_(-1) {} + constexpr MeasureLength(float length) : length_(length) { + Expects(length >= 0); + } + + MeasureLength(const MeasureLength& other) = default; + MeasureLength& operator=(const MeasureLength& other) = default; + MeasureLength& operator=(float length) { + Expects(length >= 0); + length_ = length; + return *this; + } + + ~MeasureLength() = default; + + constexpr static MeasureLength Infinate() { + return MeasureLength{tag_infinate}; + } + + constexpr bool IsInfinate() const { return length_ < 0; } + constexpr float GetLength() const { + return length_ < 0 ? std::numeric_limits<float>::max() : length_; + } + + constexpr bool operator==(MeasureLength other) const { + return (length_ < 0 && other.length_ < 0) || length_ == other.length_; + } + + constexpr bool operator!=(MeasureLength other) const { + return !operator==(other); + } + + private: + // -1 for infinate + float length_; +}; + +struct MeasureRequirement { + MeasureLength max_width; + MeasureLength max_height; + + constexpr MeasureRequirement() = default; + constexpr MeasureRequirement(MeasureLength max_width, + MeasureLength max_height) + : max_width(max_width), max_height(max_height) {} + + constexpr MeasureRequirement(const Size& max_size) + : max_width(max_size.width), max_height(max_size.height) {} + + constexpr bool Satisfy(const Size& size) const { + if (!max_width.IsInfinate() && max_width.GetLength() < size.width) + return false; + if (!max_height.IsInfinate() && max_height.GetLength() < size.height) + return false; + return true; + } + + constexpr Size GetMaxSize() const { + return Size{max_width.GetLength(), max_height.GetLength()}; + } + + constexpr static MeasureRequirement Infinate() { + return MeasureRequirement{MeasureLength::Infinate(), + MeasureLength::Infinate()}; + } +}; +} // namespace cru::ui::render diff --git a/include/cru/ui/render/RenderObject.hpp b/include/cru/ui/render/RenderObject.hpp index 7cfa3883..9de2cc27 100644 --- a/include/cru/ui/render/RenderObject.hpp +++ b/include/cru/ui/render/RenderObject.hpp @@ -1,12 +1,22 @@ #pragma once #include "Base.hpp" +#include "MeasureRequirement.hpp" #include "cru/common/Event.hpp" namespace cru::ui::render { + // Render object will not destroy its children when destroyed. Control must // manage lifecycle of its render objects. Since control will destroy its // children when destroyed, render objects will be destroyed along with it. +// +// To write a custom RenderObject, override following methods: +// public: +// void Draw(platform::graph::IPainter* painter) override; +// RenderObject* HitTest(const Point& point) override; +// protected: +// Size OnMeasureContent(const MeasureRequirement& requirement) override; +// void OnLayoutContent(const Rect& content_rect) override; class RenderObject : public Object { friend WindowRenderObject; @@ -41,12 +51,12 @@ class RenderObject : public Object { void AddChild(RenderObject* render_object, Index position); void RemoveChild(Index position); + // Offset from parent's lefttop to lefttop of this render object. Margin is + // accounted for. Point GetOffset() const { return offset_; } - void SetOffset(const Point& offset) { offset_ = offset; } + Size GetSize() const { return size_; } Point GetTotalOffset() const; Point FromRootToContent(const Point& point) const; - Size GetSize() const { return size_; } - void SetSize(const Size& size) { size_ = size; } Thickness GetMargin() const { return margin_; } void SetMargin(const Thickness& margin) { margin_ = margin; } @@ -54,16 +64,16 @@ class RenderObject : public Object { 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; - } + Size GetMeasuredSize() const { return measured_size_; } - void Measure(const Size& available_size); + void Measure(const MeasureRequirement& requirement); + // Size of rect must not be negative. void Layout(const Rect& rect); virtual void Draw(platform::graph::IPainter* painter) = 0; + // Param point must be relative the lefttop of render object including margin. + // Add offset before pass point to children. virtual RenderObject* HitTest(const Point& point) = 0; public: @@ -82,9 +92,23 @@ class RenderObject : public Object { // default is to invalidate both layout and paint virtual void OnRemoveChild(RenderObject* removed_child, Index position); - virtual void OnMeasureCore(const Size& available_size); - virtual void OnLayoutCore(const Rect& rect); - virtual Size OnMeasureContent(const Size& available_size) = 0; + // Size measure including margin and padding. Please reduce margin and padding + // or other custom things and pass the result content measure requirement to + // OnMeasureContent. + // Return value must not be negative and not bigger than requirement. + virtual Size OnMeasureCore(const MeasureRequirement& requirement); + + // Size including margin and padding. Please reduce margin and padding or + // other custom things and pass the result content rect to OnLayoutContent. + // Parameter size are never negative. + virtual void OnLayoutCore(const Size& size); + + // Do not consider margin or padding in this method because they are already + // considered in OnMeasureCore. Returned size should never be bigger than + // requirement. + virtual Size OnMeasureContent(const MeasureRequirement& requirement) = 0; + + // Lefttop of content_rect should be added when calculated children's offset. virtual void OnLayoutContent(const Rect& content_rect) = 0; virtual void OnAfterLayout(); @@ -113,6 +137,6 @@ class RenderObject : public Object { Thickness margin_{}; Thickness padding_{}; - Size preferred_size_{}; + Size measured_size_{}; }; } // namespace cru::ui::render diff --git a/include/cru/ui/render/ScrollRenderObject.hpp b/include/cru/ui/render/ScrollRenderObject.hpp index dcf6dae6..924b8ca6 100644 --- a/include/cru/ui/render/ScrollRenderObject.hpp +++ b/include/cru/ui/render/ScrollRenderObject.hpp @@ -21,8 +21,12 @@ class ScrollRenderObject : public RenderObject { void SetScrollOffset(const Point& offset); protected: - void OnAddChild(RenderObject* new_child, Index position) override; - void OnRemoveChild(RenderObject* removed_child, Index position) override; + // Logic: + // If available size is bigger than child's preferred size, then child's + // preferred size is taken. + // If not, all available size is taken while forming a scroll area. + Size OnMeasureContent(const MeasureRequirement& requirement) override; + void OnLayoutContent(const Rect& content_rect) override; private: Point scroll_offset_; diff --git a/include/cru/ui/render/StackLayoutRenderObject.hpp b/include/cru/ui/render/StackLayoutRenderObject.hpp index a5bf9335..2320e1ca 100644 --- a/include/cru/ui/render/StackLayoutRenderObject.hpp +++ b/include/cru/ui/render/StackLayoutRenderObject.hpp @@ -11,7 +11,7 @@ class StackLayoutRenderObject ~StackLayoutRenderObject() = default; protected: - Size OnMeasureContent(const Size& available_size) override; + Size OnMeasureContent(const MeasureRequirement& requirement) override; void OnLayoutContent(const Rect& content_rect) override; }; } // namespace cru::ui::render diff --git a/include/cru/ui/render/TextRenderObject.hpp b/include/cru/ui/render/TextRenderObject.hpp index 7a81ba51..96bb5997 100644 --- a/include/cru/ui/render/TextRenderObject.hpp +++ b/include/cru/ui/render/TextRenderObject.hpp @@ -64,7 +64,7 @@ class TextRenderObject : public RenderObject { RenderObject* HitTest(const Point& point) override; protected: - Size OnMeasureContent(const Size& available_size) override; + Size OnMeasureContent(const MeasureRequirement& requirement) override; void OnLayoutContent(const Rect& content_rect) override; void OnAfterLayout() override; diff --git a/include/cru/ui/render/WindowRenderObject.hpp b/include/cru/ui/render/WindowRenderObject.hpp index 00bce29b..d993dc58 100644 --- a/include/cru/ui/render/WindowRenderObject.hpp +++ b/include/cru/ui/render/WindowRenderObject.hpp @@ -16,7 +16,7 @@ class WindowRenderObject : public RenderObject { RenderObject* HitTest(const Point& point) override; protected: - Size OnMeasureContent(const Size& available_size) override; + Size OnMeasureContent(const MeasureRequirement& requirement) override; void OnLayoutContent(const Rect& content_rect) override; private: diff --git a/include/cru/win/graph/direct/TextLayout.hpp b/include/cru/win/graph/direct/TextLayout.hpp index 1a378ed4..40c63dbe 100644 --- a/include/cru/win/graph/direct/TextLayout.hpp +++ b/include/cru/win/graph/direct/TextLayout.hpp @@ -4,6 +4,7 @@ #include "cru/platform/graph/TextLayout.hpp" +#include <limits> #include <memory> namespace cru::platform::graph::win::direct { @@ -47,8 +48,8 @@ class DWriteTextLayout : public DirectGraphResource, std::string text_; std::wstring w_text_; std::shared_ptr<DWriteFont> font_; - float max_width_ = 10000.0f; - float max_height_ = 10000.0f; + float max_width_ = std::numeric_limits<float>::max(); + float max_height_ = std::numeric_limits<float>::max(); Microsoft::WRL::ComPtr<IDWriteTextLayout> text_layout_; }; } // namespace cru::platform::graph::win::direct diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt index f37982e9..68efa903 100644 --- a/src/ui/CMakeLists.txt +++ b/src/ui/CMakeLists.txt @@ -54,6 +54,7 @@ target_sources(cru_ui PUBLIC ${CRU_UI_INCLUDE_DIR}/render/FlexLayoutRenderObject.hpp ${CRU_UI_INCLUDE_DIR}/render/LayoutRenderObject.hpp ${CRU_UI_INCLUDE_DIR}/render/LayoutUtility.hpp + ${CRU_UI_INCLUDE_DIR}/render/MeasureRequirement.hpp ${CRU_UI_INCLUDE_DIR}/render/RenderObject.hpp ${CRU_UI_INCLUDE_DIR}/render/ScrollRenderObject.hpp ${CRU_UI_INCLUDE_DIR}/render/StackLayoutRenderObject.hpp diff --git a/src/ui/UiHost.cpp b/src/ui/UiHost.cpp index 069e68de..7047d43f 100644 --- a/src/ui/UiHost.cpp +++ b/src/ui/UiHost.cpp @@ -1,12 +1,12 @@ #include "cru/ui/UiHost.hpp" +#include "RoutedEventDispatch.hpp" #include "cru/common/Logger.hpp" #include "cru/platform/graph/Painter.hpp" #include "cru/platform/native/UiApplication.hpp" #include "cru/platform/native/Window.hpp" -#include "cru/ui/render/WindowRenderObject.hpp" #include "cru/ui/Window.hpp" -#include "RoutedEventDispatch.hpp" +#include "cru/ui/render/WindowRenderObject.hpp" namespace cru::ui { using platform::native::INativeWindow; @@ -28,7 +28,6 @@ CRU_DEFINE_EVENT_NAME(MouseDown) CRU_DEFINE_EVENT_NAME(MouseUp) CRU_DEFINE_EVENT_NAME(KeyDown) CRU_DEFINE_EVENT_NAME(KeyUp) -CRU_DEFINE_EVENT_NAME(Char) #undef CRU_DEFINE_EVENT_NAME } // namespace event_names @@ -93,10 +92,10 @@ inline void BindNativeEvent( } // namespace UiHost::UiHost(Window* window) - : mouse_hover_control_(nullptr), + : window_control_(window), + mouse_hover_control_(nullptr), focus_control_(window), - mouse_captured_control_(nullptr), - window_control_(window) { + mouse_captured_control_(nullptr) { native_window_resolver_ = IUiApplication::GetInstance()->CreateWindow(nullptr); diff --git a/src/ui/render/BorderRenderObject.cpp b/src/ui/render/BorderRenderObject.cpp index a656cb99..fbc3c65f 100644 --- a/src/ui/render/BorderRenderObject.cpp +++ b/src/ui/render/BorderRenderObject.cpp @@ -73,84 +73,97 @@ RenderObject* BorderRenderObject::HitTest(const Point& point) { } } -void BorderRenderObject::OnMeasureCore(const Size& available_size) { +Size BorderRenderObject::OnMeasureCore(const MeasureRequirement& requirement) { + if (!is_border_enabled_) { + return RenderObject::OnMeasureCore(requirement); + } + const auto margin = GetMargin(); const auto padding = GetPadding(); - Size margin_border_padding_size{ - margin.GetHorizontalTotal() + padding.GetHorizontalTotal(), - margin.GetVerticalTotal() + padding.GetVerticalTotal()}; - - if (is_border_enabled_) { - margin_border_padding_size.width += border_thickness_.GetHorizontalTotal(); - margin_border_padding_size.height += border_thickness_.GetVerticalTotal(); + const Size space_size{margin.GetHorizontalTotal() + + padding.GetHorizontalTotal() + + border_thickness_.GetHorizontalTotal(), + margin.GetVerticalTotal() + padding.GetVerticalTotal() + + border_thickness_.GetVerticalTotal()}; + + auto coerced_space_size = space_size; + + MeasureRequirement content_requirement = requirement; + + if (!requirement.max_width.IsInfinate()) { + const auto max_width = requirement.max_width.GetLength(); + if (coerced_space_size.width > max_width) { + log::Warn( + "Measure: horizontal length of padding and margin is bigger than " + "available length."); + coerced_space_size.width = max_width; + } + content_requirement.max_width = max_width - coerced_space_size.width; } - auto coerced_margin_border_padding_size = margin_border_padding_size; - if (coerced_margin_border_padding_size.width > available_size.width) { - log::Warn( - "Measure: horizontal length of padding, border and margin is bigger " - "than available length."); - coerced_margin_border_padding_size.width = available_size.width; - } - if (coerced_margin_border_padding_size.height > available_size.height) { - log::Warn( - "Measure: vertical length of padding, border and margin is bigger " - "than available length."); - coerced_margin_border_padding_size.height = available_size.height; + if (!requirement.max_height.IsInfinate()) { + const auto max_height = requirement.max_height.GetLength(); + if (coerced_space_size.height > max_height) { + log::Warn( + "Measure: horizontal length of padding and margin is bigger than " + "available length."); + coerced_space_size.height = max_height; + } + content_requirement.max_height = max_height - coerced_space_size.height; } - const auto coerced_content_available_size = - available_size - coerced_margin_border_padding_size; + const auto content_size = OnMeasureContent(content_requirement); - const auto actual_content_size = - OnMeasureContent(coerced_content_available_size); + return coerced_space_size + content_size; +} // namespace cru::ui::render - SetPreferredSize(coerced_margin_border_padding_size + actual_content_size); -} +void BorderRenderObject::OnLayoutCore(const Size& size) { + if (!is_border_enabled_) { + return RenderObject::OnLayoutCore(size); + } -void BorderRenderObject::OnLayoutCore(const Rect& rect) { const auto margin = GetMargin(); const auto padding = GetPadding(); - Size margin_border_padding_size{ - margin.GetHorizontalTotal() + padding.GetHorizontalTotal(), - margin.GetVerticalTotal() + padding.GetVerticalTotal()}; - - if (is_border_enabled_) { - margin_border_padding_size.width += border_thickness_.GetHorizontalTotal(); - margin_border_padding_size.height += border_thickness_.GetVerticalTotal(); - } + Size space_size{margin.GetHorizontalTotal() + padding.GetHorizontalTotal() + + border_thickness_.GetHorizontalTotal(), + margin.GetVerticalTotal() + padding.GetVerticalTotal() + + border_thickness_.GetVerticalTotal()}; - const auto content_available_size = - rect.GetSize() - margin_border_padding_size; - auto coerced_content_available_size = content_available_size; + auto content_size = size - space_size; - if (coerced_content_available_size.width < 0) { + if (content_size.width < 0) { log::Warn( "Layout: horizontal length of padding, border and margin is bigger " "than available length."); - coerced_content_available_size.width = 0; + content_size.width = 0; } - if (coerced_content_available_size.height < 0) { + if (content_size.height < 0) { log::Warn( "Layout: vertical length of padding, border and margin is bigger " "than available length."); - coerced_content_available_size.height = 0; + content_size.height = 0; + } + + Point lefttop{margin.left + padding.left + border_thickness_.left, + margin.top + padding.top + border_thickness_.top}; + if (lefttop.x > size.width) { + lefttop.x = size.width; } + if (lefttop.y > size.height) { + lefttop.y = size.height; + } + + const Rect content_rect{lefttop, content_size}; - OnLayoutContent( - Rect{margin.left + (is_border_enabled_ ? border_thickness_.left : 0) + - padding.left, - margin.top + (is_border_enabled_ ? border_thickness_.top : 0) + - padding.top, - coerced_content_available_size.width, - coerced_content_available_size.height}); + OnLayoutContent(content_rect); } -Size BorderRenderObject::OnMeasureContent(const Size& available_size) { +Size BorderRenderObject::OnMeasureContent( + const MeasureRequirement& requirement) { const auto child = GetChild(); if (child) { - child->Measure(available_size); - return child->GetPreferredSize(); + child->Measure(requirement); + return child->GetMeasuredSize(); } else { return Size{}; } diff --git a/src/ui/render/CanvasRenderObject.cpp b/src/ui/render/CanvasRenderObject.cpp index 16ac9239..72eb3570 100644 --- a/src/ui/render/CanvasRenderObject.cpp +++ b/src/ui/render/CanvasRenderObject.cpp @@ -18,8 +18,9 @@ RenderObject* CanvasRenderObject::HitTest(const Point& point) { return padding_rect.IsPointInside(point) ? this : nullptr; } -Size CanvasRenderObject::OnMeasureContent(const Size& available_size) { - return Min(available_size, GetDesiredSize()); +Size CanvasRenderObject::OnMeasureContent( + const MeasureRequirement& requirement) { + return Min(requirement.GetMaxSize(), GetDesiredSize()); } void CanvasRenderObject::OnLayoutContent(const Rect& content_rect) { diff --git a/src/ui/render/FlexLayoutRenderObject.cpp b/src/ui/render/FlexLayoutRenderObject.cpp index b609fd97..079bc5a9 100644 --- a/src/ui/render/FlexLayoutRenderObject.cpp +++ b/src/ui/render/FlexLayoutRenderObject.cpp @@ -6,111 +6,115 @@ #include <functional> namespace cru::ui::render { -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; - 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 - 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); - } +Size FlexLayoutRenderObject::OnMeasureContent( + const MeasureRequirement& requirement) { + const bool horizontal = (direction_ == FlexDirection::Horizontal || + direction_ == FlexDirection::HorizontalReverse); + + const auto main_max_length = + horizontal ? requirement.max_width : requirement.max_height; + const auto cross_max_length = + horizontal ? requirement.max_height : requirement.max_width; 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; + std::function<void(Size&, const Size&)> calculate_result_size; + std::function<MeasureRequirement(MeasureLength main, MeasureLength cross)> + create_requirement; - if (direction_ == FlexDirection::Horizontal || - direction_ == FlexDirection::HorizontalReverse) { + if (horizontal) { 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); }; + calculate_result_size = [](Size& result, const Size& child_size) { + result.width += child_size.width; + result.height = std::max(result.height, child_size.height); + }; + create_requirement = [](MeasureLength main, MeasureLength cross) { + return MeasureRequirement{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); }; + calculate_result_size = [](Size& result, const Size& child_size) { + result.height += child_size.height; + result.width = std::max(result.width, child_size.width); + }; + create_requirement = [](MeasureLength main, MeasureLength cross) { + return MeasureRequirement{cross, main}; + }; } const auto& children = GetChildren(); + Index children_count = children.size(); - 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 = 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 = - 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 += GetChildLayoutData(i)->flex_grow; + if (!main_max_length.IsInfinate()) { + float remain_main_length = main_max_length.GetLength(); - for (const int i : grow_children) { - const float distributed_grow_length = - remain_main_length * (GetChildLayoutData(i)->flex_grow / total_grow); + for (Index i = 0; i < children_count; i++) { 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); + child->Measure(create_requirement(remain_main_length, cross_max_length)); + const auto measure_result = child->GetMeasuredSize(); + remain_main_length -= get_main_length(measure_result); } - } - if (remain_main_length < 0) { - float total_shrink = 0; - for (const int i : shrink_chilren) - total_shrink += GetChildLayoutData(i)->flex_shrink; + if (remain_main_length > 0) { + std::vector<Index> expand_children; + float total_expand_factor = 0; + + for (Index i = 0; i < children_count; i++) { + const auto factor = GetChildLayoutData(i)->expand_factor; + if (factor > 0) { + expand_children.push_back(i); + total_expand_factor += factor; + } + } - for (const int i : shrink_chilren) { - const float distributed_shrink_length = // negative - remain_main_length * - (GetChildLayoutData(i)->flex_shrink / total_shrink); + for (const int i : expand_children) { + const float distributed_grow_length = + remain_main_length * + (GetChildLayoutData(i)->expand_factor / total_expand_factor); + const auto child = children[i]; + const float new_main_length = + get_main_length(child->GetMeasuredSize()) + + distributed_grow_length; + child->Measure(create_requirement(new_main_length, cross_max_length)); + } + } else if (remain_main_length < 0) { + std::vector<Index> shrink_children; + float total_shrink_factor = 0; + + for (Index i = 0; i < children_count; i++) { + const auto factor = GetChildLayoutData(i)->shrink_factor; + if (factor > 0) { + shrink_children.push_back(i); + total_shrink_factor += factor; + } + } + + for (const int i : shrink_children) { + const float distributed_shrink_length = // negative + remain_main_length * + (GetChildLayoutData(i)->shrink_factor / total_shrink_factor); + const auto child = children[i]; + float new_main_length = get_main_length(child->GetMeasuredSize()) + + distributed_shrink_length; + new_main_length = new_main_length > 0 ? new_main_length : 0; + child->Measure(create_requirement(new_main_length, cross_max_length)); + } + } + } else { + for (Index i = 0; i < children_count; i++) { 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); + child->Measure(requirement); } } - return create_size(get_main_length(available_size) - - (remain_main_length > 0 ? remain_main_length : 0), - max_cross_length); + Size result; + for (auto child : children) { + calculate_result_size(result, child->GetMeasuredSize()); + } + + return result; } void FlexLayoutRenderObject::OnLayoutContent(const Rect& content_rect) { @@ -125,7 +129,7 @@ void FlexLayoutRenderObject::OnLayoutContent(const Rect& content_rect) { case internal::align_end: return start_point + total_length - content_length; default: - return 0; + return start_point; } }; @@ -134,7 +138,7 @@ void FlexLayoutRenderObject::OnLayoutContent(const Rect& content_rect) { direction_ == FlexDirection::HorizontalReverse) { float actual_content_width = 0; for (const auto child : children) { - actual_content_width += child->GetPreferredSize().width; + actual_content_width += child->GetMeasuredSize().width; } const float content_anchor_x = @@ -144,7 +148,7 @@ void FlexLayoutRenderObject::OnLayoutContent(const Rect& content_rect) { float anchor_x = 0; for (int i = 0; i < static_cast<int>(children.size()); i++) { const auto child = children[i]; - const auto size = child->GetPreferredSize(); + const auto size = child->GetMeasuredSize(); float real_anchor_x = anchor_x + content_anchor_x; if (direction_ == FlexDirection::Horizontal) @@ -164,7 +168,7 @@ void FlexLayoutRenderObject::OnLayoutContent(const Rect& content_rect) { } else { float actual_content_height = 0; for (const auto child : children) { - actual_content_height = child->GetPreferredSize().height; + actual_content_height = child->GetMeasuredSize().height; } const float content_anchor_y = @@ -174,7 +178,7 @@ void FlexLayoutRenderObject::OnLayoutContent(const Rect& content_rect) { float anchor_y = 0; for (int i = 0; i < static_cast<int>(children.size()); i++) { const auto child = children[i]; - const auto size = child->GetPreferredSize(); + const auto size = child->GetMeasuredSize(); float real_anchor_y = anchor_y + content_anchor_y; if (direction_ == FlexDirection::Vertical) { diff --git a/src/ui/render/RenderObject.cpp b/src/ui/render/RenderObject.cpp index 83e306a8..cdc3032f 100644 --- a/src/ui/render/RenderObject.cpp +++ b/src/ui/render/RenderObject.cpp @@ -57,14 +57,20 @@ Point RenderObject::FromRootToContent(const Point& point) const { point.y - (offset.y + rect.top)}; } -void RenderObject::Measure(const Size& available_size) { - OnMeasureCore(available_size); +void RenderObject::Measure(const MeasureRequirement& requirement) { + measured_size_ = OnMeasureCore(requirement); + + Ensures(measured_size_.width >= 0); + Ensures(measured_size_.height >= 0); + Ensures(requirement.Satisfy(measured_size_)); } void RenderObject::Layout(const Rect& rect) { - SetOffset(rect.GetLeftTop()); - SetSize(rect.GetSize()); - OnLayoutCore(Rect{Point{}, rect.GetSize()}); + Expects(rect.width >= 0); + Expects(rect.height >= 0); + offset_ = rect.GetLeftTop(); + size_ = rect.GetSize(); + OnLayoutCore(rect.GetSize()); } void RenderObject::OnParentChanged(RenderObject* old_parent, @@ -89,56 +95,72 @@ void RenderObject::OnRemoveChild(RenderObject* removed_child, Index position) { InvalidatePaint(); } -void RenderObject::OnMeasureCore(const Size& available_size) { - Size margin_padding_size{ +Size RenderObject::OnMeasureCore(const MeasureRequirement& requirement) { + const Size space_size{ margin_.GetHorizontalTotal() + padding_.GetHorizontalTotal(), margin_.GetVerticalTotal() + padding_.GetVerticalTotal()}; - auto coerced_margin_padding_size = margin_padding_size; - if (coerced_margin_padding_size.width > available_size.width) { - log::Warn( - "Measure: horizontal length of padding and margin is bigger than " - "available length."); - coerced_margin_padding_size.width = available_size.width; + auto coerced_space_size = space_size; + + MeasureRequirement content_requirement = requirement; + + if (!requirement.max_width.IsInfinate()) { + const auto max_width = requirement.max_width.GetLength(); + if (coerced_space_size.width > max_width) { + log::Warn( + "Measure: horizontal length of padding and margin is bigger than " + "available length."); + coerced_space_size.width = max_width; + } + content_requirement.max_width = max_width - coerced_space_size.width; } - if (coerced_margin_padding_size.height > available_size.height) { - log::Warn( - "Measure: vertical length of padding and margin is bigger than " - "available length."); - coerced_margin_padding_size.height = available_size.height; + + if (!requirement.max_height.IsInfinate()) { + const auto max_height = requirement.max_height.GetLength(); + if (coerced_space_size.height > max_height) { + log::Warn( + "Measure: horizontal length of padding and margin is bigger than " + "available length."); + coerced_space_size.height = max_height; + } + content_requirement.max_height = max_height - coerced_space_size.height; } - const auto coerced_content_available_size = - available_size - coerced_margin_padding_size; - const auto actual_content_size = - OnMeasureContent(coerced_content_available_size); + const auto content_size = OnMeasureContent(content_requirement); - SetPreferredSize(coerced_margin_padding_size + actual_content_size); + return coerced_space_size + content_size; } -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; +void RenderObject::OnLayoutCore(const Size& size) { + Size space_size{margin_.GetHorizontalTotal() + padding_.GetHorizontalTotal(), + margin_.GetVerticalTotal() + padding_.GetVerticalTotal()}; - if (coerced_content_available_size.width < 0) { + auto content_size = size - space_size; + + if (content_size.width < 0) { log::Warn( "Layout: horizontal length of padding and margin is bigger than " "available length."); - coerced_content_available_size.width = 0; + content_size.width = 0; } - if (coerced_content_available_size.height < 0) { + if (content_size.height < 0) { log::Warn( "Layout: vertical length of padding and margin is bigger than " "available length."); - coerced_content_available_size.height = 0; + content_size.height = 0; } - OnLayoutContent(Rect{margin_.left + padding_.left, margin_.top + padding_.top, - coerced_content_available_size.width, - coerced_content_available_size.height}); + Point lefttop{margin_.left + padding_.left, margin_.top + padding_.top}; + if (lefttop.x > size.width) { + lefttop.x = size.width; + } + if (lefttop.y > size.height) { + lefttop.y = size.height; + } + + const Rect content_rect{lefttop, content_size}; + + OnLayoutContent(content_rect); } void RenderObject::OnAfterLayout() {} diff --git a/src/ui/render/ScrollRenderObject.cpp b/src/ui/render/ScrollRenderObject.cpp index 33e9e407..f1df9d8b 100644 --- a/src/ui/render/ScrollRenderObject.cpp +++ b/src/ui/render/ScrollRenderObject.cpp @@ -1 +1,78 @@ #include "cru/ui/render/ScrollRenderObject.hpp" + +#include "cru/platform/graph/Painter.hpp" +#include "cru/platform/graph/util/Painter.hpp" + +#include <algorithm> + +namespace cru::ui::render { +namespace { +// This method assumes margin offset is already considered. +// It promises that it won't return negetive value. +Point CalculateChildOffsetOfScroll(const Point& scroll_offset, + const Size& content_area_size, + const Thickness& padding, + const Size& child_size) { + Point result(scroll_offset); + + Point max_scroll(child_size - content_area_size); + max_scroll.x = std::max(max_scroll.x, 0.f); + max_scroll.y = std::max(max_scroll.y, 0.f); + + auto coerce = [](float& n, float max) { + if (n < 0) + n = 0; + else if (n > max) + n = max; + }; + + coerce(result.x, scroll_offset.x); + coerce(result.y, scroll_offset.y); + + result.x += padding.left; + result.y += padding.top; + + return result; +} +} // namespace + +void ScrollRenderObject::Draw(platform::graph::IPainter* painter) { + for (auto child : this->GetChildren()) { + painter->PushLayer(this->GetPaddingRect()); + const auto true_offset = + CalculateChildOffsetOfScroll(scroll_offset_, GetContentRect().GetSize(), + GetPadding(), child->GetSize()); + platform::graph::util::WithTransform( + painter, Matrix::Translation(true_offset.x, true_offset.y), + [child](platform::graph::IPainter* p) { child->Draw(p); }); + painter->PopLayer(); + } +} + +RenderObject* ScrollRenderObject::HitTest(const Point& point) { + const auto rect = GetPaddingRect(); + return rect.IsPointInside(point) ? this : nullptr; +} + +void ScrollRenderObject::SetScrollOffset(const Point& offset) { + scroll_offset_ = offset; + InvalidatePaint(); +} + +Size ScrollRenderObject::OnMeasureContent( + const MeasureRequirement& requirement) { + CRU_UNUSED(requirement); + // TODO! + // const auto& children = GetChildren(); + // if (children.empty()) return Size{}; + // const auto child = children.front(); + // child->Measure(MeasureRequirement::Infinate()); + // const auto preferred_size = child->GetMeasuredSize(); + return Size{}; +} // namespace cru::ui::render + +void ScrollRenderObject::OnLayoutContent(const Rect& content_rect) { + CRU_UNUSED(content_rect); + // TODO! +} +} // namespace cru::ui::render diff --git a/src/ui/render/StackLayoutRenderObject.cpp b/src/ui/render/StackLayoutRenderObject.cpp index d08e136d..7dce1a83 100644 --- a/src/ui/render/StackLayoutRenderObject.cpp +++ b/src/ui/render/StackLayoutRenderObject.cpp @@ -3,13 +3,14 @@ #include <algorithm> namespace cru::ui::render { -Size StackLayoutRenderObject::OnMeasureContent(const Size& available_size) { +Size StackLayoutRenderObject::OnMeasureContent( + const MeasureRequirement& requirement) { 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); + child->Measure(requirement); + const auto& measure_result = child->GetMeasuredSize(); + size.width = std::max(size.width, measure_result.width); + size.height = std::max(size.height, measure_result.height); } return size; } @@ -26,7 +27,7 @@ void StackLayoutRenderObject::OnLayoutContent(const Rect& rect) { case internal::align_end: return start_point + total_length - content_length; default: - return 0; + return start_point; } }; @@ -36,7 +37,7 @@ void StackLayoutRenderObject::OnLayoutContent(const Rect& rect) { for (int i = 0; i < count; i++) { const auto layout_data = GetChildLayoutData(i); const auto child = children[i]; - const auto& size = child->GetPreferredSize(); + const auto& size = child->GetMeasuredSize(); child->Layout( Rect{calculate_anchor(static_cast<int>(layout_data->horizontal), rect.left, rect.width, size.width), diff --git a/src/ui/render/TextRenderObject.cpp b/src/ui/render/TextRenderObject.cpp index 05acd086..ececfbc2 100644 --- a/src/ui/render/TextRenderObject.cpp +++ b/src/ui/render/TextRenderObject.cpp @@ -4,8 +4,10 @@ #include "cru/platform/graph/Factory.hpp" #include "cru/platform/graph/TextLayout.hpp" #include "cru/platform/graph/util/Painter.hpp" +#include "cru/ui/render/LayoutUtility.hpp" #include <algorithm> +#include <limits> namespace cru::ui::render { TextRenderObject::TextRenderObject( @@ -158,10 +160,10 @@ RenderObject* TextRenderObject::HitTest(const Point& point) { return padding_rect.IsPointInside(point) ? this : nullptr; } -Size TextRenderObject::OnMeasureContent(const Size& available_size) { - text_layout_->SetMaxWidth(available_size.width); - text_layout_->SetMaxHeight(available_size.height); - return text_layout_->GetTextBounds().GetSize(); +Size TextRenderObject::OnMeasureContent(const MeasureRequirement& requirement) { + text_layout_->SetMaxWidth(requirement.max_height.GetLength()); + text_layout_->SetMaxHeight(requirement.max_height.GetLength()); + return Min(text_layout_->GetTextBounds().GetSize(), requirement.GetMaxSize()); } void TextRenderObject::OnLayoutContent(const Rect& content_rect) { diff --git a/src/ui/render/WindowRenderObject.cpp b/src/ui/render/WindowRenderObject.cpp index cd1f806f..28afe01d 100644 --- a/src/ui/render/WindowRenderObject.cpp +++ b/src/ui/render/WindowRenderObject.cpp @@ -34,9 +34,14 @@ RenderObject* WindowRenderObject::HitTest(const Point& point) { return Rect{Point{}, GetSize()}.IsPointInside(point) ? this : nullptr; } -Size WindowRenderObject::OnMeasureContent(const Size& available_size) { - if (const auto child = GetChild()) child->Measure(available_size); - return available_size; +Size WindowRenderObject::OnMeasureContent( + const MeasureRequirement& requirement) { + if (const auto child = GetChild()) { + child->Measure(requirement); + return child->GetMeasuredSize(); + } else { + return Size{}; + } } void WindowRenderObject::OnLayoutContent(const Rect& content_rect) { |