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 */
unsigned largest_size = 0;
for (Widget *w = _children.first(); w; w = w->next())
_children.for_each([&] (Widget const &w) {
largest_size =
max(largest_size, _direction == VERTICAL ? w->min_size().w()
: w->min_size().h());
max(largest_size, _direction == VERTICAL ? w.min_size().w()
: w.min_size().h()); });
/* position children on one row/column */
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) {
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 dy = child_min_size.h() - min(w->margin.bottom, next_top_margin);
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 dy = child_min_size.h() - min(w.margin.bottom, next_top_margin);
position = position + Point(0, dy);
} else {
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 dx = child_min_size.w() - min(w->margin.right, next_left_margin);
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 dx = child_min_size.w() - min(w.margin.right, next_left_margin);
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
@ -85,12 +85,12 @@ struct Menu_view::Box_layout_widget : Widget
void _layout() override
{
for (Widget *w = _children.first(); w; w = w->next()) {
_children.for_each([&] (Widget &w) {
if (_direction == VERTICAL)
w->size(Area(geometry().w(), w->min_size().h()));
w.size(Area(geometry().w(), w.min_size().h()));
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;
if (Widget *child = _children.first())
child->geometry(Rect(Point(margin.left + padding.left,
margin.top + padding.top + dy),
child->min_size()));
_children.for_each([&] (Widget &child) {
child.geometry(Rect(Point(margin.left + padding.left,
margin.top + padding.top + dy),
child.min_size()));
});
}
Area min_size() const override
{
/* determine minimum child size */
Widget const * const child = _children.first();
Area const child_min_size = child ? child->min_size() : Area(300, 10);
Area child_min_size(300, 10);
_children.for_each([&] (Widget const &child) {
child_min_size = child.min_size(); });
/* don't get smaller than the background texture */
Area const texture_size = default_texture->size();
@ -140,9 +142,9 @@ struct Menu_view::Button_widget : Widget, Animator::Item
void _layout() override
{
for (Widget *w = _children.first(); w; w = w->next())
w->size(Area(geometry().w() - _space().w(),
geometry().h() - _space().h()));
_children.for_each([&] (Widget &w) {
w.size(Area(geometry().w() - _space().w(),
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
* 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;
Allocator &_alloc;
@ -440,10 +440,7 @@ struct Menu_view::Depgraph_widget : Widget
~Depgraph_widget()
{
while (Widget *w = _children.first()) {
_children.remove(w);
_model_update_policy.destroy_element(*w);
}
_children.destroy_all_elements(_model_update_policy);
}
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 };
}
update_list_model_from_xml(_model_update_policy, _children, node);
_children.update_from_xml(_model_update_policy, node);
/*
* Import dependencies
@ -511,9 +508,9 @@ struct Menu_view::Depgraph_widget : Widget
*
* 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) {
if (!node.belongs_to(*w))
if (!node.belongs_to(w))
return;
apply_to_primary_dependency(node, [&] (Node &parent) {
@ -525,16 +522,15 @@ struct Menu_view::Depgraph_widget : Widget
node.breadth_size(_depth_direction);
});
});
}
});
/*
* Apply layout to the children, determine _min_size
*/
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) {
if (!node.belongs_to(*w))
if (!node.belongs_to(w))
return;
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),
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
@ -560,18 +556,18 @@ struct Menu_view::Depgraph_widget : Widget
if (_depth_direction.value == Depth_direction::NORTH
|| _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)
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)
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();
}
@ -657,8 +653,8 @@ struct Menu_view::Depgraph_widget : Widget
void _layout() override
{
for (Widget *w = _children.first(); w; w = w->next())
w->size(w->geometry().area());
_children.for_each([&] (Widget &w) {
w.size(w.geometry().area()); });
}
};

View File

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

View File

@ -47,17 +47,18 @@ struct Menu_view::Frame_widget : Widget
/*
* layout
*/
if (Widget *child = _children.first())
child->geometry(Rect(Point(margin.left + padding.left,
margin.top + padding.top),
child->min_size()));
_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
{
/* determine minimum child size */
Widget const * const child = _children.first();
Area const child_min_size = child ? child->min_size() : Area(0, 0);
/* determine minimum child size (there is only one child) */
Area child_min_size(0, 0);
_children.for_each([&] (Widget const &child) {
child_min_size = child.min_size(); });
/* don't get smaller than the background texture */
Area const texture_size = texture ? texture->size() : Area(0, 0);
@ -81,9 +82,9 @@ struct Menu_view::Frame_widget : Widget
void _layout() override
{
if (Widget *child = _children.first())
child->size(Area(geometry().w() - _space().w(),
geometry().h() - _space().h()));
_children.for_each([&] (Widget &child) {
child.size(Area(geometry().w() - _space().w(),
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
{
if (Widget const * const child = _children.first())
return child->animated_geometry().area();
Area result(1, 1);
return Area(1, 1);
_children.for_each([&] (Widget const &child) {
result = child.animated_geometry().area(); });
return result;
}
void update(Xml_node node) override
@ -51,16 +53,18 @@ struct Menu_view::Root_widget : Widget
_update_children(node);
if (Widget *child = _children.first())
child->geometry(Rect(Point(0, 0), child->min_size()));
_children.for_each([&] (Widget &child) {
child.geometry(Rect(Point(0, 0), child.min_size())); });
}
Area min_size() const override
{
if (Widget const * const child = _children.first())
return child->min_size();
Area result(1, 1);
return Area(1, 1);
_children.for_each([&] (Widget const &child) {
result = child.min_size(); });
return result;
}
void draw(Surface<Pixel_rgb888> &pixel_surface,
@ -72,10 +76,10 @@ struct Menu_view::Root_widget : Widget
void _layout() override
{
if (Widget *child = _children.first()) {
child->size(geometry().area());
child->position(Point(0, 0));
}
_children.for_each([&] (Widget &child) {
child.size(geometry().area());
child.position(Point(0, 0));
});
}
};

View File

@ -16,10 +16,10 @@
/* Genode includes */
#include <util/xml_generator.h>
#include <util/list_model.h>
/* local includes */
#include <widget_factory.h>
#include <list_model_from_xml.h>
#include <animated_geometry.h>
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:
@ -99,9 +99,9 @@ class Menu_view::Widget : public List<Widget>::Element
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;
@ -129,15 +129,15 @@ class Menu_view::Widget : public List<Widget>::Element
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,
Surface<Pixel_alpha8> &alpha_surface,
Point at) const
{
for (Widget const *w = _children.first(); w; w = w->next())
w->draw(pixel_surface, alpha_surface, at + w->_animated_geometry.p1());
_children.for_each([&] (Widget const &w) {
w.draw(pixel_surface, alpha_surface, at + w._animated_geometry.p1()); });
}
virtual void _layout() { }
@ -192,10 +192,7 @@ class Menu_view::Widget : public List<Widget>::Element
virtual ~Widget()
{
while (Widget *w = _children.first()) {
_children.remove(w);
_model_update_policy.destroy_element(*w);
}
_children.destroy_all_elements(_model_update_policy);
}
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))
return Unique_id();
for (Widget const *w = _children.first(); w; w = w->next()) {
Unique_id res = w->hovered(at - w->geometry().p1());
if (res.valid())
return res;
}
Unique_id result { };
_children.for_each([&] (Widget const &w) {
Unique_id const id = w.hovered(at - w.geometry().p1());
if (id.valid())
result = id;
});
return _unique_id;
}
@ -256,9 +254,8 @@ class Menu_view::Widget : public List<Widget>::Element
xml.attribute("width", geometry().w());
xml.attribute("height", geometry().h());
for (Widget const *w = _children.first(); w; w = w->next()) {
w->gen_hover_model(xml, at - w->geometry().p1());
}
_children.for_each([&] (Widget const &w) {
w.gen_hover_model(xml, at - w.geometry().p1()); });
});
}
}