base: add util/list_model.h

The new 'List_model' utility helps with the implementation of
component-internal data models created and updated from XML.

Fixes #2653
This commit is contained in:
Norman Feske 2018-01-23 17:25:53 +01:00
parent cde8163770
commit 10f1616c28
9 changed files with 322 additions and 236 deletions

View File

@ -0,0 +1,220 @@
/*
* \brief List-based data model created and updated from XML
* \author Norman Feske
* \date 2017-08-09
*
* The 'List_model' stores a component-internal representation of XML-node
* content. The XML information is imported according to an 'Update_policy',
* which specifies how the elements of the data model are created, destroyed,
* and updated. The elements are ordered according to the order of XML nodes.
*/
/*
* Copyright (C) 2017 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
#ifndef _INCLUDE__UTIL__LIST_MODEL_H_
#define _INCLUDE__UTIL__LIST_MODEL_H_
#include <util/xml_node.h>
namespace Genode { template <typename> class List_model; }
template <typename ELEM>
class Genode::List_model
{
private:
List<ELEM> _elements { };
public:
struct Update_policy;
class Element : private List<ELEM>::Element
{
private:
/* used by 'List_model::update_from_xml' only */
friend class List_model;
friend class List<ELEM>;
ELEM *_next() const { return List<ELEM>::Element::next(); }
public:
/**
* Return the element's neighbor if present, otherwise nullptr
*/
ELEM const *next() const { return List<ELEM>::Element::next(); }
};
struct Unknown_element_type : Exception { };
~List_model()
{
if (_elements.first())
warning("list model not empty at destruction time");
}
/**
* Update data model according to XML structure 'node'
*
* \throw Unknown_element_type
*/
template <typename POLICY>
inline void update_from_xml(POLICY &policy, Xml_node node);
/**
* Call functor 'fn' for each const element
*/
template <typename FN>
void for_each(FN const &fn) const
{
for (Element const *e = _elements.first(); e; e = e->next())
fn(static_cast<ELEM const &>(*e));
}
/**
* Call functor 'fn' for each non-const element
*/
template <typename FN>
void for_each(FN const &fn)
{
Element *next = nullptr;
for (Element *e = _elements.first(); e; e = next) {
next = e->_next();
fn(static_cast<ELEM &>(*e));
}
}
/**
* Remove all elements from the data model
*
* This method should be called at the destruction time of the
* 'List_model'.
*
* List-model elements are not implicitly destroyed by the destructor
* because the 'policy' needed to destruct elements is not kept as
* member of the list model (multiple policies may applied to the same
* list model).
*/
template <typename POLICY>
void destroy_all_elements(POLICY &policy)
{
Element *next = nullptr;
for (Element *e = _elements.first(); e; e = next) {
next = e->_next();
ELEM &elem = static_cast<ELEM &>(*e);
_elements.remove(&elem);
policy.destroy_element(elem);
}
}
};
template <typename ELEM>
template <typename POLICY>
void Genode::List_model<ELEM>::update_from_xml(POLICY &policy, Xml_node node)
{
typedef typename POLICY::Element Element;
List<Element> updated_list;
Element *last_updated = nullptr; /* used for appending to 'updated_list' */
node.for_each_sub_node([&] (Xml_node sub_node) {
/* skip XML nodes that are unrelated to the data model */
if (!policy.node_is_element(sub_node))
return;
/* look up corresponding element in original list */
Element *curr = _elements.first();
while (curr && !policy.element_matches_xml_node(*curr, sub_node))
curr = curr->_next();
/* consume existing element or create new one */
if (curr) {
_elements.remove(curr);
} else {
/* \throw Unknown_element_type */
curr = &policy.create_element(sub_node);
}
/* append current element to 'updated_list' */
updated_list.insert(curr, last_updated);
last_updated = curr;
policy.update_element(*curr, sub_node);
});
/* remove stale elements */
Element *next = nullptr;
for (Element *e = _elements.first(); e; e = next) {
next = e->_next();
policy.destroy_element(*e);
}
/* use 'updated_list' list new data model */
_elements = updated_list;
}
/**
* Policy interface to be supplied to 'List_model::update_from_xml'
*
* \param ELEM element type, must be a list element
*
* This class template is merely a blue print of a policy to document the
* interface.
*/
template <typename ELEM>
struct Genode::List_model<ELEM>::Update_policy
{
typedef List_model<ELEM>::Unknown_element_type Unknown_element_type;
/*
* Type that needs to be supplied by the policy implementation
*/
typedef ELEM Element;
/**
* Destroy element
*
* When this function is called, the element is no longer contained
* in the model's list.
*/
void destroy_element(ELEM &elem);
/**
* Create element of the type given in the 'elem_node'
*
* \throw List_model::Unknown_element_type
*/
ELEM &create_element(Xml_node elem_node);
/**
* Import element properties from XML node
*/
void update_element(ELEM &elem, Xml_node elem_node);
/**
* Return true if element corresponds to XML node
*/
static bool element_matches_xml_node(Element const &, Xml_node);
/**
* Return true if XML node should be imported
*
* This method allows the policy to disregard certain XML node types from
* building the data model.
*/
static bool node_is_element(Xml_node node) { return true; }
};
#endif /* _INCLUDE__UTIL__LIST_MODEL_H_ */

View File

@ -44,31 +44,31 @@ struct Menu_view::Box_layout_widget : Widget
/* determine largest size among our children */ /* determine largest size among our children */
unsigned largest_size = 0; unsigned largest_size = 0;
for (Widget *w = _children.first(); w; w = w->next()) _children.for_each([&] (Widget const &w) {
largest_size = largest_size =
max(largest_size, _direction == VERTICAL ? w->min_size().w() max(largest_size, _direction == VERTICAL ? w.min_size().w()
: w->min_size().h()); : w.min_size().h()); });
/* position children on one row/column */ /* position children on one row/column */
Point position(0, 0); Point position(0, 0);
for (Widget *w = _children.first(); w; w = w->next()) { _children.for_each([&] (Widget &w) {
Area const child_min_size = w->min_size(); Area const child_min_size = w.min_size();
if (_direction == VERTICAL) { if (_direction == VERTICAL) {
w->geometry(Rect(position, Area(largest_size, child_min_size.h()))); w.geometry(Rect(position, Area(largest_size, child_min_size.h())));
unsigned const next_top_margin = w->next() ? w->next()->margin.top : 0; unsigned const next_top_margin = w.next() ? w.next()->margin.top : 0;
unsigned const dy = child_min_size.h() - min(w->margin.bottom, next_top_margin); unsigned const dy = child_min_size.h() - min(w.margin.bottom, next_top_margin);
position = position + Point(0, dy); position = position + Point(0, dy);
} else { } else {
w->geometry(Rect(position, Area(child_min_size.w(), largest_size))); w.geometry(Rect(position, Area(child_min_size.w(), largest_size)));
unsigned const next_left_margin = w->next() ? w->next()->margin.left : 0; unsigned const next_left_margin = w.next() ? w.next()->margin.left : 0;
unsigned const dx = child_min_size.w() - min(w->margin.right, next_left_margin); unsigned const dx = child_min_size.w() - min(w.margin.right, next_left_margin);
position = position + Point(dx, 0); position = position + Point(dx, 0);
} }
_min_size = Area(w->geometry().x2() + 1, w->geometry().y2() + 1); _min_size = Area(w.geometry().x2() + 1, w.geometry().y2() + 1);
} });
} }
Area min_size() const override Area min_size() const override
@ -85,12 +85,12 @@ struct Menu_view::Box_layout_widget : Widget
void _layout() override void _layout() override
{ {
for (Widget *w = _children.first(); w; w = w->next()) { _children.for_each([&] (Widget &w) {
if (_direction == VERTICAL) if (_direction == VERTICAL)
w->size(Area(geometry().w(), w->min_size().h())); w.size(Area(geometry().w(), w.min_size().h()));
else else
w->size(Area(w->min_size().w(), geometry().h())); w.size(Area(w.min_size().w(), geometry().h()));
} });
} }
}; };

View File

@ -85,17 +85,19 @@ struct Menu_view::Button_widget : Widget, Animator::Item
bool const dy = selected ? 1 : 0; bool const dy = selected ? 1 : 0;
if (Widget *child = _children.first()) _children.for_each([&] (Widget &child) {
child->geometry(Rect(Point(margin.left + padding.left, child.geometry(Rect(Point(margin.left + padding.left,
margin.top + padding.top + dy), margin.top + padding.top + dy),
child->min_size())); child.min_size()));
});
} }
Area min_size() const override Area min_size() const override
{ {
/* determine minimum child size */ /* determine minimum child size */
Widget const * const child = _children.first(); Area child_min_size(300, 10);
Area const child_min_size = child ? child->min_size() : Area(300, 10); _children.for_each([&] (Widget const &child) {
child_min_size = child.min_size(); });
/* don't get smaller than the background texture */ /* don't get smaller than the background texture */
Area const texture_size = default_texture->size(); Area const texture_size = default_texture->size();
@ -140,9 +142,9 @@ struct Menu_view::Button_widget : Widget, Animator::Item
void _layout() override void _layout() override
{ {
for (Widget *w = _children.first(); w; w = w->next()) _children.for_each([&] (Widget &w) {
w->size(Area(geometry().w() - _space().w(), w.size(Area(geometry().w() - _space().w(),
geometry().h() - _space().h())); geometry().h() - _space().h())); });
} }

View File

@ -377,7 +377,7 @@ struct Menu_view::Depgraph_widget : Widget
* Customized model-update policy that augments the list of child widgets * Customized model-update policy that augments the list of child widgets
* with their graph-node topology * with their graph-node topology
*/ */
struct Model_update_policy : List_model_update_policy<Widget> struct Model_update_policy : List_model<Widget>::Update_policy
{ {
Widget::Model_update_policy &_generic_model_update_policy; Widget::Model_update_policy &_generic_model_update_policy;
Allocator &_alloc; Allocator &_alloc;
@ -440,10 +440,7 @@ struct Menu_view::Depgraph_widget : Widget
~Depgraph_widget() ~Depgraph_widget()
{ {
while (Widget *w = _children.first()) { _children.destroy_all_elements(_model_update_policy);
_children.remove(w);
_model_update_policy.destroy_element(*w);
}
} }
void update(Xml_node node) override void update(Xml_node node) override
@ -459,7 +456,7 @@ struct Menu_view::Depgraph_widget : Widget
if (dir_name == "west") _depth_direction = { Depth_direction::WEST }; if (dir_name == "west") _depth_direction = { Depth_direction::WEST };
} }
update_list_model_from_xml(_model_update_policy, _children, node); _children.update_from_xml(_model_update_policy, node);
/* /*
* Import dependencies * Import dependencies
@ -511,9 +508,9 @@ struct Menu_view::Depgraph_widget : Widget
* *
* The computation depends on the order of '_children'. * The computation depends on the order of '_children'.
*/ */
for (Widget *w = _children.first(); w; w = w->next()) { _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))
return; return;
apply_to_primary_dependency(node, [&] (Node &parent) { apply_to_primary_dependency(node, [&] (Node &parent) {
@ -525,16 +522,15 @@ struct Menu_view::Depgraph_widget : Widget
node.breadth_size(_depth_direction); node.breadth_size(_depth_direction);
}); });
}); });
} });
/* /*
* Apply layout to the children, determine _min_size * Apply layout to the children, determine _min_size
*/ */
Rect bounding_box(Point(0, 0), Area(0, 0)); Rect bounding_box(Point(0, 0), Area(0, 0));
for (Widget *w = _children.first(); w; w = w->next()) { _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))
return; return;
int const depth_pos = node.depth_pos(_depth_direction), int const depth_pos = node.depth_pos(_depth_direction),
@ -548,11 +544,11 @@ 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())); w.geometry(Rect(node_rect.center(w.min_size()), w.min_size()));
bounding_box = Rect::compound(bounding_box, w->geometry()); bounding_box = Rect::compound(bounding_box, w.geometry());
});
}); });
}
/* /*
* Mirror coordinates if graph grows towards north or west * Mirror coordinates if graph grows towards north or west
@ -560,18 +556,18 @@ struct Menu_view::Depgraph_widget : Widget
if (_depth_direction.value == Depth_direction::NORTH if (_depth_direction.value == Depth_direction::NORTH
|| _depth_direction.value == Depth_direction::WEST) { || _depth_direction.value == Depth_direction::WEST) {
for (Widget *w = _children.first(); w; w = w->next()) { _children.for_each([&] (Widget &w) {
int x = w->geometry().x1(), y = w->geometry().y1(); int x = w.geometry().x1(), y = w.geometry().y1();
if (_depth_direction.value == Depth_direction::NORTH) if (_depth_direction.value == Depth_direction::NORTH)
y = (int)bounding_box.h() - y - w->geometry().h(); y = (int)bounding_box.h() - y - w.geometry().h();
if (_depth_direction.value == Depth_direction::WEST) if (_depth_direction.value == Depth_direction::WEST)
x = (int)bounding_box.w() - x - w->geometry().w(); x = (int)bounding_box.w() - x - w.geometry().w();
w->geometry(Rect(Point(x, y), w->geometry().area())); w.geometry(Rect(Point(x, y), w.geometry().area()));
} });
} }
_min_size = bounding_box.area(); _min_size = bounding_box.area();
} }
@ -657,8 +653,8 @@ struct Menu_view::Depgraph_widget : Widget
void _layout() override void _layout() override
{ {
for (Widget *w = _children.first(); w; w = w->next()) _children.for_each([&] (Widget &w) {
w->size(w->geometry().area()); w.size(w.geometry().area()); });
} }
}; };

View File

@ -55,15 +55,19 @@ struct Menu_view::Float_widget : Widget
_east = node.attribute_value("east", false), _east = node.attribute_value("east", false),
_west = node.attribute_value("west", false); _west = node.attribute_value("west", false);
if (Widget *child = _children.first()) _children.for_each([&] (Widget &child) {
_place_child(*child); _place_child(child); });
} }
Area min_size() const override Area min_size() const override
{ {
Area result(0, 0);
/* determine minimum child size */ /* determine minimum child size */
Widget const * const child = _children.first(); _children.for_each([&] (Widget const &child) {
return child ? child->min_size() : Area(0, 0); result = child.min_size(); });
return result;
} }
void draw(Surface<Pixel_rgb888> &pixel_surface, void draw(Surface<Pixel_rgb888> &pixel_surface,
@ -75,10 +79,10 @@ struct Menu_view::Float_widget : Widget
void _layout() override void _layout() override
{ {
if (Widget *child = _children.first()) { _children.for_each([&] (Widget &child) {
_place_child(*child); _place_child(child);
child->size(child->geometry().area()); child.size(child.geometry().area());
} });
} }
}; };

View File

@ -47,17 +47,18 @@ struct Menu_view::Frame_widget : Widget
/* /*
* layout * layout
*/ */
if (Widget *child = _children.first()) _children.for_each([&] (Widget &child) {
child->geometry(Rect(Point(margin.left + padding.left, child.geometry(Rect(Point(margin.left + padding.left,
margin.top + padding.top), margin.top + padding.top),
child->min_size())); child.min_size())); });
} }
Area min_size() const override Area min_size() const override
{ {
/* determine minimum child size */ /* determine minimum child size (there is only one child) */
Widget const * const child = _children.first(); Area child_min_size(0, 0);
Area const child_min_size = child ? child->min_size() : Area(0, 0); _children.for_each([&] (Widget const &child) {
child_min_size = child.min_size(); });
/* don't get smaller than the background texture */ /* don't get smaller than the background texture */
Area const texture_size = texture ? texture->size() : Area(0, 0); Area const texture_size = texture ? texture->size() : Area(0, 0);
@ -81,9 +82,9 @@ struct Menu_view::Frame_widget : Widget
void _layout() override void _layout() override
{ {
if (Widget *child = _children.first()) _children.for_each([&] (Widget &child) {
child->size(Area(geometry().w() - _space().w(), child.size(Area(geometry().w() - _space().w(),
geometry().h() - _space().h())); geometry().h() - _space().h())); });
} }
}; };

View File

@ -1,138 +0,0 @@
/*
* \brief Utility for updating an internal data model from an XML structure
* \author Norman Feske
* \date 2017-08-09
*/
/*
* Copyright (C) 2017 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
#ifndef _INCLUDE__UTIL__LIST_MODEL_FROM_XML_H_
#define _INCLUDE__UTIL__LIST_MODEL_FROM_XML_H_
#include <util/xml_node.h>
namespace Genode {
template <typename POLICY>
static inline void
update_list_model_from_xml(POLICY &policy,
List<typename POLICY::Element> &list,
Xml_node node);
template <typename> struct List_model_update_policy;
}
/**
* Policy interface to be supplied to 'update_list_model_from_xml'
*
* \param ELEM element type, must be a list element
*
* This class template is merely a blue print of a policy to document the
* interface.
*/
template <typename ELEM>
struct Genode::List_model_update_policy
{
/*
* Types that need to be supplied by the policy implementation
*/
typedef ELEM Element;
struct Unknown_element_type : Exception { };
/**
* Destroy element
*
* When this function is called, the element is no longer contained
* in the model's list.
*/
void destroy_element(ELEM &elem);
/**
* Create element of the type given in the 'elem_node'
*
* \throw Unknown_element_type
*/
ELEM &create_element(Xml_node elem_node);
/**
* Import element properties from XML node
*/
void update_element(ELEM &elem, Xml_node elem_node);
/**
* Return true if element corresponds to XML node
*/
static bool element_matches_xml_node(Element const &, Xml_node);
/**
* Return true if XML node should be imported
*
* This method allows the policy to disregard certain XML node types from
* building the data model.
*/
static bool node_is_element(Xml_node node) { return true; }
};
/**
* Update 'list' data model according to XML structure 'node'
*
* \throw Unknown_element_type
*/
template <typename POLICY>
static inline void
Genode::update_list_model_from_xml(POLICY &policy,
List<typename POLICY::Element> &list,
Xml_node node)
{
typedef typename POLICY::Element Element;
List<Element> updated_list;
Element *last_updated = nullptr; /* used for appending to 'updated_list' */
node.for_each_sub_node([&] (Xml_node sub_node) {
/* skip XML nodes that are unrelated to the data model */
if (!policy.node_is_element(sub_node))
return;
/* look up corresponding element in original list */
Element *curr = list.first();
while (curr && !policy.element_matches_xml_node(*curr, sub_node))
curr = curr->next();
/* consume existing element or create new one */
if (curr) {
list.remove(curr);
} else {
/* \throw Unknown_element_type */
curr = &policy.create_element(sub_node);
}
/* append current element to 'updated_list' */
updated_list.insert(curr, last_updated);
last_updated = curr;
policy.update_element(*curr, sub_node);
});
/* remove stale elements */
Element *next = nullptr;
for (Element *e = list.first(); e; e = next) {
next = e->next();
policy.destroy_element(*e);
}
/* use 'updated_list' list new data model */
list = updated_list;
}
#endif /* _INCLUDE__UTIL__LIST_MODEL_FROM_XML_H_ */

View File

@ -29,10 +29,12 @@ struct Menu_view::Root_widget : Widget
Area animated_size() const Area animated_size() const
{ {
if (Widget const * const child = _children.first()) Area result(1, 1);
return child->animated_geometry().area();
return Area(1, 1); _children.for_each([&] (Widget const &child) {
result = child.animated_geometry().area(); });
return result;
} }
void update(Xml_node node) override void update(Xml_node node) override
@ -51,16 +53,18 @@ struct Menu_view::Root_widget : Widget
_update_children(node); _update_children(node);
if (Widget *child = _children.first()) _children.for_each([&] (Widget &child) {
child->geometry(Rect(Point(0, 0), child->min_size())); child.geometry(Rect(Point(0, 0), child.min_size())); });
} }
Area min_size() const override Area min_size() const override
{ {
if (Widget const * const child = _children.first()) Area result(1, 1);
return child->min_size();
return Area(1, 1); _children.for_each([&] (Widget const &child) {
result = child.min_size(); });
return result;
} }
void draw(Surface<Pixel_rgb888> &pixel_surface, void draw(Surface<Pixel_rgb888> &pixel_surface,
@ -72,10 +76,10 @@ struct Menu_view::Root_widget : Widget
void _layout() override void _layout() override
{ {
if (Widget *child = _children.first()) { _children.for_each([&] (Widget &child) {
child->size(geometry().area()); child.size(geometry().area());
child->position(Point(0, 0)); child.position(Point(0, 0));
} });
} }
}; };

View File

@ -16,10 +16,10 @@
/* Genode includes */ /* Genode includes */
#include <util/xml_generator.h> #include <util/xml_generator.h>
#include <util/list_model.h>
/* local includes */ /* local includes */
#include <widget_factory.h> #include <widget_factory.h>
#include <list_model_from_xml.h>
#include <animated_geometry.h> #include <animated_geometry.h>
namespace Menu_view { namespace Menu_view {
@ -45,7 +45,7 @@ struct Menu_view::Margin
}; };
class Menu_view::Widget : public List<Widget>::Element class Menu_view::Widget : public List_model<Widget>::Element
{ {
public: public:
@ -99,9 +99,9 @@ class Menu_view::Widget : public List<Widget>::Element
Widget_factory &_factory; Widget_factory &_factory;
List<Widget> _children; List_model<Widget> _children;
struct Model_update_policy : List_model_update_policy<Widget> struct Model_update_policy : List_model<Widget>::Update_policy
{ {
Widget_factory &_factory; Widget_factory &_factory;
@ -129,15 +129,15 @@ class Menu_view::Widget : public List<Widget>::Element
inline void _update_children(Xml_node node) inline void _update_children(Xml_node node)
{ {
update_list_model_from_xml(_model_update_policy, _children, node); _children.update_from_xml(_model_update_policy, node);
} }
void _draw_children(Surface<Pixel_rgb888> &pixel_surface, void _draw_children(Surface<Pixel_rgb888> &pixel_surface,
Surface<Pixel_alpha8> &alpha_surface, Surface<Pixel_alpha8> &alpha_surface,
Point at) const Point at) const
{ {
for (Widget const *w = _children.first(); w; w = w->next()) _children.for_each([&] (Widget const &w) {
w->draw(pixel_surface, alpha_surface, at + w->_animated_geometry.p1()); w.draw(pixel_surface, alpha_surface, at + w._animated_geometry.p1()); });
} }
virtual void _layout() { } virtual void _layout() { }
@ -192,10 +192,7 @@ class Menu_view::Widget : public List<Widget>::Element
virtual ~Widget() virtual ~Widget()
{ {
while (Widget *w = _children.first()) { _children.destroy_all_elements(_model_update_policy);
_children.remove(w);
_model_update_policy.destroy_element(*w);
}
} }
bool has_name(Name const &name) const { return name == _name; } bool has_name(Name const &name) const { return name == _name; }
@ -230,11 +227,12 @@ class Menu_view::Widget : public List<Widget>::Element
if (!_inner_geometry().contains(at)) if (!_inner_geometry().contains(at))
return Unique_id(); return Unique_id();
for (Widget const *w = _children.first(); w; w = w->next()) { Unique_id result { };
Unique_id res = w->hovered(at - w->geometry().p1()); _children.for_each([&] (Widget const &w) {
if (res.valid()) Unique_id const id = w.hovered(at - w.geometry().p1());
return res; if (id.valid())
} result = id;
});
return _unique_id; return _unique_id;
} }
@ -256,9 +254,8 @@ class Menu_view::Widget : public List<Widget>::Element
xml.attribute("width", geometry().w()); xml.attribute("width", geometry().w());
xml.attribute("height", geometry().h()); xml.attribute("height", geometry().h());
for (Widget const *w = _children.first(); w; w = w->next()) { _children.for_each([&] (Widget const &w) {
w->gen_hover_model(xml, at - w->geometry().p1()); w.gen_hover_model(xml, at - w.geometry().p1()); });
}
}); });
} }
} }