diff --git a/repos/gems/include/dialog/runtime.h b/repos/gems/include/dialog/runtime.h new file mode 100644 index 0000000000..b99c17bf86 --- /dev/null +++ b/repos/gems/include/dialog/runtime.h @@ -0,0 +1,110 @@ +/* + * \brief Wrapper around 'Sandboxed_runtime' for simple applications + * \author Norman Feske + * \date 2023-03-24 + */ + +/* + * Copyright (C) 2023 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 _INCLUDE__DIALOG__RUNTIME_H_ +#define _INCLUDE__DIALOG__RUNTIME_H_ + +#include +#include + +namespace Dialog { struct Runtime; } + + +class Dialog::Runtime : private Sandbox::State_handler +{ + private: + + Env &_env; + Allocator &_alloc; + + Sandbox _sandbox { _env, *this }; + + Sandboxed_runtime _runtime { _env, _alloc, _sandbox }; + + void _generate_sandbox_config(Xml_generator &xml) const + { + xml.node("report", [&] () { + xml.attribute("child_ram", "yes"); + xml.attribute("child_caps", "yes"); + xml.attribute("delay_ms", 20*1000); + }); + xml.node("parent-provides", [&] () { + + auto service_node = [&] (char const *name) { + xml.node("service", [&] () { + xml.attribute("name", name); }); }; + + service_node("ROM"); + service_node("CPU"); + service_node("PD"); + service_node("LOG"); + service_node("Gui"); + service_node("Timer"); + service_node("Report"); + service_node("File_system"); + }); + + _runtime.gen_start_nodes(xml); + } + + void _update_sandbox_config() + { + Buffered_xml const config { _alloc, "config", [&] (Xml_generator &xml) { + _generate_sandbox_config(xml); } }; + + config.with_xml_node([&] (Xml_node const &config) { + _sandbox.apply_config(config); }); + } + + /** + * Sandbox::State_handler + */ + void handle_sandbox_state() override + { + /* obtain current sandbox state */ + Buffered_xml state(_alloc, "state", [&] (Xml_generator &xml) { + _sandbox.generate_state_report(xml); }); + + bool reconfiguration_needed = false; + + state.with_xml_node([&] (Xml_node state) { + if (_runtime.apply_sandbox_state(state)) + reconfiguration_needed = true; }); + + if (reconfiguration_needed) + _update_sandbox_config(); + } + + public: + + Runtime(Env &env, Allocator &alloc) : _env(env), _alloc(alloc) { } + + struct View : Sandboxed_runtime::View + { + View(Runtime &runtime, Top_level_dialog &dialog) + : + Sandboxed_runtime::View(runtime._runtime, dialog) + { + runtime._update_sandbox_config(); + } + }; + + template + struct Event_handler : Sandboxed_runtime::Event_handler + { + Event_handler(Runtime &runtime, T &obj, void (T::*member)(Event const &)) + : Sandboxed_runtime::Event_handler(runtime._runtime, obj, member) { } + }; +}; + +#endif /* _INCLUDE__DIALOG__RUNTIME_H_ */ diff --git a/repos/gems/include/dialog/sandboxed_runtime.h b/repos/gems/include/dialog/sandboxed_runtime.h new file mode 100644 index 0000000000..6f4038d60c --- /dev/null +++ b/repos/gems/include/dialog/sandboxed_runtime.h @@ -0,0 +1,378 @@ +/* + * \brief Runtime for hosting GUI dialogs in child components + * \author Norman Feske + * \date 2023-03-24 + */ + +/* + * Copyright (C) 2023 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 _INCLUDE__DIALOG__SANDBOXED_RUNTIME_H_ +#define _INCLUDE__DIALOG__SANDBOXED_RUNTIME_H_ + +#include +#include +#include +#include +#include +#include + +namespace Dialog { struct Sandboxed_runtime; } + + +class Dialog::Sandboxed_runtime : Noncopyable +{ + public: + + class View; + + struct Event_handler_base : Interface, Noncopyable + { + virtual void handle_event(Event const &event) = 0; + }; + + template class Event_handler; + + private: + + Env &_env; + Allocator &_alloc; + Sandbox &_sandbox; + + using Views = Dictionary; + + Event::Seq_number _global_seq_number { 1 }; + + Views _views { }; + + struct Gui_session; + struct Report_session; + + using Gui_service = Sandbox::Local_service; + using Rom_service = Sandbox::Local_service; + using Report_service = Sandbox::Local_service; + + void _handle_gui_service(); + void _handle_rom_service(); + void _handle_report_service(); + + struct Service_handler : Sandbox::Local_service_base::Wakeup + { + Sandboxed_runtime &_runtime; + + using Member = void (Sandboxed_runtime::*) (); + Member _member; + + void wakeup_local_service() override + { + (_runtime.*_member)(); + } + + Service_handler(Sandboxed_runtime &runtime, Member member) + : _runtime(runtime), _member(member) { } + }; + + Service_handler _gui_handler { *this, &Sandboxed_runtime::_handle_gui_service }; + Service_handler _rom_handler { *this, &Sandboxed_runtime::_handle_rom_service }; + Service_handler _report_handler { *this, &Sandboxed_runtime::_handle_report_service }; + + Gui_service _gui_service; + Rom_service _rom_service; + Report_service _report_service; + + public: + + Sandboxed_runtime(Env &, Allocator &, Sandbox &); + + /** + * Respond to sandbox state changes + * + * \return true if the sandbox configuration needs to be updated + */ + bool apply_sandbox_state(Xml_node const &); + + void gen_start_nodes(Xml_generator &) const; +}; + + +class Dialog::Sandboxed_runtime::Report_session : public Session_object +{ + public: + + struct Handler : Interface, Genode::Noncopyable + { + virtual void handle_report() = 0; + }; + + private: + + Attached_ram_dataspace _client_ds; + Attached_ram_dataspace _local_ds; + + Constructible _xml { }; /* points inside _local_ds */ + + Handler &_handler; + + + /******************************* + ** Report::Session interface ** + *******************************/ + + Dataspace_capability dataspace() override { return _client_ds.cap(); } + + void submit(size_t length) override + { + size_t const num_bytes = min(_client_ds.size(), length); + + memcpy(_local_ds.local_addr(), _client_ds.local_addr(), + num_bytes); + + _xml.destruct(); + + try { _xml.construct(_local_ds.local_addr(), num_bytes); } + catch (...) { } + + _handler.handle_report(); + } + + void response_sigh(Signal_context_capability) override { } + + size_t obtain_response() override { return 0; } + + public: + + template + Report_session(Env &env, Handler &handler, + Entrypoint &ep, Resources const &resources, + ARGS &&... args) + : + Session_object(ep, resources, args...), + _client_ds(env.ram(), env.rm(), resources.ram_quota.value/2), + _local_ds (env.ram(), env.rm(), resources.ram_quota.value/2), + _handler(handler) + { } + + template + void with_xml(FN const &fn) const + { + if (_xml.constructed()) + fn(*_xml); + else + fn(Xml_node("")); + } +}; + + +class Dialog::Sandboxed_runtime::View : private Views::Element +{ + private: + + /* needed for privately inheriting 'Views::Element' */ + friend class Dictionary; + friend class Avl_node; + friend class Avl_tree; + + public: + + Env &_env; + + Allocator &_alloc; + + Event::Seq_number &_global_seq_number; + + Top_level_dialog &_dialog; + + bool _dialog_hovered = false; /* used to cut hover feedback loop */ + + /* sequence numbers to correlate hover info with click/clack events */ + Event::Seq_number _hover_seq_number { }; + Constructible _click_seq_number { }; + Constructible _clack_seq_number { }; + + bool _click_delivered = false; /* used to deliver each click only once */ + + bool _dragged() const + { + return _click_seq_number.constructed() + && *_click_seq_number == _global_seq_number + && _click_delivered; + } + + bool _hover_observable_without_click = false; + + struct Rom_producer : Dynamic_rom_session::Xml_producer + { + View const &_view; + + Rom_producer(View const &view) + : + Dynamic_rom_session::Xml_producer("dialog"), + _view(view) + { } + + void produce_xml(Xml_generator &xml) override + { + _view._with_dialog_hover([&] (Xml_node const &hover) { + + Event::Dragged const dragged { _view._dragged() }; + + bool const supply_hover = _view._hover_observable_without_click + || dragged.value; + + static Xml_node omitted_hover(""); + + At const at { _view._global_seq_number, + supply_hover ? hover : omitted_hover }; + + Scope<> top_level_scope(xml, at, dragged, { _view._dialog.name }); + + _view._dialog.view(top_level_scope); + }); + } + } _dialog_producer { *this }; + + Dynamic_rom_session _dialog_rom_session { + _env.ep(), _env.ram(), _env.rm(), _dialog_producer }; + + template + struct Hover_handler : Report_session::Handler + { + T &_obj; + void (T::*_member) (); + + Hover_handler(T &obj, void (T::*member)()) + : _obj(obj), _member(member) { } + + void handle_report() override + { + (_obj.*_member)(); + } + }; + + Constructible _hover_report_session { }; + + template + void _with_dialog_hover(FN const &fn) const + { + bool done = false; + + if (_hover_report_session.constructed()) + _hover_report_session->with_xml([&] (Xml_node const &hover) { + hover.with_optional_sub_node("dialog", [&] (Xml_node const &dialog) { + fn(dialog); + done = true; }); }); + + if (!done) + fn(Xml_node("")); + } + + void _handle_input_event(Input::Event const &); + + void _handle_hover(); + + Hover_handler _hover_handler { *this, &View::_handle_hover }; + + void _try_handle_click_and_clack(); + + struct Menu_view_state + { + using Start_name = String<128>; + + Start_name const _name; + Ram_quota const _initial_ram; + Cap_quota const _initial_caps; + + Ram_quota _ram = _initial_ram; + Cap_quota _caps = _initial_caps; + + unsigned _version = 0; + + void trigger_restart() + { + _version++; + _ram = _initial_ram; + _caps = _initial_caps; + } + + /** + * Adapt runtime state information to the child + * + * This method responds to RAM and cap-resource requests by increasing + * the resource quotas as needed. + * + * \param child child node of the sandbox state report + * \return true if runtime must be reconfigured so that the changes + * can take effect + */ + bool apply_child_state_report(Xml_node const &child) + { + bool result = false; + + if (child.attribute_value("name", Start_name()) != _name) + return false; + + if (child.has_sub_node("ram") && child.sub_node("ram").has_attribute("requested")) { + _ram.value *= 2; + result = true; + } + + if (child.has_sub_node("caps") && child.sub_node("caps").has_attribute("requested")) { + _caps.value += 100; + result = true; + } + + return result; + } + + Menu_view_state(Top_level_dialog::Name const &name, Ram_quota ram, Cap_quota caps) + : + _name(name), _initial_ram(ram), _initial_caps(caps) + { } + + void gen_start_node(Xml_generator &) const; + + } _menu_view_state; + + Registry _gui_sessions { }; + + public: + + View(Sandboxed_runtime &runtime, Top_level_dialog &dialog) + : + Views::Element(runtime._views, dialog.name), + _env(runtime._env), _alloc(runtime._alloc), + _global_seq_number(runtime._global_seq_number), + _dialog(dialog), + _menu_view_state(dialog.name, Ram_quota { 4*1024*1024 }, Cap_quota { 200 }) + { } + + ~View(); + + void refresh() { _dialog_rom_session.trigger_update(); } +}; + + +template +class Dialog::Sandboxed_runtime::Event_handler : Event_handler_base +{ + private: + + T &_obj; + void (T::*_member) (Event const &); + void handle_event(Event const &event) override { (_obj.*_member)(event); } + + public: + + Event_handler(Sandboxed_runtime &runtime, T &obj, void (T::*member)(Event const &)) + : _obj(obj), _member(member) + { + /* register event handler at runtime */ + (void)runtime; + } +}; + +#endif /* _INCLUDE__DIALOG__SANDBOXED_RUNTIME_H_ */ diff --git a/repos/gems/include/dialog/sub_scopes.h b/repos/gems/include/dialog/sub_scopes.h new file mode 100644 index 0000000000..fb3bb89682 --- /dev/null +++ b/repos/gems/include/dialog/sub_scopes.h @@ -0,0 +1,171 @@ +/* + * \brief Sub-scope types + * \author Norman Feske + * \date 2023-03-24 + */ + +/* + * Copyright (C) 2023 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 _INCLUDE__DIALOG__SUB_SCOPES_H_ +#define _INCLUDE__DIALOG__SUB_SCOPES_H_ + +#include + +namespace Dialog { + + template + static void with_narrowed_xml(AT const &, char const *xml_type, FN const &fn); + + struct Vbox; + struct Hbox; + struct Frame; + struct Float; + struct Button; + struct Label; + struct Min_ex; + struct Depgraph; +} + + +template +static inline void Dialog::with_narrowed_xml(AT const &at, char const *xml_type, FN const &fn) +{ + at._location.with_optional_sub_node(xml_type, [&] (Xml_node const &node) { + AT const narrowed_at { at.seq_number, node }; + fn(narrowed_at); + }); +} + + +struct Dialog::Vbox : Sub_scope +{ + template + static void view_sub_scope(SCOPE &s, FN const &fn) + { + s.node("vbox", [&] { fn(s); }); + } + + template + static void with_narrowed_at(AT const &at, FN const &fn) { + with_narrowed_xml(at, "vbox", fn); } +}; + + +struct Dialog::Hbox : Sub_scope +{ + template + static void view_sub_scope(SCOPE &s, FN const &fn) + { + s.node("hbox", [&] { fn(s); }); + } + + template + static void view_sub_scope(SCOPE &s) { s.node("hbox", [&] { }); } + + template + static void with_narrowed_at(AT const &at, FN const &fn) { + with_narrowed_xml(at, "hbox", fn); } +}; + + +struct Dialog::Frame : Sub_scope +{ + template + static void view_sub_scope(SCOPE &s, FN const &fn) + { + s.node("frame", [&] { fn(s); }); + } + + template + static void with_narrowed_at(AT const &at, FN const &fn) { + with_narrowed_xml(at, "frame", fn); } +}; + + +struct Dialog::Float : Sub_scope +{ + template + static void view_sub_scope(SCOPE &s, FN const &fn) + { + s.node("float", [&] { fn(s); }); + } + + template + static void with_narrowed_at(AT const &at, FN const &fn) { + with_narrowed_xml(at, "float", fn); } +}; + + +struct Dialog::Button : Sub_scope +{ + template + static void view_sub_scope(SCOPE &s, FN const &fn) + { + s.node("button", [&] { + fn(s); }); + } + + template + static void with_narrowed_at(AT const &at, FN const &fn) { + with_narrowed_xml(at, "button", fn); } +}; + + +struct Dialog::Label : Sub_scope +{ + template + static void view_sub_scope(SCOPE &s, TEXT const &text) + { + s.node("label", [&] { + s.attribute("text", text); }); + } + + template + static void view_sub_scope(SCOPE &s, TEXT const &text, FN const &fn) + { + s.node("label", [&] { + s.attribute("text", text); + fn(s); + }); + } + + template + static void with_narrowed_at(AT const &at, FN const &fn) { + with_narrowed_xml(at, "label", fn); } +}; + + +struct Dialog::Min_ex : Sub_scope +{ + template + static void view_sub_scope(SCOPE &s, unsigned min_ex) + { + s.node("label", [&] { + s.attribute("min_ex", min_ex); }); + } + + template + static void with_narrowed_at(AT const &at, FN const &fn) { + with_narrowed_xml(at, "label", fn); } +}; + + +struct Dialog::Depgraph : Sub_scope +{ + template + static void view_sub_scope(SCOPE &s, FN const &fn) + { + s.node("depgraph", [&] { fn(s); }); + } + + template + static void with_narrowed_at(AT const &at, FN const &fn) { + with_narrowed_xml(at, "depgraph", fn); } +}; + +#endif /* _INCLUDE__DIALOG__SUB_SCOPES_H_ */ diff --git a/repos/gems/include/dialog/types.h b/repos/gems/include/dialog/types.h new file mode 100644 index 0000000000..3dda19487a --- /dev/null +++ b/repos/gems/include/dialog/types.h @@ -0,0 +1,410 @@ +/* + * \brief Fundamental types for implementing GUI dialogs + * \author Norman Feske + * \date 2023-03-24 + */ + +/* + * Copyright (C) 2023 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 _INCLUDE__DIALOG__TYPES_H_ +#define _INCLUDE__DIALOG__TYPES_H_ + +/* Genode includes */ +#include +#include +#include +#include +#include + +namespace Dialog { + + using namespace Genode; + + struct Id; + struct Event; + struct At; + struct Clicked_at; + struct Clacked_at; + struct Dragged_at; + static inline Clicked_at const &clicked_at(At const &at); + template struct Scope; + struct Sub_scope; + struct Top_level_dialog; + template struct Widget; + template struct Widget_interface; + template struct Hosted; +} + + +namespace Dialog { namespace Meta { + + template + struct Last { using Type = typename Last::Type; }; + + template + struct Last { using Type = T; }; + + template struct List + { + template + struct Appended { using Result = List; }; + }; + + template + struct Same { static constexpr bool VALUE = false; }; + + template + struct Same { static constexpr bool VALUE = true; }; + +} } /* namespace Dialog::Meta */ + + + +struct Dialog::Id +{ + using Value = String<20>; + + Value value; + + bool operator == (Id const &other) const { return value == other.value; } + + bool valid() const { return value.length() > 1; } + + void print(Output &out) const { Genode::print(out, value); } + + static Id from_xml(Xml_node const &node) + { + return Id { node.attribute_value("name", Value()) }; + } +}; + + +struct Dialog::Event : Noncopyable +{ + /** + * ID of input-event sequence + * + * A sequence number refers to a sequence of consecutive events that + * belong together, e.g., all key events occurring while one key is held, + * or all touch motions while keeping the display touched. + */ + struct Seq_number + { + unsigned value; + + bool operator == (Seq_number const &other) const { return value == other.value; } + }; + + struct Dragged + { + bool value; /* true after click and before clack */ + }; + + Seq_number const seq_number; + + Input::Event const event; + + Event(Seq_number seq_number, Input::Event event) + : seq_number(seq_number), event(event) { } + + void print(Output &out) const + { + Genode::print(out, seq_number.value, " ", event); + } +}; + + +struct Dialog::At : Noncopyable +{ + Event::Seq_number const seq_number; + + Xml_node const &_location; /* widget hierarchy as found in hover reports */ + + bool const _valid = _location.has_attribute("name"); + + At(Event::Seq_number const seq_number, Xml_node const &location) + : seq_number(seq_number), _location(location) { } + + /* + * The last element is not interpreted as widget type. It is preserved + * to denote the type of a 'Scoped' sub dialog. + */ + template + struct Narrowed; + + template + struct Narrowed + { + template + static void with_at(AT const &at, FN const &fn) + { + HEAD::with_narrowed_at(at, [&] (AT const &narrowed_at) { + Narrowed::with_at(narrowed_at, fn); }); + } + }; + + template + struct Narrowed + { + template + static void with_at(AT const &at, FN const &fn) { fn(at); } + }; + + template + Id matching_id() const + { + struct Ignored { }; + Id result { }; + Narrowed::with_at(*this, [&] (At const &at) { + result = Id::from_xml(at._location); }); + return result; + } + + template + bool matches(Id const &id) const + { + return matching_id().value == id.value; + } + + bool matches(Event::Seq_number const &s) const + { + return s.value == seq_number.value; + } + + Id id() const { return Id::from_xml(_location); } + + void print(Output &out) const { Genode::print(out, _location); } +}; + + +struct Dialog::Clicked_at : At { using At::At; }; +struct Dialog::Clacked_at : At { using At::At; }; +struct Dialog::Dragged_at : At { using At::At; }; + + +static inline Dialog::Clicked_at const &Dialog::clicked_at(At const &at) +{ + return static_cast(at); +} + + +/** + * Tag for marking types as sub scopes + * + * This is a precaution to detect the use of wrong types as 'Scope::sub_scope' + * argument. + */ +class Dialog::Sub_scope +{ + private: Sub_scope(); /* sub scopes cannot be instantiated */ +}; + + +template +struct Dialog::Scope : Noncopyable +{ + using Hierarchy = Meta::List; + + Id const id; + + Xml_generator &xml; + + At const &hover; + + Event::Dragged const _dragged; + + unsigned _sub_scope_count = 0; + + Scope(Xml_generator &xml, At const &hover, Event::Dragged const dragged, Id const id) + : id(id), xml(xml), hover(hover), _dragged(dragged) { } + + bool dragged() const { return _dragged.value; }; + + template + void sub_scope(Id const id, ARGS &&... args) + { + /* create new 'Scope' type with 'T' appended */ + using Sub_scope = Scope; + + bool generated = false; + + /* narrow hover information according to sub-scope type */ + T::with_narrowed_at(hover, [&] (At const &narrowed_hover) { + if (id == Id::from_xml(narrowed_hover._location)) { + Sub_scope sub_scope { xml, narrowed_hover, _dragged, id }; + T::view_sub_scope(sub_scope, args...); + generated = true; + } + }); + if (generated) + return; + + static Xml_node unhovered_xml { "" }; + At const unhovered_at { hover.seq_number, unhovered_xml }; + Sub_scope sub_scope { xml, unhovered_at, _dragged, id }; + T::view_sub_scope(sub_scope, args...); + } + + template + void sub_scope(ARGS &&... args) + { + static_assert(static_cast((T *)(nullptr)) == nullptr, + "sub_scope called with type that is not a 'Sub_scope'"); + + sub_scope(Id{_sub_scope_count++}, args...); + } + + template + void widget(HOSTED &hosted, ARGS &&... args) + { + hosted._view_hosted(*this, args...); + } + + template + bool hovered(Id const &id) const + { + return hover.matching_id() == id; + } + + bool hovered() const { return hover._valid; } + + template + void node(TYPE const &type, FN const &fn) + { + xml.node(type, [&] { + xml.attribute("name", id.value); + fn(); + }); + } + + template + void sub_node(TYPE const &type, FN const &fn) + { + xml.node(type, [&] { fn(); }); + } + + template + void named_sub_node(TYPE const &type, NAME const &name, FN const &fn) + { + xml.node(type, [&] { + xml.attribute("name", name); + fn(); }); + } + + template + void attribute(NAME const &name, VALUE const &value) + { + xml.attribute(name, value); + } + + template + void as_new_scope(FN const &fn) { fn(*reinterpret_cast*>(this)); } +}; + + +template +struct Dialog::Widget : Noncopyable +{ + static_assert(static_cast((COMPOUND_SUB_SCOPE *)(nullptr)) == nullptr, + "'Widget' argument must be 'Sub_scope' type"); + + using Compound_sub_scope = COMPOUND_SUB_SCOPE; + + template + static void with_narrowed_at(AT const &at, FN const &fn) { + Compound_sub_scope::with_narrowed_at(at, fn); }; +}; + + +template +struct Dialog::Widget_interface : Noncopyable, Interface +{ + using Compound_sub_scope = COMPOUND_SUB_SCOPE; + + template + static void with_narrowed_at(AT const &at, FN const &fn) { + Compound_sub_scope::with_narrowed_at(at, fn); }; +}; + + +template +struct Dialog::Hosted : Meta::Last::Type +{ + Id const id; + + using Widget = typename Meta::Last::Type; + using Compound_sub_scope = typename Widget::Compound_sub_scope; + + template + Hosted(Id const &id, ARGS &&... args) : Widget(args...), id(id) { } + + /* + * \noapi helper for 'propagate' methods + */ + template + void _with_narrowed_at(AT const &at, FN const &fn) const + { + At::Narrowed::with_at(at, [&] (AT const &narrowed) { + if (narrowed.template matches(id)) + fn(narrowed); }); + } + + template + void propagate(Clicked_at const &at, ARGS &&... args) + { + _with_narrowed_at(at, [&] (auto const &at) { this->click(at, args...); }); + } + + template + void propagate(Clacked_at const &at, ARGS &&... args) + { + _with_narrowed_at(at, [&] (auto const &at) { this->clack(at, args...); }); + } + + template + void propagate(Dragged_at const &at, ARGS &&... args) + { + _with_narrowed_at(at, [&] (auto const &at) { this->drag(at, args...); }); + } + + /* + * \noapi used internally by 'Scope::widget' + */ + template + void _view_hosted(SCOPE &scope, ARGS &&... args) const + { + using Call_structure = typename SCOPE::Hierarchy::Appended::Result; + + constexpr bool call_structure_matches_scoped_hierarchy = + Meta::Same, Call_structure>::VALUE; + + static_assert(call_structure_matches_scoped_hierarchy, + "'view' call structure contradicts 'Scoped' hierarchy"); + + scope.as_new_scope([&] (Scope<> &s) { + s.sub_scope(id, [&] (Scope &s) { + Widget::view(s, args...); }); }); + } +}; + + +struct Dialog::Top_level_dialog : Interface, Noncopyable +{ + using Name = String<20>; + Name const name; + + Top_level_dialog(Name const &name) : name(name) { } + + virtual void view(Scope<> &) const = 0; + + virtual void click(Clicked_at const &) { }; + virtual void clack(Clacked_at const &) { }; + virtual void drag (Dragged_at const &) { }; +}; + +#endif /* _INCLUDE__DIALOG__TYPES_H_ */ diff --git a/repos/gems/include/dialog/widgets.h b/repos/gems/include/dialog/widgets.h new file mode 100644 index 0000000000..57768d00b6 --- /dev/null +++ b/repos/gems/include/dialog/widgets.h @@ -0,0 +1,139 @@ +/* + * \brief Widget types + * \author Norman Feske + * \date 2023-03-24 + */ + +/* + * Copyright (C) 2023 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 _INCLUDE__DIALOG__WIDGETS_H_ +#define _INCLUDE__DIALOG__WIDGETS_H_ + +#include + +namespace Dialog { + + template struct Select_button; + struct Toggle_button; + struct Action_button; + struct Deferred_action_button; +} + + +struct Dialog::Toggle_button : Widget