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_ */