gems: new menu-view application

The menu view generates a simple dialog of widgets and reports the
hovered element. It is meant to be embedded into applications that
require simple GUIs but don't want to deal with the pecularities of
a full-blown widget set.
This commit is contained in:
Norman Feske 2014-09-12 20:34:49 +02:00
parent 40aadb8601
commit cc303c4671
13 changed files with 1653 additions and 0 deletions

View File

@ -0,0 +1,162 @@
#
# Build
#
if {![have_spec linux]} {
puts "Runs on Linux only"
exit 0
}
set build_components {
core init drivers/timer drivers/framebuffer/sdl
server/dynamic_rom server/nitpicker
app/pointer app/menu_view
app/scout
}
build $build_components
create_boot_directory
#
# Generate config
#
append config {
<config>
<parent-provides>
<service name="ROM"/>
<service name="RAM"/>
<service name="RM"/>
<service name="LOG"/>
<service name="IRQ"/>
<service name="IO_MEM"/>
<service name="IO_PORT"/>
<service name="CAP"/>
<service name="SIGNAL"/>
</parent-provides>
<default-route>
<any-service> <parent/> <any-child/> </any-service>
</default-route>
<start name="fb_sdl">
<resource name="RAM" quantum="4M"/>
<provides>
<service name="Input"/>
<service name="Framebuffer"/>
</provides>
</start>
<start name="timer">
<resource name="RAM" quantum="1M"/>
<provides><service name="Timer"/></provides>
</start>
<start name="nitpicker">
<resource name="RAM" quantum="1M"/>
<provides><service name="Nitpicker"/></provides>
<config>
<domain name="pointer" layer="1" xray="no" origin="pointer" />
<domain name="" layer="3" />
<policy label="pointer" domain="pointer"/>
<policy label="" domain=""/>
<global-key name="KEY_SCROLLLOCK" operation="xray" />
<global-key name="KEY_SYSRQ" operation="kill" />
<global-key name="KEY_PRINT" operation="kill" />
<global-key name="KEY_F11" operation="kill" />
<global-key name="KEY_F12" operation="xray" />
</config>
</start>
<start name="pointer">
<resource name="RAM" quantum="1M"/>
<route>
<service name="Nitpicker"> <child name="nitpicker" /> </service>
<any-service> <parent/> <any-child/> </any-service>
</route>
</start>
<start name="dynamic_rom">
<resource name="RAM" quantum="4M"/>
<provides><service name="ROM"/></provides>
<config verbose="yes">
<rom name="dialog">
<inline description="example menu">
<dialog>
<frame>
<vbox>
<button name="virtualbox">
<label text="VirtualBox"/>
</button>
<button name="toolchain" hovered="yes">
<label text="Tool chain"/>
</button>
<button name="log" hovered="yes" selected="yes">
<label text="Log window"/>
</button>
<button name="config" selected="yes">
<label text="Configuration"/>
</button>
</vbox>
</frame>
</dialog>
</inline>
<sleep milliseconds="2000" />
<inline description="example menu">
<dialog>
<frame>
<vbox>
<button name="virtualbox" hovered="yes">
<label text="VirtualBox"/>
</button>
<button name="toolchain">
<label text="Tool chain"/>
</button>
<button name="log" selected="yes">
<label text="Log window"/>
</button>
<button name="config" selected="yes" hovered="yes">
<label text="Configuration"/>
</button>
</vbox>
</frame>
</dialog>
</inline>
<sleep milliseconds="2000" />
</rom>
</config>
</start>
<start name="menu_view">
<resource name="RAM" quantum="5M"/>
<config xpos="200" ypos="100">
<libc>
<vfs>
<tar name="menu_view_styles.tar" />
</vfs>
</libc>
</config>
<route>
<service name="ROM"> <if-arg key="label" value="dialog"/>
<child name="dynamic_rom" />
</service>
<any-service> <parent/> <any-child/> </any-service>
</route>
</start>
<start name="scout">
<resource name="RAM" quantum="64M" />
</start>
</config>}
install_config $config
#
# Boot modules
#
# generic modules
set boot_modules {
core init timer dynamic_rom fb_sdl nitpicker pointer menu_view
ld.lib.so libpng.lib.so libc.lib.so libm.lib.so zlib.lib.so
menu_view_styles.tar
scout
}
build_boot_image $boot_modules
run_genode_until forever

View File

@ -0,0 +1,75 @@
/*
* \brief Functor for converting pixel formats by applying dithering
* \author Norman Feske
* \date 2014-09-10
*/
/*
* 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 _DITHER_PAINTER_H_
#define _DITHER_PAINTER_H_
#include <util/dither_matrix.h>
#include <os/surface.h>
struct Dither_painter
{
/*
* Surface and texture must have the same size
*/
template <typename DST_PT, typename SRC_PT>
static inline void paint(Genode::Surface<DST_PT> &surface,
Genode::Texture<SRC_PT> const &texture)
{
if (surface.size() != texture.size()) return;
Genode::Surface_base::Rect const clipped = surface.clip();
if (!clipped.valid()) return;
unsigned const offset = surface.size().w()*clipped.y1() + clipped.x1();
DST_PT *dst, *dst_line = surface.addr() + offset;
SRC_PT const *src_pixel, *src_pixel_line = texture.pixel() + offset;
unsigned char const *src_alpha, *src_alpha_line = texture.alpha() + offset;
unsigned const line_len = surface.size().w();
for (int y = clipped.y1(), h = clipped.h() ; h--; y++) {
src_pixel = src_pixel_line;
src_alpha = src_alpha_line;
dst = dst_line;
for (int x = clipped.x1(), w = clipped.w(); w--; x++) {
int const v = Genode::Dither_matrix::value(x, y) >> 4;
SRC_PT const pixel = *src_pixel++;
unsigned char const alpha = *src_alpha++;
int const r = pixel.r() - v;
int const g = pixel.g() - v;
int const b = pixel.b() - v;
int const a = alpha ? (int)alpha - v : 0;
using Genode::min;
using Genode::max;
*dst++ = DST_PT(max(0, r), max(0, g), max(0, b), max(0, a));
}
src_pixel_line += line_len;
src_alpha_line += line_len;
dst_line += line_len;
}
}
};
#endif /* _DITHER_PAINTER_H_ */

View File

@ -0,0 +1,442 @@
/*
* \brief Menu view
* \author Norman Feske
* \date 2009-09-11
*/
/*
* 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.
*/
/* local includes */
#include "widgets.h"
/* Genode includes */
#include <input/event.h>
#include <os/reporter.h>
struct Menu_view::Main
{
Nitpicker::Connection nitpicker;
/*
* The back buffer (surface) is RGB888 with interleaved alpha values.
*/
struct Buffer
{
Nitpicker::Connection &nitpicker;
Framebuffer::Mode const mode;
/**
* Return dataspace capability for virtual framebuffer
*/
Dataspace_capability _ds_cap(Nitpicker::Connection &nitpicker)
{
/* setup virtual framebuffer mode */
nitpicker.buffer(mode, true);
if (mode.format() != Framebuffer::Mode::RGB565) {
PWRN("Color mode %d not supported\n", (int)mode.format());
return Dataspace_capability();
}
return nitpicker.framebuffer()->dataspace();
}
Attached_dataspace fb_ds { _ds_cap(nitpicker) };
size_t pixel_surface_num_bytes() const
{
return size().count()*sizeof(Pixel_rgb888);
}
size_t alpha_surface_num_bytes() const
{
return size().count();
}
Attached_ram_dataspace pixel_surface_ds { env()->ram_session(), pixel_surface_num_bytes() };
Attached_ram_dataspace alpha_surface_ds { env()->ram_session(), alpha_surface_num_bytes() };
/**
* Constructor
*/
Buffer(Nitpicker::Connection &nitpicker, Area size)
:
nitpicker(nitpicker),
mode(size.w(), size.h(), nitpicker.mode().format())
{ }
/**
* Return size of virtual framebuffer
*/
Surface_base::Area size() const
{
return Surface_base::Area(mode.width(), mode.height());
}
/**
* Return back buffer as RGB888 painting surface
*/
Surface<Pixel_rgb888> pixel_surface()
{
return Surface<Pixel_rgb888>(pixel_surface_ds.local_addr<Pixel_rgb888>(), size());
}
Surface<Pixel_alpha8> alpha_surface()
{
return Surface<Pixel_alpha8>(alpha_surface_ds.local_addr<Pixel_alpha8>(), size());
}
void reset_surface()
{
size_t const num_pixels = pixel_surface().size().count();
Genode::memset(alpha_surface().addr(), 0, num_pixels);
Genode::memset(pixel_surface().addr(), 0, num_pixels*sizeof(Pixel_rgb888));
}
template <typename DST_PT, typename SRC_PT>
void _convert_back_to_front(DST_PT *front_base,
Texture<SRC_PT> const &texture,
Rect const clip_rect)
{
Surface<DST_PT> surface(front_base, size());
surface.clip(clip_rect);
Dither_painter::paint(surface, texture);
}
void _update_input_mask()
{
unsigned const num_pixels = size().count();
unsigned char * const alpha_base = fb_ds.local_addr<unsigned char>()
+ mode.bytes_per_pixel()*num_pixels;
unsigned char * const input_base = alpha_base + num_pixels;
unsigned char const *src = alpha_base;
unsigned char *dst = input_base;
/*
* Set input mask for all pixels where the alpha value is above a
* given threshold. The threshold is defines such that typical
* drop shadows are below the value.
*/
unsigned char const threshold = 100;
for (unsigned i = 0; i < num_pixels; i++)
*dst++ = (*src++) > threshold;
}
void flush_surface()
{
/* represent back buffer as texture */
Texture<Pixel_rgb888>
texture(pixel_surface_ds.local_addr<Pixel_rgb888>(),
alpha_surface_ds.local_addr<unsigned char>(),
size());
// XXX track dirty rectangles
Rect const clip_rect(Point(0, 0), size());
Pixel_rgb565 *pixel_base = fb_ds.local_addr<Pixel_rgb565>();
Pixel_alpha8 *alpha_base = fb_ds.local_addr<Pixel_alpha8>()
+ mode.bytes_per_pixel()*size().count();
_convert_back_to_front(pixel_base, texture, clip_rect);
_convert_back_to_front(alpha_base, texture, clip_rect);
_update_input_mask();
}
};
Lazy_volatile_object<Buffer> buffer;
Nitpicker::Session::View_handle view_handle = nitpicker.create_view();
Point position;
Rect _view_geometry;
void _update_view()
{
if (_view_geometry.p1() == position
&& _view_geometry.area() == buffer->size())
return;
/* display view behind all others */
typedef Nitpicker::Session::Command Command;
_view_geometry = Rect(position, buffer->size());
nitpicker.enqueue<Command::Geometry>(view_handle, _view_geometry);
nitpicker.enqueue<Command::To_front>(view_handle);
nitpicker.execute();
}
Signal_receiver &sig_rec;
/**
* Function called on config change or mode change
*/
void handle_dialog_update(unsigned);
Signal_dispatcher<Main> dialog_update_dispatcher = {
sig_rec, *this, &Main::handle_dialog_update};
Style_database styles;
Animator animator;
Widget_factory widget_factory { *env()->heap(), styles, animator };
Root_widget root_widget { widget_factory, Xml_node("<dialog/>"), Widget::Unique_id() };
Attached_rom_dataspace dialog_rom { "dialog" };
Attached_dataspace input_ds { nitpicker.input()->dataspace() };
Widget::Unique_id hovered;
void handle_config(unsigned);
Signal_dispatcher<Main> config_dispatcher = {
sig_rec, *this, &Main::handle_config};
void handle_input(unsigned);
Signal_dispatcher<Main> input_dispatcher = {
sig_rec, *this, &Main::handle_input};
/*
* Timer used for animating widgets
*/
struct Frame_timer : Timer::Connection
{
enum { PERIOD = 10 };
unsigned curr_frame() const { return elapsed_ms() / PERIOD; }
void schedule() { trigger_once(Frame_timer::PERIOD*1000); }
} timer;
void handle_frame_timer(unsigned);
Signal_dispatcher<Main> frame_timer_dispatcher = {
sig_rec, *this, &Main::handle_frame_timer};
Genode::Reporter hover_reporter = { "hover" };
bool schedule_redraw = false;
/**
* Frame of last call of 'handle_frame_timer'
*/
unsigned last_frame = 0;
/**
* Number of frames between two redraws
*/
enum { REDRAW_PERIOD = 4 };
/**
* 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(Signal_receiver &sig_rec) : sig_rec(sig_rec)
{
dialog_rom.sigh(dialog_update_dispatcher);
config()->sigh(config_dispatcher);
nitpicker.input()->sigh(input_dispatcher);
timer.sigh(frame_timer_dispatcher);
/* apply initial configuration */
handle_config(0);
}
};
void Menu_view::Main::handle_dialog_update(unsigned)
{
try {
position = Decorator::point_attribute(config()->xml_node());
} catch (...) { }
dialog_rom.update();
try {
Xml_node dialog_xml(dialog_rom.local_addr<char>());
root_widget.update(dialog_xml);
} catch (...) {
PERR("failed to construct widget tree");
}
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.
*/
if (timer.curr_frame() != last_frame)
handle_frame_timer(0);
else
timer.schedule();
}
void Menu_view::Main::handle_config(unsigned)
{
config()->reload();
try {
hover_reporter.enabled(config()->xml_node().sub_node("report")
.attribute("hover")
.has_value("yes"));
} catch (...) {
hover_reporter.enabled(false);
}
handle_dialog_update(0);
}
void Menu_view::Main::handle_input(unsigned)
{
Input::Event const *ev_buf = input_ds.local_addr<Input::Event>();
unsigned const num_events = nitpicker.input()->flush();
for (unsigned i = 0; i < num_events; i++) {
Input::Event ev = ev_buf[i];
if (ev.is_absolute_motion()) {
Point const at = Point(ev.ax(), ev.ay()) - position;
Widget::Unique_id const new_hovered = root_widget.hovered(at);
if (hovered != new_hovered) {
if (hover_reporter.is_enabled()) {
Genode::Reporter::Xml_generator xml(hover_reporter, [&] () {
root_widget.gen_hover_model(xml, at);
});
}
hovered = new_hovered;
}
}
/*
* Reset hover model when losing the focus
*/
if ((ev.type() == Input::Event::FOCUS && ev.code() == 0)
|| (ev.type() == Input::Event::LEAVE)) {
hovered = Widget::Unique_id();
if (hover_reporter.is_enabled()) {
Genode::Reporter::Xml_generator xml(hover_reporter, [&] () { });
}
}
}
}
void Menu_view::Main::handle_frame_timer(unsigned)
{
frame_cnt++;
unsigned const curr_frame = timer.curr_frame();
if (animator.active()) {
unsigned const passed_frames = curr_frame - last_frame;
if (passed_frames > 0) {
for (unsigned 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 old_size = buffer.is_constructed() ? buffer->size() : Area();
Area const size = root_widget.min_size();
if (!buffer.is_constructed() || size != old_size)
buffer.construct(nitpicker, size);
else
buffer->reset_surface();
root_widget.size(size);
root_widget.position(Point(0, 0));
Surface<Pixel_rgb888> pixel_surface = buffer->pixel_surface();
Surface<Pixel_alpha8> alpha_surface = buffer->alpha_surface();
// XXX restrict redraw to dirty regions
// don't perform a full dialog update
root_widget.draw(pixel_surface, alpha_surface, Point(0, 0));
buffer->flush_surface();
nitpicker.framebuffer()->refresh(0, 0, buffer->size().w(), buffer->size().h());
_update_view();
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();
}
/*
* Silence debug messages
*/
extern "C" void _sigprocmask() { }
int main(int argc, char **argv)
{
static Genode::Signal_receiver sig_rec;
static Menu_view::Main application(sig_rec);
/* process incoming signals */
for (;;) {
using namespace Genode;
Signal sig = sig_rec.wait_for_signal();
Signal_dispatcher_base *dispatcher =
dynamic_cast<Signal_dispatcher_base *>(sig.context());
if (dispatcher)
dispatcher->dispatch(sig.num());
}
}

View File

@ -0,0 +1,177 @@
/*
* \brief Menu view
* \author Norman Feske
* \date 2009-09-11
*/
/*
* 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 _STYLE_DATABASE_H_
#define _STYLE_DATABASE_H_
/* gems includes */
#include <gems/file.h>
#include <gems/png_image.h>
/* local includes */
#include "types.h"
namespace Menu_view { struct Style_database; }
class Menu_view::Style_database
{
private:
enum { PATH_MAX_LEN = 200 };
struct Texture_entry : List<Texture_entry>::Element
{
String<PATH_MAX_LEN> path;
File png_file;
Png_image png_image;
Texture<Pixel_rgb888> &texture;
/**
* Constructor
*
* \throw Reading_failed
*/
Texture_entry(char const *path, Allocator &alloc)
:
path(path),
png_file(path, alloc),
png_image(png_file.data<void>()),
texture(*png_image.texture<Pixel_rgb888>())
{ }
};
struct Font_entry : List<Font_entry>::Element
{
String<PATH_MAX_LEN> path;
File tff_file;
Text_painter::Font font;
/**
* Constructor
*
* \throw Reading_failed
*/
Font_entry(char const *path, Allocator &alloc)
:
path(path),
tff_file(path, alloc),
font(tff_file.data<char>())
{ }
};
/*
* The list is mutable because it is populated as a side effect of
* calling the const lookup function.
*/
List<Texture_entry> mutable _textures;
List<Font_entry> mutable _fonts;
template <typename T>
T const *_lookup(List<T> &list, char const *path) const
{
for (T const *e = list.first(); e; e = e->next())
if (Genode::strcmp(e->path.string(), path) == 0)
return e;
return 0;
}
typedef String<256> Path;
/*
* Assemble path name 'styles/<widget>/<style>/<name>.<extension>'
*/
static Path _construct_path(Xml_node node,
char const *name, char const *extension)
{
char widget[64];
node.type_name(widget, sizeof(widget));
char style[PATH_MAX_LEN];
style[0] = 0;
try {
node.attribute("style").value(style, sizeof(style));
}
catch (Xml_node::Nonexistent_attribute) {
/* no style defined */
Genode::strncpy(style, "default", sizeof(style));
}
char path[PATH_MAX_LEN];
path[0] = 0;
Genode::snprintf(path, sizeof(path), "/styles/%s/%s/%s.%s",
widget, style, name, extension);
return Path(path);
}
public:
Texture<Pixel_rgb888> const *texture(Xml_node node, char const *png_name) const
{
Path const path = _construct_path(node, png_name, "png");
if (Texture_entry const *e = _lookup(_textures, path.string()))
return &e->texture;
/*
* Load and remember PNG image
*/
try {
Texture_entry *e = new (env()->heap())
Texture_entry(path.string(), *env()->heap());
_textures.insert(e);
return &e->texture;
} catch (File::Reading_failed) {
PWRN("could not read texture data from file \"%s\"", path.string());
return 0;
}
return 0;
}
Text_painter::Font const *font(Xml_node node, char const *tff_name) const
{
Path const path = _construct_path(node, tff_name, "tff");
if (Font_entry const *e = _lookup(_fonts, path.string()))
return &e->font;
/*
* Load and remember font
*/
try {
Font_entry *e = new (env()->heap())
Font_entry(path.string(), *env()->heap());
_fonts.insert(e);
return &e->font;
} catch (File::Reading_failed) {
PWRN("could not read font from file \"%s\"", path.string());
return 0;
}
return 0;
}
};
#endif /* _STYLE_DATABASE_H_ */

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 698 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -0,0 +1,10 @@
TARGET = menu_view
SRC_CC = main.cc
LIBS = base config libc libpng zlib blit file
INC_DIR += $(PRG_DIR)
.PHONY: menu_view_styles.tar
$(TARGET): menu_view_styles.tar
menu_view_styles.tar:
$(VERBOSE)cd $(PRG_DIR); tar cf $(PWD)/bin/$@ styles

View File

@ -0,0 +1,42 @@
/*
* \brief Menu view
* \author Norman Feske
* \date 2009-09-11
*/
/*
* 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 _TYPES_H_
#define _TYPES_H_
/* Genode includes */
#include <nitpicker_session/connection.h>
#include <base/printf.h>
#include <util/misc_math.h>
#include <os/config.h>
#include <decorator/xml_utils.h>
#include <nitpicker_gfx/box_painter.h>
#include <nitpicker_gfx/texture_painter.h>
#include <os/attached_dataspace.h>
#include <os/attached_rom_dataspace.h>
#include <os/pixel_rgb565.h>
#include <os/pixel_alpha8.h>
#include <os/texture_rgb888.h>
#include <util/volatile_object.h>
#include <nitpicker_gfx/text_painter.h>
namespace Menu_view {
using namespace Genode;
typedef Surface_base::Point Point;
typedef Surface_base::Area Area;
typedef Surface_base::Rect Rect;
}
#endif /* _TYPES_H_ */

View File

@ -0,0 +1,745 @@
/*
* \brief Menu view
* \author Norman Feske
* \date 2009-09-11
*/
/*
* 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 _WIDGETS_H_
#define _WIDGETS_H_
/* Genode includes */
#include <util/xml_generator.h>
#include <timer_session/connection.h>
/* demo includes */
#include <scout_gfx/icon_painter.h>
#include <util/lazy_value.h>
/* gems includes */
#include <gems/animator.h>
/* local includes */
#include "style_database.h"
#include <dither_painter.h>
namespace Menu_view {
struct Margin;
struct Widget;
struct Root_widget;
struct Frame_widget;
struct Button_widget;
struct Label_widget;
struct Vbox_widget;
struct Widget_factory;
struct Main;
typedef Margin Padding;
}
class Menu_view::Widget_factory
{
private:
unsigned _unique_id_cnt = 0;
public:
Allocator &alloc;
Style_database &styles;
Animator &animator;
Widget_factory(Allocator &alloc, Style_database &styles, Animator &animator)
:
alloc(alloc), styles(styles), animator(animator)
{ }
Widget *create(Xml_node node);
void destroy(Widget *widget) { Genode::destroy(alloc, widget); }
};
struct Menu_view::Margin
{
unsigned left, right, top, bottom;
Margin(unsigned left, unsigned right, unsigned top, unsigned bottom)
:
left(left), right(right), top(top), bottom(bottom)
{ }
unsigned horizontal() const { return left + right; }
unsigned vertical() const { return top + bottom; }
};
class Menu_view::Widget : public List<Widget>::Element
{
public:
enum { NAME_MAX_LEN = 32 };
typedef String<NAME_MAX_LEN> Name;
typedef Name Type_name;
struct Unique_id
{
unsigned value = 0;
/**
* Constructor
*
* Only to be called by widget factory.
*/
Unique_id(unsigned value) : value(value) { }
/**
* Default constructor creates invalid ID
*/
Unique_id() { }
bool operator != (Unique_id const &other) { return other.value != value; }
bool valid() const { return value != 0; }
};
private:
Widget *_previously_inserted = nullptr;
Type_name const _type_name;
Name const _name;
Unique_id const _unique_id;
protected:
Widget_factory &_factory;
List<Widget> _children;
Widget *_lookup_child(Name const &name)
{
for (Widget *w = _children.first(); w; w = w->next())
if (w->_name == name)
return w;
return nullptr;
}
static Type_name _node_type_name(Xml_node node)
{
char type[NAME_MAX_LEN];
node.type_name(type, sizeof(type));
return Type_name(type);
}
static Name _node_name(Xml_node node)
{
return Decorator::string_attribute(node, "name", _node_type_name(node));
}
void _update_children(Xml_node node)
{
_previously_inserted = nullptr;
for (unsigned i = 0; i < node.num_sub_nodes(); i++) {
Xml_node const child_node = node.sub_node(i);
Name const name = _node_name(child_node);
Widget *w = _lookup_child(name);
if (!w) {
w = _factory.create(child_node);
/* ignore unknown widget types */
if (!w) continue;
/* append after previously inserted widget */
_children.insert(w, _previously_inserted);
_previously_inserted = w;
}
if (w)
w->update(child_node);
}
}
void _draw_children(Surface<Pixel_rgb888> &pixel_surface,
Surface<Pixel_alpha8> &alpha_surface,
Point at) const
{
for (Widget const *w = _children.first(); w; w = w->next())
w->draw(pixel_surface, alpha_surface, at + w->geometry.p1());
}
virtual void _layout() { }
Rect _inner_geometry() const
{
return Rect(Point(margin.left, margin.top),
Area(geometry.w() - margin.horizontal(),
geometry.h() - margin.vertical()));
}
public:
Margin margin { 0, 0, 0, 0 };
/*
* Position relative to the parent widget and actual size, defined by
* the parent
*/
Rect geometry;
Widget(Widget_factory &factory, Xml_node node, Unique_id unique_id)
:
_type_name(_node_type_name(node)),
_name(_node_name(node)),
_unique_id(unique_id),
_factory(factory)
{ }
virtual ~Widget()
{
while (Widget *w = _children.first()) {
_children.remove(w);
_factory.destroy(w);
}
}
virtual void update(Xml_node node) = 0;
virtual Area min_size() const = 0;
virtual void draw(Surface<Pixel_rgb888> &pixel_surface,
Surface<Pixel_alpha8> &alpha_surface,
Point at) const = 0;
void size(Area size)
{
geometry = Rect(geometry.p1(), size);
_layout();
}
void position(Point position)
{
geometry = Rect(position, geometry.area());
}
/**
* Return unique ID of inner-most hovered widget
*
* This function is used to track changes of the hover model.
*/
virtual Unique_id hovered(Point at) const
{
if (!_inner_geometry().contains(at))
return Unique_id();
for (Widget const *w = _children.first(); w; w = w->next()) {
Unique_id res = w->hovered(at - w->geometry.p1());
if (res.valid())
return res;
}
return _unique_id;
}
virtual void gen_hover_model(Xml_generator &xml, Point at) const
{
if (_inner_geometry().contains(at)) {
xml.node(_type_name.string(), [&]() {
xml.attribute("name", _name.string());
xml.attribute("xpos", geometry.x1());
xml.attribute("ypos", geometry.y1());
xml.attribute("width", geometry.w());
xml.attribute("height", geometry.h());
for (Widget const *w = _children.first(); w; w = w->next()) {
w->gen_hover_model(xml, at - w->geometry.p1());
}
});
}
}
};
struct Menu_view::Root_widget : Widget
{
Root_widget(Widget_factory &factory, Xml_node node, Unique_id unique_id)
:
Widget(factory, node, unique_id)
{ }
void update(Xml_node node) override
{
char const *dialog_tag = "dialog";
if (!node.has_type(dialog_tag)) {
PERR("no valid <dialog> tag found");
return;
}
if (!node.num_sub_nodes()) {
PWRN("empty <dialog> node");
return;
}
_update_children(node);
}
Area min_size() const override
{
if (Widget const * const child = _children.first())
return child->min_size();
return Area(1, 1);
}
void draw(Surface<Pixel_rgb888> &pixel_surface,
Surface<Pixel_alpha8> &alpha_surface,
Point at) const
{
_draw_children(pixel_surface, alpha_surface, at);
}
void _layout() override
{
if (Widget *child = _children.first()) {
child->size(geometry.area());
child->position(Point(0, 0));
}
}
};
struct Menu_view::Frame_widget : Widget
{
Texture<Pixel_rgb888> const * texture = nullptr;
Padding padding { 6, 6, 16, 16 };
Area _space() const
{
return Area(margin.horizontal() + padding.horizontal(),
margin.vertical() + padding.vertical());
}
Frame_widget(Widget_factory &factory, Xml_node node, Unique_id unique_id)
:
Widget(factory, node, unique_id)
{
margin = { 14, 14, 14, 14 };
}
void update(Xml_node node) override
{
texture = _factory.styles.texture(node, "background");
_update_children(node);
/*
* layout
*/
if (Widget *child = _children.first())
child->geometry = Rect(Point(margin.left + padding.left,
margin.top + padding.top),
child->min_size());
}
Area min_size() const override
{
/* determine minimum child size */
Widget const * const child = _children.first();
Area const child_min_size = child ? child->min_size() : Area(0, 0);
/* don't get smaller than the background texture */
Area const texture_size = texture ? texture->size() : Area(0, 0);
return Area(max(_space().w() + child_min_size.w(), texture_size.w()),
max(_space().h() + child_min_size.h(), texture_size.h()));
}
void draw(Surface<Pixel_rgb888> &pixel_surface,
Surface<Pixel_alpha8> &alpha_surface,
Point at) const
{
Icon_painter::paint(pixel_surface, Rect(at, geometry.area()),
*texture, 255);
Icon_painter::paint(alpha_surface, Rect(at, geometry.area()),
*texture, 255);
_draw_children(pixel_surface, alpha_surface, at);
}
void _layout() override
{
if (Widget *child = _children.first())
child->size(Area(geometry.w() - _space().w(),
geometry.h() - _space().h()));
}
};
struct Menu_view::Vbox_widget : Widget
{
Area _min_size; /* value cached from layout computation */
Vbox_widget(Widget_factory &factory, Xml_node node, Unique_id unique_id)
:
Widget(factory, node, unique_id)
{ }
void update(Xml_node node) override
{
_update_children(node);
/*
* Apply layout to the children
*/
/* determine largest width among our children */
unsigned width = 0;
for (Widget *w = _children.first(); w; w = w->next())
width = max(width, w->min_size().w());
/* position children on one column */
unsigned height = 0;
Point position(0, 0);
for (Widget *w = _children.first(); w; w = w->next()) {
unsigned const child_min_h = w->min_size().h();
w->geometry = Rect(position, Area(width, child_min_h));
unsigned const next_top_margin = w->next() ? w->next()->margin.top : 0;
unsigned const dy = child_min_h - min(w->margin.bottom, next_top_margin);
position = position + Point(0, dy);
height = w->geometry.y2() + 1;
}
_min_size = Area(width, height);
}
Area min_size() const override
{
return _min_size;
}
void draw(Surface<Pixel_rgb888> &pixel_surface,
Surface<Pixel_alpha8> &alpha_surface,
Point at) const
{
_draw_children(pixel_surface, alpha_surface, at);
}
void _layout() override
{
for (Widget *w = _children.first(); w; w = w->next())
w->size(Area(geometry.w(), w->min_size().h()));
}
};
namespace Menu_view { template <typename PT> class Scratch_surface; }
template <typename PT>
class Menu_view::Scratch_surface
{
private:
Area _size;
Allocator &_alloc;
unsigned char *_base = nullptr;
size_t _num_bytes = 0;
size_t _needed_bytes(Area size)
{
/* account for pixel buffer and alpha channel */
return size.count()*sizeof(PT) + size.count();
}
void _release()
{
if (_base) {
_alloc.free(_base, _num_bytes);
_base = nullptr;
}
}
unsigned char *_pixel_base() const { return _base; }
unsigned char *_alpha_base() const
{
return _base + _size.count()*sizeof(PT);
}
public:
Scratch_surface(Allocator &alloc) : _alloc(alloc) { }
~Scratch_surface()
{
_release();
}
void reset(Area size)
{
if (_num_bytes < _needed_bytes(size)) {
_release();
_size = size;
_num_bytes = _needed_bytes(size);
_base = (unsigned char *)_alloc.alloc(_num_bytes);
}
Genode::memset(_base, 0, _num_bytes);
}
Surface<PT> pixel_surface() const
{
return Surface<PT>((PT *)_pixel_base(), _size);
}
Surface<Pixel_alpha8> alpha_surface() const
{
return Surface<Pixel_alpha8>((Pixel_alpha8 *)_alpha_base(), _size);
}
Texture<PT> texture() const
{
return Texture<PT>((PT *)_pixel_base(), _alpha_base(), _size);
}
};
struct Menu_view::Button_widget : Widget, Animator::Item
{
bool hovered = false;
bool selected = false;
Texture<Pixel_rgb888> const * default_texture = nullptr;
Texture<Pixel_rgb888> const * hovered_texture = nullptr;
Lazy_value<int> blend;
Padding padding { 9, 9, 2, 1 };
Area _space() const
{
return Area(margin.horizontal() + padding.horizontal(),
margin.vertical() + padding.vertical());
}
static bool _enabled(Xml_node node, char const *attr)
{
return node.has_attribute(attr) && node.attribute(attr).has_value("yes");
}
Button_widget(Widget_factory &factory, Xml_node node, Unique_id unique_id)
:
Widget(factory, node, unique_id), Animator::Item(factory.animator)
{
margin = { 8, 8, 8, 8 };
}
void update(Xml_node node)
{
bool const new_hovered = _enabled(node, "hovered");
bool const new_selected = _enabled(node, "selected");
if (new_selected) {
default_texture = _factory.styles.texture(node, "selected");
hovered_texture = _factory.styles.texture(node, "hselected");
} else {
default_texture = _factory.styles.texture(node, "default");
hovered_texture = _factory.styles.texture(node, "hovered");
}
if (new_hovered != hovered) {
if (new_hovered) {
blend.dst(255 << 8, 3);
} else {
blend.dst(0, 20);
}
animated(blend != blend.dst());
}
hovered = new_hovered;
selected = new_selected;
_update_children(node);
bool const dy = selected ? 1 : 0;
if (Widget *child = _children.first())
child->geometry = Rect(Point(margin.left + padding.left,
margin.top + padding.top + dy),
child->min_size());
}
Area min_size() const override
{
/* determine minimum child size */
Widget const * const child = _children.first();
Area const child_min_size = child ? child->min_size() : Area(300, 10);
/* don't get smaller than the background texture */
Area const texture_size = default_texture->size();
return Area(max(_space().w() + child_min_size.w(), texture_size.w()),
max(_space().h() + child_min_size.h(), texture_size.h()));
}
void draw(Surface<Pixel_rgb888> &pixel_surface,
Surface<Pixel_alpha8> &alpha_surface,
Point at) const
{
static Scratch_surface<Pixel_rgb888> scratch(_factory.alloc);
Area const texture_size = default_texture->size();
Rect const texture_rect(Point(0, 0), texture_size);
/*
* Mix from_texture and to_texture according to the blend value
*/
scratch.reset(texture_size);
Surface<Pixel_rgb888> scratch_pixel_surface = scratch.pixel_surface();
Surface<Pixel_alpha8> scratch_alpha_surface = scratch.alpha_surface();
Icon_painter::paint(scratch_pixel_surface, texture_rect, *default_texture, 255);
Icon_painter::paint(scratch_alpha_surface, texture_rect, *default_texture, 255);
Icon_painter::paint(scratch_pixel_surface, texture_rect, *hovered_texture, blend >> 8);
Icon_painter::paint(scratch_alpha_surface, texture_rect, *hovered_texture, blend >> 8);
/*
* Apply blended texture to target surface
*/
Icon_painter::paint(pixel_surface, Rect(at, geometry.area()),
scratch.texture(), 255);
Icon_painter::paint(alpha_surface, Rect(at, geometry.area()),
scratch.texture(), 255);
_draw_children(pixel_surface, alpha_surface, at);
}
void _layout() override
{
for (Widget *w = _children.first(); w; w = w->next())
w->size(Area(geometry.w() - _space().w(),
geometry.h() - _space().h()));
}
/******************************
** Animator::Item interface **
******************************/
void animate() override
{
blend.animate();
animated(blend != blend.dst());
}
};
struct Menu_view::Label_widget : Widget
{
Text_painter::Font const *font = nullptr;
enum { LABEL_MAX_LEN = 256 };
typedef String<200> Text;
Text text;
Label_widget(Widget_factory &factory, Xml_node node, Unique_id unique_id)
:
Widget(factory, node, unique_id)
{ }
void update(Xml_node node)
{
font = _factory.styles.font(node, "font");
text = Decorator::string_attribute(node, "text", Text(""));
}
Area min_size() const override
{
if (!font)
return Area(0, 0);
return Area(font->str_w(text.string()),
font->str_h(text.string()));
}
void draw(Surface<Pixel_rgb888> &pixel_surface,
Surface<Pixel_alpha8> &alpha_surface,
Point at) const
{
if (!font) return;
Area text_size = min_size();
int const dx = (int)geometry.w() - text_size.w(),
dy = (int)geometry.h() - text_size.h();
Point const centered = Point(dx/2, dy/2);
Text_painter::paint(pixel_surface, at + centered, *font,
Color(0, 0, 0), text.string());
}
};
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) Vbox_widget (*this, node, unique_id);
if (node.has_type("frame")) w = new (alloc) Frame_widget (*this, node, unique_id);
if (!w) {
char type[64];
type[0] = 0;
node.type_name(type, sizeof(type));
PERR("unknown widget type '%s'", type);
return 0;
}
return w;
}
#endif /* _WIDGETS_H_ */