diff options
Diffstat (limited to 'include/cru')
-rw-r--r-- | include/cru/ui/render/BorderRenderObject.hpp | 8 | ||||
-rw-r--r-- | include/cru/ui/render/CanvasRenderObject.hpp | 13 | ||||
-rw-r--r-- | include/cru/ui/render/FlexLayoutRenderObject.hpp | 2 | ||||
-rw-r--r-- | include/cru/ui/render/LayoutUtility.hpp | 7 | ||||
-rw-r--r-- | include/cru/ui/render/MeasureRequirement.hpp | 209 | ||||
-rw-r--r-- | include/cru/ui/render/RenderObject.hpp | 98 | ||||
-rw-r--r-- | include/cru/ui/render/ScrollRenderObject.hpp | 2 | ||||
-rw-r--r-- | include/cru/ui/render/StackLayoutRenderObject.hpp | 2 | ||||
-rw-r--r-- | include/cru/ui/render/TextRenderObject.hpp | 17 | ||||
-rw-r--r-- | include/cru/ui/render/WindowRenderObject.hpp | 3 |
10 files changed, 286 insertions, 75 deletions
diff --git a/include/cru/ui/render/BorderRenderObject.hpp b/include/cru/ui/render/BorderRenderObject.hpp index b1fc4ba2..6c33473a 100644 --- a/include/cru/ui/render/BorderRenderObject.hpp +++ b/include/cru/ui/render/BorderRenderObject.hpp @@ -67,9 +67,11 @@ class BorderRenderObject : public RenderObject { RenderObject* HitTest(const Point& point) override; protected: - Size OnMeasureCore(const MeasureRequirement& requirement) override; - void OnLayoutCore(const Size& size) override; - Size OnMeasureContent(const MeasureRequirement& requirement) override; + Size OnMeasureCore(const MeasureRequirement& requirement, + const MeasureSize& preferred_size) override; + void OnLayoutCore() override; + Size OnMeasureContent(const MeasureRequirement& requirement, + const MeasureSize& preferred_size) override; void OnLayoutContent(const Rect& content_rect) override; void OnAfterLayout() override; diff --git a/include/cru/ui/render/CanvasRenderObject.hpp b/include/cru/ui/render/CanvasRenderObject.hpp index 402302cb..323b4800 100644 --- a/include/cru/ui/render/CanvasRenderObject.hpp +++ b/include/cru/ui/render/CanvasRenderObject.hpp @@ -2,9 +2,9 @@ #include "RenderObject.hpp" namespace cru::ui::render { -// The measure logic for `CanvasRenderObject` is that you set a desired size by -// `SetDesiredSize` (not `SetPreferredSize`) and it will compare desired size -// and available size and use the smaller one (by `Min`). +// Layout logic: +// If no preferred size is set. Then (100, 100) is used and then coerced to +// required range. class CanvasRenderObject : public RenderObject { public: CanvasRenderObject(); @@ -21,14 +21,11 @@ class CanvasRenderObject : public RenderObject { Size GetDesiredSize() const { return desired_size_; } - // Set the desired size. This is the content size excluding padding and - // margin. - void SetDesiredSize(const Size& new_size) { desired_size_ = new_size; } - IEvent<CanvasPaintEventArgs>* PaintEvent() { return &paint_event_; } protected: - Size OnMeasureContent(const MeasureRequirement& requirement) override; + Size OnMeasureContent(const MeasureRequirement& requirement, + const MeasureSize& preferred_size) 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 217ca6cb..f82bffad 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 MeasureRequirement& requirement) override; + Size OnMeasureContent(const MeasureRequirement& requirement, const MeasureSize& preferred_size) override; void OnLayoutContent(const Rect& content_rect) override; private: diff --git a/include/cru/ui/render/LayoutUtility.hpp b/include/cru/ui/render/LayoutUtility.hpp deleted file mode 100644 index 63d13fd3..00000000 --- a/include/cru/ui/render/LayoutUtility.hpp +++ /dev/null @@ -1,7 +0,0 @@ -#pragma once -#include "Base.hpp" - -namespace cru::ui::render { -Size Min(const Size& left, const Size& right); -Size Max(const Size& left, const Size& right); -} // namespace cru::ui::render diff --git a/include/cru/ui/render/MeasureRequirement.hpp b/include/cru/ui/render/MeasureRequirement.hpp index 661f40a9..5af5a057 100644 --- a/include/cru/ui/render/MeasureRequirement.hpp +++ b/include/cru/ui/render/MeasureRequirement.hpp @@ -1,23 +1,34 @@ #pragma once #include "Base.hpp" +#include <algorithm> #include <limits> namespace cru::ui::render { -class MeasureLength { +constexpr Size Min(const Size& left, const Size& right) { + return Size{std::min(left.width, right.width), + std::min(left.height, right.height)}; +} + +constexpr Size Max(const Size& left, const Size& right) { + return Size{std::max(left.width, right.width), + std::max(left.height, right.height)}; +} + +class MeasureLength final { public: struct tag_not_specify_t {}; constexpr static tag_not_specify_t tag_not_specify{}; - constexpr MeasureLength() : MeasureLength(0) {} + constexpr MeasureLength() : MeasureLength(tag_not_specify) {} constexpr MeasureLength(tag_not_specify_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) { + constexpr MeasureLength& operator=(const MeasureLength& other) = default; + constexpr MeasureLength& operator=(float length) { Expects(length >= 0); length_ = length; return *this; @@ -25,17 +36,44 @@ class MeasureLength { ~MeasureLength() = default; - constexpr static MeasureLength NotSpecify() { + constexpr static MeasureLength NotSpecified() { return MeasureLength{tag_not_specify}; } - // What not specify means depends on situation. - // For max size, it means no limit. - constexpr bool IsNotSpecify() const { return length_ < 0; } + constexpr bool IsSpecified() const { return length_ >= 0.0f; } + + // What not specified means depends on situation. + constexpr bool IsNotSpecified() const { return length_ < 0.0f; } + + constexpr float GetLengthOr(float value) const { + return length_ < 0 ? value : length_; + } // If not specify max value of float is returned. - constexpr float GetLength() const { - return length_ < 0 ? std::numeric_limits<float>::max() : length_; + constexpr float GetLengthOrMax() const { + return GetLengthOr(std::numeric_limits<float>::max()); + } + + // If not specify 0 is returned. + constexpr float GetLengthOr0() const { return GetLengthOr(0.f); } + + // If not specify, return value is undefined. + constexpr float GetLengthOrUndefined() const { return length_; } + + // Not operator overload because this semantics is not very clear. + constexpr MeasureLength Plus(float length) const { + if (IsSpecified()) + return length_ + length; + else + return NotSpecified(); + } + + // Not operator overload because this semantics is not very clear. + constexpr MeasureLength Minus(float length) const { + if (IsSpecified()) + return std::max(length_ - length, 0.f); + else + return NotSpecified(); } constexpr bool operator==(MeasureLength other) const { @@ -46,38 +84,151 @@ class MeasureLength { return !operator==(other); } + constexpr static MeasureLength Min(MeasureLength left, MeasureLength right) { + if (left.IsNotSpecified()) { + if (right.IsNotSpecified()) + return NotSpecified(); + else + return right; + } else { + if (left.IsNotSpecified()) + return left; + else + return std::min(left.length_, right.length_); + } + } + + constexpr static MeasureLength Max(MeasureLength left, MeasureLength right) { + if (left.IsNotSpecified()) { + if (right.IsNotSpecified()) + return NotSpecified(); + else + return right; + } else { + if (left.IsNotSpecified()) + return left; + else + return std::max(left.length_, right.length_); + } + } + private: - // -1 for infinate + // -1 for not specify float length_; }; +struct MeasureSize { + MeasureLength width; + MeasureLength height; + + constexpr MeasureSize() = default; + constexpr MeasureSize(MeasureLength width, MeasureLength height) + : width(width), height(height) {} + constexpr MeasureSize(const Size& size) + : width(size.width), height(size.height) {} + constexpr MeasureSize(const MeasureSize& other) = default; + + MeasureSize& operator=(const MeasureSize& other) = default; + constexpr MeasureSize& operator=(const Size& other) { + width = other.width; + height = other.height; + return *this; + } + + constexpr Size GetSizeOrMax() const { + return Size{width.GetLengthOrMax(), height.GetLengthOrMax()}; + } + + constexpr Size GetSizeOr0() const { + return Size{width.GetLengthOr0(), height.GetLengthOr0()}; + } + + constexpr MeasureSize Plus(const Size& size) const { + return MeasureSize{width.Plus(size.width), height.Plus(size.height)}; + } + + constexpr MeasureSize Minus(const Size& size) const { + return MeasureSize{width.Minus(size.width), height.Minus(size.height)}; + } + + constexpr MeasureSize OverrideBy(const MeasureSize& size) const { + return MeasureSize{ + size.width.IsSpecified() ? size.width.GetLengthOrUndefined() + : this->width, + size.height.IsSpecified() ? size.height.GetLengthOrUndefined() + : this->height, + }; + } + + constexpr static MeasureSize NotSpecified() { + return MeasureSize{MeasureLength::NotSpecified(), + MeasureLength::NotSpecified()}; + } + + constexpr static MeasureSize Min(const MeasureSize& left, + const MeasureSize& right) { + return MeasureSize{MeasureLength::Min(left.width, right.width), + MeasureLength::Min(left.height, right.height)}; + } + + constexpr static MeasureSize Max(const MeasureSize& left, + const MeasureSize& right) { + return MeasureSize{MeasureLength::Max(left.width, right.width), + MeasureLength::Max(left.height, right.height)}; + } +}; + struct MeasureRequirement { - MeasureLength max_width; - MeasureLength max_height; + MeasureSize max; + MeasureSize min; 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 MeasureRequirement(const MeasureSize& max, const MeasureSize& min) + : max(max), min(min) {} constexpr bool Satisfy(const Size& size) const { - if (!max_width.IsNotSpecify() && max_width.GetLength() < size.width) - return false; - if (!max_height.IsNotSpecify() && max_height.GetLength() < size.height) - return false; - return true; + return max.width.GetLengthOrMax() >= size.width && + max.height.GetLengthOrMax() >= size.height && + min.width.GetLengthOr0() <= size.width && + min.height.GetLengthOr0() <= size.height; + } + + constexpr MeasureRequirement Normalize() const { + MeasureRequirement result = *this; + if (result.min.width.GetLengthOr0() > result.max.width.GetLengthOrMax()) + result.min.width = result.max.width; + + if (result.min.height.GetLengthOr0() > result.max.height.GetLengthOrMax()) + result.min.height = result.max.height; + return result; + } + + constexpr Size Coerce(const Size& size) const { + // This order guarentees result is the same as normalized. + return Min(Max(size, min.GetSizeOr0()), max.GetSizeOrMax()); } - constexpr Size GetMaxSize() const { - return Size{max_width.GetLength(), max_height.GetLength()}; + constexpr MeasureSize Coerce(const MeasureSize& size) const { + MeasureSize result = size; + if (result.width.IsSpecified()) + // This order guarentees result is the same as normalized. + result.width = std::min(std::max(min.width.GetLengthOr0(), + result.width.GetLengthOrUndefined()), + max.width.GetLengthOrMax()); + + if (result.height.IsSpecified()) + // This order guarentees result is the same as normalized. + result.height = std::min(std::max(min.height.GetLengthOr0(), + result.height.GetLengthOrUndefined()), + max.height.GetLengthOrMax()); + + return result; } - constexpr static MeasureRequirement Infinate() { - return MeasureRequirement{MeasureLength::NotSpecify(), - MeasureLength::NotSpecify()}; + constexpr static MeasureRequirement Merge(const MeasureRequirement& left, + const MeasureRequirement& right) { + return MeasureRequirement{MeasureSize::Min(left.max, right.max), + MeasureSize::Max(left.min, right.min)}; } }; } // namespace cru::ui::render diff --git a/include/cru/ui/render/RenderObject.hpp b/include/cru/ui/render/RenderObject.hpp index 72716d2e..823eabd6 100644 --- a/include/cru/ui/render/RenderObject.hpp +++ b/include/cru/ui/render/RenderObject.hpp @@ -10,6 +10,23 @@ namespace cru::ui::render { // manage lifecycle of its render objects. Since control will destroy its // children when destroyed, render objects will be destroyed along with it. // +// About layout: +// Render object should be able to deal with arbitrary size as the result of +// measure and layout. +// +// Parent may pass calculated preferred size down. But the preferred size set on +// child itself takes precedence. +// +// Each render object should obey the measure requirement to set size and report +// a warning when that requirement can't be satisfied with probably bigger size +// of children than that of itself and optional visual effect to indicate that. +// +// If size of chilren are less than min size requirement, then render object +// should try to fill the rest area. If size of children are more than max size +// requirement, then render object should display a warning of the layout +// problem and use the max size of itself with children exceeding its bound. +// (Or better you could use some visual effect to indicate that.) +// // To write a custom RenderObject, override following methods: // public: // void Draw(platform::graph::IPainter* painter) override; @@ -59,16 +76,46 @@ class RenderObject : public Object { Point FromRootToContent(const Point& point) const; Thickness GetMargin() const { return margin_; } - void SetMargin(const Thickness& margin) { margin_ = margin; } + void SetMargin(const Thickness& margin) { + margin_ = margin; + InvalidateLayout(); + } Thickness GetPadding() const { return padding_; } - void SetPadding(const Thickness& padding) { padding_ = padding; } + void SetPadding(const Thickness& padding) { + padding_ = padding; + InvalidateLayout(); + } + + MeasureSize GetPreferredSize() const { return preferred_size_; } + void SetPreferredSize(const MeasureSize& preferred_size) { + preferred_size_ = preferred_size; + InvalidateLayout(); + } + + MeasureSize GetMinSize() const { return custom_measure_requirement_.min; } + void SetMinSize(const MeasureSize& min_size) { + custom_measure_requirement_.min = min_size; + InvalidateLayout(); + } - Size GetMeasuredSize() const { return measured_size_; } + MeasureSize GetMaxSize() const { return custom_measure_requirement_.max; } + void SetMaxSize(const MeasureSize& max_size) { + custom_measure_requirement_.max = max_size; + InvalidateLayout(); + } - void Measure(const MeasureRequirement& requirement); - // Size of rect must not be negative. - void Layout(const Rect& rect); + MeasureRequirement GetCustomMeasureRequirement() const { + return custom_measure_requirement_; + } + + // This will call OnMeasureCore and set the size of this render object. + // This can be called multiple times on children during measure to adjust for + // better size. + void Measure(const MeasureRequirement& requirement, + const MeasureSize& preferred_size); + // This will set offset of this render object and call OnLayoutCore. + void Layout(const Point& offset); virtual void Draw(platform::graph::IPainter* painter) = 0; @@ -95,21 +142,25 @@ class RenderObject : public Object { virtual void OnRemoveChild(RenderObject* removed_child, Index position); // 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; - + // or other custom things and pass the result content measure requirement and + // preferred size to OnMeasureContent. Return value must not be negative and + // must obey requirement. Preferred size may not be in range so need to be + // coerced. + virtual Size OnMeasureCore(const MeasureRequirement& requirement, + const MeasureSize& preferred_size); + + // Please reduce margin and padding or other custom things and pass the result + // content rect to OnLayoutContent. + virtual void OnLayoutCore(); + + // Override this function to measure content and children(Call Measure on + // them). Do not consider margin or padding in this method because they are + // already considered in OnMeasureCore. Returned size must obey requirement. + // Caller should guarantee preferred_size is corerced into required range. + virtual Size OnMeasureContent(const MeasureRequirement& requirement, + const MeasureSize& preferred_size) = 0; + + // Layout all content and children(Call Layout on them). // Lefttop of content_rect should be added when calculated children's offset. virtual void OnLayoutContent(const Rect& content_rect) = 0; @@ -136,9 +187,10 @@ class RenderObject : public Object { Point offset_{}; Size size_{}; + MeasureSize preferred_size_; + MeasureRequirement custom_measure_requirement_; + Thickness margin_{}; Thickness padding_{}; - - Size measured_size_{}; }; } // namespace cru::ui::render diff --git a/include/cru/ui/render/ScrollRenderObject.hpp b/include/cru/ui/render/ScrollRenderObject.hpp index 774501a1..9799c0d9 100644 --- a/include/cru/ui/render/ScrollRenderObject.hpp +++ b/include/cru/ui/render/ScrollRenderObject.hpp @@ -27,7 +27,7 @@ class ScrollRenderObject : public RenderObject { // 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; + Size OnMeasureContent(const MeasureRequirement& requirement, const MeasureSize& preferred_size) override; void OnLayoutContent(const Rect& content_rect) override; private: diff --git a/include/cru/ui/render/StackLayoutRenderObject.hpp b/include/cru/ui/render/StackLayoutRenderObject.hpp index 2320e1ca..902c9d5c 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 MeasureRequirement& requirement) override; + Size OnMeasureContent(const MeasureRequirement& requirement, const MeasureSize& preferred_size) 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 96bb5997..efa8c2b8 100644 --- a/include/cru/ui/render/TextRenderObject.hpp +++ b/include/cru/ui/render/TextRenderObject.hpp @@ -4,6 +4,19 @@ #include <string> namespace cru::ui::render { +// Layout logic: +// 1. If preferred width is set then it is taken to do a text measure. If not +// set, then max width is taken to do that. +// 2. If the actual width of text after measure exceeds the required width, +// coerced value is returned and an error is reported. If preferred width is set +// and actual width is smaller than that, then preferred width is used. If +// preferred width is not set, then the same thing is applied to min width. +// 3. If actual height of text is bigger than max height, an error is reported +// and coerced value is returned. Or height is lifted up to be at least +// preferred size if set or min height. +// +// If the result layout box is bigger than actual text box, then text is center +// aligned. class TextRenderObject : public RenderObject { public: constexpr static float default_caret_width = 2; @@ -64,7 +77,9 @@ class TextRenderObject : public RenderObject { RenderObject* HitTest(const Point& point) override; protected: - Size OnMeasureContent(const MeasureRequirement& requirement) override; + // See remarks of this class. + Size OnMeasureContent(const MeasureRequirement& requirement, + const MeasureSize& preferred_size) 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 d993dc58..d64a216d 100644 --- a/include/cru/ui/render/WindowRenderObject.hpp +++ b/include/cru/ui/render/WindowRenderObject.hpp @@ -16,7 +16,8 @@ class WindowRenderObject : public RenderObject { RenderObject* HitTest(const Point& point) override; protected: - Size OnMeasureContent(const MeasureRequirement& requirement) override; + Size OnMeasureContent(const MeasureRequirement& requirement, + const MeasureSize& preferred_size) override; void OnLayoutContent(const Rect& content_rect) override; private: |