diff --git a/repos/gems/src/app/sculpt_manager/model/component.h b/repos/gems/src/app/sculpt_manager/model/component.h index cdbd19bf8b..7dc1fcb9b4 100644 --- a/repos/gems/src/app/sculpt_manager/model/component.h +++ b/repos/gems/src/app/sculpt_manager/model/component.h @@ -41,6 +41,7 @@ struct Sculpt::Component : Noncopyable Affinity::Location affinity_location { 0, 0, affinity_space.width(), affinity_space.height() }; + Priority priority = Priority::DEFAULT; bool blueprint_known = false; @@ -49,7 +50,9 @@ struct Sculpt::Component : Noncopyable Component(Allocator &alloc, Path const &path, Info const &info, Affinity::Space const space) - : _route_update_policy(alloc), path(path), info(info), affinity_space (space) { } + : + _route_update_policy(alloc), path(path), info(info), affinity_space(space) + { } ~Component() { @@ -76,7 +79,12 @@ struct Sculpt::Component : Noncopyable }); } - void gen_affinity_xml(Xml_generator &xml) const + void gen_priority(Xml_generator &xml) const + { + xml.attribute("priority", (int)priority); + } + + void gen_affinity(Xml_generator &xml) const { bool const all_cpus = affinity_space.width() == affinity_location.width() && affinity_space.height() == affinity_location.height(); diff --git a/repos/gems/src/app/sculpt_manager/model/runtime_state.h b/repos/gems/src/app/sculpt_manager/model/runtime_state.h index f2a24a50da..a6c87906a9 100644 --- a/repos/gems/src/app/sculpt_manager/model/runtime_state.h +++ b/repos/gems/src/app/sculpt_manager/model/runtime_state.h @@ -154,7 +154,8 @@ class Sculpt::Runtime_state : public Runtime_info if (construction.constructed()) { xml.attribute("pkg", construction->path); - construction->gen_affinity_xml(xml); + construction->gen_priority(xml); + construction->gen_affinity(xml); xml.node("route", [&] () { construction->gen_pd_cpu_route(xml); diff --git a/repos/gems/src/app/sculpt_manager/view/popup_dialog.cc b/repos/gems/src/app/sculpt_manager/view/popup_dialog.cc index b57488db18..ae0b18ad41 100644 --- a/repos/gems/src/app/sculpt_manager/view/popup_dialog.cc +++ b/repos/gems/src/app/sculpt_manager/view/popup_dialog.cc @@ -87,13 +87,15 @@ void Popup_dialog::_gen_pkg_elements(Xml_generator &xml, _pd_route.generate(xml); - if (_resources.constructed() && component.affinity_space.total() > 1) { + if (_resources.constructed()) { + xml.node("frame", [&] { xml.node("vbox", [&] () { - bool const selected = _route_selected(_resources->start_name()); + + bool const selected = _route_selected("resources"); if (!selected) - _gen_route_entry(xml, _resources->start_name(), + _gen_route_entry(xml, "resources", "Resource assignment ...", false, "enter"); if (selected) { @@ -444,6 +446,23 @@ void Popup_dialog::click(Action &action) _state = PKG_SHOWN; _selected_route.destruct(); _pd_route.reset(); + + } else if (_resource_dialog_selected()) { + + bool const clicked_on_different_route = clicked_route.valid() + && (clicked_route != ""); + if (clicked_on_different_route) { + + /* close resource dialog */ + _selected_route.construct(clicked_route); + + } else { + + if (_resources.constructed()) + action.apply_to_construction([&] (Component &component) { + _resources->click(component); }); + } + } else { bool clicked_on_selected_route = false; @@ -488,12 +507,6 @@ void Popup_dialog::click(Action &action) _selected_route.construct(clicked_route); } - if (_resources.constructed()) { - action.apply_to_construction([&] (Component &component) { - _resources->click(component); - }); - } - action.apply_to_construction([&] (Component &component) { _pd_route.click(component); }); diff --git a/repos/gems/src/app/sculpt_manager/view/popup_dialog.h b/repos/gems/src/app/sculpt_manager/view/popup_dialog.h index 033c615f2b..ebd0c1cee5 100644 --- a/repos/gems/src/app/sculpt_manager/view/popup_dialog.h +++ b/repos/gems/src/app/sculpt_manager/view/popup_dialog.h @@ -140,6 +140,11 @@ struct Sculpt::Popup_dialog : Dialog return _selected_route.constructed() && id == _selected_route->string(); } + bool _resource_dialog_selected() const + { + return _route_selected("resources"); + } + template void _apply_to_selected_route(Action &action, FN const &fn) { @@ -204,7 +209,7 @@ struct Sculpt::Popup_dialog : Dialog if (_resources.constructed() && hover_result == Dialog::Hover_result::UNMODIFIED) - return _resources->hover(hover, "frame", "vbox", "frame", "vbox"); + return _resources->match_sub_dialog(hover, "frame", "vbox", "frame", "vbox"); return hover_result; } @@ -462,9 +467,9 @@ struct Sculpt::Popup_dialog : Dialog if (_state < PKG_REQUESTED) return; - if (!_resources.constructed()) - _resources.construct(construction.affinity_space, - construction.affinity_location); + _resources.construct(construction.affinity_space, + construction.affinity_location, + construction.priority); _pkg_rom_missing = blueprint_rom_missing(blueprint, construction.path); _pkg_missing = blueprint_missing (blueprint, construction.path); diff --git a/repos/gems/src/app/sculpt_manager/view/resource_dialog.cc b/repos/gems/src/app/sculpt_manager/view/resource_dialog.cc index 24055d695e..3756152f84 100644 --- a/repos/gems/src/app/sculpt_manager/view/resource_dialog.cc +++ b/repos/gems/src/app/sculpt_manager/view/resource_dialog.cc @@ -1,11 +1,12 @@ /* * \brief Resource assignment dialog * \author Alexander Boettcher + * \author Norman Feske * \date 2020-07-22 */ /* - * Copyright (C) 2020 Genode Labs GmbH + * Copyright (C) 2020-2021 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. @@ -15,66 +16,185 @@ using namespace Sculpt; -void Resource_dialog::_gen_affinity_entry(Xml_generator &xml, - Start_name const &name) const + +void Resource_dialog::_gen_affinity_section(Xml_generator &xml) const { - gen_named_node(xml, "hbox", name, [&] () { - gen_named_node(xml, "float", "center", [&] () { - xml.attribute("north", "yes"); - xml.attribute("south", "yes"); + unsigned const CELL_WIDTH_EX = 4; - xml.node("vbox", [&] () { - if (_space.height() > 1) { - xml.node("label", [&] () { - xml.attribute("text", String<12>("Cores 0-", _space.width() - 1)); - }); - } else { - xml.node("label", [&] () { - xml.attribute("text", String<12>("CPUs 0-", _space.width() - 1)); + using Id = Hoverable_item::Id; + + auto gen_small_label = [] (Xml_generator &xml, auto id, auto fn) { + gen_named_node(xml, "label", id, [&] () { + xml.attribute("font", "annotation/regular"); + fn(); }); }; + + auto gen_cell_hspacer = [&] (Xml_generator &xml, auto id) { + gen_small_label(xml, id, [&] () { + xml.attribute("min_ex", CELL_WIDTH_EX); }); }; + + auto gen_cell_cpu = [&] (Xml_generator &xml, auto name, bool selected) + { + gen_named_node(xml, "vbox", name, [&] () { + + gen_named_node(xml, "button", name, [&] () { + xml.attribute("style", "checkbox"); + _space_item.gen_hovered_attr(xml, name); + + if (selected) + xml.attribute("selected", "yes"); + + xml.node("hbox", [&] () { }); + }); + gen_cell_hspacer(xml, "below"); + }); + }; + + auto gen_cell_number = [&] (Xml_generator &xml, Id n) + { + gen_named_node(xml, "vbox", n, [&] () { + gen_cell_hspacer(xml, "above"); + gen_named_node(xml, "float", "number", [&] () { + gen_small_label(xml, "number", [&] () { + xml.attribute("text", n); }); }); + gen_cell_hspacer(xml, "below"); + }); + }; + + auto cpu_selected = [] (Affinity::Location location, unsigned x, unsigned y) + { + return (unsigned(location.xpos()) <= x) + && (x < location.xpos() + location.width()) + && (unsigned(location.ypos()) <= y) + && (y < location.ypos() + location.height()); + }; + + _gen_dialog_section(xml, "affinity", "Affinity", [&] () { + + gen_named_node(xml, "vbox", "selection", [&] () { + + bool const have_hyperthreads = (_space.height() > 1); + + gen_named_node(xml, "hbox", "labeledcores", [&] () { + gen_named_node(xml, "vbox", "cores", [&] () { + + for (unsigned y = 0; y < _space.height(); y++) { + + gen_named_node(xml, "hbox", Id("row", y), [&] () { + + for (unsigned x = 0; x < _space.width(); x++) + gen_cell_cpu(xml, _cpu_id(x, y), + cpu_selected(_location, x, y)); + + if (have_hyperthreads) + gen_named_node(xml, "float", "number", [&] () { + gen_small_label(xml, "number", [&] () { + xml.attribute("min_ex", 2); + xml.attribute("text", y); }); }); + }); + } + }); + if (have_hyperthreads) { + gen_named_node(xml, "float", "hyperthreadslabel", [&] () { + xml.node("vbox", [&] () { + + unsigned line = 0; + + auto gen_leftaligned = [&] (auto text) { + gen_named_node(xml, "float", Id(line++), [&] () { + xml.attribute("west", "yes"); + gen_small_label(xml, "label", [&] () { + xml.attribute("text", text); }); }); }; + + gen_leftaligned("Hyper"); + gen_leftaligned("threads"); + }); }); } - for (unsigned y = 0; y < _space.height(); y++) { - bool const row = unsigned(_location.ypos()) <= y && y < _location.ypos() + _location.height(); - String<12> const row_id("row", y); + }); - gen_named_node(xml, "hbox", row_id, [&] () { - if (_space.height() > 1) { - xml.node("label", [&] () { - xml.attribute("text", String<12>("Thread ", y)); - }); - } - for (unsigned x = 0; x < _space.width(); x++) { - String<12> const name_cpu("cpu", x, "x", y); - bool const column = unsigned(_location.xpos()) <= x && x < _location.xpos() + _location.width(); - gen_named_node(xml, "button", name_cpu, [&] () { - if (row && column) - xml.attribute("selected", "yes"); + gen_named_node(xml, "float", "corelabels", [&] () { + xml.attribute("west", "yes"); + xml.node("vbox", [&] () { - xml.attribute("style", "radio"); - _space_item.gen_hovered_attr(xml, name_cpu); - xml.node("hbox", [&] () { }); - }); - } - }); - } + xml.node("hbox", [&] () { + for (unsigned x = 0; x < _space.width(); x++) + gen_cell_number(xml, x); }); + + gen_small_label(xml, "cores", [&] () { + xml.attribute("text", "Cores"); }); + }); }); }); }); } + +void Resource_dialog::_gen_priority_section(Xml_generator &xml) const +{ + _gen_dialog_section(xml, "priority", "Priority", [&] () { + + gen_named_node(xml, "vbox", "selection", [&] () { + + auto gen_radiobutton = [&] (auto id, auto text) + { + gen_named_node(xml, "hbox", id, [&] () { + + gen_named_node(xml, "float", "left", [&] () { + xml.attribute("west", "yes"); + + gen_named_node(xml, "hbox", id, [&] () { + gen_named_node(xml, "button", "button", [&] () { + + xml.attribute("style", "radio"); + _priority_item.gen_button_attr(xml, id); + xml.node("hbox", [&] () { }); + }); + gen_named_node(xml, "label", "name", [&] () { + xml.attribute("text", text); + xml.attribute("min_ex", 13); + }); + }); + }); + + gen_named_node(xml, "hbox", "right", [&] () { }); + }); + }; + + gen_radiobutton("driver", "Driver"); + gen_radiobutton("multimedia", "Multimedia"); + gen_radiobutton("default", "Default"); + gen_radiobutton("background", "Background"); + }); + + gen_named_node(xml, "hbox", "right", [&] () { }); + }); +} + + void Resource_dialog::click(Component &component) { if (component.affinity_space.total() <= 1) return; - Route::Id const clicked_space = _space_item._hovered; - if (!clicked_space.valid()) + Hoverable_item::Id const clicked_space = _space_item._hovered; + if (clicked_space.valid()) { + _click_space(component, clicked_space); return; + } - for (unsigned y = 0; y < component.affinity_space.height(); y++) { - for (unsigned x = 0; x < component.affinity_space.width(); x++) { - String<12> const name_cpu("cpu", x, "x", y); - if (name_cpu != clicked_space) + Hoverable_item::Id const clicked_priority = _priority_item._hovered; + if (clicked_priority.valid()) { + _click_priority(component, clicked_priority); + return; + } +} + + +void Resource_dialog::_click_space(Component &component, Hoverable_item::Id clicked_space) +{ + for (unsigned y = 0; y < _space.height(); y++) { + for (unsigned x = 0; x < _space.width(); x++) { + if (_cpu_id(x, y) != clicked_space) continue; unsigned loc_x = _location.xpos(); @@ -145,3 +265,22 @@ void Resource_dialog::click(Component &component) } } } + + +void Resource_dialog::_click_priority(Component &component, Hoverable_item::Id priority) +{ + _priority_item.select(priority); + + /* propagate priority change to component */ + auto priority_value = [] (Hoverable_item::Id string) + { + if (string == "background") return Priority::BACKGROUND; + if (string == "default") return Priority::DEFAULT; + if (string == "multimedia") return Priority::MULTIMEDIA; + if (string == "driver") return Priority::DRIVER; + + return Priority::BACKGROUND; + }; + + component.priority = priority_value(priority); +} diff --git a/repos/gems/src/app/sculpt_manager/view/resource_dialog.h b/repos/gems/src/app/sculpt_manager/view/resource_dialog.h index 3679ab704b..c6c80722ad 100644 --- a/repos/gems/src/app/sculpt_manager/view/resource_dialog.h +++ b/repos/gems/src/app/sculpt_manager/view/resource_dialog.h @@ -1,11 +1,12 @@ /* * \brief Resource assignment dialog * \author Alexander Boettcher + * \author Norman Feske * \date 2020-07-22 */ /* - * Copyright (C) 2020 Genode Labs GmbH + * Copyright (C) 2020-2021 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. @@ -22,6 +23,7 @@ #include #include #include +#include namespace Sculpt { struct Resource_dialog; } @@ -31,41 +33,115 @@ struct Sculpt::Resource_dialog : Noncopyable, Dialog Affinity::Space const _space; Affinity::Location _location; Hoverable_item _space_item { }; + Selectable_item _priority_item { }; - - Resource_dialog(Affinity::Space space, Affinity::Location location) - : _space(space), _location(location) - { } - - void _gen_affinity_entry(Xml_generator &, Start_name const &) const; - - Hover_result hover(Xml_node hover_node) override + static char const *_priority_id(Priority priority) { - Dialog::Hover_result const hover_result = hover(hover_node); - return hover_result; + switch (priority) { + case Priority::DRIVER: return "driver"; + case Priority::MULTIMEDIA: return "multimedia"; + case Priority::BACKGROUND: return "background"; + default: break; + }; + return "default"; } - template - Hover_result hover(Xml_node hover, ARGS &&... args) + static Hoverable_item::Id _cpu_id(unsigned x, unsigned y) { - Dialog::Hover_result const hover_result = Dialog::any_hover_changed( - _space_item.match(hover, args..., "hbox", "float", "vbox", "hbox", "button", "name")); - - return hover_result; + return Hoverable_item::Id("cpu", x, "x", y); } - Start_name start_name() const { return "cpus"; } + Resource_dialog(Affinity::Space space, Affinity::Location location, + Priority priority) + : + _space(space), _location(location) + { + _priority_item.select(_priority_id(priority)); + } + + void _gen_affinity_section(Xml_generator &) const; + void _gen_priority_section(Xml_generator &) const; + + Hover_result hover(Xml_node hover) override + { + return Dialog::any_hover_changed( + _space_item.match(hover, + "vbox", "float", "hbox", /* _gen_dialog_section */ + "vbox", "hbox", "vbox", "hbox", /* _gen_affinity_section */ + "vbox", "button", "name" /* gen_cell_cpu */ + ), + _priority_item.match(hover, + "vbox", "float", "hbox", /* _gen_dialog_section */ + "vbox", "hbox", "float", "hbox", "name")); + } void click(Component &); + void _click_space (Component &, Hoverable_item::Id); + void _click_priority(Component &, Hoverable_item::Id); + + template + void _gen_dialog_section(Xml_generator &xml, Hoverable_item::Id id, + char const *text, FN const &gen_content_fn) const + { + gen_named_node(xml, "float", id, [&] () { + xml.attribute("west", "yes"); + + xml.node("hbox", [&] () { + + gen_named_node(xml, "vbox", "title", [&] () { + xml.node("float", [&] () { + + xml.attribute("north", "yes"); + xml.attribute("west", "yes"); + + xml.node("hbox", [&] () { + + /* + * The button is used to vertically align the "Priority" + * label with the text of the first radio button. + * The leading space is used to horizontally align + * the label with the "Resource assignment ..." dialog + * title. + */ + gen_named_node(xml, "button", "spacer", [&] () { + xml.attribute("style", "invisible"); + xml.node("hbox", [&] () { }); }); + + gen_named_node(xml, "label", "label", [&] () { + xml.attribute("text", String<32>(" ", text)); }); + }); + }); + + gen_named_node(xml, "label", "spacer", [&] () { + xml.attribute("min_ex", 11); }); + }); + + gen_content_fn(); + }); + }); + } + void generate(Xml_generator &xml) const override { - _gen_affinity_entry(xml, start_name()); + auto gen_vspacer = [&] (auto id) { + gen_named_node(xml, "label", id, [&] () { + xml.attribute("text", ""); + xml.attribute("font", "annotation/regular"); }); }; + + xml.node("vbox", [&] () { + gen_vspacer("spacer1"); + _gen_affinity_section(xml); + gen_vspacer("spacer2"); + _gen_priority_section(xml); + gen_vspacer("spacer3"); + }); } void reset() override { _space_item._hovered = Hoverable_item::Id(); + _priority_item.reset(); _location = Affinity::Location(); } };