/* * \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 namespace Window_layouter { class Target_list; } class Window_layouter::Target_list { private: Allocator &_alloc; Registry > _targets { }; /* * Keep information of the rules internally to reproduce this * information in 'gen_screens'. */ Constructible _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(_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 * node. Subseqent nodes are ignored. The node may * contain any number of nodes. Each node may contain * any number of nodes, which, in turn, can contain * nodes. */ void update_from_xml(Xml_node rules, Area screen_size) { _targets.for_each([&] (Registered &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(_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 void for_each(FN const &fn) const { _targets.for_each(fn); } }; #endif /* _TARGET_LIST_H_ */