menu view: cleanly separate update, layout phases

This patch improves the separation of the update and layout phases to
avoid superfluous geometry animations of its child widgets. Prior this
patch, 'Widget::geometry' was called in both phases, potentially
triggering geometry animations with intermediate values at the update
phase.

Related to issue #3221
This commit is contained in:
Norman Feske 2019-03-10 15:24:57 +01:00 committed by Christian Helmuth
parent ba5de21db4
commit 122c404883
6 changed files with 98 additions and 54 deletions

View File

@ -101,11 +101,6 @@ struct Menu_view::Button_widget : Widget, Animator::Item
_selected = new_selected; _selected = new_selected;
_update_children(node); _update_children(node);
_children.for_each([&] (Widget &child) {
child.geometry(Rect(Point(margin.left + _padding.left,
margin.top + _padding.top),
child.min_size())); });
} }
Area min_size() const override Area min_size() const override
@ -176,9 +171,20 @@ struct Menu_view::Button_widget : Widget, Animator::Item
void _layout() override void _layout() override
{ {
_children.for_each([&] (Widget &w) { _children.for_each([&] (Widget &child) {
w.size(Area(geometry().w() - _space().w(),
geometry().h() - _space().h())); }); child.position(Point(margin.left + _padding.left,
margin.top + _padding.top));
Area const avail = geometry().area();
unsigned const
w = avail.w() >= _space().w() ? avail.w() - _space().w() : 0,
h = avail.h() >= _space().h() ? avail.h() - _space().w() : 0;
child.size(Area(max(w, child.min_size().w()),
max(h, child.min_size().h())));
});
} }

View File

@ -28,8 +28,6 @@ namespace Menu_view { struct Depgraph_widget; }
struct Menu_view::Depgraph_widget : Widget struct Menu_view::Depgraph_widget : Widget
{ {
Area _min_size { }; /* value cached from layout computation */
struct Depth_direction struct Depth_direction
{ {
enum Value { EAST, WEST, NORTH, SOUTH }; enum Value { EAST, WEST, NORTH, SOUTH };
@ -78,6 +76,24 @@ struct Menu_view::Depgraph_widget : Widget
Registry<Registered<Anchor> > _server_anchors { }; Registry<Registered<Anchor> > _server_anchors { };
Registry<Registered<Anchor> > _client_anchors { }; Registry<Registered<Anchor> > _client_anchors { };
Rect _widget_geometry { Point(0, 0), Area(0, 0) };
/**
* Set cached widget geometry, calculated during 'update'
*/
void widget_geometry(Rect geometry) { _widget_geometry = geometry; }
/**
* Propagate cached geometry to widget, called during '_layout'
*
* Calling this method may trigger the widget's geometry animation.
*/
void apply_layout_to_widget()
{
_widget.position(_widget_geometry.p1());
_widget.size(_widget_geometry.area());
}
struct Dependency : Animator::Item struct Dependency : Animator::Item
{ {
Anchor::Type const _type; Anchor::Type const _type;
@ -556,7 +572,8 @@ struct Menu_view::Depgraph_widget : Widget
if (client && !server) { if (client && !server) {
warning("node '", client_name, "' depends on " warning("node '", client_name, "' depends on "
"non-existing node '", server_name, "'"); "non-existing node '", server_name, "'");
client->_widget.geometry(Rect(Point(0, 0), Area(0, 0))); client->_widget.position(Point(0, 0));
client->_widget.size(Area(0, 0));
} }
}); });
@ -588,9 +605,17 @@ struct Menu_view::Depgraph_widget : Widget
}); });
/* /*
* Apply layout to the children, determine _min_size * Calculate the bounding box and the designated geometries of all
* widgets.
*
* The bounding box dictates the 'min_size' of the depgraph widget.
*
* The computed widget geometries are stored in their corresponding
* 'Node' objects but are not immediately propagated to the widgets.
* The computed geometries are applied to the widgets in '_layout'
* phase.
*/ */
Rect bounding_box(Point(0, 0), Area(0, 0)); _bounding_box = Rect(Point(0, 0), Area(0, 0));
_children.for_each([&] (Widget &w) { _children.for_each([&] (Widget &w) {
_nodes.for_each([&] (Registered_node &node) { _nodes.for_each([&] (Registered_node &node) {
if (!node.belongs_to(w)) if (!node.belongs_to(w))
@ -607,35 +632,16 @@ struct Menu_view::Depgraph_widget : Widget
: Rect(Point(breadth_pos, depth_pos), : Rect(Point(breadth_pos, depth_pos),
Area(breadth_size, depth_size)); Area(breadth_size, depth_size));
w.geometry(Rect(node_rect.center(w.min_size()), w.min_size())); Rect geometry(node_rect.center(w.min_size()), w.min_size());
bounding_box = Rect::compound(bounding_box, w.geometry()); node.widget_geometry(geometry);
_bounding_box = Rect::compound(_bounding_box, geometry);
}); });
}); });
/*
* Mirror coordinates if graph grows towards north or west
*/
if (_depth_direction.value == Depth_direction::NORTH
|| _depth_direction.value == Depth_direction::WEST) {
_children.for_each([&] (Widget &w) {
int x = w.geometry().x1(), y = w.geometry().y1();
if (_depth_direction.value == Depth_direction::NORTH)
y = (int)bounding_box.h() - y - w.geometry().h();
if (_depth_direction.value == Depth_direction::WEST)
x = (int)bounding_box.w() - x - w.geometry().w();
w.geometry(Rect(Point(x, y), w.geometry().area()));
});
}
_min_size = bounding_box.area();
} }
Area min_size() const override { return _min_size; } Area min_size() const override { return _bounding_box.area(); }
void _draw_connect(Surface<Pixel_rgb888> &pixel_surface, void _draw_connect(Surface<Pixel_rgb888> &pixel_surface,
Surface<Pixel_alpha8> &alpha_surface, Surface<Pixel_alpha8> &alpha_surface,
@ -720,6 +726,36 @@ struct Menu_view::Depgraph_widget : Widget
void _layout() override void _layout() override
{ {
/*
* Apply layout to the children
*/
_nodes.for_each([&] (Registered_node &node) {
if (&node != &_root_node)
node.apply_layout_to_widget(); });
/*
* Mirror coordinates if graph grows towards north or west
*/
if (_depth_direction.value == Depth_direction::NORTH
|| _depth_direction.value == Depth_direction::WEST) {
_children.for_each([&] (Widget &w) {
int x = w.geometry().x1(), y = w.geometry().y1();
if (_depth_direction.value == Depth_direction::NORTH)
y = (int)_bounding_box.h() - y - w.geometry().h();
if (_depth_direction.value == Depth_direction::WEST)
x = (int)_bounding_box.w() - x - w.geometry().w();
w.position(Point(x, y));
});
}
/*
* Prompt each child to update its layout
*/
_children.for_each([&] (Widget &w) { _children.for_each([&] (Widget &w) {
w.size(w.geometry().area()); }); w.size(w.geometry().area()); });
} }

View File

@ -43,7 +43,8 @@ struct Menu_view::Float_widget : Widget
int const x = _west ? 0 : _east ? w_space : w_space / 2; int const x = _west ? 0 : _east ? w_space : w_space / 2;
int const y = _north ? 0 : _south ? h_space : h_space / 2; int const y = _north ? 0 : _south ? h_space : h_space / 2;
child.geometry(Rect(Point(x, y), Area(w, h))); child.position(Point(x, y));
child.size(Area(w, h));
} }
void update(Xml_node node) override void update(Xml_node node) override

View File

@ -44,13 +44,6 @@ struct Menu_view::Frame_widget : Widget
_update_children(node); _update_children(node);
/*
* layout
*/
_children.for_each([&] (Widget &child) {
child.geometry(Rect(Point(margin.left + padding.left,
margin.top + padding.top),
child.min_size())); });
} }
Area min_size() const override Area min_size() const override
@ -83,8 +76,19 @@ struct Menu_view::Frame_widget : Widget
void _layout() override void _layout() override
{ {
_children.for_each([&] (Widget &child) { _children.for_each([&] (Widget &child) {
child.size(Area(geometry().w() - _space().w(),
geometry().h() - _space().h())); }); child.position(Point(margin.left + padding.left,
margin.top + padding.top));
Area const avail = geometry().area();
unsigned const
w = avail.w() >= _space().w() ? avail.w() - _space().w() : 0,
h = avail.h() >= _space().h() ? avail.h() - _space().w() : 0;
child.size(Area(max(w, child.min_size().w()),
max(h, child.min_size().h())));
});
} }
private: private:

View File

@ -74,8 +74,8 @@ struct Menu_view::Root_widget : Widget
void _layout() override void _layout() override
{ {
_children.for_each([&] (Widget &child) { _children.for_each([&] (Widget &child) {
child.size(geometry().area());
child.position(Point(0, 0)); child.position(Point(0, 0));
child.size(_geometry.area());
}); });
} }
}; };

View File

@ -176,12 +176,6 @@ class Menu_view::Widget : List_model<Widget>::Element
Margin margin { 0, 0, 0, 0 }; Margin margin { 0, 0, 0, 0 };
void geometry(Rect geometry)
{
_geometry = geometry;
_trigger_geometry_animation();
}
Rect geometry() const { return _geometry; } Rect geometry() const { return _geometry; }
Rect animated_geometry() const { return _animated_geometry.rect(); } Rect animated_geometry() const { return _animated_geometry.rect(); }
@ -220,6 +214,9 @@ class Menu_view::Widget : List_model<Widget>::Element
Surface<Pixel_alpha8> &alpha_surface, Surface<Pixel_alpha8> &alpha_surface,
Point at) const = 0; Point at) const = 0;
/**
* Set widget size and update the widget tree's layout accordingly
*/
void size(Area size) void size(Area size)
{ {
_geometry = Rect(_geometry.p1(), size); _geometry = Rect(_geometry.p1(), size);