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;
_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
@ -176,9 +171,20 @@ struct Menu_view::Button_widget : Widget, Animator::Item
void _layout() override
{
_children.for_each([&] (Widget &w) {
w.size(Area(geometry().w() - _space().w(),
geometry().h() - _space().h())); });
_children.for_each([&] (Widget &child) {
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
{
Area _min_size { }; /* value cached from layout computation */
struct Depth_direction
{
enum Value { EAST, WEST, NORTH, SOUTH };
@ -78,6 +76,24 @@ struct Menu_view::Depgraph_widget : Widget
Registry<Registered<Anchor> > _server_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
{
Anchor::Type const _type;
@ -556,7 +572,8 @@ struct Menu_view::Depgraph_widget : Widget
if (client && !server) {
warning("node '", client_name, "' depends on "
"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) {
_nodes.for_each([&] (Registered_node &node) {
if (!node.belongs_to(w))
@ -607,35 +632,16 @@ struct Menu_view::Depgraph_widget : Widget
: Rect(Point(breadth_pos, depth_pos),
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,
Surface<Pixel_alpha8> &alpha_surface,
@ -720,6 +726,36 @@ struct Menu_view::Depgraph_widget : Widget
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) {
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 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

View File

@ -44,13 +44,6 @@ struct Menu_view::Frame_widget : Widget
_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
@ -83,8 +76,19 @@ struct Menu_view::Frame_widget : Widget
void _layout() override
{
_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:

View File

@ -74,8 +74,8 @@ struct Menu_view::Root_widget : Widget
void _layout() override
{
_children.for_each([&] (Widget &child) {
child.size(geometry().area());
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 };
void geometry(Rect geometry)
{
_geometry = geometry;
_trigger_geometry_animation();
}
Rect geometry() const { return _geometry; }
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,
Point at) const = 0;
/**
* Set widget size and update the widget tree's layout accordingly
*/
void size(Area size)
{
_geometry = Rect(_geometry.p1(), size);