mirror of
https://github.com/genodelabs/genode.git
synced 2024-12-21 22:47:50 +00:00
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:
parent
40aadb8601
commit
cc303c4671
162
repos/gems/run/menu_view.run
Normal file
162
repos/gems/run/menu_view.run
Normal 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
|
75
repos/gems/src/app/menu_view/dither_painter.h
Normal file
75
repos/gems/src/app/menu_view/dither_painter.h
Normal 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_ */
|
442
repos/gems/src/app/menu_view/main.cc
Normal file
442
repos/gems/src/app/menu_view/main.cc
Normal 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());
|
||||
}
|
||||
}
|
177
repos/gems/src/app/menu_view/style_database.h
Normal file
177
repos/gems/src/app/menu_view/style_database.h
Normal 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_ */
|
BIN
repos/gems/src/app/menu_view/styles/button/default/default.png
Normal file
BIN
repos/gems/src/app/menu_view/styles/button/default/default.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.0 KiB |
BIN
repos/gems/src/app/menu_view/styles/button/default/hovered.png
Normal file
BIN
repos/gems/src/app/menu_view/styles/button/default/hovered.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
BIN
repos/gems/src/app/menu_view/styles/button/default/hselected.png
Normal file
BIN
repos/gems/src/app/menu_view/styles/button/default/hselected.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 698 B |
BIN
repos/gems/src/app/menu_view/styles/button/default/selected.png
Normal file
BIN
repos/gems/src/app/menu_view/styles/button/default/selected.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
BIN
repos/gems/src/app/menu_view/styles/frame/default/background.png
Normal file
BIN
repos/gems/src/app/menu_view/styles/frame/default/background.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
BIN
repos/gems/src/app/menu_view/styles/label/default/font.tff
Normal file
BIN
repos/gems/src/app/menu_view/styles/label/default/font.tff
Normal file
Binary file not shown.
10
repos/gems/src/app/menu_view/target.mk
Normal file
10
repos/gems/src/app/menu_view/target.mk
Normal 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
|
42
repos/gems/src/app/menu_view/types.h
Normal file
42
repos/gems/src/app/menu_view/types.h
Normal 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_ */
|
745
repos/gems/src/app/menu_view/widgets.h
Normal file
745
repos/gems/src/app/menu_view/widgets.h
Normal 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_ */
|
Loading…
Reference in New Issue
Block a user