Minimalistic decorator for window system

This commit is contained in:
Norman Feske 2013-01-04 22:02:18 +01:00
parent 20090000d2
commit 09ec663d2d
11 changed files with 1905 additions and 0 deletions

View File

@ -0,0 +1,190 @@
#
# 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/report_rom server/nitpicker app/decorator
}
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="report_rom">
<resource name="RAM" quantum="2M"/>
<provides> <service name="ROM" />
<service name="Report" /> </provides>
<config>
<rom>
<policy label="decorator -> pointer" report="nitpicker -> pointer"/>
</rom>
</config>
</start>
<start name="nitpicker">
<resource name="RAM" quantum="1M"/>
<provides><service name="Nitpicker"/></provides>
<config>
<report pointer="yes" />
<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="dynamic_rom">
<resource name="RAM" quantum="4M"/>
<provides><service name="ROM"/></provides>
<config verbose="yes">
<rom name="window_layout">
<inline description="initial state" />
<sleep milliseconds="500" />
<inline description="open window 1">
<window_layout>
<window id="1" title="Genode Toolchain"
xpos="100" ypos="50" width="200" height="200"
focused="yes" />
</window_layout>
</inline>
<sleep milliseconds="1000" />
<inline description="open window 2 behind window 1">
<window_layout>
<window id="1" title="Genode Toolchain"
xpos="100" ypos="50" width="200" height="200"
focused="yes" />
<window id="2" title="Arora (2)"
xpos="170" ypos="150" width="300" height="200" />
</window_layout>
</inline>
<sleep milliseconds="1000" />
<inline description="open window 3 in front">
<window_layout>
<window id="3" title="Launchpad"
xpos="210" ypos="250" width="400" height="200" />
<window id="1" title="Genode Toolchain"
xpos="100" ypos="50" width="200" height="200"
focused="yes" />
<window id="2" title="Arora (2)"
xpos="170" ypos="150" width="300" height="200" />
</window_layout>
</inline>
<sleep milliseconds="1000" />
<inline description="bring window 1 to front">
<window_layout>
<window id="1" title="Genode Toolchain"
xpos="100" ypos="50" width="200" height="200"
focused="yes" />
<window id="3" title="Launchpad"
xpos="210" ypos="250" width="400" height="200" />
<window id="2" title="Arora (2)"
xpos="170" ypos="150" width="300" height="200" />
</window_layout>
</inline>
<sleep milliseconds="1000" />
<inline description="change title of window 1">
<window_layout>
<window id="1" title="Genode Toolchain (running)"
xpos="100" ypos="50" width="200" height="200"
focused="yes" />
<window id="3" title="Launchpad"
xpos="210" ypos="250" width="400" height="200" />
<window id="2" title="Arora (2)"
xpos="170" ypos="150" width="300" height="200" />
</window_layout>
</inline>
<sleep milliseconds="1000" />
<inline description="change focus to window 3">
<window_layout>
<window id="1" title="Genode Toolchain (running)"
xpos="100" ypos="50" width="200" height="200" />
<window id="3" title="Launchpad"
xpos="210" ypos="250" width="400" height="200"
focused="yes" />
<window id="2" title="Arora (2)"
xpos="170" ypos="150" width="300" height="200" />
</window_layout>
</inline>
<sleep milliseconds="1000" />
<inline description="move window 3">
<window_layout>
<window id="1" title="Genode Toolchain"
xpos="100" ypos="50" width="200" height="200" />
<window id="3" title="Launchpad"
xpos="310" ypos="300" width="500" height="300"
focused="yes" />
<window id="2" title="Arora (2)"
xpos="170" ypos="150" width="300" height="200" />
</window_layout>
</inline>
<sleep milliseconds="1000" />
<empty />
<sleep milliseconds="1000" />
</rom>
</config>
</start>
<start name="decorator">
<resource name="RAM" quantum="4M"/>
<route>
<service name="ROM">
<if-arg key="label" value="pointer" />
<child name="report_rom" />
</service>
<service name="ROM">
<if-arg key="label" value="window_layout" />
<child name="dynamic_rom" />
</service>
<any-service> <parent/> <any-child/> </any-service>
</route>
</start>
</config>}
install_config $config
#
# Boot modules
#
# generic modules
set boot_modules {
core init timer dynamic_rom report_rom fb_sdl nitpicker decorator
}
build_boot_image $boot_modules
run_genode_until forever

View File

@ -0,0 +1,76 @@
/*
* \brief Utility for implementing animated objects
* \author Norman Feske
* \date 2014-06-24
*/
/*
* 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 _ANIMATOR_H_
#define _ANIMATOR_H_
class Animator
{
public:
class Item;
private:
friend class Item;
Genode::List<Item> _items;
public:
inline void animate();
};
/**
* Interface to be implemented by animated objects
*/
class Animator::Item : public Genode::List<Item>::Element
{
private:
Animator &_animator;
bool _animated = false;
public:
Item(Animator &animator) : _animator(animator) { }
virtual ~Item() { animated(false); }
virtual void animate() = 0;
void animated(bool animated)
{
if (animated == _animated)
return;
if (animated)
_animator._items.insert(this);
else
_animator._items.remove(this);
_animated = animated;
}
bool animated() const { return _animated; }
};
inline void Animator::animate()
{
for (Item *item = _items.first(); item; item = item->next())
item->animate();
}
#endif /* _ANIMATOR_H_ */

View File

@ -0,0 +1,101 @@
/*
* \brief Graphics back end for example window decorator
* \author Norman Feske
* \date 2014-01-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 _CANVAS_H_
#define _CANVAS_H_
#include <decorator/types.h>
namespace Decorator {
typedef Text_painter::Font Font;
Font &default_font();
template <typename PT> class Canvas;
class Clip_guard;
}
#define FONT_START_SYMBOL _binary_droidsansb10_tff_start
extern char FONT_START_SYMBOL;
/**
* Return default font
*/
Decorator::Font &Decorator::default_font()
{
static Font font(&FONT_START_SYMBOL);
return font;
}
/**
* Abstract interface of graphics back end
*/
struct Decorator::Canvas_base
{
virtual Rect clip() const = 0;
virtual void clip(Rect) = 0;
virtual void draw_box(Rect, Color) = 0;
virtual void draw_text(Point, Font const &, Color, char const *) = 0;
};
template <typename PT>
class Decorator::Canvas : public Decorator::Canvas_base
{
private:
Genode::Surface<PT> _surface;
public:
Canvas(PT *base, Area size) : _surface(base, size) { }
Rect clip() const override { return _surface.clip(); }
void clip(Rect rect) override { _surface.clip(rect); }
void draw_box(Rect rect, Color color) override
{
Box_painter::paint(_surface, rect, color);
}
void draw_text(Point pos, Font const &font,
Color color, char const *string) override
{
Text_painter::paint(_surface, pos, font, color, string);
}
};
class Decorator::Clip_guard : Rect
{
private:
Canvas_base &_canvas;
Rect const _orig_rect;
public:
Clip_guard(Canvas_base &canvas, Rect clip_rect)
:
_canvas(canvas), _orig_rect(canvas.clip())
{
_canvas.clip(Rect::intersect(_orig_rect, clip_rect));
}
~Clip_guard() { _canvas.clip(_orig_rect); }
};
#endif /* _CANVAS_H_ */

View File

@ -0,0 +1,243 @@
/*
* \brief Example window decorator that mimics the Motif look
* \author Norman Feske
* \date 2013-01-04
*/
/*
* Copyright (C) 2013-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.
*/
/* Genode includes */
#include <base/printf.h>
#include <base/signal.h>
#include <nitpicker_session/connection.h>
#include <os/pixel_rgb565.h>
#include <os/attached_rom_dataspace.h>
#include <os/reporter.h>
/* Nitpicker graphics backend */
#include <nitpicker_gfx/text_painter.h>
#include <nitpicker_gfx/box_painter.h>
/* decorator includes */
#include <decorator/window_stack.h>
#include <decorator/xml_utils.h>
/* local includes */
#include "canvas.h"
#include "window.h"
namespace Decorator {
using namespace Genode;
struct Main;
}
struct Decorator::Main : Window_factory_base
{
Signal_receiver &sig_rec;
Nitpicker::Connection nitpicker;
Framebuffer::Mode mode = { nitpicker.mode() };
Attached_dataspace fb_ds = { (nitpicker.buffer(mode, false),
nitpicker.framebuffer()->dataspace()) };
Canvas<Pixel_rgb565> canvas = { fb_ds.local_addr<Pixel_rgb565>(),
Area(mode.width(), mode.height()) };
Window_stack window_stack = { *this };
/**
* Install handler for responding to window-layout changes
*/
void handle_window_layout_update(unsigned);
Signal_dispatcher<Main> window_layout_dispatcher = {
sig_rec, *this, &Main::handle_window_layout_update };
Attached_rom_dataspace window_layout { "window_layout" };
/**
* Install handler for responding to pointer-position updates
*/
void handle_pointer_update(unsigned);
Signal_dispatcher<Main> pointer_dispatcher = {
sig_rec, *this, &Main::handle_pointer_update };
Attached_rom_dataspace pointer { "pointer" };
Window_base::Hover hover;
Reporter hover_reporter = { "hover" };
bool window_layout_update_needed = false;
Animator animator;
/**
* Install handler for responding to nitpicker sync events
*/
void handle_nitpicker_sync(unsigned);
Signal_dispatcher<Main> nitpicker_sync_dispatcher = {
sig_rec, *this, &Main::handle_nitpicker_sync };
/**
* Constructor
*/
Main(Signal_receiver &sig_rec) : sig_rec(sig_rec)
{
window_layout.sigh(window_layout_dispatcher);
pointer.sigh(pointer_dispatcher);
nitpicker.framebuffer()->sync_sigh(nitpicker_sync_dispatcher);
hover_reporter.enabled(true);
}
/**
* Window_factory_base interface
*/
Window_base *create(Xml_node window_node) override
{
return new (env()->heap())
Window(attribute(window_node, "id", 0UL), nitpicker, animator);
}
/**
* Window_factory_base interface
*/
void destroy(Window_base *window) override
{
Genode::destroy(env()->heap(), static_cast<Window *>(window));
}
};
void Decorator::Main::handle_window_layout_update(unsigned)
{
window_layout.update();
window_layout_update_needed = true;
}
void Decorator::Main::handle_nitpicker_sync(unsigned)
{
bool model_updated = false;
if (window_layout_update_needed && window_layout.is_valid()) {
try {
Xml_node xml(window_layout.local_addr<char>(),
window_layout.size());
window_stack.update_model(xml);
model_updated = true;
} catch (Xml_node::Invalid_syntax) {
/*
* An error occured with processing the XML model. Flush the
* internal representation.
*/
window_stack.flush();
}
window_layout_update_needed = false;
}
bool const windows_animated = window_stack.schedule_animated_windows();
animator.animate();
if (!model_updated && !windows_animated)
return;
Dirty_rect dirty = window_stack.draw(canvas);
window_stack.update_nitpicker_views();
nitpicker.execute();
dirty.flush([&] (Rect const &r) {
nitpicker.framebuffer()->refresh(r.x1(), r.y1(), r.w(), r.h()); });
}
static Decorator::Window_base::Hover
find_hover(Genode::Xml_node pointer_node, Decorator::Window_stack &window_stack)
{
if (!pointer_node.has_attribute("xpos")
|| !pointer_node.has_attribute("ypos"))
return Decorator::Window_base::Hover();
return window_stack.hover(Decorator::point_attribute(pointer_node));
}
void Decorator::Main::handle_pointer_update(unsigned)
{
pointer.update();
if (!pointer.is_valid())
return;
Xml_node const pointer_node(pointer.local_addr<char>());
Window_base::Hover const new_hover = find_hover(pointer_node, window_stack);
/* produce report only if hover state changed */
if (new_hover != hover) {
hover = new_hover;
Reporter::Xml_generator xml(hover_reporter, [&] ()
{
if (hover.window_id > 0) {
xml.node("window", [&] () {
xml.attribute("id", hover.window_id);
if (hover.left_sizer) xml.node("left_sizer");
if (hover.right_sizer) xml.node("right_sizer");
if (hover.top_sizer) xml.node("top_sizer");
if (hover.bottom_sizer) xml.node("bottom_sizer");
if (hover.title) xml.node("title");
});
}
});
}
}
int main(int argc, char **argv)
{
static Genode::Signal_receiver sig_rec;
static Decorator::Main application(sig_rec);
/* import initial state */
application.handle_pointer_update(0);
application.handle_window_layout_update(0);
/* 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,8 @@
TARGET = decorator
SRC_CC = main.cc
LIBS = base config
TFF_DIR = $(call select_from_repositories,src/app/scout/data)
SRC_BIN = droidsansb10.tff
INC_DIR += $(PRG_DIR)
vpath %.tff $(TFF_DIR)

View File

@ -0,0 +1,472 @@
/*
* \brief Example window decorator that mimics the Motif look
* \author Norman Feske
* \date 2014-01-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 _WINDOW_H_
#define _WINDOW_H_
#include <util/lazy_value.h>
#include <decorator/window.h>
/* local includes */
#include <animator.h>
namespace Decorator { class Window; }
class Decorator::Window : public Window_base
{
public:
typedef Genode::String<200> Title;
private:
Title _title;
bool _focused = false;
Animator &_animator;
static unsigned const _corner_size = 16;
static unsigned const _border_size = 4;
static unsigned const _title_height = 16;
static Border _border() {
return Border(_border_size + _title_height,
_border_size, _border_size, _border_size); }
Color _bright = { 255, 255, 255, 64 };
Color _dark = { 0, 0, 0, 127 };
Color _base_color() const { return Color(45, 49, 65); }
class Element : public Animator::Item
{
public:
enum Type { TITLE, LEFT, RIGHT, TOP, BOTTOM,
TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT,
UNDEFINED };
private:
static Color _add(Color c1, Color c2)
{
return Color(Genode::min(c1.r + c2.r, 255),
Genode::min(c1.g + c2.g, 255),
Genode::min(c1.b + c2.b, 255));
}
Type _type;
/*
* Color value in 8.4 fixpoint format. We use four bits to
* represent the fractional part to enable smooth
* interpolation between the color values.
*/
Lazy_value<unsigned> _r, _g, _b;
bool _focused = false;
bool _highlighted = false;
static Color _dst_color(bool focused, bool highlighted, Color base)
{
Color result = base;
if (focused)
result = _add(result, Color(70, 70, 70));
if (highlighted)
result = _add(result, Color(65, 60, 55));
return result;
}
unsigned _anim_steps(bool focused, bool highlighted) const
{
/* quick fade-in when gaining the focus or hover highlight */
if ((!_focused && focused) || (!_highlighted && highlighted))
return 20;
/* slow fade-out when leaving focus or hover highlight */
return 180;
}
bool _apply_state(bool focused, bool highlighted, Color base_color)
{
Color const dst_color = _dst_color(focused, highlighted, base_color);
unsigned const steps = _anim_steps(focused, highlighted);
_r.dst(dst_color.r << 4, steps);
_g.dst(dst_color.g << 4, steps);
_b.dst(dst_color.b << 4, steps);
/* schedule animation */
animate();
_focused = focused;
_highlighted = highlighted;
return true;
}
public:
Element(Type type, Animator &animator, Color base_color)
:
Animator::Item(animator),
_type(type)
{
_apply_state(false, false, base_color);
}
Type type() const { return _type; }
char const *type_name() const
{
switch (_type) {
case UNDEFINED: return "";
case TITLE: return "title";
case LEFT: return "left";
case RIGHT: return "right";
case TOP: return "top";
case BOTTOM: return "bottom";
case TOP_LEFT: return "top_left";
case TOP_RIGHT: return "top_right";
case BOTTOM_LEFT: return "bottom_left";
case BOTTOM_RIGHT: return "bottom_right";
}
return "";
}
Color color() const { return Color(_r >> 4, _g >> 4, _b >> 4); }
/**
* \return true if state has changed
*/
bool apply_state(bool focused, bool highlighted, Color base_color)
{
if (_focused == focused && _highlighted == highlighted)
return false;
return _apply_state(focused, highlighted, base_color);
}
/**
* Animator::Item interface
*/
void animate() override;
};
/*
* The element order must correspond to the order of enum values
* because the type is used as index into the '_elements' array.
*/
Element _elements[9] { { Element::TITLE, _animator, _base_color() },
{ Element::LEFT, _animator, _base_color() },
{ Element::RIGHT, _animator, _base_color() },
{ Element::TOP, _animator, _base_color() },
{ Element::BOTTOM, _animator, _base_color() },
{ Element::TOP_LEFT, _animator, _base_color() },
{ Element::TOP_RIGHT, _animator, _base_color() },
{ Element::BOTTOM_LEFT, _animator, _base_color() },
{ Element::BOTTOM_RIGHT, _animator, _base_color() } };
Element &element(Element::Type type)
{
return _elements[type];
}
Element const &element(Element::Type type) const
{
return _elements[type];
}
unsigned num_elements() const { return sizeof(_elements)/sizeof(Element); }
void _draw_hline(Canvas_base &canvas, Point pos, unsigned w,
bool at_left, bool at_right,
unsigned border, Color color) const
{
int const x1 = at_left ? (pos.x()) : (pos.x() + w - border);
int const x2 = at_right ? (pos.x() + w - 1) : (pos.x() + border - 1);
canvas.draw_box(Rect(Point(x1, pos.y()),
Point(x2, pos.y())), color);
}
void _draw_vline(Canvas_base &canvas, Point pos, unsigned h,
bool at_top, bool at_bottom,
unsigned border, Color color) const
{
int const y1 = at_top ? (pos.y()) : (pos.y() + h - border);
int const y2 = at_bottom ? (pos.y() + h - 1) : (pos.y() + border - 1);
canvas.draw_box(Rect(Point(pos.x(), y1),
Point(pos.x(), y2)), color);
}
void _draw_raised_frame(Canvas_base &canvas, Rect rect) const
{
_draw_hline(canvas, rect.p1(), rect.w(), true, true, 0, _bright);
_draw_vline(canvas, rect.p1(), rect.h(), true, true, 0, _bright);
_draw_hline(canvas, Point(rect.p1().x(), rect.p2().y()), rect.w(),
true, true, 0, _dark);
_draw_vline(canvas, Point(rect.p2().x(), rect.p1().y()), rect.h(),
true, true, 0, _dark);
}
void _draw_raised_box(Canvas_base &canvas, Rect rect, Color color) const
{
canvas.draw_box(rect, color);
_draw_raised_frame(canvas, rect);
}
void _draw_title_box(Canvas_base &canvas, Rect rect, Color color) const
{
canvas.draw_box(rect, color);
for (unsigned i = 0; i < rect.h(); i++)
canvas.draw_box(Rect(rect.p1() + Point(0, i),
Area(rect.w(), 1)),
Color(255,255,255, 30 + (rect.h() - i)*4));
_draw_raised_frame(canvas, rect);
}
void _draw_corner(Canvas_base &canvas, Rect const rect,
unsigned const border,
bool const left, bool const top,
Color color) const
{
bool const bottom = !top;
bool const right = !left;
int const x1 = rect.p1().x();
int const y1 = rect.p1().y();
int const x2 = rect.p2().x();
int const y2 = rect.p2().y();
int const w = rect.w();
int const h = rect.h();
canvas.draw_box(Rect(Point(x1, top ? y1 : y2 - border + 1),
Area(w, border)), color);
canvas.draw_box(Rect(Point(left ? x1 : x2 - border + 1,
top ? y1 + border : y1),
Area(border, h - border)), color);
/* top bright line */
_draw_hline(canvas, rect.p1(), w,
top || left, top || right, border, _bright);
/* inner horizontal line */
int y = top ? y1 + border - 1 : y2 - border + 1;
_draw_hline(canvas, Point(x1, y), w, right, left, w - border,
top ? _dark : _bright);
/* bottom line */
_draw_hline(canvas, Point(x1, y2), w,
bottom || left, bottom || right, border, _dark);
/* left bright line */
_draw_vline(canvas, rect.p1(), h,
left || top, left || bottom, border, _bright);
/* inner vertical line */
int x = left ? x1 + border - 1 : x2 - border + 1;
_draw_vline(canvas, Point(x, y1), h, bottom, top, h - border + 1,
left ? _dark : _bright);
/* right line */
_draw_vline(canvas, Point(x2, y1), h,
right || top, right || bottom, border, _dark);
}
bool _apply_state(Window::Element::Type type, bool focused, bool highlighted)
{
return element(type).apply_state(_focused, highlighted, _base_color());
}
public:
Window(unsigned id, Nitpicker::Session_client &nitpicker, Animator &animator)
:
Window_base(id, nitpicker, _border()),
_animator(animator)
{ }
void draw(Canvas_base &canvas, Rect clip) const override;
bool update(Xml_node window_node) override;
Hover hover(Point) const override;
bool animated() const override
{
for (unsigned i = 0; i < num_elements(); i++)
if (_elements[i].animated())
return true;
return false;
}
};
void Decorator::Window::draw(Decorator::Canvas_base &canvas,
Decorator::Rect clip) const
{
Clip_guard clip_guard(canvas, clip);
Rect rect = outer_geometry();
Area corner(_corner_size, _corner_size);
Point p1 = rect.p1();
Point p2 = rect.p2();
bool const draw_content = false;
if (draw_content)
canvas.draw_box(geometry(), Color(10, 20, 40));
_draw_corner(canvas, Rect(p1, corner), _border_size, true, true,
element(Element::TOP_LEFT).color());
_draw_corner(canvas, Rect(Point(p1.x(), p2.y() - _corner_size + 1), corner),
_border_size, true, false,
element(Element::BOTTOM_LEFT).color());
_draw_corner(canvas, Rect(Point(p2.x() - _corner_size + 1, p1.y()), corner),
_border_size, false, true,
element(Element::TOP_RIGHT).color());
_draw_corner(canvas, Rect(Point(p2.x() - _corner_size + 1, p2.y() - _corner_size + 1), corner),
_border_size, false, false,
element(Element::BOTTOM_RIGHT).color());
_draw_raised_box(canvas, Rect(Point(p1.x() + _corner_size, p1.y()),
Area(rect.w() - 2*_corner_size, _border_size)),
element(Element::TOP).color());
_draw_raised_box(canvas, Rect(Point(p1.x() + _corner_size, p2.y() - _border_size + 1),
Area(rect.w() - 2*_corner_size, _border_size)),
element(Element::BOTTOM).color());
_draw_raised_box(canvas, Rect(Point(p1.x(), p1.y() + _corner_size),
Area(_border_size, rect.h() - 2*_corner_size)),
element(Element::LEFT).color());
_draw_raised_box(canvas, Rect(Point(p2.x() - _border_size + 1, p1.y() + _corner_size),
Area(_border_size, rect.h() - 2*_corner_size)),
element(Element::RIGHT).color());
Rect title_rect(Point(p1.x() + _border_size, p1.y() + _border_size),
Area(rect.w() - 2*_border_size, _title_height));
_draw_title_box(canvas, title_rect, element(Element::TITLE).color());
char const * const text = _title.string();;
Area const label_area(default_font().str_w(text),
default_font().str_h(text));
Point text_pos = title_rect.center(label_area) - Point(0, 1);
{
Clip_guard clip_guard(canvas, title_rect);
canvas.draw_text(text_pos + Point(1, 1), default_font(),
Color(0, 0, 0, 128), text);
Color title_color = element(Element::TITLE).color();
canvas.draw_text(text_pos, default_font(),
Color(255, 255, 255, (2*255 + title_color.r) / 3), text);
}
}
bool Decorator::Window::update(Genode::Xml_node window_node)
{
bool updated = Window_base::update(window_node);
_focused = window_node.has_attribute("focused")
&& window_node.attribute("focused").has_value("yes");
try {
Xml_node highlight = window_node.sub_node("highlight");
for (unsigned i = 0; i < num_elements(); i++)
updated |= _apply_state(_elements[i].type(), _focused,
highlight.has_sub_node(_elements[i].type_name()));
} catch (...) {
/* window node has no "highlight" sub node, reset highlighting */
for (unsigned i = 0; i < num_elements(); i++)
updated |= _apply_state(_elements[i].type(), _focused, false);
}
Title title = Decorator::string_attribute(window_node, "title", Title("<untitled>"));
updated |= !(title == _title);
_title = title;
return updated;
}
Decorator::Window_base::Hover Decorator::Window::hover(Point abs_pos) const
{
Hover hover;
if (!outer_geometry().contains(abs_pos))
return hover;
hover.window_id = id();
unsigned const x = abs_pos.x() - outer_geometry().x1(),
y = abs_pos.y() - outer_geometry().y1();
Area const area = outer_geometry().area();
bool const at_border = x < _border_size
|| x >= area.w() - _border_size
|| y < _border_size
|| y >= area.h() - _border_size;
if (at_border) {
hover.left_sizer = (x < _corner_size);
hover.top_sizer = (y < _corner_size);
hover.right_sizer = (x >= area.w() - _corner_size);
hover.bottom_sizer = (y >= area.h() - _corner_size);
} else {
hover.title = (y < _border_size + _title_height);
}
return hover;
}
void Decorator::Window::Element::animate()
{
_r.animate();
_g.animate();
_b.animate();
/* keep animation running until the destination values are reached */
Animator::Item::animated(_r != _r.dst() || _g != _g.dst() || _b != _b.dst());
}
#endif /* _WINDOW_H_ */

View File

@ -0,0 +1,38 @@
/*
* \brief Basic types used by decorator
* \author Norman Feske
* \date 2014-01-09
*/
/*
* 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 _INCLUDE__DECORATOR__TYPES_H_
#define _INCLUDE__DECORATOR__TYPES_H_
/* Genode includes */
#include <nitpicker_session/nitpicker_session.h>
#include <util/xml_node.h>
#include <util/color.h>
#include <util/geometry.h>
#include <util/color.h>
#include <util/dirty_rect.h>
#include <os/surface.h>
namespace Decorator {
typedef Genode::Surface_base::Point Point;
typedef Genode::Surface_base::Area Area;
typedef Genode::Surface_base::Rect Rect;
typedef Genode::Dirty_rect<Rect, 3> Dirty_rect;
using Genode::size_t;
using Genode::Color;
using Genode::Xml_node;
}
#endif /* _INCLUDE__DECORATOR__TYPES_H_ */

View File

@ -0,0 +1,298 @@
/*
* \brief Window representation for decorator
* \author Norman Feske
* \date 2014-01-09
*/
/*
* 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 _INCLUDE__DECORATOR__WINDOW_H_
#define _INCLUDE__DECORATOR__WINDOW_H_
/* Genode includes */
#include <util/list.h>
#include <util/string.h>
#include <util/xml_generator.h>
#include <nitpicker_session/client.h>
#include <base/snprintf.h>
/* decorator includes */
#include <decorator/types.h>
#include <decorator/xml_utils.h>
namespace Decorator {
class Canvas_base;
class Window_base;
typedef Genode::List<Window_base> Window_list;
}
class Decorator::Window_base : public Window_list::Element
{
public:
struct Border
{
unsigned top, left, right, bottom;
Border(unsigned top, unsigned left, unsigned right, unsigned bottom)
: top(top), left(left), right(right), bottom(bottom) { }
};
struct Hover
{
bool left_sizer = false,
right_sizer = false,
top_sizer = false,
bottom_sizer = false,
title = false;
unsigned window_id = 0;
bool operator != (Hover const &other) const
{
return other.left_sizer != left_sizer
|| other.right_sizer != right_sizer
|| other.top_sizer != top_sizer
|| other.bottom_sizer != bottom_sizer
|| other.title != title
|| other.window_id != window_id;
}
};
private:
Nitpicker::Session_client &_nitpicker;
/*
* Geometry of content
*/
Rect _geometry;
/*
* Unique window ID
*/
unsigned const _id;
/*
* Flag indicating that the current window position has been propagated
* to the window's corresponding nitpicker views.
*/
bool _nitpicker_views_up_to_date = false;
/*
* Flag indicating that the stacking position of the window within the
* window stack has changed. The new stacking position must be
* propagated to nitpicker.
*/
bool _nitpicker_stacking_up_to_date = false;
unsigned _topped_cnt = 0;
bool _global_to_front = false;
Nitpicker::Session::View_handle _neighbor;
Border const _border;
struct Nitpicker_view
{
Nitpicker::Session_client &_nitpicker;
Nitpicker::Session::View_handle _handle { _nitpicker.create_view() };
typedef Nitpicker::Session::Command Command;
Nitpicker_view(Nitpicker::Session_client &nitpicker, unsigned id = 0)
:
_nitpicker(nitpicker)
{
/*
* We supply the window ID as label for the anchor view.
*/
if (id) {
char buf[128];
Genode::snprintf(buf, sizeof(buf), "%d", id);
_nitpicker.enqueue<Command::Title>(_handle, buf);
}
}
~Nitpicker_view()
{
_nitpicker.destroy_view(_handle);
}
Nitpicker::Session::View_handle handle() const { return _handle; }
void stack(Nitpicker::Session::View_handle neighbor)
{
_nitpicker.enqueue<Command::To_front>(_handle, neighbor);
}
void place(Rect rect)
{
_nitpicker.enqueue<Command::Geometry>(_handle, rect);
Point offset = Point(0, 0) - rect.p1();
_nitpicker.enqueue<Command::Offset>(_handle, offset);
}
};
Nitpicker_view _bottom_view, _right_view, _left_view, _top_view;
Nitpicker_view _content_view;
public:
Window_base(unsigned id, Nitpicker::Session_client &nitpicker,
Border border)
:
_nitpicker(nitpicker), _id(id), _border(border),
_bottom_view(nitpicker),
_right_view(nitpicker),
_left_view(nitpicker),
_top_view(nitpicker),
_content_view(nitpicker, _id)
{ }
void stack(Nitpicker::Session::View_handle neighbor)
{
_neighbor = neighbor;
_nitpicker_stacking_up_to_date = false;
}
Nitpicker::Session::View_handle frontmost_view() const
{
return _bottom_view.handle();
}
Rect outer_geometry() const
{
return Rect(_geometry.p1() - Point(_border.left, _border.top),
_geometry.p2() + Point(_border.right, _border.bottom));
}
void border_rects(Rect *top, Rect *left, Rect *right, Rect *bottom) const
{
outer_geometry().cut(_geometry, top, left, right, bottom);
}
unsigned long id() const { return _id; }
Rect geometry() const { return _geometry; }
bool is_in_front_of(Window_base const &neighbor) const
{
return _neighbor == neighbor.frontmost_view();
}
/**
* Draw window elements
*
* \param canvas graphics back end
* \param clip clipping area to apply
*/
virtual void draw(Canvas_base &canvas, Rect clip) const = 0;
/**
* Update internal window representation from XML model
*
* \return true if window changed
*
* We do not immediately update the views as part of the update
* function because at the time when updating the model, the
* decorations haven't been redrawn already. If we updated the
* nitpicker views at this point, we would reveal not-yet-drawn pixels.
*/
virtual bool update(Xml_node window_node)
{
bool result = false;
/*
* Detect the need to bring the window to the top of the global
* view stack.
*/
unsigned const topped_cnt = attribute(window_node, "topped", 0UL);
if (topped_cnt != _topped_cnt) {
_global_to_front = true;
_topped_cnt = topped_cnt;
_nitpicker_stacking_up_to_date = false;
result = true;
}
/*
* Detect geometry changes
*/
Rect new_geometry = rect_attribute(window_node);
if (new_geometry.p1() != _geometry.p1()
|| new_geometry.p2() != _geometry.p2()) {
_geometry = new_geometry;
_nitpicker_views_up_to_date = false;
result = true;
}
return result;
}
virtual void update_nitpicker_views()
{
if (!_nitpicker_views_up_to_date) {
/* update view positions */
Rect top, left, right, bottom;
border_rects(&top, &left, &right, &bottom);
_content_view.place(_geometry);
_top_view .place(top);
_left_view .place(left);
_right_view .place(right);
_bottom_view .place(bottom);
_nitpicker_views_up_to_date = true;
}
if (!_nitpicker_stacking_up_to_date) {
/*
* Bring the view to the global top of the view stack if the
* 'topped' counter changed. Otherwise, we refer to a
* session-local neighbor for the restacking operation.
*/
Nitpicker::Session::View_handle neighbor = _neighbor;
if (_global_to_front) {
neighbor = Nitpicker::Session::View_handle();
_global_to_front = false;
}
_content_view.stack(neighbor);
_top_view.stack(_content_view.handle());
_left_view.stack(_top_view.handle());
_right_view.stack(_left_view.handle());
_bottom_view.stack(_right_view.handle());
_nitpicker_stacking_up_to_date = true;
}
}
/**
* Report information about element at specified position
*
* \param position screen position
*/
virtual Hover hover(Point position) const = 0;
/**
* Return true if window needs to be redrawn event if the window layout
* model has not changed
*/
virtual bool animated() const { return false; }
};
#endif /* _INCLUDE__DECORATOR__WINDOW_H_ */

View File

@ -0,0 +1,31 @@
/*
* \brief Interface for creating and destroying windows
* \author Norman Feske
* \date 2014-01-09
*/
/*
* 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 _INCLUDE__DECORATOR__WINDOW_FACTORY_H_
#define _INCLUDE__DECORATOR__WINDOW_FACTORY_H_
#include <decorator/types.h>
namespace Decorator {
struct Window_base;
struct Window_factory_base;
}
struct Decorator::Window_factory_base
{
virtual Window_base *create (Xml_node) = 0;
virtual void destroy (Window_base *) = 0;
};
#endif /* _INCLUDE__DECORATOR__WINDOW_FACTORY_H_ */

View File

@ -0,0 +1,314 @@
/*
* \brief Window-stack handling for decorator
* \author Norman Feske
* \date 2014-01-09
*/
/*
* 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 _INCLUDE__DECORATOR__WINDOW_STACK_H_
#define _INCLUDE__DECORATOR__WINDOW_STACK_H_
/* Genode includes */
#include <nitpicker_session/nitpicker_session.h>
#include <base/printf.h>
/* local includes */
#include <decorator/types.h>
#include <decorator/window.h>
#include <decorator/window_factory.h>
#include <decorator/xml_utils.h>
namespace Decorator { class Window_stack; }
class Decorator::Window_stack
{
private:
Window_list _windows;
Window_factory_base &_window_factory;
Dirty_rect mutable _dirty_rect;
inline void _draw_rec(Canvas_base &canvas, Window_base const *win,
Rect rect) const;
Window_base *_lookup_by_id(unsigned const id)
{
for (Window_base *win = _windows.first(); win; win = win->next())
if (win->id() == id)
return win;
return 0;
}
static inline
Xml_node _xml_node_by_window_id(Genode::Xml_node node, unsigned id)
{
for (node = node.sub_node("window"); ; node = node.next()) {
if (node.has_type("window") && attribute(node, "id", 0UL) == id)
return node;
if (node.is_last()) break;
}
throw Xml_node::Nonexistent_sub_node();
}
void _destroy(Window_base &window)
{
_windows.remove(&window);
_window_factory.destroy(&window);
}
/**
* Generate window list in reverse order
*
* After calling this function, the '_windows' list is empty.
*/
Window_list _reversed_window_list()
{
Window_list reversed;
while (Window_base *w = _windows.first()) {
_windows.remove(w);
reversed.insert(w);
}
return reversed;
}
public:
Window_stack(Window_factory_base &window_factory)
:
_window_factory(window_factory)
{ }
Dirty_rect draw(Canvas_base &canvas) const
{
Dirty_rect result = _dirty_rect;
_dirty_rect.flush([&] (Rect const &rect) {
_draw_rec(canvas, _windows.first(), rect); });
return result;
}
inline void update_model(Xml_node root_node);
bool schedule_animated_windows()
{
bool redraw_needed = false;
for (Window_base *win = _windows.first(); win; win = win->next()) {
if (win->animated()) {
_dirty_rect.mark_as_dirty(win->outer_geometry());
redraw_needed = true;
}
}
return redraw_needed;
}
void update_nitpicker_views()
{
/*
* Update nitpicker views in reverse order (back-most first). The
* reverse order is important because the stacking position of a
* view is propagated by referring to the neighbor the view is in
* front of. By starting with the back-most view, we make sure that
* each view is always at its final stacking position when
* specified as neighbor of another view.
*/
Window_list reversed = _reversed_window_list();
while (Window_base *win = reversed.first()) {
win->update_nitpicker_views();
reversed.remove(win);
_windows.insert(win);
}
}
void flush()
{
while (Window_base *window = _windows.first())
_destroy(*window);
}
Window_base::Hover hover(Point pos) const
{
for (Window_base const *win = _windows.first(); win; win = win->next())
if (win->outer_geometry().contains(pos))
return win->hover(pos);
return Window_base::Hover();
}
};
void Decorator::Window_stack::_draw_rec(Decorator::Canvas_base &canvas,
Decorator::Window_base const *win,
Decorator::Rect rect) const
{
Rect clipped;
/* find next window that intersects with the rectangle */
for ( ; win && !(clipped = Rect::intersect(win->outer_geometry(), rect)).valid(); )
win = win->next();;
/* check if we hit the bottom of the window stack */
if (!win) return;
/* draw areas around the current window */
if (Window_base const * const next = win->next()) {
Rect top, left, right, bottom;
rect.cut(clipped, &top, &left, &right, &bottom);
if (top.valid()) _draw_rec(canvas, next, top);
if (left.valid()) _draw_rec(canvas, next, left);
if (right.valid()) _draw_rec(canvas, next, right);
if (bottom.valid()) _draw_rec(canvas, next, bottom);
}
/* draw current window */
win->draw(canvas, clipped);
}
void Decorator::Window_stack::update_model(Genode::Xml_node root_node)
{
/*
* Step 1: Remove windows that are no longer present.
*/
for (Window_base *window = _windows.first(), *next = 0; window; window = next) {
next = window->next();
try {
_xml_node_by_window_id(root_node, window->id()); }
catch (Xml_node::Nonexistent_sub_node) {
_destroy(*window); };
}
/*
* Step 2: Update window properties of already present windows.
*/
for (Window_base *window = _windows.first(); window; window = window->next()) {
/*
* After step 1, a Xml_node::Nonexistent_sub_node exception can no
* longer occur. All windows remaining in the window stack are present
* in the XML model.
*/
try {
Rect const orig_geometry = window->outer_geometry();
if (window->update(_xml_node_by_window_id(root_node, window->id()))) {
_dirty_rect.mark_as_dirty(orig_geometry);
_dirty_rect.mark_as_dirty(window->outer_geometry());
}
}
catch (Xml_node::Nonexistent_sub_node) {
PERR("could not look up window %ld in XML model", window->id()); }
}
/*
* Step 3: Add new appearing windows to the window stack.
*/
for_each_sub_node(root_node, "window", [&] (Xml_node window_node) {
unsigned long const id = attribute(window_node, "id", 0UL);
if (!_lookup_by_id(id)) {
Window_base *new_window = _window_factory.create(window_node);
if (new_window) {
new_window->update(window_node);
/*
* Insert new window in front of all other windows.
*
* Immediately propagate the new stacking position of the new
* window to nitpicker ('update_nitpicker_views'). Otherwise,
*/
new_window->stack(_windows.first()
? _windows.first()->frontmost_view()
: Nitpicker::Session::View_handle());
_windows.insert(new_window);
_dirty_rect.mark_as_dirty(new_window->outer_geometry());
}
}
});
/*
* Step 4: Adjust window order.
*/
Window_base *previous_window = 0;
Window_base *window = _windows.first();
for_each_sub_node(root_node, "window", [&] (Xml_node window_node) {
if (!window) {
PERR("unexpected end of window list during re-ordering");
return;
}
unsigned long const id = attribute(window_node, "id", 0UL);
if (window->id() != id) {
window = _lookup_by_id(id);
if (!window) {
PERR("window lookup unexpectedly failed during re-ordering");
return;
}
_windows.remove(window);
_windows.insert(window, previous_window);
_dirty_rect.mark_as_dirty(window->outer_geometry());
}
previous_window = window;
window = window->next();
});
/*
* Propagate changed stacking order to nitpicker
*
* First, we reverse the window list. The 'reversed' list starts with
* the back-most window. We then go throuh each window back to front
* and check if its neighbor is consistent with its position in the
* window list.
*/
Window_list reversed = _reversed_window_list();
if (Window_base * const back_most = reversed.first()) {
/* keep back-most window as is */
reversed.remove(back_most);
_windows.insert(back_most);
/* check consistency between window list order and view stacking */
while (Window_base *w = reversed.first()) {
Window_base * const neighbor = _windows.first();
reversed.remove(w);
_windows.insert(w);
/* propagate change stacking order to nitpicker */
if (w->is_in_front_of(*neighbor))
continue;
w->stack(neighbor->frontmost_view());
}
}
}
#endif /* _INCLUDE__DECORATOR__WINDOW_STACK_H_ */

View File

@ -0,0 +1,134 @@
/*
* \brief Utilities for XML parsing
* \author Norman Feske
* \date 2014-01-09
*/
/*
* 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 _INCLUDE__DECORATOR__XML_UTILS_H_
#define _INCLUDE__DECORATOR__XML_UTILS_H_
#include <decorator/types.h>
namespace Decorator {
template <typename T>
static T attribute(Xml_node const &, char const *, T);
template <size_t CAPACITY>
Genode::String<CAPACITY> string_attribute(Xml_node const &, char const *,
Genode::String<CAPACITY> const &);
static Point point_attribute(Xml_node const &);
static Area area_attribute(Xml_node const &);
static Rect rect_attribute(Xml_node const &);
template <typename FUNC>
static void for_each_sub_node(Xml_node, char const *, FUNC const &);
static Color color(Xml_node const &);
}
/**
* Read attribute value from XML node
*
* \param node XML node
* \param name attribute name
* \param default_value value returned if no such attribute exists
*/
template <typename T>
static T
Decorator::attribute(Xml_node const &node, char const *name, T default_value)
{
T result = default_value;
if (node.has_attribute(name))
node.attribute(name).value(&result);
return result;
}
/**
* Read string from XML node
*/
template <Genode::size_t CAPACITY>
Genode::String<CAPACITY>
Decorator::string_attribute(Xml_node const &node, char const *attr,
Genode::String<CAPACITY> const &default_value)
{
if (!node.has_attribute(attr))
return default_value;
char buf[CAPACITY];
node.attribute(attr).value(buf, sizeof(buf));
return Genode::String<CAPACITY>(buf);
}
/**
* Read point position from XML node
*/
static inline Decorator::Point Decorator::point_attribute(Genode::Xml_node const &point)
{
return Point(attribute(point, "xpos", 0L),
attribute(point, "ypos", 0L)); }
/**
* Read area size from XML node
*/
static inline Decorator::Area Decorator::area_attribute(Genode::Xml_node const &area)
{
return Area(attribute(area, "width", 0UL),
attribute(area, "height", 0UL));
}
/**
* Read rectangle coordinates from XML node
*/
static inline Decorator::Rect Decorator::rect_attribute(Genode::Xml_node const &rect)
{
return Rect(point_attribute(rect), area_attribute(rect));
}
/**
* Apply functor 'func' to all XML sub nodes of given type
*/
template <typename FUNC>
static void
Decorator::for_each_sub_node(Genode::Xml_node node, char const *type,
FUNC const &func)
{
if (!node.has_sub_node(type))
return;
for (node = node.sub_node(type); ; node = node.next()) {
if (node.has_type(type))
func(node);
if (node.is_last()) break;
}
}
/**
* Read color attribute from XML node
*/
static inline Genode::Color Decorator::color(Genode::Xml_node const &color)
{
return attribute(color, "color", Color(0, 0, 0));
}
#endif /* _INCLUDE__DECORATOR__XML_UTILS_H_ */