mirror of
https://github.com/genodelabs/genode.git
synced 2025-02-21 10:01:57 +00:00
Dialog API
The new API at gems/include/dialog/ aids the creation of simple GUI applications based on the menu-view widget renderer. Its use is illustrated by the simple test application at src/test/dialog/ that is accompanied with the dialog.run script. Issue #5008
This commit is contained in:
parent
6895175764
commit
4fdc999087
110
repos/gems/include/dialog/runtime.h
Normal file
110
repos/gems/include/dialog/runtime.h
Normal file
@ -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 <dialog/sandboxed_runtime.h>
|
||||||
|
#include <os/buffered_xml.h>
|
||||||
|
|
||||||
|
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 <typename T>
|
||||||
|
struct Event_handler : Sandboxed_runtime::Event_handler<T>
|
||||||
|
{
|
||||||
|
Event_handler(Runtime &runtime, T &obj, void (T::*member)(Event const &))
|
||||||
|
: Sandboxed_runtime::Event_handler<T>(runtime._runtime, obj, member) { }
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* _INCLUDE__DIALOG__RUNTIME_H_ */
|
378
repos/gems/include/dialog/sandboxed_runtime.h
Normal file
378
repos/gems/include/dialog/sandboxed_runtime.h
Normal file
@ -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 <util/dictionary.h>
|
||||||
|
#include <os/dynamic_rom_session.h>
|
||||||
|
#include <base/session_object.h>
|
||||||
|
#include <report_session/report_session.h>
|
||||||
|
#include <sandbox/sandbox.h>
|
||||||
|
#include <dialog/types.h>
|
||||||
|
|
||||||
|
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 <typename T> class Event_handler;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
Env &_env;
|
||||||
|
Allocator &_alloc;
|
||||||
|
Sandbox &_sandbox;
|
||||||
|
|
||||||
|
using Views = Dictionary<View, Top_level_dialog::Name>;
|
||||||
|
|
||||||
|
Event::Seq_number _global_seq_number { 1 };
|
||||||
|
|
||||||
|
Views _views { };
|
||||||
|
|
||||||
|
struct Gui_session;
|
||||||
|
struct Report_session;
|
||||||
|
|
||||||
|
using Gui_service = Sandbox::Local_service<Gui_session>;
|
||||||
|
using Rom_service = Sandbox::Local_service<Dynamic_rom_session>;
|
||||||
|
using Report_service = Sandbox::Local_service<Report_session>;
|
||||||
|
|
||||||
|
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<Report::Session>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
struct Handler : Interface, Genode::Noncopyable
|
||||||
|
{
|
||||||
|
virtual void handle_report() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
Attached_ram_dataspace _client_ds;
|
||||||
|
Attached_ram_dataspace _local_ds;
|
||||||
|
|
||||||
|
Constructible<Xml_node> _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<char>(), _client_ds.local_addr<char>(),
|
||||||
|
num_bytes);
|
||||||
|
|
||||||
|
_xml.destruct();
|
||||||
|
|
||||||
|
try { _xml.construct(_local_ds.local_addr<char>(), num_bytes); }
|
||||||
|
catch (...) { }
|
||||||
|
|
||||||
|
_handler.handle_report();
|
||||||
|
}
|
||||||
|
|
||||||
|
void response_sigh(Signal_context_capability) override { }
|
||||||
|
|
||||||
|
size_t obtain_response() override { return 0; }
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
template <typename... ARGS>
|
||||||
|
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 <typename FN>
|
||||||
|
void with_xml(FN const &fn) const
|
||||||
|
{
|
||||||
|
if (_xml.constructed())
|
||||||
|
fn(*_xml);
|
||||||
|
else
|
||||||
|
fn(Xml_node("<empty/>"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class Dialog::Sandboxed_runtime::View : private Views::Element
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
|
||||||
|
/* needed for privately inheriting 'Views::Element' */
|
||||||
|
friend class Dictionary<View, Top_level_dialog::Name>;
|
||||||
|
friend class Avl_node<View>;
|
||||||
|
friend class Avl_tree<View>;
|
||||||
|
|
||||||
|
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<Event::Seq_number> _click_seq_number { };
|
||||||
|
Constructible<Event::Seq_number> _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("<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 <typename T>
|
||||||
|
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<Report_session> _hover_report_session { };
|
||||||
|
|
||||||
|
template <typename FN>
|
||||||
|
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("<empty/>"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handle_input_event(Input::Event const &);
|
||||||
|
|
||||||
|
void _handle_hover();
|
||||||
|
|
||||||
|
Hover_handler<View> _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_session> _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 <typename T>
|
||||||
|
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_ */
|
171
repos/gems/include/dialog/sub_scopes.h
Normal file
171
repos/gems/include/dialog/sub_scopes.h
Normal file
@ -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 <dialog/types.h>
|
||||||
|
|
||||||
|
namespace Dialog {
|
||||||
|
|
||||||
|
template <typename AT, typename FN>
|
||||||
|
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 <typename AT, typename FN>
|
||||||
|
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 <typename SCOPE, typename FN>
|
||||||
|
static void view_sub_scope(SCOPE &s, FN const &fn)
|
||||||
|
{
|
||||||
|
s.node("vbox", [&] { fn(s); });
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename AT, typename FN>
|
||||||
|
static void with_narrowed_at(AT const &at, FN const &fn) {
|
||||||
|
with_narrowed_xml(at, "vbox", fn); }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct Dialog::Hbox : Sub_scope
|
||||||
|
{
|
||||||
|
template <typename SCOPE, typename FN>
|
||||||
|
static void view_sub_scope(SCOPE &s, FN const &fn)
|
||||||
|
{
|
||||||
|
s.node("hbox", [&] { fn(s); });
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename SCOPE>
|
||||||
|
static void view_sub_scope(SCOPE &s) { s.node("hbox", [&] { }); }
|
||||||
|
|
||||||
|
template <typename AT, typename FN>
|
||||||
|
static void with_narrowed_at(AT const &at, FN const &fn) {
|
||||||
|
with_narrowed_xml(at, "hbox", fn); }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct Dialog::Frame : Sub_scope
|
||||||
|
{
|
||||||
|
template <typename SCOPE, typename FN>
|
||||||
|
static void view_sub_scope(SCOPE &s, FN const &fn)
|
||||||
|
{
|
||||||
|
s.node("frame", [&] { fn(s); });
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename AT, typename FN>
|
||||||
|
static void with_narrowed_at(AT const &at, FN const &fn) {
|
||||||
|
with_narrowed_xml(at, "frame", fn); }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct Dialog::Float : Sub_scope
|
||||||
|
{
|
||||||
|
template <typename SCOPE, typename FN>
|
||||||
|
static void view_sub_scope(SCOPE &s, FN const &fn)
|
||||||
|
{
|
||||||
|
s.node("float", [&] { fn(s); });
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename AT, typename FN>
|
||||||
|
static void with_narrowed_at(AT const &at, FN const &fn) {
|
||||||
|
with_narrowed_xml(at, "float", fn); }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct Dialog::Button : Sub_scope
|
||||||
|
{
|
||||||
|
template <typename SCOPE, typename FN>
|
||||||
|
static void view_sub_scope(SCOPE &s, FN const &fn)
|
||||||
|
{
|
||||||
|
s.node("button", [&] {
|
||||||
|
fn(s); });
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename AT, typename FN>
|
||||||
|
static void with_narrowed_at(AT const &at, FN const &fn) {
|
||||||
|
with_narrowed_xml(at, "button", fn); }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct Dialog::Label : Sub_scope
|
||||||
|
{
|
||||||
|
template <typename SCOPE, typename TEXT>
|
||||||
|
static void view_sub_scope(SCOPE &s, TEXT const &text)
|
||||||
|
{
|
||||||
|
s.node("label", [&] {
|
||||||
|
s.attribute("text", text); });
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename SCOPE, typename TEXT, typename FN>
|
||||||
|
static void view_sub_scope(SCOPE &s, TEXT const &text, FN const &fn)
|
||||||
|
{
|
||||||
|
s.node("label", [&] {
|
||||||
|
s.attribute("text", text);
|
||||||
|
fn(s);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename AT, typename FN>
|
||||||
|
static void with_narrowed_at(AT const &at, FN const &fn) {
|
||||||
|
with_narrowed_xml(at, "label", fn); }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct Dialog::Min_ex : Sub_scope
|
||||||
|
{
|
||||||
|
template <typename SCOPE>
|
||||||
|
static void view_sub_scope(SCOPE &s, unsigned min_ex)
|
||||||
|
{
|
||||||
|
s.node("label", [&] {
|
||||||
|
s.attribute("min_ex", min_ex); });
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename AT, typename FN>
|
||||||
|
static void with_narrowed_at(AT const &at, FN const &fn) {
|
||||||
|
with_narrowed_xml(at, "label", fn); }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct Dialog::Depgraph : Sub_scope
|
||||||
|
{
|
||||||
|
template <typename SCOPE, typename FN>
|
||||||
|
static void view_sub_scope(SCOPE &s, FN const &fn)
|
||||||
|
{
|
||||||
|
s.node("depgraph", [&] { fn(s); });
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename AT, typename FN>
|
||||||
|
static void with_narrowed_at(AT const &at, FN const &fn) {
|
||||||
|
with_narrowed_xml(at, "depgraph", fn); }
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* _INCLUDE__DIALOG__SUB_SCOPES_H_ */
|
410
repos/gems/include/dialog/types.h
Normal file
410
repos/gems/include/dialog/types.h
Normal file
@ -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 <util/xml_node.h>
|
||||||
|
#include <util/xml_generator.h>
|
||||||
|
#include <base/log.h>
|
||||||
|
#include <input/event.h>
|
||||||
|
#include <input/keycodes.h>
|
||||||
|
|
||||||
|
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 <typename...> struct Scope;
|
||||||
|
struct Sub_scope;
|
||||||
|
struct Top_level_dialog;
|
||||||
|
template <typename> struct Widget;
|
||||||
|
template <typename> struct Widget_interface;
|
||||||
|
template <typename...> struct Hosted;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
namespace Dialog { namespace Meta {
|
||||||
|
|
||||||
|
template <typename, typename... TAIL>
|
||||||
|
struct Last { using Type = typename Last<TAIL...>::Type; };
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct Last<T> { using Type = T; };
|
||||||
|
|
||||||
|
template <typename... ARGS> struct List
|
||||||
|
{
|
||||||
|
template <typename LAST>
|
||||||
|
struct Appended { using Result = List<ARGS..., LAST>; };
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T1, typename T2>
|
||||||
|
struct Same { static constexpr bool VALUE = false; };
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct Same<T, T> { 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 <typename...>
|
||||||
|
struct Narrowed;
|
||||||
|
|
||||||
|
template <typename HEAD, typename... TAIL>
|
||||||
|
struct Narrowed<HEAD, TAIL...>
|
||||||
|
{
|
||||||
|
template <typename AT, typename FN>
|
||||||
|
static void with_at(AT const &at, FN const &fn)
|
||||||
|
{
|
||||||
|
HEAD::with_narrowed_at(at, [&] (AT const &narrowed_at) {
|
||||||
|
Narrowed<TAIL...>::with_at(narrowed_at, fn); });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename LAST_IGNORED>
|
||||||
|
struct Narrowed<LAST_IGNORED>
|
||||||
|
{
|
||||||
|
template <typename AT, typename FN>
|
||||||
|
static void with_at(AT const &at, FN const &fn) { fn(at); }
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename... ARGS>
|
||||||
|
Id matching_id() const
|
||||||
|
{
|
||||||
|
struct Ignored { };
|
||||||
|
Id result { };
|
||||||
|
Narrowed<ARGS..., Ignored>::with_at(*this, [&] (At const &at) {
|
||||||
|
result = Id::from_xml(at._location); });
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... HIERARCHY>
|
||||||
|
bool matches(Id const &id) const
|
||||||
|
{
|
||||||
|
return matching_id<HIERARCHY...>().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<Clicked_at const &>(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 <typename... HIERARCHY>
|
||||||
|
struct Dialog::Scope : Noncopyable
|
||||||
|
{
|
||||||
|
using Hierarchy = Meta::List<HIERARCHY...>;
|
||||||
|
|
||||||
|
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 <typename T, typename... ARGS>
|
||||||
|
void sub_scope(Id const id, ARGS &&... args)
|
||||||
|
{
|
||||||
|
/* create new 'Scope' type with 'T' appended */
|
||||||
|
using Sub_scope = Scope<HIERARCHY..., T>;
|
||||||
|
|
||||||
|
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 { "<hover/>" };
|
||||||
|
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 <typename T, typename... ARGS>
|
||||||
|
void sub_scope(ARGS &&... args)
|
||||||
|
{
|
||||||
|
static_assert(static_cast<Sub_scope *>((T *)(nullptr)) == nullptr,
|
||||||
|
"sub_scope called with type that is not a 'Sub_scope'");
|
||||||
|
|
||||||
|
sub_scope<T>(Id{_sub_scope_count++}, args...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename HOSTED, typename... ARGS>
|
||||||
|
void widget(HOSTED &hosted, ARGS &&... args)
|
||||||
|
{
|
||||||
|
hosted._view_hosted(*this, args...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... ARGS>
|
||||||
|
bool hovered(Id const &id) const
|
||||||
|
{
|
||||||
|
return hover.matching_id<ARGS...>() == id;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hovered() const { return hover._valid; }
|
||||||
|
|
||||||
|
template <typename TYPE, typename FN>
|
||||||
|
void node(TYPE const &type, FN const &fn)
|
||||||
|
{
|
||||||
|
xml.node(type, [&] {
|
||||||
|
xml.attribute("name", id.value);
|
||||||
|
fn();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename TYPE, typename FN>
|
||||||
|
void sub_node(TYPE const &type, FN const &fn)
|
||||||
|
{
|
||||||
|
xml.node(type, [&] { fn(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename TYPE, typename NAME, typename FN>
|
||||||
|
void named_sub_node(TYPE const &type, NAME const &name, FN const &fn)
|
||||||
|
{
|
||||||
|
xml.node(type, [&] {
|
||||||
|
xml.attribute("name", name);
|
||||||
|
fn(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename NAME, typename VALUE>
|
||||||
|
void attribute(NAME const &name, VALUE const &value)
|
||||||
|
{
|
||||||
|
xml.attribute(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename FN>
|
||||||
|
void as_new_scope(FN const &fn) { fn(*reinterpret_cast<Scope<>*>(this)); }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
template <typename COMPOUND_SUB_SCOPE>
|
||||||
|
struct Dialog::Widget : Noncopyable
|
||||||
|
{
|
||||||
|
static_assert(static_cast<Sub_scope *>((COMPOUND_SUB_SCOPE *)(nullptr)) == nullptr,
|
||||||
|
"'Widget' argument must be 'Sub_scope' type");
|
||||||
|
|
||||||
|
using Compound_sub_scope = COMPOUND_SUB_SCOPE;
|
||||||
|
|
||||||
|
template <typename AT, typename FN>
|
||||||
|
static void with_narrowed_at(AT const &at, FN const &fn) {
|
||||||
|
Compound_sub_scope::with_narrowed_at(at, fn); };
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
template <typename COMPOUND_SUB_SCOPE>
|
||||||
|
struct Dialog::Widget_interface : Noncopyable, Interface
|
||||||
|
{
|
||||||
|
using Compound_sub_scope = COMPOUND_SUB_SCOPE;
|
||||||
|
|
||||||
|
template <typename AT, typename FN>
|
||||||
|
static void with_narrowed_at(AT const &at, FN const &fn) {
|
||||||
|
Compound_sub_scope::with_narrowed_at(at, fn); };
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
template <typename... HIERARCHY>
|
||||||
|
struct Dialog::Hosted : Meta::Last<HIERARCHY...>::Type
|
||||||
|
{
|
||||||
|
Id const id;
|
||||||
|
|
||||||
|
using Widget = typename Meta::Last<HIERARCHY...>::Type;
|
||||||
|
using Compound_sub_scope = typename Widget::Compound_sub_scope;
|
||||||
|
|
||||||
|
template <typename... ARGS>
|
||||||
|
Hosted(Id const &id, ARGS &&... args) : Widget(args...), id(id) { }
|
||||||
|
|
||||||
|
/*
|
||||||
|
* \noapi helper for 'propagate' methods
|
||||||
|
*/
|
||||||
|
template <typename AT, typename FN>
|
||||||
|
void _with_narrowed_at(AT const &at, FN const &fn) const
|
||||||
|
{
|
||||||
|
At::Narrowed<HIERARCHY...>::with_at(at, [&] (AT const &narrowed) {
|
||||||
|
if (narrowed.template matches<Compound_sub_scope>(id))
|
||||||
|
fn(narrowed); });
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... ARGS>
|
||||||
|
void propagate(Clicked_at const &at, ARGS &&... args)
|
||||||
|
{
|
||||||
|
_with_narrowed_at(at, [&] (auto const &at) { this->click(at, args...); });
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... ARGS>
|
||||||
|
void propagate(Clacked_at const &at, ARGS &&... args)
|
||||||
|
{
|
||||||
|
_with_narrowed_at(at, [&] (auto const &at) { this->clack(at, args...); });
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... ARGS>
|
||||||
|
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 <typename SCOPE, typename... ARGS>
|
||||||
|
void _view_hosted(SCOPE &scope, ARGS &&... args) const
|
||||||
|
{
|
||||||
|
using Call_structure = typename SCOPE::Hierarchy::Appended<Widget>::Result;
|
||||||
|
|
||||||
|
constexpr bool call_structure_matches_scoped_hierarchy =
|
||||||
|
Meta::Same<Meta::List<HIERARCHY...>, 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<Compound_sub_scope>(id, [&] (Scope<Compound_sub_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_ */
|
139
repos/gems/include/dialog/widgets.h
Normal file
139
repos/gems/include/dialog/widgets.h
Normal file
@ -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 <dialog/sub_scopes.h>
|
||||||
|
|
||||||
|
namespace Dialog {
|
||||||
|
|
||||||
|
template <typename> struct Select_button;
|
||||||
|
struct Toggle_button;
|
||||||
|
struct Action_button;
|
||||||
|
struct Deferred_action_button;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct Dialog::Toggle_button : Widget<Button>
|
||||||
|
{
|
||||||
|
template <typename FN>
|
||||||
|
void view(Scope<Button> &s, bool selected, FN const &fn) const
|
||||||
|
{
|
||||||
|
bool const hovered = (s.hovered() && (!s.dragged() || selected));
|
||||||
|
|
||||||
|
if (selected) s.attribute("selected", "yes");
|
||||||
|
if (hovered) s.attribute("hovered", "yes");
|
||||||
|
|
||||||
|
fn(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
void view(Scope<Button> &s, bool selected) const
|
||||||
|
{
|
||||||
|
view(s, selected, [&] (Scope<Button> &s) {
|
||||||
|
s.sub_scope<Dialog::Label>(s.id.value); });
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename FN>
|
||||||
|
void click(Clicked_at const &, FN const &toggle_fn) { toggle_fn(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
template <typename ENUM>
|
||||||
|
struct Dialog::Select_button : Widget<Button>
|
||||||
|
{
|
||||||
|
ENUM const _value;
|
||||||
|
|
||||||
|
Select_button(ENUM value) : _value(value) { }
|
||||||
|
|
||||||
|
void view(Scope<Button> &s, ENUM selected_value) const
|
||||||
|
{
|
||||||
|
bool const selected = (selected_value == _value),
|
||||||
|
hovered = (s.hovered() && !s.dragged() && !selected);
|
||||||
|
|
||||||
|
if (selected) s.attribute("selected", "yes");
|
||||||
|
if (hovered) s.attribute("hovered", "yes");
|
||||||
|
|
||||||
|
s.sub_scope<Dialog::Label>(s.id.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename FN>
|
||||||
|
void click(Clicked_at const &, FN const &select_fn) { select_fn(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct Dialog::Action_button : Widget<Button>
|
||||||
|
{
|
||||||
|
Event::Seq_number _seq_number { };
|
||||||
|
|
||||||
|
template <typename FN>
|
||||||
|
void view(Scope<Button> &s, FN const &fn) const
|
||||||
|
{
|
||||||
|
bool const selected = _seq_number == s.hover.seq_number,
|
||||||
|
hovered = (s.hovered() && (!s.dragged() || selected));
|
||||||
|
|
||||||
|
if (selected) s.attribute("selected", "yes");
|
||||||
|
if (hovered) s.attribute("hovered", "yes");
|
||||||
|
|
||||||
|
fn(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
void view(Scope<Button> &s) const
|
||||||
|
{
|
||||||
|
view(s, [&] (Scope<Button> &s) { s.sub_scope<Label>(s.id.value); });
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename FN>
|
||||||
|
void click(Clicked_at const &at, FN const &activate_fn)
|
||||||
|
{
|
||||||
|
_seq_number = at.seq_number;
|
||||||
|
activate_fn();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct Dialog::Deferred_action_button : Widget<Button>
|
||||||
|
{
|
||||||
|
Event::Seq_number _seq_number { }; /* remembered at proposal time */
|
||||||
|
|
||||||
|
template <typename FN>
|
||||||
|
void view(Scope<Button> &s, FN const &fn) const
|
||||||
|
{
|
||||||
|
bool const selected = s.hovered() && s.dragged() && s.hover.matches(_seq_number),
|
||||||
|
hovered = s.hovered() && (!s.dragged() || selected);
|
||||||
|
|
||||||
|
if (selected) s.attribute("selected", "yes");
|
||||||
|
if (hovered) s.attribute("hovered", "yes");
|
||||||
|
|
||||||
|
fn(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
void view(Scope<Button> &s) const
|
||||||
|
{
|
||||||
|
view(s, [&] (Scope<Button> &s) { s.sub_scope<Label>(s.id.value); });
|
||||||
|
}
|
||||||
|
|
||||||
|
void click(Clicked_at const &at)
|
||||||
|
{
|
||||||
|
_seq_number = at.seq_number;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename FN>
|
||||||
|
void clack(Clacked_at const &at, FN const &activate_fn)
|
||||||
|
{
|
||||||
|
if (at.matches(_seq_number))
|
||||||
|
activate_fn();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* _INCLUDE__DIALOG__WIDGETS_H_ */
|
4
repos/gems/lib/mk/dialog.mk
Normal file
4
repos/gems/lib/mk/dialog.mk
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
SRC_CC += sandboxed_runtime.cc
|
||||||
|
LIBS += sandbox
|
||||||
|
|
||||||
|
vpath %.cc $(REP_DIR)/src/lib/dialog
|
115
repos/gems/run/dialog.run
Normal file
115
repos/gems/run/dialog.run
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
create_boot_directory
|
||||||
|
|
||||||
|
import_from_depot [depot_user]/src/[base_src] \
|
||||||
|
[depot_user]/pkg/[drivers_interactive_pkg] \
|
||||||
|
[depot_user]/pkg/fonts_fs \
|
||||||
|
[depot_user]/src/init \
|
||||||
|
[depot_user]/src/report_rom \
|
||||||
|
[depot_user]/src/nitpicker \
|
||||||
|
[depot_user]/src/libc \
|
||||||
|
[depot_user]/src/libpng \
|
||||||
|
[depot_user]/src/zlib \
|
||||||
|
[depot_user]/src/vfs_import
|
||||||
|
|
||||||
|
install_config {
|
||||||
|
<config>
|
||||||
|
<parent-provides>
|
||||||
|
<service name="PD"/>
|
||||||
|
<service name="CPU"/>
|
||||||
|
<service name="ROM"/>
|
||||||
|
<service name="RM"/>
|
||||||
|
<service name="LOG"/>
|
||||||
|
<service name="IRQ"/>
|
||||||
|
<service name="IO_MEM"/>
|
||||||
|
<service name="IO_PORT"/>
|
||||||
|
</parent-provides>
|
||||||
|
|
||||||
|
<default caps="100"/>
|
||||||
|
|
||||||
|
<default-route>
|
||||||
|
<any-service> <parent/> <any-child/> </any-service>
|
||||||
|
</default-route>
|
||||||
|
|
||||||
|
<start name="timer">
|
||||||
|
<resource name="RAM" quantum="1M"/>
|
||||||
|
<provides><service name="Timer"/></provides>
|
||||||
|
</start>
|
||||||
|
|
||||||
|
<start name="drivers" caps="1500" managing_system="yes">
|
||||||
|
<resource name="RAM" quantum="64M"/>
|
||||||
|
<binary name="init"/>
|
||||||
|
<route>
|
||||||
|
<service name="ROM" label="config"> <parent label="drivers.config"/> </service>
|
||||||
|
<service name="Timer"> <child name="timer"/> </service>
|
||||||
|
<service name="Capture"> <child name="nitpicker"/> </service>
|
||||||
|
<service name="Event"> <child name="nitpicker"/> </service>
|
||||||
|
<any-service> <parent/> </any-service>
|
||||||
|
</route>
|
||||||
|
</start>
|
||||||
|
|
||||||
|
<start name="report_rom">
|
||||||
|
<resource name="RAM" quantum="1M"/>
|
||||||
|
<provides> <service name="Report"/> <service name="ROM"/> </provides>
|
||||||
|
<config verbose="yes">
|
||||||
|
<policy label="text_area.1 -> hover" report="nitpicker -> hover"/>
|
||||||
|
<policy label="text_area.2 -> clipboard" report="text_area.2 -> clipboard"/>
|
||||||
|
</config>
|
||||||
|
</start>
|
||||||
|
|
||||||
|
<start name="nitpicker">
|
||||||
|
<resource name="RAM" quantum="4M"/>
|
||||||
|
<provides>
|
||||||
|
<service name="Gui"/> <service name="Capture"/> <service name="Event"/>
|
||||||
|
</provides>
|
||||||
|
<config focus="rom">
|
||||||
|
<capture/> <event/>
|
||||||
|
<report hover="yes"/>
|
||||||
|
<background color="#123456"/>
|
||||||
|
<domain name="pointer" layer="1" content="client" label="no" origin="pointer" />
|
||||||
|
<domain name="default" layer="3" content="client" label="no" hover="always" />
|
||||||
|
<domain name="second" layer="2" xpos="200" ypos="300" content="client" label="no" hover="always" />
|
||||||
|
|
||||||
|
<policy label_prefix="pointer" domain="pointer"/>
|
||||||
|
<policy label_prefix="text_area.2" domain="second"/>
|
||||||
|
<default-policy domain="default"/>
|
||||||
|
</config>
|
||||||
|
</start>
|
||||||
|
|
||||||
|
<start name="pointer">
|
||||||
|
<resource name="RAM" quantum="1M"/>
|
||||||
|
<route>
|
||||||
|
<service name="Gui"> <child name="nitpicker" /> </service>
|
||||||
|
<any-service> <parent/> <any-child/> </any-service>
|
||||||
|
</route>
|
||||||
|
</start>
|
||||||
|
|
||||||
|
<start name="fonts_fs" caps="300">
|
||||||
|
<resource name="RAM" quantum="8M"/>
|
||||||
|
<binary name="vfs"/>
|
||||||
|
<route>
|
||||||
|
<service name="ROM" label="config"> <parent label="fonts_fs.config"/> </service>
|
||||||
|
<any-service> <parent/> </any-service>
|
||||||
|
</route>
|
||||||
|
<provides> <service name="File_system"/> </provides>
|
||||||
|
</start>
|
||||||
|
|
||||||
|
<start name="test-dialog" caps="1000">
|
||||||
|
<resource name="RAM" quantum="8M"/>
|
||||||
|
<config/>
|
||||||
|
<route>
|
||||||
|
<service name="ROM" label="hover"> <child name="report_rom"/> </service>
|
||||||
|
<any-service> <parent/> <any-child/> </any-service>
|
||||||
|
</route>
|
||||||
|
</start>
|
||||||
|
|
||||||
|
</config>}
|
||||||
|
|
||||||
|
set fd [open [run_dir]/genode/focus w]
|
||||||
|
puts $fd "<focus label=\"test-dialog -> \"/>"
|
||||||
|
close $fd
|
||||||
|
|
||||||
|
build { test/dialog app/menu_view }
|
||||||
|
|
||||||
|
build_boot_image [build_artifacts]
|
||||||
|
|
||||||
|
run_genode_until forever
|
409
repos/gems/src/lib/dialog/sandboxed_runtime.cc
Normal file
409
repos/gems/src/lib/dialog/sandboxed_runtime.cc
Normal file
@ -0,0 +1,409 @@
|
|||||||
|
/*
|
||||||
|
* \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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <dialog/sandboxed_runtime.h>
|
||||||
|
#include <base/attached_ram_dataspace.h>
|
||||||
|
#include <gui_session/connection.h>
|
||||||
|
#include <input/component.h>
|
||||||
|
|
||||||
|
using namespace Dialog;
|
||||||
|
|
||||||
|
|
||||||
|
static bool click(Input::Event const &event)
|
||||||
|
{
|
||||||
|
bool result = false;
|
||||||
|
|
||||||
|
if (event.key_press(Input::BTN_LEFT))
|
||||||
|
result = true;
|
||||||
|
|
||||||
|
event.handle_touch([&] (Input::Touch_id id, float, float) {
|
||||||
|
if (id.value == 0)
|
||||||
|
result = true; });
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static bool clack(Input::Event const &event)
|
||||||
|
{
|
||||||
|
bool result = false;
|
||||||
|
|
||||||
|
if (event.key_release(Input::BTN_LEFT))
|
||||||
|
result = true;
|
||||||
|
|
||||||
|
event.handle_touch_release([&] (Input::Touch_id id) {
|
||||||
|
if (id.value == 0)
|
||||||
|
result = true; });
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct Sandboxed_runtime::Gui_session : Session_object<Gui::Session>
|
||||||
|
{
|
||||||
|
Env &_env;
|
||||||
|
|
||||||
|
View &_view;
|
||||||
|
|
||||||
|
Registry<Gui_session>::Element _element;
|
||||||
|
|
||||||
|
using View_capability = Gui::View_capability;
|
||||||
|
|
||||||
|
Gui::Connection _connection;
|
||||||
|
|
||||||
|
Input::Session_component _input_component { _env, _env.ram() };
|
||||||
|
|
||||||
|
Signal_handler<Gui_session> _input_handler {
|
||||||
|
_env.ep(), *this, &Gui_session::_handle_input };
|
||||||
|
|
||||||
|
bool _clicked = false;
|
||||||
|
|
||||||
|
void _handle_input()
|
||||||
|
{
|
||||||
|
_connection.input()->for_each_event([&] (Input::Event const &ev) {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Assign new event sequence number, pass seq event to menu view
|
||||||
|
* to ensure freshness of hover information.
|
||||||
|
*/
|
||||||
|
bool const orig_clicked = _clicked;
|
||||||
|
|
||||||
|
if (click(ev)) _clicked = true;
|
||||||
|
if (clack(ev)) _clicked = false;
|
||||||
|
|
||||||
|
if (orig_clicked != _clicked) {
|
||||||
|
_view._global_seq_number.value++;
|
||||||
|
_input_component.submit(Input::Seq_number { _view._global_seq_number.value });
|
||||||
|
}
|
||||||
|
|
||||||
|
/* local event (click/clack) handling */
|
||||||
|
_view._handle_input_event(ev);
|
||||||
|
|
||||||
|
/* forward event to menu_view */
|
||||||
|
_input_component.submit(ev);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... ARGS>
|
||||||
|
Gui_session(Env &env, View &view, ARGS &&... args)
|
||||||
|
:
|
||||||
|
Session_object(args...),
|
||||||
|
_env(env), _view(view),
|
||||||
|
_element(_view._gui_sessions, *this),
|
||||||
|
_connection(env, _label.string())
|
||||||
|
{
|
||||||
|
_connection.input()->sigh(_input_handler);
|
||||||
|
_env.ep().manage(_input_component);
|
||||||
|
_input_component.event_queue().enabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
~Gui_session() { _env.ep().dissolve(_input_component); }
|
||||||
|
|
||||||
|
void upgrade(Session::Resources const &resources)
|
||||||
|
{
|
||||||
|
_connection.upgrade(resources);
|
||||||
|
}
|
||||||
|
|
||||||
|
Framebuffer::Session_capability framebuffer_session() override {
|
||||||
|
return _connection.framebuffer_session(); }
|
||||||
|
|
||||||
|
Input::Session_capability input_session() override {
|
||||||
|
return _input_component.cap(); }
|
||||||
|
|
||||||
|
View_handle create_view(View_handle parent) override {
|
||||||
|
return _connection.create_view(parent); }
|
||||||
|
|
||||||
|
void destroy_view(View_handle view) override {
|
||||||
|
_connection.destroy_view(view); }
|
||||||
|
|
||||||
|
View_handle view_handle(View_capability view_cap, View_handle handle) override {
|
||||||
|
return _connection.view_handle(view_cap, handle); }
|
||||||
|
|
||||||
|
View_capability view_capability(View_handle view) override {
|
||||||
|
return _connection.view_capability(view); }
|
||||||
|
|
||||||
|
void release_view_handle(View_handle view) override {
|
||||||
|
_connection.release_view_handle(view); }
|
||||||
|
|
||||||
|
Dataspace_capability command_dataspace() override {
|
||||||
|
return _connection.command_dataspace(); }
|
||||||
|
|
||||||
|
void execute() override {
|
||||||
|
_connection.execute(); }
|
||||||
|
|
||||||
|
Framebuffer::Mode mode() override {
|
||||||
|
return _connection.mode(); }
|
||||||
|
|
||||||
|
void mode_sigh(Signal_context_capability sigh) override {
|
||||||
|
_connection.mode_sigh(sigh); }
|
||||||
|
|
||||||
|
void buffer(Framebuffer::Mode mode, bool use_alpha) override
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Do not call 'Connection::buffer' to avoid paying session quota
|
||||||
|
* from our own budget.
|
||||||
|
*/
|
||||||
|
_connection.Client::buffer(mode, use_alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
void focus(Capability<Gui::Session> session) override {
|
||||||
|
_connection.focus(session); }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
Sandboxed_runtime::Sandboxed_runtime(Env &env, Allocator &alloc, Sandbox &sandbox)
|
||||||
|
:
|
||||||
|
_env(env), _alloc(alloc), _sandbox(sandbox),
|
||||||
|
_gui_service (_sandbox, _gui_handler),
|
||||||
|
_rom_service (_sandbox, _rom_handler),
|
||||||
|
_report_service(_sandbox, _report_handler)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
|
||||||
|
bool Sandboxed_runtime::apply_sandbox_state(Xml_node const &state)
|
||||||
|
{
|
||||||
|
bool reconfiguration_needed = false;
|
||||||
|
|
||||||
|
state.for_each_sub_node("child", [&] (Xml_node const &child) {
|
||||||
|
using Name = Top_level_dialog::Name;
|
||||||
|
Name const name = child.attribute_value("name", Name());
|
||||||
|
_views.with_element(name,
|
||||||
|
[&] (View &view) {
|
||||||
|
if (view._menu_view_state.apply_child_state_report(child))
|
||||||
|
reconfiguration_needed = true; },
|
||||||
|
[&] /* no view named after this child */ { });
|
||||||
|
});
|
||||||
|
|
||||||
|
return reconfiguration_needed;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Sandboxed_runtime::_handle_rom_service()
|
||||||
|
{
|
||||||
|
_rom_service.for_each_requested_session([&] (Rom_service::Request &request) {
|
||||||
|
if (request.label.last_element() == "dialog") {
|
||||||
|
_views.with_element(request.label.prefix(),
|
||||||
|
[&] (View &view) {
|
||||||
|
request.deliver_session(view._dialog_rom_session); },
|
||||||
|
[&] /* no view named after this child */ { });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
_rom_service.for_each_session_to_close([&] (Dynamic_rom_session &) {
|
||||||
|
warning("closing of Dynamic_rom_session session not handled");
|
||||||
|
return Rom_service::Close_response::CLOSED;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Sandboxed_runtime::_handle_report_service()
|
||||||
|
{
|
||||||
|
_report_service.for_each_requested_session([&] (Report_service::Request &request) {
|
||||||
|
if (request.label.last_element() == "hover") {
|
||||||
|
_views.with_element(request.label.prefix(),
|
||||||
|
[&] (View &view) {
|
||||||
|
view._hover_report_session.construct(_env, view._hover_handler, _env.ep(),
|
||||||
|
request.resources, "", request.diag);
|
||||||
|
request.deliver_session(*view._hover_report_session);
|
||||||
|
},
|
||||||
|
[&] /* no view named after this child */ { });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
_report_service.for_each_session_to_close([&] (Report_session &) {
|
||||||
|
warning("closing of Report_session not handled");
|
||||||
|
return Report_service::Close_response::CLOSED;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Sandboxed_runtime::_handle_gui_service()
|
||||||
|
{
|
||||||
|
_gui_service.for_each_requested_session([&] (Gui_service::Request &request) {
|
||||||
|
_views.with_element(request.label.prefix(),
|
||||||
|
[&] (View &view) {
|
||||||
|
Gui_session &session = *new (_alloc)
|
||||||
|
Gui_session(_env, view, _env.ep(),
|
||||||
|
request.resources, "", request.diag);
|
||||||
|
request.deliver_session(session);
|
||||||
|
},
|
||||||
|
[&] {
|
||||||
|
warning("unexpected GUI-sesssion request, label=", request.label);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
_gui_service.for_each_upgraded_session([&] (Gui_session &session,
|
||||||
|
Session::Resources const &amount) {
|
||||||
|
session.upgrade(amount);
|
||||||
|
return Gui_service::Upgrade_response::CONFIRMED;
|
||||||
|
});
|
||||||
|
|
||||||
|
_gui_service.for_each_session_to_close([&] (Gui_session &session) {
|
||||||
|
destroy(_alloc, &session);
|
||||||
|
return Gui_service::Close_response::CLOSED;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Sandboxed_runtime::gen_start_nodes(Xml_generator &xml) const
|
||||||
|
{
|
||||||
|
_views.for_each([&] (View const &view) {
|
||||||
|
view._menu_view_state.gen_start_node(xml); });
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Sandboxed_runtime::View::Menu_view_state::gen_start_node(Xml_generator &xml) const
|
||||||
|
{
|
||||||
|
xml.node("start", [&] () {
|
||||||
|
|
||||||
|
xml.attribute("name", _name);
|
||||||
|
xml.attribute("version", _version);
|
||||||
|
xml.attribute("caps", _caps.value);
|
||||||
|
|
||||||
|
xml.node("resource", [&] () {
|
||||||
|
xml.attribute("name", "RAM");
|
||||||
|
Number_of_bytes const bytes(_ram.value);
|
||||||
|
xml.attribute("quantum", String<64>(bytes)); });
|
||||||
|
|
||||||
|
xml.node("binary", [&] () {
|
||||||
|
xml.attribute("name", "menu_view"); });
|
||||||
|
|
||||||
|
xml.node("config", [&] () {
|
||||||
|
|
||||||
|
xml.node("report", [&] () {
|
||||||
|
xml.attribute("hover", "yes"); });
|
||||||
|
|
||||||
|
xml.node("libc", [&] () {
|
||||||
|
xml.attribute("stderr", "/dev/log"); });
|
||||||
|
|
||||||
|
xml.node("vfs", [&] () {
|
||||||
|
xml.node("tar", [&] () {
|
||||||
|
xml.attribute("name", "menu_view_styles.tar"); });
|
||||||
|
xml.node("dir", [&] () {
|
||||||
|
xml.attribute("name", "dev");
|
||||||
|
xml.node("log", [&] () { });
|
||||||
|
});
|
||||||
|
xml.node("dir", [&] () {
|
||||||
|
xml.attribute("name", "fonts");
|
||||||
|
xml.node("fs", [&] () {
|
||||||
|
xml.attribute("label", "fonts");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
xml.node("route", [&] () {
|
||||||
|
|
||||||
|
xml.node("service", [&] () {
|
||||||
|
xml.attribute("name", "ROM");
|
||||||
|
xml.attribute("label", "dialog");
|
||||||
|
xml.node("local", [&] () { });
|
||||||
|
});
|
||||||
|
|
||||||
|
xml.node("service", [&] () {
|
||||||
|
xml.attribute("name", "Report");
|
||||||
|
xml.attribute("label", "hover");
|
||||||
|
xml.node("local", [&] () { });
|
||||||
|
});
|
||||||
|
|
||||||
|
xml.node("service", [&] () {
|
||||||
|
xml.attribute("name", "Gui");
|
||||||
|
xml.node("local", [&] () { });
|
||||||
|
});
|
||||||
|
|
||||||
|
xml.node("service", [&] () {
|
||||||
|
xml.attribute("name", "File_system");
|
||||||
|
xml.attribute("label", "fonts");
|
||||||
|
xml.node("parent", [&] () {
|
||||||
|
xml.attribute("label", "fonts"); });
|
||||||
|
});
|
||||||
|
|
||||||
|
xml.node("any-service", [&] () {
|
||||||
|
xml.node("parent", [&] () { }); });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Sandboxed_runtime::View::_handle_input_event(Input::Event const &event)
|
||||||
|
{
|
||||||
|
if (event.absolute_motion()) _hover_observable_without_click = true;
|
||||||
|
if (event.touch()) _hover_observable_without_click = false;
|
||||||
|
|
||||||
|
if (click(event) && !_click_seq_number.constructed()) {
|
||||||
|
_click_seq_number.construct(_global_seq_number);
|
||||||
|
_click_delivered = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clack(event))
|
||||||
|
_clack_seq_number.construct(_global_seq_number);
|
||||||
|
|
||||||
|
_try_handle_click_and_clack();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Sandboxed_runtime::View::_handle_hover()
|
||||||
|
{
|
||||||
|
bool const orig_dialog_hovered = _dialog_hovered;
|
||||||
|
|
||||||
|
if (_hover_report_session.constructed())
|
||||||
|
_hover_report_session->with_xml([&] (Xml_node const &hover) {
|
||||||
|
_hover_seq_number = { hover.attribute_value("seq_number", 0U) };
|
||||||
|
_dialog_hovered = (hover.num_sub_nodes() > 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (orig_dialog_hovered != _dialog_hovered || _dialog_hovered)
|
||||||
|
_dialog_rom_session.trigger_update();
|
||||||
|
|
||||||
|
if (_click_delivered && _click_seq_number.constructed()) {
|
||||||
|
_with_dialog_hover([&] (Xml_node const &hover) {
|
||||||
|
Dragged_at at(*_click_seq_number, hover);
|
||||||
|
_dialog.drag(at);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_try_handle_click_and_clack();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void Sandboxed_runtime::View::_try_handle_click_and_clack()
|
||||||
|
{
|
||||||
|
Constructible<Event::Seq_number> &click = _click_seq_number,
|
||||||
|
&clack = _clack_seq_number;
|
||||||
|
|
||||||
|
if (!_click_delivered && click.constructed() && *click == _hover_seq_number) {
|
||||||
|
_with_dialog_hover([&] (Xml_node const &hover) {
|
||||||
|
Clicked_at at(*click, hover);
|
||||||
|
_dialog.click(at);
|
||||||
|
_click_delivered = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (click.constructed() && clack.constructed() && *clack== _hover_seq_number) {
|
||||||
|
_with_dialog_hover([&] (Xml_node const &hover) {
|
||||||
|
/* use click seq number for to associate clack with click */
|
||||||
|
Clacked_at at(*click, hover);
|
||||||
|
_dialog.clack(at);
|
||||||
|
});
|
||||||
|
|
||||||
|
click.destruct();
|
||||||
|
clack.destruct();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Sandboxed_runtime::View::~View()
|
||||||
|
{
|
||||||
|
_gui_sessions.for_each([&] (Gui_session &session) {
|
||||||
|
destroy(_alloc, &session); });
|
||||||
|
}
|
134
repos/gems/src/test/dialog/main.cc
Normal file
134
repos/gems/src/test/dialog/main.cc
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
/*
|
||||||
|
* \brief Test for Genode's dialog API
|
||||||
|
* \author Norman Feske
|
||||||
|
* \date 2023-03-25
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <base/component.h>
|
||||||
|
#include <dialog/runtime.h>
|
||||||
|
#include <dialog/widgets.h>
|
||||||
|
|
||||||
|
namespace Dialog_test {
|
||||||
|
using namespace Dialog;
|
||||||
|
struct Main;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct Dialog_test::Main
|
||||||
|
{
|
||||||
|
Env &_env;
|
||||||
|
Heap _heap { _env.ram(), _env.rm() };
|
||||||
|
|
||||||
|
Runtime _runtime { _env, _heap };
|
||||||
|
|
||||||
|
struct Main_dialog : Top_level_dialog
|
||||||
|
{
|
||||||
|
Hosted<Vbox, Action_button> _inspect { Id { "Inspect" } };
|
||||||
|
Hosted<Vbox, Deferred_action_button> _confirm { Id { "Confirm" } };
|
||||||
|
Hosted<Vbox, Deferred_action_button> _cancel { Id { "Cancel" } };
|
||||||
|
|
||||||
|
enum class Payment { CASH, CARD } _payment = Payment::CASH;
|
||||||
|
|
||||||
|
using Payment_button = Select_button<Payment>;
|
||||||
|
|
||||||
|
Hosted<Vbox, Hbox, Payment_button> _cash { Id { "Cash" }, Payment::CASH },
|
||||||
|
_card { Id { "Card" }, Payment::CARD };
|
||||||
|
|
||||||
|
Main_dialog(Name const &name) : Top_level_dialog(name) { }
|
||||||
|
|
||||||
|
struct Dishes : Widget<Vbox>
|
||||||
|
{
|
||||||
|
Id _items[4] { { "Pizza" }, { "Salad" }, { "Pasta" }, { "Soup" } };
|
||||||
|
|
||||||
|
Id selected_item { };
|
||||||
|
|
||||||
|
void view(Scope<Vbox> &s) const
|
||||||
|
{
|
||||||
|
for (Id const &id : _items) {
|
||||||
|
s.sub_scope<Button>(id, [&] (Scope<Vbox, Button> &s) {
|
||||||
|
|
||||||
|
bool const selected = (id == selected_item),
|
||||||
|
hovered = (s.hovered() && (!s.dragged() || selected));
|
||||||
|
|
||||||
|
if (selected) s.attribute("selected", "yes");
|
||||||
|
if (hovered) s.attribute("hovered", "yes");
|
||||||
|
|
||||||
|
s.sub_scope<Label>(id.value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void click(Clicked_at const &at)
|
||||||
|
{
|
||||||
|
for (Id const &id : _items)
|
||||||
|
if (at.matches<Vbox, Button>(id))
|
||||||
|
selected_item = id;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Hosted<Vbox, Frame, Dishes> _dishes { Id { "dishes" } };
|
||||||
|
|
||||||
|
void view(Scope<> &s) const override
|
||||||
|
{
|
||||||
|
s.sub_scope<Vbox>([&] (Scope<Vbox> &s) {
|
||||||
|
s.sub_scope<Min_ex>(15);
|
||||||
|
|
||||||
|
s.sub_scope<Frame>([&] (Scope<Vbox, Frame> &s) {
|
||||||
|
s.widget(_dishes); });
|
||||||
|
|
||||||
|
if (_dishes.selected_item.valid()) {
|
||||||
|
s.widget(_inspect);
|
||||||
|
s.sub_scope<Hbox>([&] (Scope<Vbox, Hbox> &s) {
|
||||||
|
s.widget(_cash, _payment);
|
||||||
|
s.widget(_card, _payment);
|
||||||
|
});
|
||||||
|
s.widget(_confirm);
|
||||||
|
s.widget(_cancel);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void click(Clicked_at const &at) override
|
||||||
|
{
|
||||||
|
_dishes .propagate(at);
|
||||||
|
_inspect.propagate(at, [&] { log("inspect activated!"); });
|
||||||
|
_confirm.propagate(at);
|
||||||
|
_cancel .propagate(at);
|
||||||
|
_cash .propagate(at, [&] { _payment = Payment::CASH; });
|
||||||
|
_card .propagate(at, [&] { _payment = Payment::CARD; });
|
||||||
|
}
|
||||||
|
|
||||||
|
void clack(Clacked_at const &at) override
|
||||||
|
{
|
||||||
|
_confirm.propagate(at, [&] { log("confirm activated!"); });
|
||||||
|
_cancel .propagate(at, [&] { _dishes.selected_item = { }; });
|
||||||
|
}
|
||||||
|
|
||||||
|
} _main_dialog { "main" };
|
||||||
|
|
||||||
|
Runtime::View _main_view { _runtime, _main_dialog };
|
||||||
|
|
||||||
|
/* handler used to respond to keyboard input */
|
||||||
|
Runtime::Event_handler<Main> _event_handler { _runtime, *this, &Main::_handle_event };
|
||||||
|
|
||||||
|
void _handle_event(Dialog::Event const &event)
|
||||||
|
{
|
||||||
|
log("_handle_event: ", event);
|
||||||
|
}
|
||||||
|
|
||||||
|
Main(Env &env) : _env(env) { }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
void Component::construct(Genode::Env &env)
|
||||||
|
{
|
||||||
|
static Dialog_test::Main main(env);
|
||||||
|
}
|
||||||
|
|
3
repos/gems/src/test/dialog/target.mk
Normal file
3
repos/gems/src/test/dialog/target.mk
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
TARGET = test-dialog
|
||||||
|
SRC_CC = main.cc
|
||||||
|
LIBS += base dialog
|
Loading…
x
Reference in New Issue
Block a user