diff options
| -rw-r--r-- | include/cru/ui/controls/ControlHost.h | 4 | ||||
| -rw-r--r-- | include/cru/ui/render/RenderObject.h | 78 | ||||
| -rw-r--r-- | src/ui/controls/Control.cpp | 4 | ||||
| -rw-r--r-- | src/ui/controls/ControlHost.cpp | 10 | ||||
| -rw-r--r-- | src/ui/render/RenderObject.cpp | 39 |
5 files changed, 80 insertions, 55 deletions
diff --git a/include/cru/ui/controls/ControlHost.h b/include/cru/ui/controls/ControlHost.h index a27be835..fffbbb34 100644 --- a/include/cru/ui/controls/ControlHost.h +++ b/include/cru/ui/controls/ControlHost.h @@ -19,8 +19,8 @@ class CRU_UI_API ControlHost : public Object { platform::gui::INativeWindow* GetNativeWindow(); - void InvalidateLayout(); - void InvalidatePaint(); + void ScheduleRepaint(); + void ScheduleRelayout(); void Repaint(); void Relayout(); diff --git a/include/cru/ui/render/RenderObject.h b/include/cru/ui/render/RenderObject.h index 05fee5df..bb772401 100644 --- a/include/cru/ui/render/RenderObject.h +++ b/include/cru/ui/render/RenderObject.h @@ -31,23 +31,6 @@ struct BoxConstraint { }; /** - * ### 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.) * * ### Create New Render Object * @@ -57,9 +40,8 @@ struct BoxConstraint { * void Draw(platform::graphics::IPainter* painter) override; * RenderObject* HitTest(const Point& point) override; * protected: - * Size OnMeasureContent(const MeasureRequirement& requirement, const - * MeasureSize& preferred_size) override; void OnLayoutContent(const Rect& - * content_rect) override; + * Size OnMeasureContent(const MeasureRequirement& requirement) override; + * void OnLayoutContent(const Rect& content_rect) override; */ class CRU_UI_API RenderObject : public Object { CRU_DEFINE_CLASS_LOG_TAG("cru::ui::render::RenderObject") @@ -74,6 +56,15 @@ class CRU_UI_API RenderObject : public Object { RenderObject* GetParent() { return parent_; } void SetParent(RenderObject* new_parent); + template <typename F> + void WalkUp(const F& f, bool include_this = true) { + auto parent = include_this ? this : GetParent(); + while (parent) { + f(parent); + parent = parent->GetParent(); + } + } + // Offset from parent's lefttop to lefttop of this render object. Margin is // accounted for. Point GetOffset() { return offset_; } @@ -103,17 +94,29 @@ class CRU_UI_API RenderObject : public Object { return custom_measure_requirement_; } - // This method will merge requirement passed by argument and requirement of - // the render object using MeasureRequirement::Merge and then call - // MeasureRequirement::Normalize on it. And it will use preferred size of the - // render object to override the one passed by argument. Then pass the two to - // OnMeasureCore and use the return value of it to set the size of this render - // object. This can be called multiple times on children during measure to - // adjust for better size. + /** + * First checks if layout cache is still valid. If not, invokes OnMeasureCore + * to do the real measure and save the result. Use GetMeasureResultSize to get + * the result size. + * + * The cache is valid only if, + * 1. Measure requirement does not change. + * 2. InvalidateLayout is not called on any descendents. + */ void Measure(const MeasureRequirement& requirement); - // This will set offset of this render object and call OnLayoutCore. + + /** + * Sets offset and size (from GetMeasureResult) of this render object and call + * OnLayoutCore. This will set layout as valid. + */ void Layout(const Point& offset); + /** + * Sets offset and size of this render object and call OnLayoutCore. This will + * set layout as valid. + */ + void Layout(const Rect& rect); + virtual Thickness GetTotalSpaceThickness(); virtual Thickness GetInnerSpaceThickness(); @@ -138,17 +141,20 @@ class CRU_UI_API RenderObject : public Object { CRU_DEFINE_EVENT(Destroy, RenderObject*) protected: - // Size measure including margin and padding. Please reduce margin and padding - // 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. - // Note: Implementation should coerce the preferred size into the requirement - // when pass them to OnMeasureContent. + /** + * Implementation should adjust the requirement for content (like decreasing + * the size of padding and margin), call OnMeasureContent to measure content + * and adjust back to get the final measure result. + * + * The default implementation in RenderObject of calculating content's + * requirement is first merging the given requirement with RenderObject's + * custom measure requirement and then reducing GetTotalSpaceThickness. + */ virtual Size OnMeasureCore(const MeasureRequirement& requirement); // Please reduce margin and padding or other custom things and pass the result // content rect to OnLayoutContent. - virtual void OnLayoutCore(); + virtual void OnLayoutCore(const Rect& rect); // Override this function to measure content and children(Call Measure on // them). Do not consider margin or padding in this method because they are @@ -176,6 +182,8 @@ class CRU_UI_API RenderObject : public Object { Thickness margin_; Thickness padding_; + bool layout_valid_; + MeasureRequirement last_measure_requirement_; Size measure_result_size_; MeasureRequirement custom_measure_requirement_; }; diff --git a/src/ui/controls/Control.cpp b/src/ui/controls/Control.cpp index 41644755..2a94252f 100644 --- a/src/ui/controls/Control.cpp +++ b/src/ui/controls/Control.cpp @@ -110,7 +110,7 @@ void Control::InsertChildAt(Control* control, Index index) { OnChildInserted(control, index); if (host_) { - host_->InvalidateLayout(); + host_->ScheduleRelayout(); } } @@ -131,7 +131,7 @@ void Control::RemoveChildAt(Index index) { OnChildRemoved(control, index); if (host_) { - host_->InvalidateLayout(); + host_->ScheduleRelayout(); } } diff --git a/src/ui/controls/ControlHost.cpp b/src/ui/controls/ControlHost.cpp index caa907da..7c934b7b 100644 --- a/src/ui/controls/ControlHost.cpp +++ b/src/ui/controls/ControlHost.cpp @@ -124,9 +124,9 @@ ControlHost::CreateNativeWindow() { return std::unique_ptr<platform::gui::INativeWindow>(native_window); } -void ControlHost::InvalidatePaint() { native_window_->RequestRepaint(); } +void ControlHost::ScheduleRepaint() { native_window_->RequestRepaint(); } -void ControlHost::InvalidateLayout() { +void ControlHost::ScheduleRelayout() { relayout_schedule_canceler_.Reset( platform::gui::IUiApplication::GetInstance()->SetImmediate( [this] { Relayout(); })); @@ -139,7 +139,7 @@ bool ControlHost::IsLayoutPreferToFillWindow() const { void ControlHost::SetLayoutPreferToFillWindow(bool value) { if (value == layout_prefer_to_fill_window_) return; layout_prefer_to_fill_window_ = value; - InvalidateLayout(); + ScheduleRelayout(); } void ControlHost::Repaint() { @@ -172,7 +172,7 @@ void ControlHost::RelayoutWithSize(const Size& available_size, AfterLayoutEvent_.Raise(nullptr); - InvalidatePaint(); + ScheduleRepaint(); } Control* ControlHost::GetFocusControl() { return focus_control_; } @@ -250,7 +250,7 @@ void ControlHost::OnNativeResize(platform::gui::INativeWindow* window, CRU_UNUSED(window) CRU_UNUSED(size) - InvalidateLayout(); + ScheduleRelayout(); } void ControlHost::OnNativeFocus(platform::gui::INativeWindow* window, diff --git a/src/ui/render/RenderObject.cpp b/src/ui/render/RenderObject.cpp index 59dc6d6a..424fd2ee 100644 --- a/src/ui/render/RenderObject.cpp +++ b/src/ui/render/RenderObject.cpp @@ -10,7 +10,10 @@ namespace cru::ui::render { const BoxConstraint BoxConstraint::kNotLimit{Size::kMax, Size::kZero}; RenderObject::RenderObject(std::string name) - : name_(std::move(name)), control_(nullptr), parent_(nullptr) {} + : name_(std::move(name)), + control_(nullptr), + parent_(nullptr), + layout_valid_(false) {} RenderObject::~RenderObject() { DestroyEvent_.Raise(this); } @@ -82,25 +85,38 @@ void RenderObject::Measure(const MeasureRequirement& requirement) { CRU_LOG_TAG_DEBUG("{} Measure begins, requirement {}.", this->GetDebugPathInTree(), requirement); + if (layout_valid_ && requirement == last_measure_requirement_) { + return; + } + measure_result_size_ = OnMeasureCore(requirement); if (measure_result_size_.width < 0 || measure_result_size_.height < 0) { throw Exception("Measure result size is invalid."); } + last_measure_requirement_ = requirement; + CRU_LOG_TAG_DEBUG("{} Measure ends, result size: {}.", this->GetDebugPathInTree(), measure_result_size_); } void RenderObject::Layout(const Point& offset) { - CRU_LOG_TAG_DEBUG("{} Layout begins, offset: {}, size: {}.", - this->GetDebugPathInTree(), offset, measure_result_size_); + Layout({offset, GetMeasureResultSize()}); +} - offset_ = offset; - size_ = measure_result_size_; +void RenderObject::Layout(const Rect& rect) { + CRU_LOG_TAG_DEBUG("{} Layout begins, rect: {}.", this->GetDebugPathInTree(), + rect); - OnResize(size_); + offset_ = rect.GetLeftTop(); + auto new_size = rect.GetSize(); + if (size_ != new_size) { + size_ = new_size; + OnResize(new_size); + } - OnLayoutCore(); + OnLayoutCore(rect); + layout_valid_ = true; CRU_LOG_TAG_DEBUG("{} Layout ends.", this->GetDebugPathInTree()); } @@ -120,8 +136,8 @@ Size RenderObject::OnMeasureCore(const MeasureRequirement& requirement) { return space_size + content_size; } -void RenderObject::OnLayoutCore() { - auto total_size = GetMeasureResultSize(); +void RenderObject::OnLayoutCore(const Rect& rect) { + auto total_size = rect.GetSize(); auto outer_space = GetTotalSpaceThickness(); auto content_size = (total_size - outer_space.GetTotalSize()).AtLeast0(); @@ -165,14 +181,15 @@ controls::ControlHost* RenderObject::GetControlHost() { } void RenderObject::InvalidateLayout() { + WalkUp([](RenderObject* ro) { ro->layout_valid_ = false; }); if (auto host = GetControlHost()) { - host->InvalidateLayout(); + host->ScheduleRelayout(); } } void RenderObject::InvalidatePaint() { if (auto window = GetControlHost()) { - window->InvalidatePaint(); + window->ScheduleRepaint(); } } |
