diff --git a/repos/gems/run/menu_view.run b/repos/gems/run/menu_view.run index b684798cb0..553b73cc69 100644 --- a/repos/gems/run/menu_view.run +++ b/repos/gems/run/menu_view.run @@ -309,7 +309,7 @@ install_config { - + @@ -317,10 +317,12 @@ install_config { + + - - + + @@ -330,6 +332,23 @@ install_config { build { app/menu_view } +set fd [open [run_dir]/genode/fixed_dialog w] +puts $fd { + + + + + + + + +} +close $fd + build_boot_image [build_artifacts] run_genode_until forever diff --git a/repos/gems/src/app/file_vault/sandbox.h b/repos/gems/src/app/file_vault/sandbox.h index 4cd1f676b2..12d8d6148f 100644 --- a/repos/gems/src/app/file_vault/sandbox.h +++ b/repos/gems/src/app/file_vault/sandbox.h @@ -177,6 +177,9 @@ namespace File_vault { }); }); }); + + xml.node("dialog", [&] { + xml.attribute("name", "dialog"); }); }); xml.node("route", [&] () { diff --git a/repos/gems/src/app/menu_view/dialog.h b/repos/gems/src/app/menu_view/dialog.h new file mode 100644 index 0000000000..e13c04ec9c --- /dev/null +++ b/repos/gems/src/app/menu_view/dialog.h @@ -0,0 +1,286 @@ +/* + * \brief Top-level dialog + * \author Norman Feske + * \date 2024-04-03 + */ + +/* + * Copyright (C) 2024 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 _DIALOG_H_ +#define _DIALOG_H_ + +/* Genode include */ +#include + +/* gems includes */ +#include + +/* local includes */ +#include +#include +#include + +namespace Menu_view { + + struct Dialog; + + using Dialogs = List_model; +} + + +struct Menu_view::Dialog : List_model::Element +{ + Env &_env; + + Widget_factory &_global_widget_factory; + + Animator _local_animator { }; + + Widget_factory _widget_factory { _global_widget_factory.alloc, + _global_widget_factory.styles, + _local_animator }; + + struct Action : Interface + { + virtual void trigger_redraw() = 0; + virtual void hover_changed() = 0; + virtual void observed_seq_number(Input::Seq_number) = 0; + }; + + Action &_action; + + using Name = Widget::Name; + + Name const _name; + + static Name _name_from_attr(Xml_node const &node) + { + return node.attribute_value("name", Name()); + } + + Gui::Connection _gui { _env, _name }; + + Attached_dataspace _input_ds { _env.rm(), _gui.input()->dataspace() }; + + Signal_handler _input_handler = { + _env.ep(), *this, &Dialog::_handle_input}; + + void _handle_input(); + + Constructible _buffer { }; + + Gui::Session::View_handle const _view_handle = _gui.create_view(); + + Point _position { }; + + /** + * Last pointer position at the time of the most recent hovering report, + * in screen coordinate space. + */ + Point _hovered_position { }; + + bool _hovered = false; + bool _redraw_scheduled = false; + + Area _configured_size { }; + Area _visible_size { }; + Rect _view_geometry { }; + + bool _opaque = false; + Color _background_color { }; + + Area _root_widget_size() const + { + Area const min_size = _root_widget.min_size(); + return Area(max(_configured_size.w(), min_size.w()), + max(_configured_size.h(), min_size.h())); + } + + void _update_view(Rect geometry) + { + if (_view_geometry.p1() == geometry.p1() + && _view_geometry.area() == geometry.area()) + return; + + using Command = Gui::Session::Command; + using View_handle = Gui::Session::View_handle; + + _view_geometry = geometry; + _gui.enqueue(_view_handle, _view_geometry); + _gui.enqueue(_view_handle, View_handle()); + _gui.execute(); + } + + Root_widget _root_widget { _name, Widget::Unique_id { }, _widget_factory, Xml_node("") }; + + Attached_rom_dataspace _dialog_rom { _env, _name.string() }; + + Signal_handler _dialog_handler { + _env.ep(), *this, &Dialog::_handle_dialog }; + + void _handle_dialog(); + + Dialog(Env &env, Widget_factory &widget_factory, Action &action, + Xml_node const &node) + : + _env(env), _global_widget_factory(widget_factory), _action(action), + _name(_name_from_attr(node)) + { + _dialog_rom.sigh(_dialog_handler); + _dialog_handler.local_submit(); + _gui.input()->sigh(_input_handler); + } + + Widget::Hovered hovered_widget() const + { + return _root_widget.hovered(_hovered_position); + } + + void gen_hover(Xml_generator &xml) const + { + if (_hovered) + _root_widget.gen_hover_model(xml, _hovered_position); + } + + void redraw() + { + if (!_redraw_scheduled) + return; + + Area const size = _root_widget_size(); + + unsigned const buffer_w = _buffer.constructed() ? _buffer->size().w() : 0, + buffer_h = _buffer.constructed() ? _buffer->size().h() : 0; + + Area const max_size(max(buffer_w, size.w()), max(buffer_h, size.h())); + + bool const size_increased = (max_size.w() > buffer_w) + || (max_size.h() > buffer_h); + + if (!_buffer.constructed() || size_increased) + _buffer.construct(_gui, max_size, _env.ram(), _env.rm(), + _opaque ? Gui_buffer::Alpha::OPAQUE + : Gui_buffer::Alpha::ALPHA, + _background_color); + else + _buffer->reset_surface(); + + _root_widget.position(Point(0, 0)); + + _buffer->apply_to_surface([&] (Surface &pixel, + Surface &alpha) { + _root_widget.draw(pixel, alpha, Point(0, 0)); + }); + + _buffer->flush_surface(); + _gui.framebuffer()->refresh(0, 0, _buffer->size().w(), _buffer->size().h()); + _update_view(Rect(_position, size)); + + _redraw_scheduled = false; + } + + bool hovered() const { return _hovered; } + + void animate() + { + bool const progress = _local_animator.active(); + + _local_animator.animate(); + + if (progress) + _redraw_scheduled = true; + } + + bool animation_in_progress() const { return _local_animator.active(); } + + bool redraw_scheduled() const { return _redraw_scheduled; } + + /* + * List_model + */ + + static bool type_matches(Xml_node const &node) { return node.has_type("dialog"); } + + bool matches(Xml_node const &node) const { return _name_from_attr(node) == _name; } + + void update(Xml_node const &node) + { + _position = Point::from_xml(node); + _configured_size = Area ::from_xml(node); + _opaque = node.attribute_value("opaque", false); + _background_color = node.attribute_value("background", Color(127, 127, 127, 255)); + } +}; + + +void Menu_view::Dialog::_handle_dialog() +{ + _dialog_rom.update(); + + Xml_node const dialog = _dialog_rom.xml(); + + if (dialog.has_type("empty")) + return; + + _root_widget.update(dialog); + _root_widget.size(_root_widget_size()); + + _redraw_scheduled = true; + + _action.hover_changed(); + _action.trigger_redraw(); +} + + +void Menu_view::Dialog::_handle_input() +{ + Point const orig_hovered_position = _hovered_position; + bool const orig_hovered = _hovered; + + bool seq_number_changed = false; + + _gui.input()->for_each_event([&] (Input::Event const &ev) { + + ev.handle_seq_number([&] (Input::Seq_number seq_number) { + seq_number_changed = true; + _action.observed_seq_number(seq_number); }); + + auto hover_at = [&] (int x, int y) + { + _hovered = true; + _hovered_position = Point(x, y) - _position; + }; + + auto unhover = [&] () + { + _hovered = false; + _hovered_position = Point(); + }; + + ev.handle_absolute_motion([&] (int x, int y) { + hover_at(x, y); }); + + ev.handle_touch([&] (Input::Touch_id id, float x, float y) { + if (id.value == 0) + hover_at((int)x, (int)y); }); + + /* + * Reset hover model when losing the focus + */ + if (ev.hover_leave()) + unhover(); + }); + + bool const hover_changed = orig_hovered != _hovered + || orig_hovered_position != _hovered_position; + + if (hover_changed || seq_number_changed) + _action.hover_changed(); +} + +#endif /* _DIALOG_H_ */ diff --git a/repos/gems/src/app/menu_view/main.cc b/repos/gems/src/app/menu_view/main.cc index ed97ae2505..f2f96a1a8b 100644 --- a/repos/gems/src/app/menu_view/main.cc +++ b/repos/gems/src/app/menu_view/main.cc @@ -11,90 +11,35 @@ * under the terms of the GNU Affero General Public License version 3. */ -/* local includes */ -#include "widget_factory.h" -#include "button_widget.h" -#include "label_widget.h" -#include "box_layout_widget.h" -#include "root_widget.h" -#include "float_widget.h" -#include "frame_widget.h" -#include "depgraph_widget.h" - /* Genode includes */ #include -#include #include #include #include -/* gems includes */ -#include +/* local includes */ +#include +#include +#include +#include +#include +#include +#include namespace Menu_view { struct Main; } -struct Menu_view::Main +struct Menu_view::Main : Dialog::Action { Env &_env; - Gui::Connection _gui { _env }; - - Constructible _buffer { }; - - Gui::Session::View_handle _view_handle = _gui.create_view(); - - /** - * Dialog position in screen coordinate space - */ - Point _position { }; - - /** - * Last pointer position at the time of the most recent hovering report, - * in screen coordinate space. - */ - Point _hovered_position { }; - - bool _dialog_hovered = false; - - Area _configured_size { }; - Area _visible_size { }; - - Area _root_widget_size() const - { - Area const min_size = _root_widget.min_size(); - return Area(max(_configured_size.w(), min_size.w()), - max(_configured_size.h(), min_size.h())); - } - - Rect _view_geometry { }; - - void _update_view(Rect geometry) - { - if (_view_geometry.p1() == geometry.p1() - && _view_geometry.area() == geometry.area()) - return; - - /* display view behind all others */ - typedef Gui::Session::Command Command; - typedef Gui::Session::View_handle View_handle; - - _view_geometry = geometry; - _gui.enqueue(_view_handle, _view_geometry); - _gui.enqueue(_view_handle, View_handle()); - _gui.execute(); - } - - /** - * Function called on config change or mode change - */ - void _handle_dialog_update(); - - Signal_handler
_dialog_update_handler = { - _env.ep(), *this, &Main::_handle_dialog_update}; - Attached_rom_dataspace _config { _env, "config" }; + Signal_handler
_config_handler = { + _env.ep(), *this, &Main::_handle_config}; + + void _handle_config(); + Heap _heap { _env.ram(), _env.rm() }; Vfs::Env &_vfs_env; @@ -104,57 +49,41 @@ struct Menu_view::Main Directory _styles_dir { _root_dir, "styles" }; Style_database _styles { _env.ep(), _env.ram(), _env.rm(), _heap, - _fonts_dir, _styles_dir, _dialog_update_handler }; + _fonts_dir, _styles_dir, _config_handler }; - Animator _animator { }; + Animator _global_animator { }; - Widget_factory _widget_factory { _heap, _styles, _animator }; + Widget_factory _widget_factory { _heap, _styles, _global_animator }; - Root_widget _root_widget { _widget_factory, Xml_node(""), Widget::Unique_id() }; + Dialogs _dialogs { }; - Attached_rom_dataspace _dialog_rom { _env, "dialog" }; - - Attached_dataspace _input_ds { _env.rm(), _gui.input()->dataspace() }; - - bool _opaque = false; - - Color _background_color { }; - - Widget::Hovered _last_reported_hovered { }; - - void _handle_config(); - - Signal_handler
_config_handler = { - _env.ep(), *this, &Main::_handle_config}; + Widget::Hovered _reported_hovered { }; struct Input_seq_number { Constructible _curr { }; - bool _processed = false; + bool _reported = false; - void update(Input::Seq_number const &seq_number) + void update(Input::Seq_number const &seq) { - _curr.construct(seq_number); - _processed = false; + _curr.construct(seq); + _reported = false; } - bool changed() const { return _curr.constructed() && !_processed; } - - void mark_as_processed() { _processed = true; } - - void generate(Xml_generator &xml) + void generate(Xml_generator &xml) const { if (_curr.constructed()) xml.attribute("seq_number", _curr->value); } + void mark_as_reported() { _reported = true; } + + bool changed() const { return !_reported; } + } _input_seq_number { }; - void _handle_input(); - - Signal_handler
_input_handler = { - _env.ep(), *this, &Main::_handle_input}; + struct Frame { uint64_t count; }; /* * Timer used for animating widgets @@ -163,7 +92,7 @@ struct Menu_view::Main { enum { PERIOD = 10 }; - Genode::uint64_t curr_frame() const { return elapsed_ms() / PERIOD; } + Frame curr_frame() const { return { elapsed_ms() / PERIOD }; } void schedule() { trigger_once((Genode::uint64_t)Frame_timer::PERIOD*1000); } @@ -173,6 +102,41 @@ struct Menu_view::Main void _handle_frame_timer(); + /** + * Dialog::Action + */ + void trigger_redraw() override + { + /* + * If we have not processed a period for at least one frame, perform the + * processing immediately. This way, we avoid latencies when the dialog + * model is updated sporadically. + */ + Frame const curr_frame = _timer.curr_frame(); + if (curr_frame.count != _last_frame.count) { + + if (curr_frame.count - _last_frame.count > 10) + _last_frame = curr_frame; + + _handle_frame_timer(); + } else { + _timer.schedule(); + } + } + + /** + * Dialog::Action + */ + void hover_changed() override { _update_hover_report(); } + + /** + * Dialog::Action + */ + void observed_seq_number(Input::Seq_number const seq) override + { + _input_seq_number.update(seq); + } + Signal_handler
_frame_timer_handler = { _env.ep(), *this, &Main::_handle_frame_timer}; @@ -180,17 +144,15 @@ struct Menu_view::Main void _update_hover_report(); - bool _schedule_redraw = false; - /** * Frame of last call of 'handle_frame_timer' */ - Genode::uint64_t _last_frame = 0; + Frame _last_frame { }; /** * Number of frames between two redraws */ - enum { REDRAW_PERIOD = 2 }; + static constexpr unsigned REDRAW_PERIOD = 2; /** * Counter used for triggering redraws. Incremented in each frame-timer @@ -203,15 +165,10 @@ struct Menu_view::Main : _env(env), _vfs_env(libc_vfs_env) { - _dialog_rom.sigh(_dialog_update_handler); _config.sigh(_config_handler); - - _gui.input()->sigh(_input_handler); + _config_handler.local_submit(); /* apply initial configuration */ _timer.sigh(_frame_timer_handler); - - /* apply initial configuration */ - _handle_config(); } }; @@ -221,66 +178,35 @@ void Menu_view::Main::_update_hover_report() if (!_hover_reporter.constructed()) return; - if (!_dialog_hovered) { + unsigned hovered_dialogs = 0; + + _dialogs.for_each([&] (Dialog const &dialog) { + + if (!dialog.hovered()) + return; + + hovered_dialogs++; + if (hovered_dialogs != 1) + return; + + Widget::Hovered const hovered = dialog.hovered_widget(); + + if ((hovered != _reported_hovered) || _input_seq_number.changed()) { + + _hover_reporter->generate([&] (Xml_generator &xml) { + _input_seq_number.generate(xml); + dialog.gen_hover(xml); + }); + _reported_hovered = hovered; + _input_seq_number.mark_as_reported(); + } + }); + + if (hovered_dialogs == 0) _hover_reporter->generate([&] (Xml_generator &) { }); - return; - } - Widget::Hovered const new_hovered = _root_widget.hovered(_hovered_position); - - bool const hover_changed = (_last_reported_hovered != new_hovered); - - if (hover_changed || _input_seq_number.changed()) { - - _hover_reporter->generate([&] (Xml_generator &xml) { - _input_seq_number.generate(xml); - _root_widget.gen_hover_model(xml, _hovered_position); - }); - - _last_reported_hovered = new_hovered; - _input_seq_number.mark_as_processed(); - } -} - - -void Menu_view::Main::_handle_dialog_update() -{ - _styles.flush_outdated_styles(); - - Xml_node const config = _config.xml(); - - _position = Point::from_xml(config); - _configured_size = Area ::from_xml(config); - - _dialog_rom.update(); - - Xml_node dialog = _dialog_rom.xml(); - - if (dialog.has_type("empty")) - return; - - _root_widget.update(dialog); - _root_widget.size(_root_widget_size()); - - _update_hover_report(); - - _schedule_redraw = true; - - /* - * If we have not processed a period for at least one frame, perform the - * processing immediately. This way, we avoid latencies when the dialog - * model is updated sporadically. - */ - Genode::uint64_t const curr_frame = _timer.curr_frame(); - if (curr_frame != _last_frame) { - - if (curr_frame - _last_frame > 10) - _last_frame = curr_frame; - - _handle_frame_timer(); - } else { - _timer.schedule(); - } + if (hovered_dialogs > 1) + warning("more than one dialog unexpectedly hovered at the same time"); } @@ -288,65 +214,29 @@ void Menu_view::Main::_handle_config() { _config.update(); + _styles.flush_outdated_styles(); + Xml_node const config = _config.xml(); config.with_optional_sub_node("report", [&] (Xml_node const &report) { _hover_reporter.conditional(report.attribute_value("hover", false), - _env, "hover", "hover"); - }); - - _opaque = config.attribute_value("opaque", false); - - _background_color = config.attribute_value("background", Color(127, 127, 127, 255)); + _env, "hover", "hover"); }); config.with_optional_sub_node("vfs", [&] (Xml_node const &vfs_node) { _vfs_env.root_dir().apply_config(vfs_node); }); - _handle_dialog_update(); -} + _dialogs.update_from_xml(config, + /* create */ + [&] (Xml_node const &node) -> Dialog & { + return *new (_heap) Dialog(_env, _widget_factory, *this, node); }, -void Menu_view::Main::_handle_input() -{ - Point const orig_hovered_position = _hovered_position; - bool const orig_dialog_hovered = _dialog_hovered; + /* destroy */ + [&] (Dialog &d) { destroy(_heap, &d); }, - _gui.input()->for_each_event([&] (Input::Event const &ev) { - - ev.handle_seq_number([&] (Input::Seq_number seq_number) { - _input_seq_number.update(seq_number); }); - - auto hover_at = [&] (int x, int y) - { - _dialog_hovered = true; - _hovered_position = Point(x, y) - _position; - }; - - auto unhover = [&] () - { - _dialog_hovered = false; - _hovered_position = Point(); - }; - - ev.handle_absolute_motion([&] (int x, int y) { - hover_at(x, y); }); - - ev.handle_touch([&] (Input::Touch_id id, float x, float y) { - if (id.value == 0) - hover_at((int)x, (int)y); }); - - /* - * Reset hover model when losing the focus - */ - if (ev.hover_leave()) - unhover(); - }); - - bool const hover_changed = orig_dialog_hovered != _dialog_hovered - || orig_hovered_position != _hovered_position; - - if (hover_changed || _input_seq_number.changed()) - _update_hover_report(); + /* update */ + [&] (Dialog &d, Xml_node const &node) { d.update(node); } + ); } @@ -354,68 +244,39 @@ void Menu_view::Main::_handle_frame_timer() { _frame_cnt++; - Genode::uint64_t const curr_frame = _timer.curr_frame(); + Frame const curr_frame = _timer.curr_frame(); - if (_animator.active()) { + unsigned const passed_frames = + max(unsigned(curr_frame.count - _last_frame.count), 4U); - Genode::uint64_t const passed_frames = max(curr_frame - _last_frame, 4U); + if (passed_frames > 0) + _dialogs.for_each([&] (Dialog &dialog) { + for (unsigned i = 0; i < passed_frames; i++) + dialog.animate(); }); - if (passed_frames > 0) { - - for (Genode::uint64_t i = 0; i < passed_frames; i++) - _animator.animate(); - - _schedule_redraw = true; - } - } + bool any_redraw_scheduled = false; + _dialogs.for_each([&] (Dialog const &dialog) { + any_redraw_scheduled |= dialog.redraw_scheduled(); }); _last_frame = curr_frame; - if (_schedule_redraw && _frame_cnt >= REDRAW_PERIOD) { + bool const redraw_skipped = any_redraw_scheduled && (_frame_cnt < REDRAW_PERIOD); + if (!redraw_skipped) { _frame_cnt = 0; - - Area const size = _root_widget_size(); - - unsigned const buffer_w = _buffer.constructed() ? _buffer->size().w() : 0, - buffer_h = _buffer.constructed() ? _buffer->size().h() : 0; - - Area const max_size(max(buffer_w, size.w()), max(buffer_h, size.h())); - - bool const size_increased = (max_size.w() > buffer_w) - || (max_size.h() > buffer_h); - - if (!_buffer.constructed() || size_increased) - _buffer.construct(_gui, max_size, _env.ram(), _env.rm(), - _opaque ? Gui_buffer::Alpha::OPAQUE - : Gui_buffer::Alpha::ALPHA, - _background_color); - else - _buffer->reset_surface(); - - _root_widget.position(Point(0, 0)); - - // XXX restrict redraw to dirty regions - // don't perform a full dialog update - _buffer->apply_to_surface([&] (Surface &pixel, - Surface &alpha) { - _root_widget.draw(pixel, alpha, Point(0, 0)); - }); - - _buffer->flush_surface(); - _gui.framebuffer()->refresh(0, 0, _buffer->size().w(), _buffer->size().h()); - _update_view(Rect(_position, size)); - - _schedule_redraw = false; + _dialogs.for_each([&] (Dialog &dialog) { + dialog.redraw(); }); } + bool any_animation_in_progress = false; + _dialogs.for_each([&] (Dialog const &dialog) { + any_animation_in_progress |= dialog.animation_in_progress(); }); + /* * Deactivate timer periods when idle, activate timer when an animation is * in progress or a redraw is pending. */ - bool const redraw_pending = _schedule_redraw && _frame_cnt != 0; - - if (_animator.active() || redraw_pending) + if (any_animation_in_progress || redraw_skipped) _timer.schedule(); } diff --git a/repos/gems/src/app/menu_view/root_widget.h b/repos/gems/src/app/menu_view/root_widget.h index cb7e469979..ea824e23ab 100644 --- a/repos/gems/src/app/menu_view/root_widget.h +++ b/repos/gems/src/app/menu_view/root_widget.h @@ -15,16 +15,17 @@ #define _ROOT_WIDGET_H_ /* local includes */ -#include "widget.h" +#include namespace Menu_view { struct Root_widget; } struct Menu_view::Root_widget : Widget { - Root_widget(Widget_factory &factory, Xml_node node, Unique_id unique_id) + Root_widget(Name const &name, Unique_id const &id, + Widget_factory &factory, Xml_node const &node) : - Widget(factory, node, unique_id) + Widget(name, id, factory, node) { } Area animated_size() const diff --git a/repos/gems/src/app/menu_view/types.h b/repos/gems/src/app/menu_view/types.h index 99c24ff874..a2cf878a0f 100644 --- a/repos/gems/src/app/menu_view/types.h +++ b/repos/gems/src/app/menu_view/types.h @@ -33,9 +33,9 @@ namespace Menu_view { using Genode::size_t; - typedef Surface_base::Point Point; - typedef Surface_base::Area Area; - typedef Surface_base::Rect Rect; + using Point = Surface_base::Point; + using Area = Surface_base::Area; + using Rect = Surface_base::Rect; } #endif /* _TYPES_H_ */ diff --git a/repos/gems/src/app/menu_view/widget.h b/repos/gems/src/app/menu_view/widget.h index 87c5a1fac4..2403ee07f0 100644 --- a/repos/gems/src/app/menu_view/widget.h +++ b/repos/gems/src/app/menu_view/widget.h @@ -56,11 +56,9 @@ class Menu_view::Widget : List_model::Element using List_model::Element::next; - enum { NAME_MAX_LEN = 32 }; - typedef String Name; - - typedef Name Type_name; - typedef String<10> Version; + using Name = String<32>; + using Version = String<10>; + using Type_name = Name; struct Unique_id { @@ -205,13 +203,16 @@ class Menu_view::Widget : List_model::Element Point(r.x2() - margin.right, r.y2() - margin.bottom)); } - Widget(Widget_factory &factory, Xml_node node, Unique_id unique_id) + Widget(Name const &name, Unique_id const id, + Widget_factory &factory, Xml_node const &node) : - _type_name(node.type()), - _name(node_name(node)), - _version(node_version(node)), - _unique_id(unique_id), - _factory(factory) + _type_name(node.type()), _name(name), _version(node_version(node)), + _unique_id(id), _factory(factory) + { } + + Widget(Widget_factory &factory, Xml_node const &node, Unique_id const id) + : + Widget(node_name(node), id, factory, node) { } virtual ~Widget()