388 lines
8.5 KiB
C++
388 lines
8.5 KiB
C++
/*
|
|
* \brief Menu dialog
|
|
* \author Norman Feske
|
|
* \date 2014-10-04
|
|
*/
|
|
|
|
/*
|
|
* Copyright (C) 2014 Genode Labs GmbH
|
|
*
|
|
* This file is part of the Genode OS framework, which is distributed
|
|
* under the terms of the GNU General Public License version 2.
|
|
*/
|
|
|
|
#ifndef _MENU_DIALOG_H_
|
|
#define _MENU_DIALOG_H_
|
|
|
|
/* Genode includes */
|
|
#include <timer_session/connection.h>
|
|
|
|
/* local includes */
|
|
#include <fading_dialog.h>
|
|
#include <subsystem_manager.h>
|
|
#include <context_dialog.h>
|
|
|
|
namespace Launcher { struct Menu_dialog; }
|
|
|
|
|
|
class Launcher::Menu_dialog : Input_event_handler, Dialog_generator,
|
|
Hover_handler, Dialog_model,
|
|
Context_dialog::Response_handler
|
|
{
|
|
private:
|
|
|
|
typedef String<128> Title;
|
|
|
|
struct Element : List<Element>::Element
|
|
{
|
|
Label label;
|
|
Title title;
|
|
|
|
bool hovered = false;
|
|
bool touched = false;
|
|
bool running = false;
|
|
|
|
Element(Xml_node node)
|
|
:
|
|
label(Decorator::string_attribute(node, "name", Label(""))),
|
|
title(Decorator::string_attribute(node, "title", Title(label.string())))
|
|
{ }
|
|
};
|
|
|
|
List<Element> _elements;
|
|
|
|
void _generate_dialog_elements(Xml_generator &xml)
|
|
{
|
|
for (Element const *e = _elements.first(); e; e = e->next()) {
|
|
|
|
xml.node("button", [&] () {
|
|
xml.attribute("name", e->label.string());
|
|
|
|
if ((e->hovered && !_click_in_progress)
|
|
|| (e->hovered && e->touched))
|
|
xml.attribute("hovered", "yes");
|
|
|
|
if (e->running || e->touched)
|
|
xml.attribute("selected", "yes");
|
|
|
|
xml.node("label", [&] () {
|
|
xml.attribute("text", e->title.string());
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
class Lookup_failed { };
|
|
|
|
Element const &_lookup_const(Label const &label) const
|
|
{
|
|
for (Element const *e = _elements.first(); e; e = e->next())
|
|
if (e->label == label)
|
|
return *e;
|
|
|
|
throw Lookup_failed();
|
|
}
|
|
|
|
Element &_lookup(Label const &label)
|
|
{
|
|
for (Element *e = _elements.first(); e; e = e->next())
|
|
if (e->label == label)
|
|
return *e;
|
|
|
|
throw Lookup_failed();
|
|
}
|
|
|
|
Fading_dialog::Position _position { 32, 32 };
|
|
|
|
Timer::Connection _timer;
|
|
Subsystem_manager &_subsystem_manager;
|
|
Nitpicker::Session &_nitpicker;
|
|
Fading_dialog _dialog;
|
|
|
|
Rect _hovered_rect;
|
|
|
|
unsigned _key_cnt = 0;
|
|
Label _clicked;
|
|
bool _click_in_progress = false;
|
|
|
|
Signal_rpc_member<Menu_dialog> _timer_dispatcher;
|
|
|
|
Label _context_subsystem;
|
|
Context_dialog _context_dialog;
|
|
|
|
Label _hovered() const
|
|
{
|
|
for (Element const *e = _elements.first(); e; e = e->next())
|
|
if (e->hovered)
|
|
return e->label;
|
|
|
|
return Label("");
|
|
}
|
|
|
|
bool _running(Label const &label) const
|
|
{
|
|
try { return _lookup_const(label).running; }
|
|
catch (Lookup_failed) { return false; }
|
|
}
|
|
|
|
void _running(Label const &label, bool running)
|
|
{
|
|
try { _lookup(label).running = running; }
|
|
catch (Lookup_failed) { }
|
|
}
|
|
|
|
void _touch(Label const &label)
|
|
{
|
|
for (Element *e = _elements.first(); e; e = e->next())
|
|
e->touched = (e->label == label);
|
|
}
|
|
|
|
/**
|
|
* Lookup subsystem in config
|
|
*/
|
|
static Xml_node _subsystem(Xml_node config, char const *name)
|
|
{
|
|
Xml_node node = config.sub_node("subsystem");
|
|
for (;; node = node.next("subsystem")) {
|
|
if (node.attribute("name").has_value(name))
|
|
return node;
|
|
}
|
|
}
|
|
|
|
void _start(Label const &label)
|
|
{
|
|
try {
|
|
_subsystem_manager.start(_subsystem(config()->xml_node(),
|
|
label.string()));
|
|
_running(label, true);
|
|
|
|
dialog_changed();
|
|
|
|
} catch (Xml_node::Nonexistent_sub_node) {
|
|
PERR("no subsystem config found for \"%s\"", label.string());
|
|
} catch (Subsystem_manager::Invalid_config) {
|
|
PERR("invalid subsystem configuration for \"%s\"", label.string());
|
|
}
|
|
}
|
|
|
|
void _kill(Label const &label)
|
|
{
|
|
_subsystem_manager.kill(label.string());
|
|
_running(label, false);
|
|
dialog_changed();
|
|
_dialog.update();
|
|
|
|
_context_dialog.visible(false);
|
|
}
|
|
|
|
void _hide(Label const &label)
|
|
{
|
|
_nitpicker.session_control(selector(label.string()),
|
|
Nitpicker::Session::SESSION_CONTROL_HIDE);
|
|
|
|
_context_dialog.visible(false);
|
|
}
|
|
|
|
void _handle_timer(unsigned)
|
|
{
|
|
if (_click_in_progress && _hovered() == _clicked) {
|
|
|
|
_touch("");
|
|
|
|
Fading_dialog::Position position(_hovered_rect.p2().x(),
|
|
_hovered_rect.p1().y() - 44);
|
|
_context_subsystem = _clicked;
|
|
_context_dialog.position(_position + position);
|
|
_context_dialog.visible(true);
|
|
}
|
|
|
|
_click_in_progress = false;
|
|
}
|
|
|
|
public:
|
|
|
|
Menu_dialog(Server::Entrypoint &ep, Cap_session &cap, Ram_session &ram,
|
|
Report_rom_slave &report_rom_slave,
|
|
Subsystem_manager &subsystem_manager,
|
|
Nitpicker::Session &nitpicker)
|
|
:
|
|
_subsystem_manager(subsystem_manager),
|
|
_nitpicker(nitpicker),
|
|
_dialog(ep, cap, ram, report_rom_slave, "menu_dialog", "menu_hover",
|
|
*this, *this, *this, *this,
|
|
_position),
|
|
_timer_dispatcher(ep, *this, &Menu_dialog::_handle_timer),
|
|
_context_dialog(ep, cap, ram, report_rom_slave, *this)
|
|
{
|
|
_timer.sigh(_timer_dispatcher);
|
|
}
|
|
|
|
/**
|
|
* Dialog_generator interface
|
|
*/
|
|
void generate_dialog(Xml_generator &xml) override
|
|
{
|
|
xml.node("frame", [&] () {
|
|
xml.node("vbox", [&] () {
|
|
_generate_dialog_elements(xml);
|
|
});
|
|
});
|
|
}
|
|
|
|
Rect _hovered_button_rect(Xml_node hover) const
|
|
{
|
|
Point p(0, 0);
|
|
|
|
for (;; hover = hover.sub_node()) {
|
|
|
|
p = p + Point(point_attribute(hover));
|
|
|
|
if (hover.has_type("button"))
|
|
return Rect(p, area_attribute(hover));
|
|
|
|
if (!hover.num_sub_nodes())
|
|
break;
|
|
}
|
|
|
|
return Rect();
|
|
}
|
|
|
|
/**
|
|
* Hover_handler interface
|
|
*/
|
|
void hover_changed(Xml_node hover) override
|
|
{
|
|
Label const old_hovered = _hovered();
|
|
|
|
for (Element *e = _elements.first(); e; e = e->next())
|
|
e->hovered = false;
|
|
|
|
try {
|
|
Xml_node button = hover.sub_node("dialog")
|
|
.sub_node("frame")
|
|
.sub_node("vbox")
|
|
.sub_node("button");
|
|
|
|
for (Element *e = _elements.first(); e; e = e->next()) {
|
|
|
|
Label const label =
|
|
Decorator::string_attribute(button, "name", Label(""));
|
|
|
|
if (e->label == label) {
|
|
e->hovered = true;
|
|
|
|
_hovered_rect = _hovered_button_rect(hover);
|
|
}
|
|
}
|
|
} catch (Xml_node::Nonexistent_sub_node) { }
|
|
|
|
Label const new_hovered = _hovered();
|
|
|
|
if (old_hovered != new_hovered)
|
|
dialog_changed();
|
|
}
|
|
|
|
/**
|
|
* Input_event_handler interface
|
|
*/
|
|
bool handle_input_event(Input::Event const &ev) override
|
|
{
|
|
if (ev.type() == Input::Event::MOTION)
|
|
return true;
|
|
|
|
if (ev.type() == Input::Event::PRESS) _key_cnt++;
|
|
if (ev.type() == Input::Event::RELEASE) _key_cnt--;
|
|
|
|
if (ev.type() == Input::Event::PRESS
|
|
&& ev.keycode() == Input::BTN_LEFT
|
|
&& _key_cnt == 1) {
|
|
|
|
_context_dialog.visible(false);
|
|
|
|
Label const hovered = _hovered();
|
|
|
|
_click_in_progress = true;
|
|
_clicked = hovered;
|
|
_touch(hovered);
|
|
|
|
enum { CONTEXT_DELAY = 500 };
|
|
|
|
if (_running(hovered)) {
|
|
_nitpicker.session_control(selector(hovered.string()),
|
|
Nitpicker::Session::SESSION_CONTROL_TO_FRONT);
|
|
_nitpicker.session_control(selector(hovered.string()),
|
|
Nitpicker::Session::SESSION_CONTROL_SHOW);
|
|
_timer.trigger_once(CONTEXT_DELAY*1000);
|
|
}
|
|
}
|
|
|
|
if (ev.type() == Input::Event::RELEASE
|
|
&& _click_in_progress && _key_cnt == 0) {
|
|
|
|
Label const hovered = _hovered();
|
|
|
|
if (_clicked == hovered) {
|
|
|
|
if (!_running(hovered))
|
|
_start(hovered);
|
|
} else {
|
|
_touch("");
|
|
}
|
|
|
|
_clicked = Label("");
|
|
_click_in_progress = false;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Context_dialog::Response_handler interface
|
|
*/
|
|
void handle_context_kill() override
|
|
{
|
|
_kill(_context_subsystem);
|
|
}
|
|
|
|
/**
|
|
* Context_dialog::Response_handler interface
|
|
*/
|
|
void handle_context_hide() override
|
|
{
|
|
_hide(_context_subsystem);
|
|
}
|
|
|
|
void visible(bool visible)
|
|
{
|
|
_dialog.visible(visible);
|
|
|
|
if (!visible)
|
|
_context_dialog.visible(false);
|
|
}
|
|
|
|
void update()
|
|
{
|
|
if (_elements.first()) {
|
|
PERR("subsequent updates are not supported");
|
|
return;
|
|
}
|
|
|
|
Element *last = nullptr;
|
|
|
|
Xml_node subsystems = config()->xml_node();
|
|
|
|
subsystems.for_each_sub_node("subsystem",
|
|
[&] (Xml_node subsystem)
|
|
{
|
|
Element * const e = new (env()->heap()) Element(subsystem);
|
|
|
|
_elements.insert(e, last);
|
|
last = e;
|
|
});
|
|
|
|
_dialog.update();
|
|
}
|
|
};
|
|
|
|
#endif /* _MENU_DIALOG_H_ */
|