mirror of
https://github.com/genodelabs/genode.git
synced 2025-02-27 03:38:32 +00:00
menu_view: depgraph widget
The new <depgraph> widget arranges child widgets in the form of a dependency graph.
This commit is contained in:
parent
f073c51b49
commit
cdebd0a994
665
repos/gems/src/app/menu_view/depgraph_widget.h
Normal file
665
repos/gems/src/app/menu_view/depgraph_widget.h
Normal file
@ -0,0 +1,665 @@
|
|||||||
|
/*
|
||||||
|
* \brief Widget that organizes child widgets as a directed graph
|
||||||
|
* \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 _DEPGRAPH_WIDGET_H_
|
||||||
|
#define _DEPGRAPH_WIDGET_H_
|
||||||
|
|
||||||
|
/* Genode includes */
|
||||||
|
#include <base/registry.h>
|
||||||
|
|
||||||
|
/* gems includes */
|
||||||
|
#include <polygon_gfx/line_painter.h>
|
||||||
|
|
||||||
|
/* local includes */
|
||||||
|
#include "widget.h"
|
||||||
|
|
||||||
|
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 };
|
||||||
|
Value value;
|
||||||
|
bool horizontal() const { return value == EAST || value == WEST; }
|
||||||
|
|
||||||
|
} _depth_direction { Depth_direction::EAST };
|
||||||
|
|
||||||
|
struct Node
|
||||||
|
{
|
||||||
|
Allocator &_alloc;
|
||||||
|
Widget &_widget;
|
||||||
|
|
||||||
|
struct Anchor
|
||||||
|
{
|
||||||
|
Node &_remote;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dependency type
|
||||||
|
*
|
||||||
|
* Primary dependency nodes define the topology of the tree
|
||||||
|
* whereas secondary dependencies are weaker links that are
|
||||||
|
* displayed but ignored for the topology.
|
||||||
|
*/
|
||||||
|
enum Type { PRIMARY, SECONDARY } const _type;
|
||||||
|
|
||||||
|
Anchor(Node &remote, Type type) : _remote(remote), _type(type) { }
|
||||||
|
|
||||||
|
virtual ~Anchor() { }
|
||||||
|
|
||||||
|
bool primary() const { return _type == PRIMARY; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return breadth position of the anchored component
|
||||||
|
*
|
||||||
|
* The returned value is used to compute the order of anchors
|
||||||
|
* along the node edges.
|
||||||
|
*/
|
||||||
|
int remote_centered_breadth_pos(Depth_direction dir) const
|
||||||
|
{
|
||||||
|
return _remote.centered_breadth_pos(dir);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Registry<Registered<Anchor> > _server_anchors;
|
||||||
|
Registry<Registered<Anchor> > _client_anchors;
|
||||||
|
|
||||||
|
struct Dependency
|
||||||
|
{
|
||||||
|
Anchor::Type const _type;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Dependencies are marked as out of date at the beginning of the
|
||||||
|
* update procedure. Each dependency visited during the update is
|
||||||
|
* marked as up-to-date. The remaining out-of-date dependencies
|
||||||
|
* are stale and must be destroyed.
|
||||||
|
*/
|
||||||
|
bool up_to_date = true;
|
||||||
|
|
||||||
|
Node &_server;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Connection points at both ends of the dependency
|
||||||
|
*/
|
||||||
|
Registered<Anchor> _anchor_at_server;
|
||||||
|
Registered<Anchor> _anchor_at_client;
|
||||||
|
|
||||||
|
Dependency(Node &client, Node &server, Anchor::Type type)
|
||||||
|
:
|
||||||
|
_type(type), _server(server),
|
||||||
|
_anchor_at_server(server._server_anchors, client, type),
|
||||||
|
_anchor_at_client(client._client_anchors, server, type)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
virtual ~Dependency() { }
|
||||||
|
|
||||||
|
bool depends_on(Node const &n) const { return &_server == &n; }
|
||||||
|
|
||||||
|
unsigned server_depth_pos(Depth_direction dir) const
|
||||||
|
{
|
||||||
|
return _server.depth_pos(dir) + _server.depth_size(dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned server_breadth_pos(Depth_direction dir) const
|
||||||
|
{
|
||||||
|
return _server.breadth_pos(dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned server_breadth_alignment(Depth_direction dir) const
|
||||||
|
{
|
||||||
|
unsigned const children_size = _server.layout_breadth_child_offset;
|
||||||
|
unsigned const total_size = _server.breadth_size(dir);
|
||||||
|
|
||||||
|
return children_size < total_size ? (total_size - children_size)/2 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool primary() const { return _type == Anchor::PRIMARY; }
|
||||||
|
|
||||||
|
template <typename FN>
|
||||||
|
void apply_to_server(FN const &fn) { fn(_server); }
|
||||||
|
|
||||||
|
template <typename FN>
|
||||||
|
void apply_to_server(FN const &fn) const { fn(_server); }
|
||||||
|
};
|
||||||
|
|
||||||
|
Registry<Registered<Dependency> > _deps;
|
||||||
|
|
||||||
|
void cut_dependencies()
|
||||||
|
{
|
||||||
|
_deps.for_each([&] (Dependency &dep) {
|
||||||
|
destroy(_alloc, &dep); });
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Helper variable for calculating the layout of dependent (child) nodes
|
||||||
|
*
|
||||||
|
* This variable tracks the offset of the last visited child node.
|
||||||
|
* During the layouting procedure, it gets successively increased.
|
||||||
|
*/
|
||||||
|
unsigned layout_breadth_child_offset = 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Breadth position relative to the node's primary dependency node.
|
||||||
|
*/
|
||||||
|
unsigned layout_breadth_offset = 0;
|
||||||
|
|
||||||
|
Node(Allocator &alloc, Widget &widget) : _alloc(alloc), _widget(widget) { }
|
||||||
|
|
||||||
|
template <typename FN>
|
||||||
|
void for_each_dependent_node(FN const &fn)
|
||||||
|
{
|
||||||
|
_server_anchors.for_each([&] (Anchor &anchor) { fn(anchor._remote); });
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual ~Node() { cut_dependencies(); }
|
||||||
|
|
||||||
|
bool has_deps() const
|
||||||
|
{
|
||||||
|
bool result = false;
|
||||||
|
_deps.for_each([&] (Dependency const &) { result = true; });
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool belongs_to(Widget const &w) { return &_widget == &w; }
|
||||||
|
|
||||||
|
bool has_name(Name const &name) const { return _widget.has_name(name); }
|
||||||
|
|
||||||
|
unsigned depth_size(Depth_direction dir) const
|
||||||
|
{
|
||||||
|
return dir.horizontal() ? _widget.min_size().w() : _widget.min_size().h();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accumulate breadth of all clients of the node
|
||||||
|
*/
|
||||||
|
unsigned _breadth_clients_size(Depth_direction dir) const
|
||||||
|
{
|
||||||
|
unsigned sum_clients_size = 0;
|
||||||
|
|
||||||
|
_server_anchors.for_each([&] (Anchor const &anchor) {
|
||||||
|
if (anchor.primary())
|
||||||
|
sum_clients_size += anchor._remote.breadth_size(dir); });
|
||||||
|
|
||||||
|
return sum_clients_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return breadth of the node, including the widget and all children
|
||||||
|
*/
|
||||||
|
unsigned breadth_size(Depth_direction dir) const
|
||||||
|
{
|
||||||
|
unsigned const widget_size =
|
||||||
|
dir.horizontal() ? _widget.min_size().h() : _widget.min_size().w();
|
||||||
|
|
||||||
|
unsigned const breadth_padding = 10;
|
||||||
|
|
||||||
|
return max(widget_size + breadth_padding, _breadth_clients_size(dir));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return 'depth' position of node within the dependency tree
|
||||||
|
*/
|
||||||
|
unsigned depth_pos(Depth_direction dir) const
|
||||||
|
{
|
||||||
|
/* maximum depth position of all nodes we depend on */
|
||||||
|
unsigned max_deps_depth = 0;
|
||||||
|
_deps.for_each([&] (Dependency const &dep) {
|
||||||
|
max_deps_depth = max(max_deps_depth, dep.server_depth_pos(dir)); });
|
||||||
|
|
||||||
|
unsigned depth_padding = 10;
|
||||||
|
return max_deps_depth + depth_padding;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return breadth position of our primary dependency (parent) node
|
||||||
|
* within the dependency tree
|
||||||
|
*/
|
||||||
|
unsigned _primary_dep_breadth_pos(Depth_direction dir) const
|
||||||
|
{
|
||||||
|
unsigned result = 0;
|
||||||
|
_deps.for_each([&] (Registered<Dependency> const &dep) {
|
||||||
|
if (dep.primary()) {
|
||||||
|
result = dep.server_breadth_pos(dir)
|
||||||
|
+ dep.server_breadth_alignment(dir); } });
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return absolute 'breadth' position of node within the dependency tree
|
||||||
|
*
|
||||||
|
* This method relies on the prior computed 'layout_breadth_offset'
|
||||||
|
* values (of this and its chain of primary dependency nodes).
|
||||||
|
*/
|
||||||
|
unsigned breadth_pos(Depth_direction dir) const
|
||||||
|
{
|
||||||
|
return _primary_dep_breadth_pos(dir) + layout_breadth_offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
void mark_deps_as_out_of_date()
|
||||||
|
{
|
||||||
|
_deps.for_each([&] (Dependency &dep) { dep.up_to_date = false; });
|
||||||
|
}
|
||||||
|
|
||||||
|
void depends_on(Node &node, Anchor::Type type)
|
||||||
|
{
|
||||||
|
bool dependency_exists = false;
|
||||||
|
_deps.for_each([&] (Dependency &dep) {
|
||||||
|
if (dep.depends_on(node)) {
|
||||||
|
dep.up_to_date = true; /* skip in 'destroy_stale_deps' */
|
||||||
|
dependency_exists = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!dependency_exists)
|
||||||
|
new (_alloc) Registered<Dependency>(_deps, *this, node, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
void destroy_stale_deps()
|
||||||
|
{
|
||||||
|
_deps.for_each([&] (Registered<Dependency> &dep) {
|
||||||
|
if (!dep.up_to_date)
|
||||||
|
destroy(_alloc, &dep); });
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename FN>
|
||||||
|
bool apply_to_primary_dependency(FN &fn)
|
||||||
|
{
|
||||||
|
bool result = false;
|
||||||
|
_deps.for_each([&] (Registered<Dependency> &dep) {
|
||||||
|
if (dep.primary()) {
|
||||||
|
dep.apply_to_server(fn);
|
||||||
|
result = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
int centered_breadth_pos(Depth_direction dir) const
|
||||||
|
{
|
||||||
|
return dir.horizontal() ? (_widget.geometry.y1() + _widget.geometry.y2()) / 2
|
||||||
|
: (_widget.geometry.x1() + _widget.geometry.x2()) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned _edge_size(Depth_direction dir) const
|
||||||
|
{
|
||||||
|
if (dir.horizontal())
|
||||||
|
return max(0, (int)_widget.geometry.h() - (int)_widget.margin.top
|
||||||
|
- (int)_widget.margin.bottom);
|
||||||
|
else
|
||||||
|
return max(0, (int)_widget.geometry.w() - (int)_widget.margin.left
|
||||||
|
- (int)_widget.margin.right);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return position of connection point at node edge
|
||||||
|
*/
|
||||||
|
unsigned _edge_pos(Registry<Registered<Anchor> > const &anchors,
|
||||||
|
Node const &client, Depth_direction dir) const
|
||||||
|
{
|
||||||
|
int const client_pos = client.centered_breadth_pos(dir);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Count number of anchors lower than the client-node position and
|
||||||
|
* the total number of clients. The anchor points are positioned
|
||||||
|
* along the widget edge in the order of the client positions to
|
||||||
|
* avoid intersecting dependency lines.
|
||||||
|
*/
|
||||||
|
int lower_cnt = 0, total_cnt = 0;
|
||||||
|
anchors.for_each([&] (Anchor const &anchor) {
|
||||||
|
total_cnt++;
|
||||||
|
if (anchor.remote_centered_breadth_pos(dir) < client_pos)
|
||||||
|
lower_cnt++;
|
||||||
|
});
|
||||||
|
|
||||||
|
return ((lower_cnt + 1)*_edge_size(dir)) / (total_cnt + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
Point server_anchor_point(Node const &client, Depth_direction dir) const
|
||||||
|
{
|
||||||
|
int const pos = _edge_pos(_server_anchors, client, dir);
|
||||||
|
Rect const edges = _widget.edges();
|
||||||
|
|
||||||
|
switch (dir.value) {
|
||||||
|
case Depth_direction::EAST: return Point(edges.x2(), edges.y1() + pos);
|
||||||
|
case Depth_direction::WEST: return Point(edges.x1(), edges.y1() + pos);
|
||||||
|
case Depth_direction::NORTH: return Point(edges.x1() + pos, edges.y1());
|
||||||
|
case Depth_direction::SOUTH: return Point(edges.x1() + pos, edges.y2());
|
||||||
|
}
|
||||||
|
return Point(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
Point client_anchor_point(Node const &client, Depth_direction dir) const
|
||||||
|
{
|
||||||
|
int const pos = _edge_pos(_client_anchors, client, dir);
|
||||||
|
Rect const edges = _widget.edges();
|
||||||
|
|
||||||
|
switch (dir.value) {
|
||||||
|
case Depth_direction::EAST: return Point(edges.x1(), edges.y1() + pos);
|
||||||
|
case Depth_direction::WEST: return Point(edges.x2(), edges.y1() + pos);
|
||||||
|
case Depth_direction::NORTH: return Point(edges.x1() + pos, edges.y2());
|
||||||
|
case Depth_direction::SOUTH: return Point(edges.x1() + pos, edges.y1());
|
||||||
|
}
|
||||||
|
return Point(0, 0);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef Registered<Node> Registered_node;
|
||||||
|
typedef Registry<Registered_node> Node_registry;
|
||||||
|
|
||||||
|
Node_registry _nodes;
|
||||||
|
|
||||||
|
Registered_node _root_node { _nodes, _factory.alloc, *this };
|
||||||
|
|
||||||
|
template <typename FN>
|
||||||
|
void apply_to_primary_dependency(Node &node, FN const &fn)
|
||||||
|
{
|
||||||
|
if (node.apply_to_primary_dependency(fn))
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* node has no primary dependency defined, use root node */
|
||||||
|
fn(_root_node);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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>
|
||||||
|
{
|
||||||
|
Widget::Model_update_policy &_generic_model_update_policy;
|
||||||
|
Allocator &_alloc;
|
||||||
|
Node_registry &_nodes;
|
||||||
|
|
||||||
|
Model_update_policy(Widget::Model_update_policy &policy,
|
||||||
|
Allocator &alloc, Node_registry &nodes)
|
||||||
|
:
|
||||||
|
_generic_model_update_policy(policy), _alloc(alloc), _nodes(nodes)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
void _destroy_node(Registered_node &node)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* If a server node vanishes, disconnect all client nodes. The
|
||||||
|
* nodes will be reconnected - if possible - after the model
|
||||||
|
* update.
|
||||||
|
*/
|
||||||
|
node.for_each_dependent_node([&] (Node &dependent) {
|
||||||
|
dependent.cut_dependencies(); });
|
||||||
|
|
||||||
|
Widget &w = node._widget;
|
||||||
|
destroy(_alloc, &node);
|
||||||
|
_generic_model_update_policy.destroy_element(w);
|
||||||
|
}
|
||||||
|
|
||||||
|
void destroy_element(Widget &w)
|
||||||
|
{
|
||||||
|
_nodes.for_each([&] (Registered_node &node) {
|
||||||
|
if (node.belongs_to(w))
|
||||||
|
_destroy_node(node); });
|
||||||
|
}
|
||||||
|
|
||||||
|
/* do not import <dep> nodes as widgets */
|
||||||
|
bool node_is_element(Xml_node node) { return !node.has_type("dep"); }
|
||||||
|
|
||||||
|
Widget &create_element(Xml_node elem_node)
|
||||||
|
{
|
||||||
|
Widget &w = _generic_model_update_policy.create_element(elem_node);
|
||||||
|
new (_alloc) Registered_node(_nodes, _alloc, w);
|
||||||
|
return w;
|
||||||
|
}
|
||||||
|
|
||||||
|
void update_element(Widget &w, Xml_node elem_node)
|
||||||
|
{
|
||||||
|
_generic_model_update_policy.update_element(w, elem_node);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool element_matches_xml_node(Widget const &w, Xml_node node)
|
||||||
|
{
|
||||||
|
return Widget::Model_update_policy::element_matches_xml_node(w, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
} _model_update_policy { Widget::_model_update_policy, _factory.alloc, _nodes };
|
||||||
|
|
||||||
|
Depgraph_widget(Widget_factory &factory, Xml_node node, Unique_id unique_id)
|
||||||
|
:
|
||||||
|
Widget(factory, node, unique_id)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
~Depgraph_widget()
|
||||||
|
{
|
||||||
|
while (Widget *w = _children.first()) {
|
||||||
|
_children.remove(w);
|
||||||
|
_model_update_policy.destroy_element(*w);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void update(Xml_node node) override
|
||||||
|
{
|
||||||
|
/* update depth direction */
|
||||||
|
{
|
||||||
|
typedef String<10> Dir_name;
|
||||||
|
Dir_name dir_name = node.attribute_value("direction", Dir_name());
|
||||||
|
_depth_direction = { Depth_direction::EAST };
|
||||||
|
if (dir_name == "north") _depth_direction = { Depth_direction::NORTH };
|
||||||
|
if (dir_name == "south") _depth_direction = { Depth_direction::SOUTH };
|
||||||
|
if (dir_name == "east") _depth_direction = { Depth_direction::EAST };
|
||||||
|
if (dir_name == "west") _depth_direction = { Depth_direction::WEST };
|
||||||
|
}
|
||||||
|
|
||||||
|
update_list_model_from_xml(_model_update_policy, _children, node);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Import dependencies
|
||||||
|
*/
|
||||||
|
_nodes.for_each([&] (Node &node) {
|
||||||
|
node.mark_deps_as_out_of_date(); });
|
||||||
|
|
||||||
|
node.for_each_sub_node([&] (Xml_node node) {
|
||||||
|
|
||||||
|
bool const primary = !node.has_type("dep");
|
||||||
|
|
||||||
|
typedef String<64> Node_name;
|
||||||
|
Node_name client_name, server_name;
|
||||||
|
if (primary) {
|
||||||
|
client_name = node.attribute_value("name", Node_name());
|
||||||
|
server_name = node.attribute_value("dep", Node_name());
|
||||||
|
} else {
|
||||||
|
client_name = node.attribute_value("node", Node_name());
|
||||||
|
server_name = node.attribute_value("on", Node_name());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!server_name.valid())
|
||||||
|
return;
|
||||||
|
|
||||||
|
Node *client = nullptr, *server = nullptr;
|
||||||
|
_nodes.for_each([&] (Node &node) {
|
||||||
|
if (node.has_name(client_name)) client = &node;
|
||||||
|
if (node.has_name(server_name)) server = &node;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (client && server && client != server)
|
||||||
|
client->depends_on(*server, primary ? Node::Anchor::PRIMARY
|
||||||
|
: Node::Anchor::SECONDARY);
|
||||||
|
if (client && !server) {
|
||||||
|
warning("node '", client_name, "' depends on "
|
||||||
|
"non-existing node '", server_name, "'");
|
||||||
|
client->_widget.geometry = Rect(Point(0, 0), Area(0, 0));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
_nodes.for_each([&] (Node &node) {
|
||||||
|
node.destroy_stale_deps(); });
|
||||||
|
|
||||||
|
_nodes.for_each([&] (Node &node) {
|
||||||
|
node.layout_breadth_child_offset = 0; });
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Compute 'layout_breadth_offset' values of all nodes
|
||||||
|
*
|
||||||
|
* The computation depends on the order of '_children'.
|
||||||
|
*/
|
||||||
|
for (Widget *w = _children.first(); w; w = w->next()) {
|
||||||
|
_nodes.for_each([&] (Registered_node &node) {
|
||||||
|
if (!node.belongs_to(*w))
|
||||||
|
return;
|
||||||
|
|
||||||
|
apply_to_primary_dependency(node, [&] (Node &parent) {
|
||||||
|
|
||||||
|
node.layout_breadth_offset = parent.layout_breadth_child_offset;
|
||||||
|
|
||||||
|
/* advance breadth offset at parent by size of current node */
|
||||||
|
parent.layout_breadth_child_offset +=
|
||||||
|
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()) {
|
||||||
|
|
||||||
|
_nodes.for_each([&] (Registered_node &node) {
|
||||||
|
if (!node.belongs_to(*w))
|
||||||
|
return;
|
||||||
|
|
||||||
|
int const depth_pos = node.depth_pos(_depth_direction),
|
||||||
|
breadth_pos = node.breadth_pos(_depth_direction),
|
||||||
|
depth_size = node.depth_size(_depth_direction),
|
||||||
|
breadth_size = node.breadth_size(_depth_direction);
|
||||||
|
|
||||||
|
Rect const node_rect = _depth_direction.horizontal()
|
||||||
|
? Rect(Point(depth_pos, breadth_pos),
|
||||||
|
Area(depth_size, breadth_size))
|
||||||
|
: Rect(Point(breadth_pos, depth_pos),
|
||||||
|
Area(breadth_size, depth_size));
|
||||||
|
|
||||||
|
w->geometry = Rect(node_rect.center(w->min_size()), w->min_size());
|
||||||
|
|
||||||
|
bounding_box = Rect::compound(bounding_box, w->geometry);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Mirror coordinates if graph grows towards north or west
|
||||||
|
*/
|
||||||
|
if (_depth_direction.value == Depth_direction::NORTH
|
||||||
|
|| _depth_direction.value == Depth_direction::WEST) {
|
||||||
|
|
||||||
|
for (Widget *w = _children.first(); w; w = w->next()) {
|
||||||
|
|
||||||
|
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; }
|
||||||
|
|
||||||
|
void _draw_connect(Surface<Pixel_rgb888> &pixel_surface,
|
||||||
|
Surface<Pixel_alpha8> &alpha_surface,
|
||||||
|
Point p1, Point p2, Color color, bool horizontal) const
|
||||||
|
{
|
||||||
|
Line_painter line_painter;
|
||||||
|
|
||||||
|
auto draw_segment = [&] (long x1, long y1, long x2, long y2)
|
||||||
|
{
|
||||||
|
auto const fx1 = Line_painter::Fixpoint::from_raw(x1),
|
||||||
|
fy1 = Line_painter::Fixpoint::from_raw(y1),
|
||||||
|
fx2 = Line_painter::Fixpoint::from_raw(x2),
|
||||||
|
fy2 = Line_painter::Fixpoint::from_raw(y2);
|
||||||
|
|
||||||
|
line_painter.paint(pixel_surface, fx1, fy1, fx2, fy2, color);
|
||||||
|
line_painter.paint(alpha_surface, fx1, fy1, fx2, fy2, color);
|
||||||
|
};
|
||||||
|
|
||||||
|
long const mid_x = (p1.x() + p2.x()) / 2,
|
||||||
|
mid_y = (p1.y() + p2.y()) / 2;
|
||||||
|
|
||||||
|
long const x1 = p1.x(),
|
||||||
|
y1 = p1.y(),
|
||||||
|
x2 = horizontal ? mid_x : p1.x(),
|
||||||
|
y2 = horizontal ? p1.y() : mid_y,
|
||||||
|
x3 = horizontal ? mid_x : p2.x(),
|
||||||
|
y3 = horizontal ? p2.y() : mid_y,
|
||||||
|
x4 = p2.x(),
|
||||||
|
y4 = p2.y();
|
||||||
|
|
||||||
|
/* subdivide the curve depending on the size of its bounding box */
|
||||||
|
unsigned const levels = max(log2(max(abs(x4 - x1), abs(y4 - y1)) >> 2), 3);
|
||||||
|
|
||||||
|
bezier(x1 << 8, y1 << 8, x2 << 8, y2 << 8,
|
||||||
|
x3 << 8, y3 << 8, x4 << 8, y4 << 8, draw_segment, levels);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _draw_connections(Surface<Pixel_rgb888> &pixel_surface,
|
||||||
|
Surface<Pixel_alpha8> &alpha_surface,
|
||||||
|
Point at, bool shadow) const
|
||||||
|
{
|
||||||
|
_nodes.for_each([&] (Node const &client) {
|
||||||
|
|
||||||
|
client._deps.for_each([&] (Node::Dependency const &dep) {
|
||||||
|
|
||||||
|
Color color;
|
||||||
|
|
||||||
|
if (shadow) {
|
||||||
|
color = dep.primary() ? Color(0, 0, 0, 150)
|
||||||
|
: Color(0, 0, 0, 50);
|
||||||
|
} else {
|
||||||
|
color = dep.primary() ? Color(255, 255, 255, 190)
|
||||||
|
: Color(255, 255, 255, 120);
|
||||||
|
}
|
||||||
|
|
||||||
|
dep.apply_to_server([&] (Node const &server) {
|
||||||
|
|
||||||
|
Point from = server.server_anchor_point(client, _depth_direction);
|
||||||
|
Point to = client.client_anchor_point(server, _depth_direction);
|
||||||
|
|
||||||
|
_draw_connect(pixel_surface, alpha_surface,
|
||||||
|
at + from, at + to, color, _depth_direction.horizontal());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void draw(Surface<Pixel_rgb888> &pixel_surface,
|
||||||
|
Surface<Pixel_alpha8> &alpha_surface,
|
||||||
|
Point at) const
|
||||||
|
{
|
||||||
|
/* draw connections twice, for the shadow and actual color */
|
||||||
|
_draw_connections(pixel_surface, alpha_surface, at + Point(0, 1), true);
|
||||||
|
_draw_connections(pixel_surface, alpha_surface, at, false);
|
||||||
|
|
||||||
|
_draw_children(pixel_surface, alpha_surface, at);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _layout() override
|
||||||
|
{
|
||||||
|
for (Widget *w = _children.first(); w; w = w->next())
|
||||||
|
w->size(w->geometry.area());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* _DEPGRAPH_WIDGET_H_ */
|
@ -19,6 +19,7 @@
|
|||||||
#include "root_widget.h"
|
#include "root_widget.h"
|
||||||
#include "float_widget.h"
|
#include "float_widget.h"
|
||||||
#include "frame_widget.h"
|
#include "frame_widget.h"
|
||||||
|
#include "depgraph_widget.h"
|
||||||
|
|
||||||
/* Genode includes */
|
/* Genode includes */
|
||||||
#include <input/event.h>
|
#include <input/event.h>
|
||||||
@ -302,12 +303,13 @@ Menu_view::Widget_factory::create(Xml_node node)
|
|||||||
|
|
||||||
Widget::Unique_id const unique_id(++_unique_id_cnt);
|
Widget::Unique_id const unique_id(++_unique_id_cnt);
|
||||||
|
|
||||||
if (node.has_type("label")) w = new (alloc) Label_widget (*this, node, unique_id);
|
if (node.has_type("label")) w = new (alloc) Label_widget (*this, node, unique_id);
|
||||||
if (node.has_type("button")) w = new (alloc) Button_widget (*this, node, unique_id);
|
if (node.has_type("button")) w = new (alloc) Button_widget (*this, node, unique_id);
|
||||||
if (node.has_type("vbox")) w = new (alloc) Box_layout_widget (*this, node, unique_id);
|
if (node.has_type("vbox")) w = new (alloc) Box_layout_widget (*this, node, unique_id);
|
||||||
if (node.has_type("hbox")) w = new (alloc) Box_layout_widget (*this, node, unique_id);
|
if (node.has_type("hbox")) w = new (alloc) Box_layout_widget (*this, node, unique_id);
|
||||||
if (node.has_type("frame")) w = new (alloc) Frame_widget (*this, node, unique_id);
|
if (node.has_type("frame")) w = new (alloc) Frame_widget (*this, node, unique_id);
|
||||||
if (node.has_type("float")) w = new (alloc) Float_widget (*this, node, unique_id);
|
if (node.has_type("float")) w = new (alloc) Float_widget (*this, node, unique_id);
|
||||||
|
if (node.has_type("depgraph")) w = new (alloc) Depgraph_widget (*this, node, unique_id);
|
||||||
|
|
||||||
if (!w) {
|
if (!w) {
|
||||||
Genode::error("unknown widget type '", node.type(), "'");
|
Genode::error("unknown widget type '", node.type(), "'");
|
||||||
|
@ -158,6 +158,15 @@ class Menu_view::Widget : public List<Widget>::Element
|
|||||||
*/
|
*/
|
||||||
Rect geometry;
|
Rect geometry;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Return x/y positions of the edges of the widget with the margin
|
||||||
|
* applied
|
||||||
|
*/
|
||||||
|
Rect edges() const { return Rect(Point(geometry.x1() + margin.left,
|
||||||
|
geometry.y1() + margin.top),
|
||||||
|
Point(geometry.x2() - margin.right,
|
||||||
|
geometry.y2() - margin.bottom)); }
|
||||||
|
|
||||||
Widget(Widget_factory &factory, Xml_node node, Unique_id unique_id)
|
Widget(Widget_factory &factory, Xml_node node, Unique_id unique_id)
|
||||||
:
|
:
|
||||||
_type_name(node_type_name(node)),
|
_type_name(node_type_name(node)),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user