aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/cru/ui/controls/ControlHost.h4
-rw-r--r--include/cru/ui/render/RenderObject.h78
-rw-r--r--src/ui/controls/Control.cpp4
-rw-r--r--src/ui/controls/ControlHost.cpp10
-rw-r--r--src/ui/render/RenderObject.cpp39
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();
}
}