diff --git a/repos/gems/src/app/decorator/canvas.h b/repos/gems/src/app/decorator/canvas.h index c96c2258c2..abb7dee208 100644 --- a/repos/gems/src/app/decorator/canvas.h +++ b/repos/gems/src/app/decorator/canvas.h @@ -14,30 +14,34 @@ #ifndef _CANVAS_H_ #define _CANVAS_H_ +/* Painters of the nitpicker and scout graphics backends */ +#include +#include +#include + +/* decorator includes */ #include namespace Decorator { + typedef Text_painter::Font Font; Font &default_font(); + + enum Texture_id { + TEXTURE_ID_CLOSER, + TEXTURE_ID_MINIMIZE, + TEXTURE_ID_MAXIMIZE, + TEXTURE_ID_WINDOWED + }; + + Genode::Texture_base const &texture_by_id(Texture_id); + + class Canvas_base; 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 */ @@ -47,6 +51,7 @@ struct Decorator::Canvas_base virtual void clip(Rect) = 0; virtual void draw_box(Rect, Color) = 0; virtual void draw_text(Point, Font const &, Color, char const *) = 0; + virtual void draw_texture(Point, Texture_id) = 0; }; @@ -75,6 +80,17 @@ class Decorator::Canvas : public Decorator::Canvas_base { Text_painter::paint(_surface, pos, font, color, string); } + + void draw_texture(Point pos, Texture_id id) + { + Genode::Texture const &texture = + static_cast const &>(texture_by_id(id)); + + unsigned const alpha = 255; + + Icon_painter::paint(_surface, Rect(pos, texture.size()), texture, alpha); + + } }; diff --git a/repos/gems/src/app/decorator/closer.rgba b/repos/gems/src/app/decorator/closer.rgba new file mode 100644 index 0000000000..46ee7b7023 Binary files /dev/null and b/repos/gems/src/app/decorator/closer.rgba differ diff --git a/repos/gems/src/app/decorator/config.h b/repos/gems/src/app/decorator/config.h new file mode 100644 index 0000000000..ccef60671f --- /dev/null +++ b/repos/gems/src/app/decorator/config.h @@ -0,0 +1,223 @@ +/* + * \brief Decorator configuration handling + * \author Norman Feske + * \date 2015-09-17 + */ + +/* + * Copyright (C) 2015 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 _CONFIG_H_ +#define _CONFIG_H_ + +/* Genode includes */ +#include +#include + +/* decorator includes */ +#include + +namespace Decorator { + + class Config; + + typedef Genode::String<200> Window_title; +} + + +class Decorator::Config +{ + public: + + class Window_control + { + public: + + enum Type { TYPE_CLOSER, TYPE_TITLE, TYPE_MAXIMIZER, + TYPE_MINIMIZER, TYPE_UNMAXIMIZER, TYPE_UNDEFINED }; + + enum Align { ALIGN_LEFT, ALIGN_CENTER, ALIGN_RIGHT }; + + private: + + Type _type = TYPE_UNDEFINED; + Align _align = ALIGN_CENTER; + + public: + + Window_control() { } + + Window_control(Type type, Align align) + : _type(type), _align(align) { } + + Type type() const { return _type; } + Align align() const { return _align; } + + static char const *type_name(Type type) + { + switch (type) { + case TYPE_CLOSER: return "closer"; + case TYPE_TITLE: return "title"; + case TYPE_MAXIMIZER: return "maximizer"; + case TYPE_MINIMIZER: return "minimizer"; + case TYPE_UNMAXIMIZER: return "unmaximizer"; + case TYPE_UNDEFINED: return "undefined"; + }; + return ""; + } + + bool operator != (Window_control const &other) const + { + return _type != other._type || _align != other._align; + } + }; + + private: + + Genode::Allocator &_alloc; + + /** + * Maximum number of configured window controls + */ + enum { MAX_WINDOW_CONTROLS = 10 }; + + /** + * Array of window elements + */ + Window_control *_window_controls[MAX_WINDOW_CONTROLS]; + + unsigned _num_window_controls = 0; + + void _reset_window_controls() + { + for (unsigned i = 0; i < MAX_WINDOW_CONTROLS; i++) { + if (_window_controls[i]) { + Genode::destroy(_alloc, _window_controls[i]); + _window_controls[i] = nullptr; + } + } + _num_window_controls = 0; + } + + public: + + Config(Genode::Allocator &alloc) : _alloc(alloc) + { + _reset_window_controls(); + } + + /** + * Exception type + */ + class Index_out_of_range { }; + + /** + * Return information about the Nth window control + * + * The index 'n' denotes the position of the window control from left + * to right. + * + * \throw Index_out_of_range + */ + Window_control window_control(unsigned n) const + { + /* return title of no controls are configured */ + if (_num_window_controls == 0 && n == 0) + return Window_control(Window_control::TYPE_TITLE, + Window_control::ALIGN_CENTER); + + if (n >= MAX_WINDOW_CONTROLS || !_window_controls[n]) + throw Index_out_of_range(); + + return *_window_controls[n]; + } + + unsigned num_window_controls() const + { + /* + * We always report at least one window control. Even if none + * was configured, we present a title. + */ + return Genode::max(_num_window_controls, 1UL); + } + + /** + * Return the base color of the window with the specified title + */ + Color base_color(Window_title const &title) const + { + Color result(68, 75, 95); + + try { + Genode::Session_policy policy(title); + result = policy.attribute_value("color", result); + + } catch (Genode::Session_policy::No_policy_defined) { } + + return result; + } + + /** + * Return gradient intensity in percent + */ + int gradient_percent(Window_title const &title) const + { + unsigned long result = + Genode::config()->xml_node().attribute_value("gradient", 32UL); + + try { + Genode::Session_policy policy(title); + result = policy.attribute_value("gradient", result); + + } catch (Genode::Session_policy::No_policy_defined) { } + + return result; + } + + /** + * Update the internally cached configuration state + */ + void update() + { + _reset_window_controls(); + + /* + * Parse configuration of window controls + */ + auto lambda = [&] (Xml_node node) { + + if (_num_window_controls >= MAX_WINDOW_CONTROLS) { + PWRN("number of configured window controls exceeds maximum"); + return; + } + + Window_control::Type type = Window_control::TYPE_UNDEFINED; + Window_control::Align align = Window_control::ALIGN_CENTER; + + if (node.has_type("title")) type = Window_control::TYPE_TITLE; + if (node.has_type("closer")) type = Window_control::TYPE_CLOSER; + if (node.has_type("maximizer")) type = Window_control::TYPE_MAXIMIZER; + if (node.has_type("minimizer")) type = Window_control::TYPE_MINIMIZER; + + if (node.has_attribute("align")) { + Genode::Xml_attribute attr = node.attribute("align"); + if (attr.has_value("left")) align = Window_control::ALIGN_LEFT; + if (attr.has_value("right")) align = Window_control::ALIGN_RIGHT; + } + + _window_controls[_num_window_controls++] = + new (_alloc) Window_control(type, align); + }; + + Xml_node config = Genode::config()->xml_node(); + + try { config.sub_node("controls").for_each_sub_node(lambda); } + catch (Xml_node::Nonexistent_sub_node) { } + } +}; + +#endif /* _CONFIG_H_ */ diff --git a/repos/gems/src/app/decorator/default_font.cc b/repos/gems/src/app/decorator/default_font.cc new file mode 100644 index 0000000000..47026e6a8f --- /dev/null +++ b/repos/gems/src/app/decorator/default_font.cc @@ -0,0 +1,34 @@ +/* + * \brief Accessor for the default font + * \author Norman Feske + * \date 2015-09-16 + */ + +/* + * Copyright (C) 2015 Genode Labs GmbH + * + * This file is part of the Genode OS framework, which is distributed + * under the terms of the GNU General Public License version 2. + */ + +/* local includes */ +#include "canvas.h" + + +/** + * Statically linked binary data + */ +extern char _binary_droidsansb10_tff_start[]; + + +/** + * Return default font + */ +Decorator::Font &Decorator::default_font() +{ + static Font font(_binary_droidsansb10_tff_start); + return font; +} + + + diff --git a/repos/gems/src/app/decorator/main.cc b/repos/gems/src/app/decorator/main.cc index 6e56f0defc..99ee30b062 100644 --- a/repos/gems/src/app/decorator/main.cc +++ b/repos/gems/src/app/decorator/main.cc @@ -19,10 +19,6 @@ #include #include -/* Nitpicker graphics backend */ -#include -#include - /* decorator includes */ #include #include @@ -104,11 +100,21 @@ struct Decorator::Main : Window_factory_base Signal_dispatcher
nitpicker_sync_dispatcher = { sig_rec, *this, &Main::handle_nitpicker_sync }; + Config config { *Genode::env()->heap() }; + + void handle_config(unsigned); + + Signal_dispatcher
config_dispatcher = { + sig_rec, *this, &Main::handle_config}; + /** * Constructor */ Main(Signal_receiver &sig_rec) : sig_rec(sig_rec) { + Genode::config()->sigh(config_dispatcher); + handle_config(0); + window_layout.sigh(window_layout_dispatcher); pointer.sigh(pointer_dispatcher); @@ -125,7 +131,7 @@ struct Decorator::Main : Window_factory_base for (unsigned retry = 0 ; retry < 2; retry ++) { try { return new (env()->heap()) - Window(attribute(window_node, "id", 0UL), nitpicker, animator); + Window(attribute(window_node, "id", 0UL), nitpicker, animator, config); } catch (Nitpicker::Session::Out_of_metadata) { PINF("Handle Out_of_metadata of nitpicker session - upgrade by 8K"); Genode::env()->parent()->upgrade(nitpicker.cap(), "ram_quota=8192"); @@ -144,6 +150,21 @@ struct Decorator::Main : Window_factory_base }; +void Decorator::Main::handle_config(unsigned) +{ + Genode::config()->reload(); + + config.update(); + + /* notify all windows to consider the updated policy */ + window_stack.for_each_window([&] (Window_base &window) { + static_cast(window).adapt_to_changed_config(); }); + + /* trigger redraw of the window stack */ + handle_window_layout_update(0); +} + + static Decorator::Window_base::Hover find_hover(Genode::Xml_node pointer_node, Decorator::Window_stack &window_stack) { @@ -181,6 +202,10 @@ static void update_hover_report(Genode::Xml_node pointer_node, if (hover.top_sizer) xml.node("top_sizer"); if (hover.bottom_sizer) xml.node("bottom_sizer"); if (hover.title) xml.node("title"); + if (hover.closer) xml.node("closer"); + if (hover.minimizer) xml.node("minimizer"); + if (hover.maximizer) xml.node("maximizer"); + if (hover.unmaximizer) xml.node("unmaximizer"); }); } }); diff --git a/repos/gems/src/app/decorator/maximize.rgba b/repos/gems/src/app/decorator/maximize.rgba new file mode 100644 index 0000000000..608a5512d7 Binary files /dev/null and b/repos/gems/src/app/decorator/maximize.rgba differ diff --git a/repos/gems/src/app/decorator/minimize.rgba b/repos/gems/src/app/decorator/minimize.rgba new file mode 100644 index 0000000000..4055fe8bf5 Binary files /dev/null and b/repos/gems/src/app/decorator/minimize.rgba differ diff --git a/repos/gems/src/app/decorator/target.mk b/repos/gems/src/app/decorator/target.mk index 71e1cc2b71..4415601439 100644 --- a/repos/gems/src/app/decorator/target.mk +++ b/repos/gems/src/app/decorator/target.mk @@ -1,8 +1,10 @@ TARGET = decorator -SRC_CC = main.cc +SRC_CC = main.cc texture_by_id.cc default_font.h window.cc +SRC_BIN = closer.rgba maximize.rgba minimize.rgba windowed.rgba +SRC_BIN += droidsansb10.tff 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) +vpath %.tff $(TFF_DIR) +vpath %.rgba $(PRG_DIR) diff --git a/repos/gems/src/app/decorator/texture_by_id.cc b/repos/gems/src/app/decorator/texture_by_id.cc new file mode 100644 index 0000000000..d8aeb55615 --- /dev/null +++ b/repos/gems/src/app/decorator/texture_by_id.cc @@ -0,0 +1,84 @@ +/* + * \brief Accessor for the default font + * \author Norman Feske + * \date 2015-09-16 + */ + +/* + * Copyright (C) 2015 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 + +/* local includes */ +#include "canvas.h" + + +template +class Icon_texture : public Chunky_texture +{ + public: + + /** + * Known dimensions of the statically linked RGBA pixel data + */ + enum { WIDTH = 14, HEIGHT = 14 }; + + Icon_texture(Genode::Ram_session &ram, unsigned char rgba_data[]) + : + Chunky_texture(ram, Genode::Surface_base::Area(WIDTH, HEIGHT)) + { + unsigned char const *src = rgba_data; + Genode::size_t const src_line_bytes = WIDTH*4; + + for (unsigned y = 0; y < HEIGHT; y++, src += src_line_bytes) + Chunky_texture::rgba(src, WIDTH, y); + } +}; + + +/** + * Statically linked binary data + */ +extern unsigned char _binary_closer_rgba_start[]; +extern unsigned char _binary_minimize_rgba_start[]; +extern unsigned char _binary_maximize_rgba_start[]; +extern unsigned char _binary_windowed_rgba_start[]; + + +/** + * Return texture for the specified texture ID + */ +Genode::Texture_base const &Decorator::texture_by_id(Texture_id id) +{ + Genode::Ram_session &ram = *Genode::env()->ram_session(); + + static Icon_texture const icons[4] { + { ram, _binary_closer_rgba_start }, + { ram, _binary_minimize_rgba_start }, + { ram, _binary_maximize_rgba_start }, + { ram, _binary_windowed_rgba_start } }; + + switch (id) { + + case TEXTURE_ID_CLOSER: /* fall through... */ + case TEXTURE_ID_MINIMIZE: + case TEXTURE_ID_MAXIMIZE: + case TEXTURE_ID_WINDOWED: + return icons[id]; + + default: + break; + }; + + struct Invalid_texture_id { }; + throw Invalid_texture_id(); +} + diff --git a/repos/gems/src/app/decorator/window.cc b/repos/gems/src/app/decorator/window.cc new file mode 100644 index 0000000000..4a9c33c87b --- /dev/null +++ b/repos/gems/src/app/decorator/window.cc @@ -0,0 +1,376 @@ +/* + * \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. + */ + +/* local includes */ +#include "window.h" + + +void Decorator::Window::draw(Decorator::Canvas_base &canvas, + Decorator::Rect clip, + Draw_behind_fn const &draw_behind_fn) 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(); + + if (_has_alpha) + draw_behind_fn.draw_behind(canvas, *this, canvas.clip()); + + _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 controls_rect(Point(p1.x() + _border_size, p1.y() + _border_size), + Area(rect.w() - 2*_border_size, _title_height)); + + + /* + * Draw left controls from left to right + */ + + Control::Align title_align = Control::ALIGN_CENTER; + + Point left_pos = controls_rect.p1(); + + for (unsigned i = 0; i < _controls.num(); i++) { + + Control control = _controls.control(i); + + /* left controls end when we reach the title */ + if (control.type() == Control::TYPE_TITLE) { + title_align = control.align(); + break; + } + + _draw_window_control(canvas, Rect(left_pos, _icon_size), control); + left_pos = left_pos + Point(_icon_size.w(), 0); + } + + /* + * Draw right controls from right to left + */ + + Point right_pos = controls_rect.p1() + Point(controls_rect.w() - _icon_size.w(), 0); + + if (_controls.num() > 0) { + for (unsigned i = _controls.num() - 1; i >= 0; i--) { + + Control control = _controls.control(i); + + /* stop when reaching the title */ + if (control.type() == Control::TYPE_TITLE) + break; + + /* detect overlap with left controls */ + if (right_pos.x() <= left_pos.x()) + break; + + _draw_window_control(canvas, Rect(right_pos, _icon_size), control); + right_pos = right_pos + Point(-_icon_size.w(), 0); + } + } + + /* + * Draw title between left and right controls + */ + + Rect title_rect(left_pos, Area(right_pos.x() - left_pos.x() + _icon_size.w(), + _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)); + + /* + * Position the text in the center of the window. + */ + Point const window_centered_text_pos = controls_rect.center(label_area) - Point(0, 1); + + /* + * Horizontal position of the title text + */ + int x = window_centered_text_pos.x(); + + /* + * If the title bar is narrower than three times the label but the text + * still fits in the title bar, we gradually change the text position + * towards the center of the title bar. If the text fits twice in the + * title bar, it is centered within the title bar. + */ + if (label_area.w() <= title_rect.w() && label_area.w()*3 > title_rect.w()) { + + int ratio = ((label_area.w()*3 - title_rect.w()) << 8) / title_rect.w(); + + if (ratio > 255) + ratio = 255; + + Point const titlebar_centered_text_pos = + title_rect.center(label_area) - Point(0, 1); + + x = (titlebar_centered_text_pos.x()*ratio + + window_centered_text_pos.x()*(255 - ratio)) >> 8; + } + + /* minimum distance between the title text and the title border */ + int const min_horizontal_padding = 4; + + /* + * Consider non-default title alignments + */ + if (title_align == Control::ALIGN_LEFT) + x = title_rect.x1() + min_horizontal_padding; + + if (title_align == Control::ALIGN_RIGHT) + x = title_rect.x2() - label_area.w() - min_horizontal_padding; + + /* + * If the text does not fit into the title bar, align it to the left + * border of the title bar to show the first part. + */ + if (label_area.w() + 2*min_horizontal_padding > title_rect.w()) + x = title_rect.x1() + min_horizontal_padding; + + { + Rect const title_content_rect(title_rect.p1() + Point(1, 1), + title_rect.p2() - Point(1, 1)); + + Clip_guard clip_guard(canvas, title_content_rect); + + Point const text_pos(x, window_centered_text_pos.y()); + + 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); + } +} + + +/** + * Return true if specified XML attribute has the given value + */ +static bool attribute_has_value(Genode::Xml_node node, + char const *attr, char const *value) +{ + return node.has_attribute(attr) && node.attribute(attr).has_value(value); +} + + +bool Decorator::Window::update(Genode::Xml_node window_node) +{ + bool updated = Window_base::update(window_node); + + _focused = attribute_has_value(window_node, "focused", "yes"); + _has_alpha = attribute_has_value(window_node, "has_alpha", "yes"); + + Window_title title = Decorator::string_attribute(window_node, "title", + Window_title("")); + updated |= !(title == _title); + _title = title; + + /* update color on title change as the title is used as policy selector */ + Color const base_color = _config.base_color(_title); + updated |= _base_color != base_color; + _base_color = base_color; + + int const gradient_percent = _config.gradient_percent(_title); + updated |= _gradient_percent != gradient_percent; + _gradient_percent = gradient_percent; + + /* update window-control configuration */ + Controls new_controls; + for (unsigned i = 0; i < _config.num_window_controls(); i++) { + + Control window_control = _config.window_control(i); + + switch (window_control.type()) { + + case Control::TYPE_CLOSER: + case Control::TYPE_MAXIMIZER: + case Control::TYPE_MINIMIZER: + case Control::TYPE_UNMAXIMIZER: + case Control::TYPE_UNDEFINED: + { + char const * const attr = + Control::type_name(window_control.type()); + + if (attribute_has_value(window_node, attr, "yes")) + new_controls.add(window_control); + break; + } + + case Control::TYPE_TITLE: + new_controls.add(window_control); + break; + }; + } + + updated |= (new_controls != _controls); + _controls = new_controls; + + 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); + } + + 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 { + + Point const titlbar_pos(_border_size, _border_size); + + hover.title = false; + hover.closer = false; + hover.minimizer = false; + hover.maximizer = false; + hover.unmaximizer = false; + + /* + * Check if pointer is located at the title bar + */ + if (y < _border_size + _title_height) { + + Control hovered_control = Control(Control::TYPE_TITLE, Control::ALIGN_CENTER); + + /* check left controls */ + { + Point pos = titlbar_pos; + for (unsigned i = 0; i < _controls.num(); i++) { + + /* controls end when we reach the title */ + if (_controls.control(i).type() == Control::TYPE_TITLE) + break; + + if (Rect(pos, _icon_size).contains(Point(x, y))) + hovered_control = _controls.control(i); + + pos = pos + Point(_icon_size.w(), 0); + } + } + + /* check right controls */ + if (_controls.num() > 0) { + + Point pos = titlbar_pos + + Point(area.w() - _border_size - _icon_size.w(), 0); + + for (unsigned i = _controls.num() - 1; i >= 0; i--) { + + /* controls end when we reach the title */ + if (_controls.control(i).type() == Control::TYPE_TITLE) + break; + + if (Rect(pos, _icon_size).contains(Point(x, y))) + hovered_control = _controls.control(i); + + pos = pos + Point(-_icon_size.w(), 0); + } + } + + switch (hovered_control.type()) { + case Control::TYPE_CLOSER: hover.closer = true; break; + case Control::TYPE_MAXIMIZER: hover.maximizer = true; break; + case Control::TYPE_MINIMIZER: hover.minimizer = true; break; + case Control::TYPE_UNMAXIMIZER: hover.unmaximizer = true; break; + case Control::TYPE_TITLE: hover.title = true; break; + case Control::TYPE_UNDEFINED: break; + }; + } + } + + 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()); +} diff --git a/repos/gems/src/app/decorator/window.h b/repos/gems/src/app/decorator/window.h index fa34cad1d4..806ef8ebf5 100644 --- a/repos/gems/src/app/decorator/window.h +++ b/repos/gems/src/app/decorator/window.h @@ -14,11 +14,12 @@ #ifndef _WINDOW_H_ #define _WINDOW_H_ -#include +/* Genode includes */ #include -/* gems includes */ -#include +/* local includes */ +#include "config.h" +#include "window_element.h" namespace Decorator { class Window; } @@ -26,18 +27,16 @@ namespace Decorator { class Window; } class Decorator::Window : public Window_base { - public: - - typedef Genode::String<200> Title; - private: - Title _title; + Window_title _title; bool _focused = false; Animator &_animator; + Config const &_config; + static unsigned const _corner_size = 16; static unsigned const _border_size = 4; static unsigned const _title_height = 16; @@ -49,141 +48,39 @@ class Decorator::Window : public Window_base Color _bright = { 255, 255, 255, 64 }; Color _dark = { 0, 0, 0, 127 }; - Color _base_color() const { return Color(45, 49, 65); } + Color _base_color = _config.base_color(_title); bool _has_alpha = false; - class Element : public Animator::Item - { - public: + Area const _icon_size { 16, 16 }; - enum Type { TITLE, LEFT, RIGHT, TOP, BOTTOM, - TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT, - UNDEFINED }; - private: + /* + * Intensity of the title-bar radient in percent. A value of 0 produces + * no gradient. A value of 100 creates a gradient from white over + * 'color' to black. + */ + Lazy_value _gradient_percent = _config.gradient_percent(_title); - 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 15; - - /* slow fade-out when leaving focus or hover highlight */ - return 20; - } - - 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; - }; + typedef Window_element Element; /* * 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 _elements[13] { { 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::CLOSER, _animator, _base_color }, + { Element::MAXIMIZE, _animator, _base_color }, + { Element::MINIMIZE, _animator, _base_color }, + { Element::UNMAXIMIZE, _animator, _base_color } }; Element &element(Element::Type type) { @@ -197,6 +94,70 @@ class Decorator::Window : public Window_base unsigned num_elements() const { return sizeof(_elements)/sizeof(Element); } + bool _apply_state(Window::Element::Type type, bool focused, bool highlighted) + { + return element(type).apply_state(_focused, highlighted, _base_color); + } + + typedef Config::Window_control Control; + + class Controls + { + public: + + enum { MAX_CONTROLS = 10 }; + + private: + + Control _controls[MAX_CONTROLS]; + + unsigned _num = 0; + + public: + + /** + * Add window control + */ + void add(Control control) + { + if (_num < MAX_CONTROLS) + _controls[_num++] = control; + } + + unsigned num() const { return _num; } + + class Index_out_of_range { }; + + /** + * Obtain Nth window control + */ + Control control(unsigned n) const + { + if (n >= MAX_CONTROLS) + throw Index_out_of_range(); + + return _controls[n]; + } + + bool operator != (Controls const &other) const + { + if (_num != other._num) return true; + + for (unsigned i = 0; i < _num; i++) + if (_controls[i] != other._controls[i]) + return true; + + return false; + } + }; + + Controls _controls; + + + /*********************** + ** Drawing utilities ** + ***********************/ + void _draw_hline(Canvas_base &canvas, Point pos, unsigned w, bool at_left, bool at_right, unsigned border, Color color) const @@ -235,13 +196,43 @@ class Decorator::Window : public Window_base _draw_raised_frame(canvas, rect); } + static Color _mix_colors(Color c1, Color c2, int alpha) + { + return Color((c1.r*alpha + c2.r*(255 - alpha)) >> 8, + (c1.g*alpha + c2.g*(255 - alpha)) >> 8, + (c1.b*alpha + c2.b*(255 - alpha)) >> 8); + } + 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++) + /* + * Produce gradient such that the upper half becomes brighter and + * the lower half becomes darker. The gradient is created by mixing + * the base color with white (for the upper half) and black (for + * the lower half). + */ + + /* alpha ascent as 8.8 fixpoint number */ + int const ascent = (_gradient_percent*255 << 8) / (rect.h()*100); + + int const mid_y = rect.h() / 2; + + Color const white(255, 255, 255), black(0, 0, 0); + + for (unsigned i = 0; i < rect.h(); i++) { + + bool const upper_half = (int)i < mid_y; + + int const alpha = upper_half + ? (ascent*(mid_y - i)) >> 8 + : (ascent*(i - mid_y)) >> 8; + + Color const line_color = + _mix_colors(upper_half ? white : black, color, alpha); + canvas.draw_box(Rect(rect.p1() + Point(0, i), - Area(rect.w(), 1)), - Color(255,255,255, 30 + (rect.h() - i)*4)); + Area(rect.w(), 1)), line_color); + } _draw_raised_frame(canvas, rect); } @@ -295,19 +286,58 @@ class Decorator::Window : public Window_base right || top, right || bottom, border, _dark); } - bool _apply_state(Window::Element::Type type, bool focused, bool highlighted) + Color _window_control_color(Control window_control) const { - return element(type).apply_state(_focused, highlighted, _base_color()); + switch (window_control.type()) { + case Control::TYPE_CLOSER: return element(Element::CLOSER).color(); + case Control::TYPE_MAXIMIZER: return element(Element::MAXIMIZE).color(); + case Control::TYPE_MINIMIZER: return element(Element::MINIMIZE).color(); + case Control::TYPE_UNMAXIMIZER: return element(Element::UNMAXIMIZE).color(); + case Control::TYPE_TITLE: return element(Element::TITLE).color(); + case Control::TYPE_UNDEFINED: break; + }; + return Color(0, 0, 0); + } + + Texture_id _window_control_texture(Control window_control) const + { + switch (window_control.type()) { + case Control::TYPE_CLOSER: return TEXTURE_ID_CLOSER; + case Control::TYPE_MAXIMIZER: return TEXTURE_ID_MAXIMIZE; + case Control::TYPE_MINIMIZER: return TEXTURE_ID_MINIMIZE; + case Control::TYPE_UNMAXIMIZER: return TEXTURE_ID_WINDOWED; + case Control::TYPE_TITLE: + case Control::TYPE_UNDEFINED: + break; + }; + + class No_texture_for_window_control { }; + throw No_texture_for_window_control(); + } + + void _draw_window_control(Canvas_base &canvas, Rect rect, + Control control) const + { + _draw_title_box(canvas, rect, _window_control_color(control)); + + canvas.draw_texture(rect.p1() + Point(1,1), + _window_control_texture(control)); } public: - Window(unsigned id, Nitpicker::Session_client &nitpicker, Animator &animator) + Window(unsigned id, Nitpicker::Session_client &nitpicker, + Animator &animator, Config const &config) : Window_base(id, nitpicker, _border()), - _animator(animator) + _animator(animator), _config(config) { } + void adapt_to_changed_config() + { + _base_color = _config.base_color(_title); + } + void draw(Canvas_base &canvas, Rect clip, Draw_behind_fn const &) const override; bool update(Xml_node window_node) override; @@ -324,153 +354,4 @@ class Decorator::Window : public Window_base } }; - -void Decorator::Window::draw(Decorator::Canvas_base &canvas, - Decorator::Rect clip, - Draw_behind_fn const &draw_behind_fn) 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(); - - if (_has_alpha) - draw_behind_fn.draw_behind(canvas, *this, canvas.clip()); - - _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"); - - _has_alpha = window_node.has_attribute("has_alpha") - && window_node.attribute("has_alpha").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/gems/src/app/decorator/window_element.h b/repos/gems/src/app/decorator/window_element.h new file mode 100644 index 0000000000..132b0ce352 --- /dev/null +++ b/repos/gems/src/app/decorator/window_element.h @@ -0,0 +1,160 @@ +/* + * \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_ELEMENT_H_ +#define _WINDOW_ELEMENT_H_ + +/* Genode includes */ +#include +#include + +/* gems includes */ +#include + +/* local includes */ +#include "canvas.h" + +namespace Decorator { class Window_element; } + + +class Decorator::Window_element : public Animator::Item +{ + public: + + enum Type { TITLE, LEFT, RIGHT, TOP, BOTTOM, + TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT, + CLOSER, MAXIMIZE, MINIMIZE, UNMAXIMIZE, 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; + + /* + * Rememeber base color to detect when it changes + */ + Color _base_color; + + /* + * 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 15; + + /* slow fade-out when leaving focus or hover highlight */ + return 20; + } + + bool _apply_state(bool focused, bool highlighted, Color base_color) + { + _base_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: + + Window_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"; + case CLOSER: return "closer"; + case MINIMIZE: return "minimize"; + case MAXIMIZE: return "maximize"; + case UNMAXIMIZE: return "unmaximize"; + } + 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 + && base_color == _base_color) + return false; + + return _apply_state(focused, highlighted, base_color); + } + + /** + * Animator::Item interface + */ + void animate() override; +}; + +#endif /* _WINDOW_ELEMENT_H_ */ diff --git a/repos/gems/src/app/decorator/windowed.rgba b/repos/gems/src/app/decorator/windowed.rgba new file mode 100644 index 0000000000..735c6fe086 Binary files /dev/null and b/repos/gems/src/app/decorator/windowed.rgba differ diff --git a/repos/os/include/decorator/window.h b/repos/os/include/decorator/window.h index 50e3e55aa3..8cde0001e2 100644 --- a/repos/os/include/decorator/window.h +++ b/repos/os/include/decorator/window.h @@ -51,7 +51,11 @@ class Decorator::Window_base : public Window_list::Element right_sizer = false, top_sizer = false, bottom_sizer = false, - title = false; + title = false, + closer = false, + minimizer = false, + maximizer = false, + unmaximizer = false; unsigned window_id = 0; @@ -62,6 +66,10 @@ class Decorator::Window_base : public Window_list::Element || other.top_sizer != top_sizer || other.bottom_sizer != bottom_sizer || other.title != title + || other.closer != closer + || other.minimizer != minimizer + || other.maximizer != maximizer + || other.unmaximizer != unmaximizer || other.window_id != window_id; } }; diff --git a/repos/os/include/decorator/window_stack.h b/repos/os/include/decorator/window_stack.h index b9cdcb9e43..ff2e97610c 100644 --- a/repos/os/include/decorator/window_stack.h +++ b/repos/os/include/decorator/window_stack.h @@ -114,6 +114,18 @@ class Decorator::Window_stack : public Window_base::Draw_behind_fn return redraw_needed; } + /** + * Apply functor to each window + * + * The functor is called with 'Window_base &' as argument. + */ + template + void for_each_window(FUNC const &func) + { + for (Window_base *win = _windows.first(); win; win = win->next()) + func(*win); + } + void update_nitpicker_views() { /*