diff --git a/repos/gems/run/decorator.run b/repos/gems/run/decorator.run
new file mode 100644
index 000000000..638675bbd
--- /dev/null
+++ b/repos/gems/run/decorator.run
@@ -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 {
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+}
+
+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
diff --git a/repos/gems/src/app/decorator/animator.h b/repos/gems/src/app/decorator/animator.h
new file mode 100644
index 000000000..52b5a5a09
--- /dev/null
+++ b/repos/gems/src/app/decorator/animator.h
@@ -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- _items;
+
+ public:
+
+ inline void animate();
+};
+
+
+/**
+ * Interface to be implemented by animated objects
+ */
+class Animator::Item : public Genode::List
- ::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_ */
diff --git a/repos/gems/src/app/decorator/canvas.h b/repos/gems/src/app/decorator/canvas.h
new file mode 100644
index 000000000..c96c2258c
--- /dev/null
+++ b/repos/gems/src/app/decorator/canvas.h
@@ -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
+
+namespace Decorator {
+ typedef Text_painter::Font Font;
+ Font &default_font();
+ template 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
+class Decorator::Canvas : public Decorator::Canvas_base
+{
+ private:
+
+ Genode::Surface _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_ */
+
diff --git a/repos/gems/src/app/decorator/main.cc b/repos/gems/src/app/decorator/main.cc
new file mode 100644
index 000000000..ed2fc302d
--- /dev/null
+++ b/repos/gems/src/app/decorator/main.cc
@@ -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
+#include
+#include
+#include
+#include
+#include
+
+/* Nitpicker graphics backend */
+#include
+#include
+
+/* decorator includes */
+#include
+#include
+
+/* 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 canvas = { fb_ds.local_addr(),
+ 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 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 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 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));
+ }
+};
+
+
+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(),
+ 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());
+
+ 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(sig.context());
+
+ if (dispatcher)
+ dispatcher->dispatch(sig.num());
+ }
+}
diff --git a/repos/gems/src/app/decorator/target.mk b/repos/gems/src/app/decorator/target.mk
new file mode 100644
index 000000000..71e1cc2b7
--- /dev/null
+++ b/repos/gems/src/app/decorator/target.mk
@@ -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)
diff --git a/repos/gems/src/app/decorator/window.h b/repos/gems/src/app/decorator/window.h
new file mode 100644
index 000000000..d5107c201
--- /dev/null
+++ b/repos/gems/src/app/decorator/window.h
@@ -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
+#include
+
+/* local includes */
+#include
+
+
+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 _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(""));
+ 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_ */
diff --git a/repos/os/include/decorator/types.h b/repos/os/include/decorator/types.h
new file mode 100644
index 000000000..8cd0a70f9
--- /dev/null
+++ b/repos/os/include/decorator/types.h
@@ -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
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace Decorator {
+ typedef Genode::Surface_base::Point Point;
+ typedef Genode::Surface_base::Area Area;
+ typedef Genode::Surface_base::Rect Rect;
+
+ typedef Genode::Dirty_rect Dirty_rect;
+
+ using Genode::size_t;
+ using Genode::Color;
+ using Genode::Xml_node;
+}
+
+#endif /* _INCLUDE__DECORATOR__TYPES_H_ */
diff --git a/repos/os/include/decorator/window.h b/repos/os/include/decorator/window.h
new file mode 100644
index 000000000..316926086
--- /dev/null
+++ b/repos/os/include/decorator/window.h
@@ -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
+#include
+#include
+#include
+#include
+
+/* decorator includes */
+#include
+#include
+
+
+namespace Decorator {
+ class Canvas_base;
+ class Window_base;
+ typedef Genode::List 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(_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(_handle, neighbor);
+ }
+
+ void place(Rect rect)
+ {
+ _nitpicker.enqueue(_handle, rect);
+ Point offset = Point(0, 0) - rect.p1();
+ _nitpicker.enqueue(_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_ */
diff --git a/repos/os/include/decorator/window_factory.h b/repos/os/include/decorator/window_factory.h
new file mode 100644
index 000000000..2782e2b38
--- /dev/null
+++ b/repos/os/include/decorator/window_factory.h
@@ -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
+
+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_ */
diff --git a/repos/os/include/decorator/window_stack.h b/repos/os/include/decorator/window_stack.h
new file mode 100644
index 000000000..37542fbcb
--- /dev/null
+++ b/repos/os/include/decorator/window_stack.h
@@ -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
+#include
+
+/* local includes */
+#include
+#include
+#include
+#include
+
+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_ */
diff --git a/repos/os/include/decorator/xml_utils.h b/repos/os/include/decorator/xml_utils.h
new file mode 100644
index 000000000..3cd72e5ef
--- /dev/null
+++ b/repos/os/include/decorator/xml_utils.h
@@ -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
+
+
+namespace Decorator {
+ template
+ static T attribute(Xml_node const &, char const *, T);
+
+ template
+ Genode::String string_attribute(Xml_node const &, char const *,
+ Genode::String const &);
+
+ static Point point_attribute(Xml_node const &);
+
+ static Area area_attribute(Xml_node const &);
+
+ static Rect rect_attribute(Xml_node const &);
+
+ template
+ 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
+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::String
+Decorator::string_attribute(Xml_node const &node, char const *attr,
+ Genode::String const &default_value)
+{
+ if (!node.has_attribute(attr))
+ return default_value;
+
+ char buf[CAPACITY];
+ node.attribute(attr).value(buf, sizeof(buf));
+ return Genode::String(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
+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_ */