263 lines
7.0 KiB
C++
263 lines
7.0 KiB
C++
/*
|
|
* \brief List of target areas where windows may be placed
|
|
* \author Norman Feske
|
|
* \date 2018-09-26
|
|
*/
|
|
|
|
/*
|
|
* Copyright (C) 2018 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 _TARGET_LIST_H_
|
|
#define _TARGET_LIST_H_
|
|
|
|
/* local includes */
|
|
#include <target.h>
|
|
|
|
namespace Window_layouter { class Target_list; }
|
|
|
|
|
|
class Window_layouter::Target_list
|
|
{
|
|
private:
|
|
|
|
Allocator &_alloc;
|
|
|
|
Registry<Registered<Target> > _targets { };
|
|
|
|
/*
|
|
* Keep information of the rules internally to reproduce this
|
|
* information in 'gen_screens'.
|
|
*/
|
|
Constructible<Buffered_xml> _rules { };
|
|
|
|
/**
|
|
* Calculate layout and populate '_targets'
|
|
*
|
|
* \param row true of 'node' is a row, false if 'node' is a column
|
|
*/
|
|
void _process_rec(Xml_node node, Rect avail, bool row)
|
|
{
|
|
unsigned long const avail_px = row ? avail.w() : avail.h();
|
|
|
|
char const *sub_node_type = row ? "column" : "row";
|
|
char const *px_size_attr = row ? "width" : "height";
|
|
|
|
unsigned long px_pos = row ? avail.x1() : avail.y1();
|
|
|
|
unsigned long const default_weight = 1;
|
|
|
|
/*
|
|
* Determinine space reserved in pixels, the total weight, and
|
|
* number of weighted rows/columns.
|
|
*/
|
|
unsigned long preserved_pixels = 0;
|
|
unsigned long total_weight = 0;
|
|
unsigned long num_weighted = 0;
|
|
|
|
/* ignore weight if pixel size is provided */
|
|
auto weight_attr_value = [&] (Xml_node node) {
|
|
return node.has_attribute(px_size_attr)
|
|
? 0 : node.attribute_value("weight", default_weight); };
|
|
|
|
node.for_each_sub_node(sub_node_type, [&] (Xml_node child) {
|
|
preserved_pixels += child.attribute_value(px_size_attr, 0UL);
|
|
total_weight += weight_attr_value(child);
|
|
num_weighted += child.has_attribute(px_size_attr) ? 0 : 1;
|
|
});
|
|
|
|
if (preserved_pixels > avail_px) {
|
|
warning("layout does not fit in available area ", avail_px, ":", node);
|
|
return;
|
|
}
|
|
|
|
/* amount of pixels we can use for weighed columns */
|
|
unsigned long const weigthed_avail = avail_px - preserved_pixels;
|
|
|
|
/*
|
|
* Calculate positions
|
|
*/
|
|
unsigned long count_weighted = 0;
|
|
unsigned long used_weighted = 0;
|
|
node.for_each_sub_node(sub_node_type, [&] (Xml_node child) {
|
|
|
|
auto calc_px_size = [&] () {
|
|
|
|
unsigned long const px = child.attribute_value(px_size_attr, 0UL);
|
|
if (px)
|
|
return px;
|
|
|
|
unsigned long const weight = weight_attr_value(child);
|
|
if (weight && total_weight)
|
|
return (((weight << 16)*weigthed_avail)/total_weight) >> 16;
|
|
|
|
return 0UL;
|
|
};
|
|
|
|
bool const weighted = !child.has_attribute(px_size_attr);
|
|
|
|
if (weighted)
|
|
count_weighted++;
|
|
|
|
/* true if target is the last weigthed column or row */
|
|
bool const last_weighted = weighted
|
|
&& (count_weighted == num_weighted);
|
|
|
|
unsigned long const px_size = last_weighted
|
|
? (weigthed_avail - used_weighted)
|
|
: calc_px_size();
|
|
|
|
if (weighted)
|
|
used_weighted += px_size;
|
|
|
|
Rect const sub_rect = row ? Rect(Point(px_pos, avail.y1()),
|
|
Area(px_size, avail.h()))
|
|
: Rect(Point(avail.x1(), px_pos),
|
|
Area(avail.w(), px_size));
|
|
|
|
_process_rec(child, sub_rect, !row);
|
|
|
|
if (child.attribute_value("name", Target::Name()).valid())
|
|
new (_alloc)
|
|
Registered<Target>(_targets, child, sub_rect);
|
|
|
|
px_pos += px_size;
|
|
});
|
|
}
|
|
|
|
static constexpr unsigned MAX_LAYER = 9999;
|
|
|
|
/**
|
|
* Generate windows for the top-most layer, starting at 'min_layer'
|
|
*
|
|
* \return layer that was processed by the method
|
|
*/
|
|
unsigned _gen_top_most_layer(Xml_generator &xml, unsigned min_layer,
|
|
Assign_list const &assignments) const
|
|
{
|
|
/* search targets for next matching layer */
|
|
unsigned layer = MAX_LAYER;
|
|
_targets.for_each([&] (Target const &target) {
|
|
if (target.layer() >= min_layer && target.layer() <= layer)
|
|
layer = target.layer(); });
|
|
|
|
/* visit all windows on the layer */
|
|
assignments.for_each([&] (Assign const &assign) {
|
|
|
|
Target::Name const target_name = assign.target_name();
|
|
|
|
/* search target by name */
|
|
_targets.for_each([&] (Target const &target) {
|
|
|
|
if (target.name() != target_name)
|
|
return;
|
|
|
|
if (target.layer() != layer)
|
|
return;
|
|
|
|
/* found target area, iterate though all assigned windows */
|
|
assign.for_each_member([&] (Assign::Member const &member) {
|
|
member.window.generate(xml); });
|
|
});
|
|
});
|
|
|
|
return layer;
|
|
}
|
|
|
|
public:
|
|
|
|
Target_list(Allocator &alloc) : _alloc(alloc) { }
|
|
|
|
/*
|
|
* The 'rules' XML node is expected to contain at least one <screen>
|
|
* node. Subseqent <screen> nodes are ignored. The <screen> node may
|
|
* contain any number of <column> nodes. Each <column> node may contain
|
|
* any number of <row> nodes, which, in turn, can contain <column>
|
|
* nodes.
|
|
*/
|
|
void update_from_xml(Xml_node rules, Area screen_size)
|
|
{
|
|
_targets.for_each([&] (Registered<Target> &target) {
|
|
destroy(_alloc, &target); });
|
|
|
|
_rules.construct(_alloc, rules);
|
|
|
|
if (!rules.has_sub_node("screen"))
|
|
return;
|
|
|
|
Xml_node const screen = rules.sub_node("screen");
|
|
|
|
Rect const avail(Point(0, 0), screen_size);
|
|
|
|
if (screen.attribute_value("name", Target::Name()).valid())
|
|
new (_alloc)
|
|
Registered<Target>(_targets, screen, avail);
|
|
|
|
_process_rec(screen, avail, true);
|
|
}
|
|
|
|
void gen_layout(Xml_generator &xml, Assign_list const &assignments) const
|
|
{
|
|
unsigned min_layer = 0;
|
|
|
|
/* iterate over layers, starting at top-most layer (0) */
|
|
for (;;) {
|
|
|
|
unsigned const layer =
|
|
_gen_top_most_layer(xml, min_layer, assignments);
|
|
|
|
if (layer == MAX_LAYER)
|
|
break;
|
|
|
|
/* skip layer in next iteration */
|
|
min_layer = layer + 1;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generate screen-layout definitions for the 'rules' report
|
|
*
|
|
* If a valid 'screen_name' is specified, move the referred screen in
|
|
* front of all others.
|
|
*/
|
|
void gen_screens(Xml_generator &xml, Target::Name const &screen_name) const
|
|
{
|
|
if (!_rules.constructed())
|
|
return;
|
|
|
|
_rules->xml().for_each_sub_node("screen", [&] (Xml_node screen) {
|
|
if (screen_name.valid()) {
|
|
Target::Name const name =
|
|
screen.attribute_value("name", Target::Name());
|
|
|
|
if (screen_name != name)
|
|
return;
|
|
}
|
|
screen.with_raw_node([&] (char const *start, size_t length) {
|
|
xml.append(start, length); });
|
|
xml.append("\n");
|
|
});
|
|
|
|
if (!screen_name.valid())
|
|
return;
|
|
|
|
_rules->xml().for_each_sub_node("screen", [&] (Xml_node screen) {
|
|
Target::Name const name = screen.attribute_value("name", Target::Name());
|
|
if (screen_name == name)
|
|
return;
|
|
|
|
screen.with_raw_node([&] (char const *start, size_t length) {
|
|
xml.append(start, length); });
|
|
xml.append("\n");
|
|
});
|
|
}
|
|
|
|
template <typename FN>
|
|
void for_each(FN const &fn) const { _targets.for_each(fn); }
|
|
};
|
|
|
|
#endif /* _TARGET_LIST_H_ */
|