genode/repos/gems/src/app/menu_view/main.cc

411 lines
9.8 KiB
C++

/*
* \brief Menu view
* \author Norman Feske
* \date 2009-09-11
*/
/*
* Copyright (C) 2014-2017 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.
*/
/* local includes */
#include "widget_factory.h"
#include "button_widget.h"
#include "label_widget.h"
#include "box_layout_widget.h"
#include "root_widget.h"
#include "float_widget.h"
#include "frame_widget.h"
#include "depgraph_widget.h"
/* Genode includes */
#include <input/event.h>
#include <os/reporter.h>
#include <timer_session/connection.h>
/* gems includes */
#include <gems/nitpicker_buffer.h>
#include <gems/vfs.h>
namespace Menu_view { struct Main; }
struct Menu_view::Main
{
Env &_env;
Nitpicker::Connection _nitpicker { _env };
Constructible<Nitpicker_buffer> _buffer { };
Nitpicker::Session::View_handle _view_handle = _nitpicker.create_view();
/**
* Dialog position in screen coordinate space
*/
Point _position { };
/**
* Last pointer position at the time of the most recent hovering report,
* in screen coordinate space.
*/
Point _hovered_position { };
bool _dialog_hovered = false;
Area _configured_size { };
Area _visible_size { };
Area _root_widget_size() const
{
Area const min_size = _root_widget.min_size();
return Area(max(_configured_size.w(), min_size.w()),
max(_configured_size.h(), min_size.h()));
}
Rect _view_geometry { };
void _update_view(Rect geometry)
{
if (_view_geometry.p1() == geometry.p1()
&& _view_geometry.area() == geometry.area())
return;
/* display view behind all others */
typedef Nitpicker::Session::Command Command;
typedef Nitpicker::Session::View_handle View_handle;
_view_geometry = geometry;
_nitpicker.enqueue<Command::Geometry>(_view_handle, _view_geometry);
_nitpicker.enqueue<Command::To_front>(_view_handle, View_handle());
_nitpicker.execute();
}
/**
* Function called on config change or mode change
*/
void _handle_dialog_update();
Signal_handler<Main> _dialog_update_handler = {
_env.ep(), *this, &Main::_handle_dialog_update};
Attached_rom_dataspace _config { _env, "config" };
Heap _heap { _env.ram(), _env.rm() };
struct Vfs_env : Vfs::Env
{
Genode::Env &_env;
Allocator &_alloc;
Vfs::File_system &_vfs;
Vfs_env(Genode::Env &env, Allocator &alloc, Vfs::File_system &vfs)
: _env(env), _alloc(alloc), _vfs(vfs) { }
Genode::Env &env() override { return _env; }
Allocator &alloc() override { return _alloc; }
Vfs::File_system &root_dir() override { return _vfs; }
} _vfs_env;
Directory _root_dir { _vfs_env };
Directory _fonts_dir { _root_dir, "fonts" };
Style_database _styles { _env.ram(), _env.rm(), _heap, _fonts_dir };
Animator _animator { };
Widget_factory _widget_factory { _heap, _styles, _animator };
Root_widget _root_widget { _widget_factory, Xml_node("<dialog/>"), Widget::Unique_id() };
Attached_rom_dataspace _dialog_rom { _env, "dialog" };
Attached_dataspace _input_ds { _env.rm(), _nitpicker.input()->dataspace() };
Widget::Unique_id _last_reported_hovered { };
void _handle_config();
Signal_handler<Main> _config_handler = {
_env.ep(), *this, &Main::_handle_config};
void _handle_input();
Signal_handler<Main> _input_handler = {
_env.ep(), *this, &Main::_handle_input};
/*
* Timer used for animating widgets
*/
struct Frame_timer : Timer::Connection
{
enum { PERIOD = 10 };
Genode::uint64_t curr_frame() const { return elapsed_ms() / PERIOD; }
void schedule() { trigger_once((Genode::uint64_t)Frame_timer::PERIOD*1000); }
Frame_timer(Env &env) : Timer::Connection(env) { }
} _timer { _env };
void _handle_frame_timer();
Signal_handler<Main> _frame_timer_handler = {
_env.ep(), *this, &Main::_handle_frame_timer};
Genode::Reporter _hover_reporter = { _env, "hover" };
void _update_hover_report();
bool _schedule_redraw = false;
/**
* Frame of last call of 'handle_frame_timer'
*/
Genode::uint64_t _last_frame = 0;
/**
* Number of frames between two redraws
*/
enum { REDRAW_PERIOD = 2 };
/**
* Counter used for triggering redraws. Incremented in each frame-timer
* period, wraps at 'REDRAW_PERIOD'. The redraw is performed when the
* counter wraps.
*/
unsigned _frame_cnt = 0;
Main(Env &env, Vfs::File_system &libc_vfs)
:
_env(env), _vfs_env(_env, _heap, libc_vfs)
{
_dialog_rom.sigh(_dialog_update_handler);
_config.sigh(_config_handler);
_nitpicker.input()->sigh(_input_handler);
_timer.sigh(_frame_timer_handler);
/* apply initial configuration */
_handle_config();
}
};
void Menu_view::Main::_update_hover_report()
{
if (!_hover_reporter.enabled())
return;
if (!_dialog_hovered) {
Genode::Reporter::Xml_generator xml(_hover_reporter, [&] () { });
return;
}
Widget::Unique_id const new_hovered = _root_widget.hovered(_hovered_position);
if (_last_reported_hovered != new_hovered) {
Genode::Reporter::Xml_generator xml(_hover_reporter, [&] () {
_root_widget.gen_hover_model(xml, _hovered_position); });
_last_reported_hovered = new_hovered;
}
}
void Menu_view::Main::_handle_dialog_update()
{
try {
Xml_node const config = _config.xml();
_position = Decorator::point_attribute(config);
_configured_size = Area(config.attribute_value("width", 0UL),
config.attribute_value("height", 0UL));
} catch (...) { }
_dialog_rom.update();
Xml_node dialog = _dialog_rom.xml();
if (dialog.has_type("empty"))
return;
_root_widget.update(dialog);
_root_widget.size(_root_widget_size());
_update_hover_report();
_schedule_redraw = true;
/*
* If we have not processed a period for at least one frame, perform the
* processing immediately. This way, we avoid latencies when the dialog
* model is updated sporadically.
*/
Genode::uint64_t const curr_frame = _timer.curr_frame();
if (curr_frame != _last_frame) {
if (curr_frame - _last_frame > 10)
_last_frame = curr_frame;
_handle_frame_timer();
} else {
_timer.schedule();
}
}
void Menu_view::Main::_handle_config()
{
_config.update();
try {
_hover_reporter.enabled(_config.xml().sub_node("report")
.attribute_value("hover", false));
} catch (...) {
_hover_reporter.enabled(false);
}
_handle_dialog_update();
}
void Menu_view::Main::_handle_input()
{
Point const orig_hovered_position = _hovered_position;
bool const orig_dialog_hovered = _dialog_hovered;
_nitpicker.input()->for_each_event([&] (Input::Event const &ev) {
ev.handle_absolute_motion([&] (int x, int y) {
_dialog_hovered = true;
_hovered_position = Point(x, y) - _position;
});
/*
* Reset hover model when losing the focus
*/
if (ev.hover_leave()) {
_dialog_hovered = false;
_hovered_position = Point();
}
});
bool const hover_changed = orig_dialog_hovered != _dialog_hovered
|| orig_hovered_position != _hovered_position;
if (hover_changed)
_update_hover_report();
}
void Menu_view::Main::_handle_frame_timer()
{
_frame_cnt++;
Genode::uint64_t const curr_frame = _timer.curr_frame();
if (_animator.active()) {
Genode::uint64_t const passed_frames = max(curr_frame - _last_frame, 4U);
if (passed_frames > 0) {
for (Genode::uint64_t i = 0; i < passed_frames; i++)
_animator.animate();
_schedule_redraw = true;
}
}
_last_frame = curr_frame;
if (_schedule_redraw && _frame_cnt >= REDRAW_PERIOD) {
_frame_cnt = 0;
Area const size = _root_widget_size();
unsigned const buffer_w = _buffer.constructed() ? _buffer->size().w() : 0,
buffer_h = _buffer.constructed() ? _buffer->size().h() : 0;
Area const max_size(max(buffer_w, size.w()), max(buffer_h, size.h()));
bool const size_increased = (max_size.w() > buffer_w)
|| (max_size.h() > buffer_h);
if (!_buffer.constructed() || size_increased)
_buffer.construct(_nitpicker, max_size, _env.ram(), _env.rm());
else
_buffer->reset_surface();
_root_widget.position(Point(0, 0));
// XXX restrict redraw to dirty regions
// don't perform a full dialog update
_buffer->apply_to_surface([&] (Surface<Pixel_rgb888> &pixel,
Surface<Pixel_alpha8> &alpha) {
_root_widget.draw(pixel, alpha, Point(0, 0));
});
_buffer->flush_surface();
_nitpicker.framebuffer()->refresh(0, 0, _buffer->size().w(), _buffer->size().h());
_update_view(Rect(_position, size));
_schedule_redraw = false;
}
/*
* Deactivate timer periods when idle, activate timer when an animation is
* in progress or a redraw is pending.
*/
bool const redraw_pending = _schedule_redraw && _frame_cnt != 0;
if (_animator.active() || redraw_pending)
_timer.schedule();
}
Menu_view::Widget *
Menu_view::Widget_factory::create(Xml_node node)
{
Widget *w = nullptr;
Widget::Unique_id const unique_id(++_unique_id_cnt);
if (node.has_type("label")) w = new (alloc) Label_widget (*this, node, unique_id);
if (node.has_type("button")) w = new (alloc) Button_widget (*this, node, unique_id);
if (node.has_type("vbox")) w = new (alloc) Box_layout_widget (*this, node, unique_id);
if (node.has_type("hbox")) w = new (alloc) Box_layout_widget (*this, node, unique_id);
if (node.has_type("frame")) w = new (alloc) Frame_widget (*this, node, unique_id);
if (node.has_type("float")) w = new (alloc) Float_widget (*this, node, unique_id);
if (node.has_type("depgraph")) w = new (alloc) Depgraph_widget (*this, node, unique_id);
if (!w) {
Genode::error("unknown widget type '", node.type(), "'");
return 0;
}
return w;
}
/*
* Silence debug messages
*/
extern "C" void _sigprocmask() { }
void Libc::Component::construct(Libc::Env &env)
{
static Menu_view::Main main(env, env.vfs());
}