From 4b9e1f106051599e56a602ffd1f8230a727e21f3 Mon Sep 17 00:00:00 2001 From: Norman Feske Date: Wed, 11 Nov 2015 16:54:08 +0100 Subject: [PATCH] Window decorator that can be styled --- repos/gems/src/app/themed_decorator/config.h | 53 ++ repos/gems/src/app/themed_decorator/main.cc | 304 ++++++++++ repos/gems/src/app/themed_decorator/target.mk | 10 + repos/gems/src/app/themed_decorator/theme.cc | 282 +++++++++ repos/gems/src/app/themed_decorator/theme.h | 85 +++ .../src/app/themed_decorator/theme/closer.png | Bin 0 -> 670 bytes .../app/themed_decorator/theme/default.png | Bin 0 -> 4323 bytes .../src/app/themed_decorator/theme/font.tff | Bin 0 -> 35988 bytes .../app/themed_decorator/theme/maximizer.png | Bin 0 -> 653 bytes .../src/app/themed_decorator/theme/metadata | 7 + .../src/app/themed_decorator/tint_painter.h | 73 +++ repos/gems/src/app/themed_decorator/window.cc | 60 ++ repos/gems/src/app/themed_decorator/window.h | 539 ++++++++++++++++++ 13 files changed, 1413 insertions(+) create mode 100644 repos/gems/src/app/themed_decorator/config.h create mode 100644 repos/gems/src/app/themed_decorator/main.cc create mode 100644 repos/gems/src/app/themed_decorator/target.mk create mode 100644 repos/gems/src/app/themed_decorator/theme.cc create mode 100644 repos/gems/src/app/themed_decorator/theme.h create mode 100644 repos/gems/src/app/themed_decorator/theme/closer.png create mode 100644 repos/gems/src/app/themed_decorator/theme/default.png create mode 100644 repos/gems/src/app/themed_decorator/theme/font.tff create mode 100644 repos/gems/src/app/themed_decorator/theme/maximizer.png create mode 100644 repos/gems/src/app/themed_decorator/theme/metadata create mode 100644 repos/gems/src/app/themed_decorator/tint_painter.h create mode 100644 repos/gems/src/app/themed_decorator/window.cc create mode 100644 repos/gems/src/app/themed_decorator/window.h diff --git a/repos/gems/src/app/themed_decorator/config.h b/repos/gems/src/app/themed_decorator/config.h new file mode 100644 index 000000000..506747b51 --- /dev/null +++ b/repos/gems/src/app/themed_decorator/config.h @@ -0,0 +1,53 @@ +/* + * \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: + + /** + * Return the base color of the window with the specified title + */ + Color base_color(Window_title const &title) const + { + Color result(0, 0, 0); + + try { + Genode::Session_policy policy(title); + result = policy.attribute_value("color", result); + + } catch (Genode::Session_policy::No_policy_defined) { } + + return result; + } +}; + +#endif /* _CONFIG_H_ */ diff --git a/repos/gems/src/app/themed_decorator/main.cc b/repos/gems/src/app/themed_decorator/main.cc new file mode 100644 index 000000000..0a8a3eafe --- /dev/null +++ b/repos/gems/src/app/themed_decorator/main.cc @@ -0,0 +1,304 @@ +/* + * \brief Window decorator that can be styled + * \author Norman Feske + * \date 2015-11-11 + */ + +/* + * 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 +#include + +/* decorator includes */ +#include +#include + +/* local includes */ +#include "window.h" + + +namespace Decorator { + using namespace Genode; + struct Main; +} + + +struct Decorator::Main : Window_factory_base +{ + Server::Entrypoint &ep; + + Window_stack window_stack = { *this }; + + /** + * Install handler for responding to window-layout changes + */ + void handle_window_layout_update(unsigned); + + Signal_rpc_member
window_layout_dispatcher = { + ep, *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_rpc_member
pointer_dispatcher = { + ep, *this, &Main::handle_pointer_update }; + + Attached_rom_dataspace pointer { "pointer" }; + + Window_base::Hover hover; + + Reporter hover_reporter = { "hover" }; + + /** + * Nitpicker connection used to sync animations + */ + Nitpicker::Connection nitpicker; + + bool window_layout_update_needed = false; + + Animator animator; + + Theme theme { *Genode::env()->heap() }; + + /** + * Process the update every 'frame_period' nitpicker sync signals. The + * 'frame_cnt' holds the counter of the nitpicker sync signals. + * + * A lower 'frame_period' value makes the decorations more responsive + * but it also puts more load on the system. + * + * If the nitpicker sync signal fires every 10 milliseconds, a + * 'frame_period' of 2 results in an update rate of 1000/20 = 50 frames per + * second. + */ + unsigned frame_cnt = 0; + unsigned frame_period = 2; + + /** + * Install handler for responding to nitpicker sync events + */ + void handle_nitpicker_sync(unsigned); + + Signal_rpc_member
nitpicker_sync_dispatcher = { + ep, *this, &Main::handle_nitpicker_sync }; + + Config config; + + void handle_config(unsigned); + + Signal_rpc_member
config_dispatcher = { + ep, *this, &Main::handle_config}; + + /** + * Constructor + */ + Main(Server::Entrypoint &ep) : ep(ep) + { + /* + * Eagerly upgrade the session quota in order to be able to create a + * high amount of view handles. + * + * XXX Consider upgrading the session quota on demand by responding + * to Out_of_metadata exceptions raised by the create_view + * and view_handle operations. Currently, these exceptions will + * abort the decorator. + */ + Genode::env()->parent()->upgrade(nitpicker, "ram_quota=256K"); + + Genode::config()->sigh(config_dispatcher); + handle_config(0); + + window_layout.sigh(window_layout_dispatcher); + pointer.sigh(pointer_dispatcher); + + nitpicker.framebuffer()->sync_sigh(nitpicker_sync_dispatcher); + + hover_reporter.enabled(true); + + /* import initial state */ + handle_pointer_update(0); + handle_window_layout_update(0); + } + + /** + * Window_factory_base interface + */ + Window_base *create(Xml_node window_node) override + { + return new (env()->heap()) + Window(attribute(window_node, "id", 0UL), nitpicker, animator, + *env()->ram_session(), theme, config); + } + + /** + * Window_factory_base interface + */ + void destroy(Window_base *window) override + { + Genode::destroy(env()->heap(), static_cast(window)); + } +}; + + +void Decorator::Main::handle_config(unsigned) +{ + Genode::config()->reload(); + + /* 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) +{ + 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)); +} + + +static void update_hover_report(Genode::Xml_node pointer_node, + Decorator::Window_stack &window_stack, + Decorator::Window_base::Hover &hover, + Genode::Reporter &hover_reporter) +{ + Decorator::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; + + Genode::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"); + if (hover.closer) xml.node("closer"); + if (hover.minimizer) xml.node("minimizer"); + if (hover.maximizer) xml.node("maximizer"); + if (hover.unmaximizer) xml.node("unmaximizer"); + }); + } + }); + } +} + + +void Decorator::Main::handle_window_layout_update(unsigned) +{ + window_layout.update(); + + window_layout_update_needed = true; +} + + +void Decorator::Main::handle_nitpicker_sync(unsigned) +{ + if (frame_cnt++ < frame_period) + return; + + frame_cnt = 0; + + 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; + + /* + * A decorator element might have appeared or disappeared under + * the pointer. + */ + if (pointer.is_valid()) + update_hover_report(Xml_node(pointer.local_addr()), + window_stack, hover, hover_reporter); + + } 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(); + + /* + * To make the perceived animation speed independent from the setting of + * 'frame_period', we update the animation as often as the nitpicker + * sync signal occurs. + */ + for (unsigned i = 0; i < frame_period; i++) + animator.animate(); + + if (!model_updated && !windows_animated) + return; + + window_stack.update_nitpicker_views(); +} + + +void Decorator::Main::handle_pointer_update(unsigned) +{ + pointer.update(); + + if (pointer.is_valid()) + update_hover_report(Xml_node(pointer.local_addr()), + window_stack, hover, hover_reporter); +} + + +/************ + ** Server ** + ************/ + +namespace Server { + + char const *name() { return "decorator_ep"; } + + size_t stack_size() { return 8*1024*sizeof(long); } + + void construct(Entrypoint &ep) + { + static Decorator::Main main(ep); + } +} diff --git a/repos/gems/src/app/themed_decorator/target.mk b/repos/gems/src/app/themed_decorator/target.mk new file mode 100644 index 000000000..25dd82767 --- /dev/null +++ b/repos/gems/src/app/themed_decorator/target.mk @@ -0,0 +1,10 @@ +TARGET = themed_decorator +SRC_CC = main.cc theme.cc window.cc +LIBS = base config server libc libpng zlib blit file +INC_DIR += $(PRG_DIR) + +.PHONY: plain_decorator_theme.tar + +$(TARGET): plain_decorator_theme.tar +plain_decorator_theme.tar: + $(VERBOSE)cd $(PRG_DIR); tar cf $(PWD)/bin/$@ theme diff --git a/repos/gems/src/app/themed_decorator/theme.cc b/repos/gems/src/app/themed_decorator/theme.cc new file mode 100644 index 000000000..1df8ac722 --- /dev/null +++ b/repos/gems/src/app/themed_decorator/theme.cc @@ -0,0 +1,282 @@ +/* + * \brief Window decorator that can be styled - theme handling + * \author Norman Feske + * \date 2015-11-12 + */ + +/* + * 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 + +/* gems includes */ +#include +#include + +/* demo includes */ +#include + +/* local includes */ +#include "theme.h" + + +enum Texture_id { TEXTURE_ID_DEFAULT, TEXTURE_ID_CLOSER, TEXTURE_ID_MAXIMIZER }; + + +struct Texture_from_png_file +{ + typedef Genode::Texture Texture; + + File png_file; + Png_image png_image { png_file.data() }; + Texture &texture { *png_image.texture() }; + + Texture_from_png_file(char const *path, Genode::Allocator &alloc) + : + png_file(path, alloc) + { } +}; + + +static Genode::Texture const & +texture_by_id(Texture_id texture_id, Genode::Allocator &alloc) +{ + if (texture_id == TEXTURE_ID_DEFAULT) { + static Texture_from_png_file texture("theme/default.png", alloc); + return texture.texture; + } + + if (texture_id == TEXTURE_ID_CLOSER) { + static Texture_from_png_file texture("theme/closer.png", alloc); + return texture.texture; + } + + if (texture_id == TEXTURE_ID_MAXIMIZER) { + static Texture_from_png_file texture("theme/maximizer.png", alloc); + return texture.texture; + } + + struct Invalid_texture_id { }; + throw Invalid_texture_id(); +} + + +static Genode::Texture const & +texture_by_element_type(Decorator::Theme::Element_type type, Genode::Allocator &alloc) +{ + switch (type) { + case Decorator::Theme::ELEMENT_TYPE_CLOSER: + return texture_by_id(TEXTURE_ID_CLOSER, alloc); + + case Decorator::Theme::ELEMENT_TYPE_MAXIMIZER: + return texture_by_id(TEXTURE_ID_MAXIMIZER, alloc); + } + struct Invalid_element_type { }; + throw Invalid_element_type(); +}; + + +static Text_painter::Font const &title_font(Genode::Allocator &alloc) +{ + static File tff_file("theme/font.tff", alloc); + static Text_painter::Font font(tff_file.data()); + + return font; +} + + +static Genode::Xml_node metadata(Genode::Allocator &alloc) +{ + static File file("theme/metadata", alloc); + + return Genode::Xml_node(file.data(), file.size()); +} + + +Decorator::Area Decorator::Theme::background_size() const +{ + Genode::Texture const &texture = texture_by_id(TEXTURE_ID_DEFAULT, _alloc); + + return texture.size(); +} + + +struct Margins_from_metadata : Decorator::Theme::Margins +{ + Margins_from_metadata(char const *sub_node, Genode::Allocator &alloc) + { + Genode::Xml_node aura = metadata(alloc).sub_node(sub_node); + top = aura.attribute_value("top", 0UL); + bottom = aura.attribute_value("bottom", 0UL); + left = aura.attribute_value("left", 0UL); + right = aura.attribute_value("right", 0UL); + } +}; + + +Decorator::Theme::Margins Decorator::Theme::aura_margins() const +{ + static Margins_from_metadata aura("aura", _alloc); + return aura; +} + + +Decorator::Theme::Margins Decorator::Theme::decor_margins() const +{ + static Margins_from_metadata decor("decor", _alloc); + return decor; +} + + +Decorator::Rect Decorator::Theme::title_geometry() const +{ + static Rect rect = rect_attribute(metadata(_alloc).sub_node("title")); + return rect; +} + + +Decorator::Rect Decorator::Theme::element_geometry(Element_type type) const +{ + if (type == ELEMENT_TYPE_CLOSER) { + static Rect rect(point_attribute(metadata(_alloc).sub_node("closer")), + texture_by_id(TEXTURE_ID_CLOSER, _alloc).size()); + return rect; + } + + if (type == ELEMENT_TYPE_MAXIMIZER) { + static Rect rect(point_attribute(metadata(_alloc).sub_node("maximizer")), + texture_by_id(TEXTURE_ID_MAXIMIZER, _alloc).size()); + return rect; + } + + struct Invalid_element_type { }; + throw Invalid_element_type(); +} + + +void Decorator::Theme::draw_background(Decorator::Pixel_surface pixel_surface, + Decorator::Alpha_surface alpha_surface, + unsigned alpha) const +{ + Genode::Texture const &texture = texture_by_id(TEXTURE_ID_DEFAULT, _alloc); + + typedef Genode::Surface_base::Point Point; + typedef Genode::Surface_base::Rect Rect; + + unsigned const left = aura_margins().left + decor_margins().left; + unsigned const right = aura_margins().right + decor_margins().right; + + unsigned const middle = left + right < pixel_surface.size().w() + ? pixel_surface.size().w() - left - right + : 0; + + Rect const orig_clip = pixel_surface.clip(); + + /* left */ + if (left) { + Rect curr_clip = Rect(Point(0, 0), Area(left, pixel_surface.size().h())); + pixel_surface.clip(curr_clip); + alpha_surface.clip(curr_clip); + + Rect const rect(Point(0, 0), pixel_surface.size()); + + Icon_painter::paint(pixel_surface, rect, texture, alpha); + Icon_painter::paint(alpha_surface, rect, texture, alpha); + } + + /* middle */ + if (middle) { + Rect curr_clip = Rect(Point(left, 0), Area(middle, pixel_surface.size().h())); + pixel_surface.clip(curr_clip); + alpha_surface.clip(curr_clip); + + Rect const rect(Point(0, 0), pixel_surface.size()); + + Icon_painter::paint(pixel_surface, rect, texture, alpha); + Icon_painter::paint(alpha_surface, rect, texture, alpha); + } + + /* right */ + if (right) { + Rect curr_clip = Rect(Point(left + middle, 0), Area(right, pixel_surface.size().h())); + pixel_surface.clip(curr_clip); + alpha_surface.clip(curr_clip); + + Point at(0, 0); + Area size = pixel_surface.size(); + + if (texture.size().w() > pixel_surface.size().w()) { + at = Point((int)pixel_surface.size().w() - (int)texture.size().w(), 0); + size = Area(texture.size().w(), size.h()); + } + + Icon_painter::paint(pixel_surface, Rect(at, size), texture, alpha); + Icon_painter::paint(alpha_surface, Rect(at, size), texture, alpha); + } + + pixel_surface.clip(orig_clip); +} + + +void Decorator::Theme::draw_title(Decorator::Pixel_surface pixel_surface, + Decorator::Alpha_surface alpha_surface, + char const *title) const +{ + Text_painter::Font const &font = title_font(_alloc); + + Area const label_area(font.str_w(title), font.str_h(title)); + Rect const surface_rect(Point(0, 0), pixel_surface.size()); + Rect const title_rect = absolute(title_geometry(), surface_rect); + Point const centered_text_pos = title_rect.center(label_area) - Point(0, 1); + + Text_painter::paint(pixel_surface, centered_text_pos, font, + Genode::Color(0, 0, 0), title); +} + + +void Decorator::Theme::draw_element(Decorator::Pixel_surface pixel_surface, + Decorator::Alpha_surface alpha_surface, + Element_type element_type, + unsigned alpha) const +{ + Genode::Texture const &texture = + texture_by_element_type(element_type, _alloc); + + Rect const surface_rect(Point(0, 0), pixel_surface.size()); + Rect const element_rect = element_geometry(element_type); + Point const pos = absolute(element_rect.p1(), surface_rect); + Rect const rect(pos, element_rect.area()); + + Icon_painter::paint(pixel_surface, rect, texture, alpha); + Icon_painter::paint(alpha_surface, rect, texture, alpha); +} + + +Decorator::Point Decorator::Theme::absolute(Decorator::Point pos, + Decorator::Rect win_rect) const +{ + Area const theme_size = background_size(); + + int x = pos.x(); + int y = pos.y(); + + if (x > (int)theme_size.w()/2) x = win_rect.w() - theme_size.w() + x; + if (y > (int)theme_size.h()/2) y = win_rect.h() - theme_size.h() + y; + + return win_rect.p1() + Point(x, y); +} + + +Decorator::Rect Decorator::Theme::absolute(Decorator::Rect rect, + Decorator::Rect win_rect) const +{ + return Rect(absolute(rect.p1(), win_rect), absolute(rect.p2(), win_rect)); +} diff --git a/repos/gems/src/app/themed_decorator/theme.h b/repos/gems/src/app/themed_decorator/theme.h new file mode 100644 index 000000000..4a485da24 --- /dev/null +++ b/repos/gems/src/app/themed_decorator/theme.h @@ -0,0 +1,85 @@ +/* + * \brief Window decorator that can be styled - theme handling + * \author Norman Feske + * \date 2015-11-12 + */ + +/* + * 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 _THEME_H_ +#define _THEME_H_ + +/* Genode includes */ +#include +#include +#include +#include + +namespace Decorator { + + class Theme; + + typedef Genode::Pixel_rgb888 Pixel_rgb888; + typedef Genode::Pixel_rgb565 Pixel_rgb565; + typedef Genode::Pixel_alpha8 Pixel_alpha8; + + typedef Genode::Surface Pixel_surface; + typedef Genode::Surface Alpha_surface; + + typedef Genode::Surface_base::Area Area; + typedef Genode::Surface_base::Point Point; + typedef Genode::Surface_base::Rect Rect; +} + + +class Decorator::Theme +{ + private: + + Genode::Allocator &_alloc; + + public: + + struct Margins + { + unsigned top, bottom, left, right; + }; + + enum Element_type { ELEMENT_TYPE_CLOSER, ELEMENT_TYPE_MAXIMIZER }; + + Theme(Genode::Allocator &alloc) : _alloc(alloc) { } + + Area background_size() const; + + Margins aura_margins() const; + + Margins decor_margins() const; + + void draw_background(Pixel_surface, Alpha_surface, unsigned alpha) const; + + void draw_title(Pixel_surface, Alpha_surface, char const *title) const; + + void draw_element(Pixel_surface, Alpha_surface, Element_type, + unsigned alpha) const; + + /** + * Return geometry of theme elements in the theme coordinate space + */ + Rect title_geometry() const; + Rect element_geometry(Element_type) const; + + /** + * Calculate screen-absolute coordinate for a position within the theme + * coordinate space + */ + Point absolute(Point theme_pos, Rect win_outer_geometry) const; + + Rect absolute(Rect theme_rect, Rect win_outer_geometry) const; +}; + +#endif /* _THEME_H_ */ diff --git a/repos/gems/src/app/themed_decorator/theme/closer.png b/repos/gems/src/app/themed_decorator/theme/closer.png new file mode 100644 index 0000000000000000000000000000000000000000..dc0e77377f868e771e2ec2f41f8e6cc2b791b8fd GIT binary patch literal 670 zcmeAS@N?(olHy`uVBq!ia0vp^LLkh+0wn(&ce?|mSkfJR9T^xl_H+M9WCijWi-X*q z7}lMWc?skwBzpw;GB8xBF)%c=FfjZA3N^f7U???UV0e|lz+g3lfkC`r&aOZkpafHr zx4R3&|Mvbf`++>p0*}aIAngIhZYQ(tfJU=B$vpo06xwGsELv>fc4!@SK!EbIwG%B%PetTyAO#A%C z)oceR2p*S7JJuty%O@eRF`?ADwqma1C2xhxH@_5?K7K7G*7L~4WYrl{RmXs5QK5@E zh5h^%T|6B#^+#B0o`z=e+qjoa%G|fD3tHtv^UfZ#@LZ_*^Z2eaG4*R-)jezFetK<% zlx|e$`sdD`1|r)Qz2-RI_QSF@di&?sdX}&BZq3+gK5uRcpX06%9A6k5uPSfY@Ib}S zW$~&rB?jD&6Wzqbgcv?-{loCie*Sk0E(ROBxBU6LcR#h}`~UC3>!bSV?ThB$zUAfs z44Q}%*N775{M_8syb^|>)V!3`A_bSkl2j`NBLhQIT|+}%Lt_O)b1NeQD`Uebu7I4x zlC=DyTq}i4GYd1_f@IyC%)HVH-SoUtE}$s|nK`LNRto9b+6pFm7F@-2OCAdC(%w160`ZD43+V4!5*1Bx$%hTQy=%(P0}8iJEdz5q2Sf@}!RPb(=; vEJ|ev@DI}sN~}~c)-%^L&@0W$PfN>8&P>diy7NZ`P%(q2tDnm{r-UW|cJuAY literal 0 HcmV?d00001 diff --git a/repos/gems/src/app/themed_decorator/theme/default.png b/repos/gems/src/app/themed_decorator/theme/default.png new file mode 100644 index 0000000000000000000000000000000000000000..8836bb291344fcc96fa2f88d0e9e1c2e27d68ded GIT binary patch literal 4323 zcmZ`+XH=6-uznLlZ&4HoHAp}KDIuYSUK2VZQUoaifqYaGdX0!6pF}ZYgMffwR1id@ z7o(ySX+|kQA}A<=6afK|cH{Twp8MmTJ+sf5*_~(3nLWERo8#hy6&8>Z002PP7H8$k zL9@TY$HSQ)5zc+$03^iR(HsD3uL|x2aC3AJ$rWn>R1e6^aTItU&eahBl9T~}oCyGb zI41IU0EpEBfO&raz}yA^iD+8O2_wz|&slq{6|nzzl(f^Yb1eMPI4=?hasP=;PnZb+ zh+=K6%-s^k7rpJHg1Tb&I!?y6_9z?BV76=d5e5zSAeWSMAC`*miLvl=#}8_rW$@+2 z#(|zjPXW5CgO<8e`tHXa4|ZEpDqtu26l(=fM5_TcA-{()=m^|gtW|15v}QMo(n{^s2K^{%%e_3|~7BeGo-Q|GHX zXZFyHI`@?N{c$^|E!BQbjvjhz%;(7j4-R%oE+=oiMPR9zh>y@$yarz@5m@w6Pd&!h zi8!-b4}Hlsn8wO5KH^2s@4!WezK~1Yc#D?Kd+-rCzMDlJ#Ny~t1YN)Uo)`*Tt6wKh)vvc52gNSS@H-IBJ8hh#3a00e$ACF>RC z+r=Y{?*@JvnfF#{esJ~K{79;9Z){QzTN5{LetN*bPiGv|4S6`dBtaVRkj7Wf&}wj} zv!3OtrbV%MW45vgB)KjYD%k%7v9;FJJQ1V-n4?At>@;s55cV zBlq{Qx6d8U>2=Ptlh8>9nN5N3HWaLH)ksAw_el*+PKc;fNDwBRV|T4>PxWqnxx3G5 z+@BHkP1}-g|I~Zf$Ph{{E?saNn@nH*8DhH4y1KvRm-e#;`9@Z1S(+t?>~JPd_KDoz zeF5S6G>GH?cU|6)LFyy^;^g+L2F}>IF}+n<@xX0nu7hZie1}@ zLt=bp5$$uji)&RYu67re_t=wr?{_yp?dnNg_ZkyMra1h3l^j2bp}i&dKOU{jXZ|}e z%IyQAWZgoX+W##Yl~VKQixgltY!t&efA`20Q+<9$1H> z=nNJ~N#%b2O^{FQW_?Zi?u{DoBPAYNk!~q<&*LL6pP+b8)7Mdz&j~(?sl4CGZO>~H zrzv_VlYNnP871d^vVn~dZNLlxedJkj!q^VZ%Q=AH_`!q6dfa|ZRb83CBRft@dsb2v z0Je4o+D5$-tNCf4c(*d~#?WLEY^s{;wgSE7=;l>npLOv0aAi~u+zLQ7rSG^qvYH^y zZCe=|&aaDVYk`-!;A2dhzXwAQQVfhm-6lcAgf1x@r{|j;6bBRSZjv%uTM>2_`2*$R zl}|iYEI3-RlJtHQw%eCdd{J{V6K3v;R|fMl(oQPmHJE-%8|Uq`p_swNj2`@$S&~E0 z5oqNnF7S}U6EwAf-zi{CGe{`@h$G(G)#ifFVGFM|kp91XbTqtd1Jk;#!O%Q!W~W1p z(-kUd9m`!Kur}#@Wp|XQcsv?)z8j!&+XLEkaGr|%lqI=e`;;Y6dGCbqrjYH@lCFtF zF)H*6P;jTBj2qI;C61v4fwiQf&@TWfl+26ng&DbS!C0xoOm=^J=!+1?xg*Nx22oU) zBMe?F+0Q+5Q8{{Zqi|hK+FKFCs&IhQTioNaXTvF?zpHYLC|s(Y(iU2vG2UkI6x3Q! zNQUZD ziM=2&rp3bUs>uPN3as_RhOl<*xwJ|G99ijeMPk zyX>enHLxhZ#i>0AZA%=$4Al=BV6lsY#qf84L9+LZQ6i7gU`7}s3YkUgH`79SQrnar z-or|-*OBuInx7s)#tas_YR9W2>u#oWgn%}ofkrr6Aq5%r#!&Gb>aS+fHnwh>ERL-^ ztzu~bk-V(VPtA6}HJ~Obj8H2uASf$H!q|1*mn4s-iEu%hG_NlRsGj8O@0P(`2*BGp zprZAsQBEq}Zu?fU%-8fC zP3)FQ;W{{w{XIxlz~y?@y<67935H38_r3};i-#SizG?U5+GywHmRH<=B1nV-QCAP7 zXMh~xLFmhNFhG@gq|~L4k#o>CAWQM?N}qL;mgN(5ja-BMxu+LJ8u=!1T6qs##7!-m zMPW|k%aAe(6>)&6%78S43A}!px0-S#eI-=ZjcF&a36R!p7>FKZ z)bM$wJKRF;qk1mVyXaxPAWeF9!kJMOdH_)eBPi$-vXo;aKAd+6P^~js4A}NrxR>`GzQ^HD=1W*E!qdEJPFf|01eQ94Zk$m( zSPHSa6dY+h3R6H|8Il@rD-H8=&DLKfVYNO(KJJoGhS8zIuHkM1?w#+px8i@0U zc|ymQ&dAVLA8Jzuy@bWPLJ(oR4lb{!Q_q1z@4rl(Px!L+Y;KuAYe@+zUL3FxIV;f{?$Li4?#R!Ug{`#K| z-SKCz4WmYCBHQBfD#DPRyqx9*1h0UQ1w~DYv7v$T8T=O3(`GK{x~tpXA4fF)bwXI> zHr)OC13)CE{rY}D!>8$|*ZVbxC#|jCk(a`jI^I9T$bZAIf6qBD?>T#iP(fDN{?VcG z3^A19&g=C3og$CD(CM%p*^?mKgylWWbbd}9a^^G2gdWe@$}Y!=m}UBdAfxb!^w}c6 zovBUVKfRkR?4=OL_a|RO>vyK_?wIa(%)41K<1BJ5px8-YV`gzIt&%G z7Lx{z2e-yU(Mc-wXFFdcI)09N_%{8Sn4dq58VhlZJnycYoXoY5@^fm1*U&Is>}(2s zKE@>UkxUl!lV06<q}bJ5<~k%D@7OFEQvVzy`w9DrceXPM2eJR9s2{ zLiivd+or%i8=2JwnWPlWw@aQyET-`(3`v5Fn)K>XXpgL#@bGpr^+fwkGqH69~^^JRq^G)7DBsPkj;!+LVFmpZU@s-9Zrt-#sF)?d}Lw7@F4yc7pw z(2I@1Up)?jc-?N+yM+A{LZ3z1>rTUSRvDEn(ybutRnVow%w(HwzO3D;&)43D3F;yu zhDpA4(7jR8(mQ=JIdla{HqRLc@hm0Bbv~N!N#)HF%*aCf-XZ}042G1Ma49iK3)R^h zryz?vO;NZBd9eKoI;~hRjPFD6+7xb`AM+6&n@D0WP3&FUY;16Jy>Z>IyIA9z&$;;0 zlp`U>S~VBxQPX#a0v5LT$v0}B1{yVMiwDlG29L>LlVij)$@$Vg?Au_R@;&2H)-?3E z*u?YX*?!C8WI8dhX2Y*ldCujfYWX1x$%=doUR+7IZ^33@&I9TGineRRr`xRpVyvOJ zU0iP=W=u-FurCX|wz^%Jd`y?20xxprwftArRO-o zeeBu(K9R4FK6PwPv+1SFcLGR1)_v1|tpCG70pL&9s2mXfD{6f_DWZ_F_t-a`)XJM5 zi{rp(!{m4QbOFPnPMNOB@jaXP1=)ek9?IZn+RJ^e4dJ#JCSv*-=Wp#9W9=DZ85I!` z92o<+21f=36X8|?F~JzPwiZ%H9f?#&YQvEzj204uMxGXM2@inKNg~&h9y9c6RrMh{ypb z4^#u{0`5Qype^79JOexr3VlBoG9I0+)ekAPz_YZUc7qM5TcWKy{!F;081Yo&;J09RM$&7tkLV z2D}2i35)~Y2c`fY19O2Tzz@JWU<U7Bd`wG3hV+70Dl5Ozy%--hy>z*6d)V0 zD}ng{Dgd>C`aomgN#H5K8}J2s11|zYff2wM;2mHRFa?+nd<@J6<^uu1D&Qwz2e2PF z4g>>{Ks=BIlqgB$1h@b-f%-sW;BlZ8&=&9ne1I2#p}?!aIN(EICh!HY1XuxV0)7S# z0LOr{z(pVuhy{{>Oh5xlmLjSMR0rw-je#eDc7P|)8R!l?3-kd711|xu0poy4z<+>Q zz~k@8mC6^F?@rs%59~p zAMH_EkUX_|);J zRLvLn&$Qy-PpbOo^cXsel@~k18AE6qR4eE`_+OmiX z1UpZYX8oyFxTn7~G_!`Rv|}2AsW88TD=I6Rfub2Gnt`GjD4GH5Gmt8S^}_#0uKgF= zjn;R})o*jZS=n&)H}P$1c6P0D7> z`ctiN!F>$P_HeAXV;X|dN!})`a?Cdc@}Cf^)$f66-h(yqg37JcDdIiw4Djm!_Y&gn zMW{&TA20)E%Y2MR>yT$i9W)cmH#BR%%ITsG5v_@#YN}L;J<3$$r)x#wFU^3Q!)_-s zqWsa0={D~#HJYcg#Z9BrkJmMXJYo-I zd9kokczFusHiOzfP^a7Ggs95K-a?*j3U1bHlVa=W{>!;n)q~N1x&rhDRJj@f*D(*a z>KXESdxrT@1#vqOV)4RKAx(J_wSQ4YZ~nu3O0iTqMoz5o<()2tw!vFd5zgSXKaqag zlgn6g)E@d}G~VB^qXSfo-*T`XUf>tfihY`JbFwHOoYR8HV{6jQHPw*swJ$YcTSG1w zrh!kdv66QMQGazzRy&MK1iU!)Q)1#~T;0ygZe5?q;`xW^A!!Giut?Jt(Qy|)<)PyR zGtF0|+-OGR+6K5n7er--e5Z$w@&|UVr&uh;i?tHg8ou9-KZ=Ekg34S03#^VJgeb7iKqd6Bd2XU=b zcaam3`TYU}^7rLer)aOVlM$f)H^>8<{%7s;hEsxu5N;GjT zxb9C28)9&O{T!tKK}6D*Uw@_C6JE#}w|TySyLJWofb-pKhTxb!3qu##z zh?c&^Vi#vtoE^plB%Ko4QQHJZ`XFw_sTHx)nQJxd`Vj09*PirVS-V&_wl1;1m)ns z0tk3Tbl>sr<*Pmi{V;qv)zw^OmRTR2aMp{!J3NLP?{|nzLDc&o*_XRSY<|MI`Gzod zw!Thn&#f|8m&!Besi2jRJhY~Y`*3n_Xrwja?uA04_V%y1#U+V3`i+beHoqc)0wzY_@2CaP_w)uVeU9oJFB8JxF z&RVeZG?yQvWJHJjit{Ur^~VZG_@N3PT{jaf?5eDe-zP`K zO2zWRQ2O%o0k}NPg8!0AM3t9J<(H?6N1GLxlO`d>z3S;6u4rmkx_lD3+c%3eI0g|7 z2Y8lDkFQq`NvBg4Djib-oQI7Vi*JbxN^gppMlclGOtNVVQ^0Dyf|(nA zw~6N)1@%x+dBHtNi9F^>@98=+USOsP*I zh-sgvGk^X<$^7sPZsujMzcLUV!|ZoV!H0Lx;N!LZ@Lne*Sj1zOE{SAUD@A)`VnKhk zj>s-&PgpOlc3NGL6&L&qX*466Z^aP=!|^gaqSOVY5fkE+jguU9&gQ;+bO_&E3(t#m zBAhJI_BSDjO=Lef{1!tg%2l8Td4>ihJNcTj~sxDoeN8QP}BMv?n(F`5=rk+io?ryqG7 zRd6Bz{FVMAK+isNJS)_9XlVK$xO}u8QqFF6d^~?ysl6b(K-(9d(@1;#6s{l7;~7yi z=pfoZCmO^8;`USMw@sD{Y3|q<+>MPHtu`%3N_&3HLZ8(3c4gWU6?K!xCk|5gLwt-z z*vS-*q_C$v4Z#Djgiak^tI^ZQq(93n+Ogz%%r{sX{^Cy~&g1N;Im}M_L{|lt>am*Y z{FbnVmiW+^Bm1(Ll{Y|0ONx1qg;a=Y=X!-{-3<8bAleuAvMNPqIFGzm%(3w0;1DX} zPlX(Oy2#%-a*3k2DajmoUdWjg+Dy0ai=?1Iy0Yaq1sJMS>fDKi8#gQgNz-9X=S{k9 zruH_-#yG0RI4Pe8!_`f?bZ(j&2Adr@5*w7s2&lb3O!bnV(Vnj8GRc*(C?o%n%n+Y< zuE#?0l|9hyfU*a9QfUX|c@-xY9hQm2Nwzcs${5&6@j)+NjD+&ILnCE_e4(fd#v(QB z0cqofbulrD6dxZ1x2Jv8`7bdsCx!p;{rv0V^La`s>zGX2;7<^K*zXhb8WWJ*ya?%+ z8}KoGRR74T%+xJ|AdP758YZLq;v;Uk!WVn#V2zPGU1iz_eS$6gYc6-$LF6UQZp8m>J*2u@1`e6W9hjhn1F27d303FLMt>PoFf zq2SHhKpQ_5WzdTfZrhg1K1SDGU>|!!l(C;%{|XI)cgr2TJ5-!^60#@V-NUBg zrv+~KH0X>~TzHda7f@yJg=TdQeuiSG<$tMHOD;63e8`8!l zAmvE2s$2Jg5hCT@4QtXQY&7zE7u@!F`0|r|F-hT*%eV59l|>8C+YP+s@a0;}&P!B0 zEX0xi6^w<-<(FvWC8sq3DEkUm8p8JwLCsDaIWSq|%dMY!YIM{{w+w_- z=n)a_%d|W(?S940JypM@O2Nmq!zMb(iZ4G|(dCrrXCBMvV7W0!R5VfClw=ML5k)42 zHq-6$2yCr=FM~Nz6h2GS6@Lg>N3%DTWEbB!IDZWi}kp40-IgR=zWy0EtAXVo1 z3?2@GjA3-r(S|M|3iwHy$(N5y6CP7nbZD0UNBD)AH-zSOag6LIe0e7Bjt~ZL+rH&H zg~3#rSkSySXQ!d~*{pqSS>e$L}kaqJ3+#-)&oJ+mPD5`K;bqNgVbZt!@1VFAAg1a;~cfnw-r zdKc=Fic)P1e&q-oBg|tRZax2xGR)>Iikk5(h;m)-bV@WEnV@!k+RpaLAN;iMjF;`RE2%AjN^)wlzxJ_6jbg_|ns9lH_q$`=d!@?W4#Vvqg!pL$^q zbv|^c3(>b7D4-p9In?cVu_3p0YAQc^=cW`>)G|}(2PA3lzi|*8%uIpE>RgM&iZ7S7 zNth06I&ac-Gqtxtc8r@G=Y>Nq-`nKmm5ZaD-6NjAEr8apqM6NMMscH zu8d+Tk>YnoNyXRgfwBX(Qnmv)rKV`knMO^{Uj)O#2qzK0AmsfJF(m8lJlzG2v z`HfpxphG$@@B3WuIySx&enf6(YS8qX%$Ff0(99%BR>_~>a_I1Ph%(Ah+GCDd$qF!E zez?7LNb+MDJmYfg6taLa)w)EgOdt3x`J7C|aQhkTh*{Mo-@zA@$(Jwd9aodfp*|e6 zu5J-_#m-gGHLq{96H{i&tZ6ynGca7mN)o2xEm67QraQwxSo-p=H|*_WF?IMAzO}Ty zKMh$c{;pUJx@hN7NXP;tR=)MQbmUf^pBw5Hr1i*Qk0t*&0K%O&+f)A1<8 zK)4INZ8=$k()aM-xw6t%`{*9jC&CUd_?@GSGlcMh-$%Yl$L` zX2p(}y4T1FU+%_P<)#BY2!CFp7OoEwcH=zk;M+(gr1O;-$OsD#e(FeAP*0tYgwHo$ zLr+BLzMOa0MBuS;@Cd|fu}E`@9q-z38JwA*#G`jK)k|l#%4S(Jg?>P?WlDJrirPalMomdk%(TMG8&h7n-> z;=MmiwSV#x&`YW4GRc*(D5F{{BXC*}p!m8yPE!T3}d5=j#+B52fB4JF_Z!n)|9WGr0N_aB&Ou>U!u(L#3BsbE}xp8 z`482(vb07FyB*5xjp%vSHyKJeIB4Y@O2`b(jwFt~F4Bm8xHr8bNa4%vzTa0`3SeJ6 znGSyJ>Z0>3&#Lk&HfE2C3kHimp>9_r$?THK8@Z`(ZH{tXuDNfR-4C8$`ZnR9eJj|gFQI3nrGb9|ZcEG(8? zw0GwyI5=!-B$Q6BKviQ(B*P$LP#0c*FWiwSs>d(maropEOKM(Bf4+|6T>H<=P~o@T z&3!O9*16u9($S(Q8UB}_5G1}RH#t~QJrc4{Jc%-TB zjt(n@*yX+Wib$j{uNeQH3m1t{SXJ2axOp;C9>cp8GvvIe`|_nfm3R693I$d56skv}%4|n}l@PTxLM$=?R~FKGo#V z%ESEKHJ#Z8FohOvZ9Y@Xf@zb%eBCQFb%Wf%dq_lK!2w)p^ld|WEJ zOmbx`%4p99XFE+-d|r4XC_7*)MJGa^jVJ?#t7n{4Y;z-^jDd|*?y5qw6MPRnBaDWA zp>V@|!C0n-Jy6!gU|1?bVatp2W<)b5(R6lU^kMs|$#aF)lni}bJ~dCpGQWOg%C|x| zdL)vUuL@d^^gAWw+}+&;uT6@RNO!^%&)OwO_T|OC`CU39<_wD#H>jHSn<%FtSFp`H z1|OGTeMt>+nld-_X`)WTx8pha6y~V#`|>hj|5jB*I{Nz`yBXs3l1Je( z{EUAlp6o;XO@fN`A@}n_oIVlHB|}d7^7CT;r(tuGX3w2B3klwUOL+A#lG8(Cg=?R) ztC;0k=prnZ$Cn=+hhJ1bYU)C7TQtcH4#Ua3+Yv8G0W)_wW69Vq};6P3K!Vo^VSYR(jXzF$0gSInlFekd?g z=m#Y0-o{5G`X{BiO#u!;czBD%rY|>4M;@J5x(-pX|5P^_CQFb% zr5g^6Ti^R*J@d)HBQmMzGRc*(D5JEVSP;y>#3JbSK-mFXDLN7QY($|W@h>%?xXfT< zp^QL5sd`@i=e7gFXegUtD^>QuFklA5mO>O3KA*kN7dhsz(}x0u4sVRSefMB1W{q7& z^m}LD-|3gbE$a;l&-{#;v~5131zQBINAd;MnGt&S_%Zc$6b-o9vVHt;!DnB-Ahabu zHp89Q^cK~;FU~ReG_hIbPGPvB{GvO$9b63lLb(9gxw)R(&l_NrN>rK?h0Q}WO{$-E zdqsT_?Ztvt1aUy`Zr5`}%Qgve^LI#aPg1TUdaWG!du!)^ z9K7GNoRc4}O3AUjFQ;}Htz;E!M?j>)tqXRBFQ@mfqoPa1u4Yb6cZzS)q>Tb0T>L863gnrTez{+=U-fFkQ-*-?#QBiN#lUIh5!}hrXMZf^Y{&T8cpolHIB(_4 z*9MBaY3a*p=QN1f^yL@*YyQSZb;iB%Wh_U=$8>Rp#h(=F?sfv~0SSF;KZRwR(9yk` zb{!XoQcq3+{B=lwQnHzq$Cr=1*rds&(GUodU2xmS@g@Rfsr<0&73aWVhs<%VK?gTg zX&r)2>A+&X1LN%M%U{*K+-KhlNZU=8XEio~OLK?>&PHQa!iLO&om z?g6 zF5{$M!{`~zf0OSU&%G%`az$IEuNN%Uq8ryZX#tC$dDUoJMcw3qP_X>8V|kDxmU zV()!Jd{*#eI7a#~_SSc}m@A~pJF!}u6K*bd!Pi!|Z)|i!F+XWD`r+D^K^#ztz3ZDn zHwK|Fqmz*6k^w7)-g7SbV^62%w=s8;e-(`6qi=J!Vqj`H4rj99YEF76%$MQI+eT^8 zX1^8lNk~~yWxW(x2Y+ka#+Nw9cB}f581-8*x}GsBN83_fRvCuhd8u$9C2=E9y6%^I z9m_~R+*b_v^l6dcb$;?G;@-A79@0R&>uye2LiO zVnzT~F!r;f(qSMSR%Bf4!()CqA}0#>=&YlQmNsZG6-I&ST=(Vpr)|M_$pLIe9$#K; zNv3vIpsd?t?+_ZHe^}IZ{tjKA5Q$l&)hl6!rF3ZR{x-L^Wcu|ij@#x*e zOzOmiW(xg)WU3dpBQ@UK6o~g@m~9kF8KL?X5m#5Hqbpjo)Oj=YFPP-vAXH9Vho6^P z2y2H6->?MvQ-%@XK_EaUPb9yLZ-=C!%OqFEqKvvlBrPmHE+YHN7{eYYJ76nCC#?IX zsMMTm37^8sGnb8pG6DsqV#tVw{~0fghO!B^Qe_Ve>tZleOGT;W3`k#Y$<_1alRd~I z86L?pMDcd$BMA4-G7!V)P(?D@NrWEKPk1 zZW%h%=V#_LY?T={%6 zhPFZKL4CPnqqDPfchc&&6y2An{MsUSQBh8xfjf9gQeb$rzMOC3^kDe%%E^j#uj#J! zuASewSK<4RyZ@2gN8|r(qW|Q{Fxdm$x|kUMZPQ#d-oJYW_Iv!@BY0%}w>=&mz63YH zX5r?0W|@M>hvCaJ6PH?BJ3UfARM2t$^U5`T-=d)KNaxV}T2KQ!aP+=!2kP%lyX3Fx aKfLq-M)L4F|EIO|kDd&ZJp0*}aIAngIhZYQ(tfQ+x6E{-7@=X)m_`W zKH27T8qLH%cm@S7eWdb`;SMv`@`gjAteKOTuDOc54Y=XPc3b1}+kfAmpMQ6Nb*cHe zmM8)Kjy`@rGe_2y^YXTz*7xj9%Wb~#{OPNwSKAqGKYiSMmMw{IP55~MCX1U}zXc|~ zVhBqud(4!f;1Kw+@_ge0%j&d~$L?OeXsTjRc<1frHcb!a;I$Vl*4q1j*LxAuJ(25J z%eqvVS4Us|_e*>|$!Ew& zP4g0$Y0sa|pk*PL>1brI`|;e(zB^XzW888+(DUic%}l@mh$wN5C~?lu%}vcKVF*gi zOGzzKa7iplwNfxLFf`RQG}JXTRxmWTGBU6-GLPa4$Vn_o%P-2cQphy3Fw-qa*3HSx zE3ME?&nx8uno^LNlUih@kglz*V4`Qim9#_01!#vV$c_|{p~*$5K#Bojbcmq^#As~; zLn{M=Ue}#(P&DM`r(~v8;@0p-f-wN7K@ns_aDG}zd16s2Lx6vnZct*Sg0Y^to`GIz dUVd6yW^!g?&eWYhDu9X^JYD@<);T3K0RU_C=AHlm literal 0 HcmV?d00001 diff --git a/repos/gems/src/app/themed_decorator/theme/metadata b/repos/gems/src/app/themed_decorator/theme/metadata new file mode 100644 index 000000000..03678ba4a --- /dev/null +++ b/repos/gems/src/app/themed_decorator/theme/metadata @@ -0,0 +1,7 @@ + + + + + <closer xpos="36" ypos="10"/> + <maximizer xpos="10" ypos="10"/> +</theme> diff --git a/repos/gems/src/app/themed_decorator/tint_painter.h b/repos/gems/src/app/themed_decorator/tint_painter.h new file mode 100644 index 000000000..c7de6ed4d --- /dev/null +++ b/repos/gems/src/app/themed_decorator/tint_painter.h @@ -0,0 +1,73 @@ +/* + * \brief Functor for tinting a surface with a color + * \author Norman Feske + * \date 2015-11-13 + */ + +/* + * 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 _TINT_PAINTER_H_ +#define _TINT_PAINTER_H_ + +#include <os/surface.h> +#include <polygon_gfx/interpolate_rgba.h> + + +struct Tint_painter +{ + typedef Genode::Surface_base::Rect Rect; + + /** + * Tint box with specified color + * + * \param rect position and size of box to tint + * \param color tinting color + */ + template <typename PT> + static inline void paint(Genode::Surface<PT> surface, + Rect rect, + Genode::Color color) + { + Rect clipped = Rect::intersect(surface.clip(), rect); + + if (!clipped.valid()) return; + + /* + * Generate lookup table (LUT) for mapping brightness values to colors. + * The specified color is used as a fixed point within the LUT. The + * other values are interpolated from black over the color to white. + */ + enum { LUT_SIZE = 256*3 }; + PT pixel_lut[LUT_SIZE]; + unsigned char alpha_lut[LUT_SIZE]; + + unsigned const lut_idx = color.r + color.g + color.b; + + Polygon::interpolate_rgba(Polygon::Color(0, 0, 0), color, + pixel_lut, alpha_lut, + lut_idx + 1, 0, 0); + + Polygon::interpolate_rgba(color, Polygon::Color(255, 255, 255), + pixel_lut + lut_idx, alpha_lut + lut_idx, + LUT_SIZE - lut_idx, 0, 0); + + + PT pix(color.r, color.g, color.b); + PT *dst, *dst_line = surface.addr() + surface.size().w()*clipped.y1() + clipped.x1(); + + for (int w, h = clipped.h() ; h--; dst_line += surface.size().w()) + for (dst = dst_line, w = clipped.w(); w--; dst++) { + PT const pixel = *dst; + *dst = pixel_lut[pixel.r() + pixel.g() + pixel.b()]; + } + + surface.flush_pixels(clipped); + } +}; + +#endif /* _INCLUDE__NITPICKER_GFX__BOX_PAINTER_H_ */ diff --git a/repos/gems/src/app/themed_decorator/window.cc b/repos/gems/src/app/themed_decorator/window.cc new file mode 100644 index 000000000..1937d753a --- /dev/null +++ b/repos/gems/src/app/themed_decorator/window.cc @@ -0,0 +1,60 @@ +/* + * \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" + +Decorator::Window_base::Hover Decorator::Window::hover(Point abs_pos) const +{ + Hover hover; + + if (!_decor_geometry().contains(abs_pos)) + return hover; + + hover.window_id = id(); + + Rect const closer_geometry = + _theme.absolute(_theme.element_geometry(Theme::ELEMENT_TYPE_CLOSER), + outer_geometry()); + if (closer_geometry.contains(abs_pos)) { + hover.closer = true; + return hover; + } + + Rect const maximizer_geometry = + _theme.absolute(_theme.element_geometry(Theme::ELEMENT_TYPE_MAXIMIZER), + outer_geometry()); + if (maximizer_geometry.contains(abs_pos)) { + hover.maximizer = true; + return hover; + } + + Rect const title_geometry = _theme.absolute(_theme.title_geometry(), + outer_geometry()); + if (title_geometry.contains(abs_pos)) { + hover.title = true; + return hover; + } + + int const x = abs_pos.x(); + int const y = abs_pos.y(); + + Area const theme_size = _theme.background_size(); + + hover.left_sizer = x < outer_geometry().x1() + (int)theme_size.w()/2; + hover.right_sizer = x > outer_geometry().x2() - (int)theme_size.w()/2; + hover.top_sizer = y < outer_geometry().y1() + (int)theme_size.h()/2; + hover.bottom_sizer = y > outer_geometry().y2() - (int)theme_size.h()/2; + + return hover; +} diff --git a/repos/gems/src/app/themed_decorator/window.h b/repos/gems/src/app/themed_decorator/window.h new file mode 100644 index 000000000..5c82e22e1 --- /dev/null +++ b/repos/gems/src/app/themed_decorator/window.h @@ -0,0 +1,539 @@ +/* + * \brief Window decorator that can be styled + * \author Norman Feske + * \date 2015-11-11 + */ + +/* + * 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 _WINDOW_H_ +#define _WINDOW_H_ + +/* Genode includes */ +#include <ram_session/ram_session.h> +#include <decorator/window.h> +#include <nitpicker_session/connection.h> +#include <os/attached_dataspace.h> +#include <util/volatile_object.h> + +/* demo includes */ +#include <util/lazy_value.h> + +/* gems includes */ +#include <gems/animator.h> +#include <gems/nitpicker_buffer.h> + +/* local includes */ +#include "theme.h" +#include "config.h" +#include "tint_painter.h" + +namespace Decorator { + + class Window; + typedef Genode::String<200> Window_title; + typedef Genode::Attached_dataspace Attached_dataspace; +} + + +class Decorator::Window : public Window_base, public Animator::Item +{ + private: + + Genode::Ram_session &_ram; + + Theme const &_theme; + + /* + * Flag indicating that the current window position has been propagated + * to the window's corresponding nitpicker views. + */ + bool _nitpicker_views_up_to_date = false; + + Nitpicker::Session::View_handle _neighbor; + + unsigned _topped_cnt = 0; + + Window_title _title; + + bool _focused = false; + + Lazy_value<int> _alpha = 0; + + Animator &_animator; + + struct Element : Animator::Item + { + Theme::Element_type const type; + + char const * const attr; + + bool _highlighted = false; + bool _present = false; + + Lazy_value<int> alpha = 0; + + int _alpha_dst() const + { + if (!_present) + return 0; + + return _highlighted ? 255 : 150; + } + + void _update_alpha_dst() + { + if ((int)alpha == _alpha_dst()) + return; + + alpha.dst(_alpha_dst(), 20); + animate(); + } + + void highlighted(bool highlighted) + { + _highlighted = highlighted; + _update_alpha_dst(); + } + + bool highlighted() const { return _highlighted; } + + void present(bool present) + { + _present = present; + _update_alpha_dst(); + } + + bool present() const { return _present; } + + void animate() override + { + alpha.animate(); + animated((int)alpha != alpha.dst()); + } + + Element(Animator &animator, Theme::Element_type type, char const *attr) + : + Animator::Item(animator), + type(type), attr(attr) + { + _update_alpha_dst(); + } + }; + + Element _closer { _animator, Theme::ELEMENT_TYPE_CLOSER, "closer" }; + Element _maximizer { _animator, Theme::ELEMENT_TYPE_MAXIMIZER, "maximizer" }; + + template <typename FN> + void _for_each_element(FN const &func) + { + func(_closer); + func(_maximizer); + } + + struct Nitpicker_view + { + typedef Nitpicker::Session::Command Command; + typedef Nitpicker::Session::View_handle View_handle; + + bool const _view_is_remote; + + Nitpicker::Session_client &_nitpicker; + + View_handle _handle; + + Nitpicker_view(Nitpicker::Session_client &nitpicker, + unsigned id = 0) + : + _view_is_remote(false), + _nitpicker(nitpicker), + _handle(_nitpicker.create_view()) + { + /* + * We supply the window ID as label for the anchor view. + */ + if (id) { + char buf[128]; + Genode::snprintf(buf, sizeof(buf), "%d", id); + + _nitpicker.enqueue<Command::Title>(_handle, buf); + } + } + + View_handle _create_remote_view(Nitpicker::Session_client &remote_nitpicker) + { + /* create view at the remote nitpicker session */ + View_handle handle = remote_nitpicker.create_view(); + Nitpicker::View_capability view_cap = remote_nitpicker.view_capability(handle); + + /* import remote view into local nitpicker session */ + return _nitpicker.view_handle(view_cap); + } + + /** + * Constructor called for creating a view that refers to a buffer + * of another nitpicker session + */ + Nitpicker_view(Nitpicker::Session_client &nitpicker, + Nitpicker::Session_client &remote_nitpicker) + : + _view_is_remote(true), + _nitpicker(nitpicker), + _handle(_create_remote_view(remote_nitpicker)) + { } + + ~Nitpicker_view() + { + if (_view_is_remote) + _nitpicker.release_view_handle(_handle); + else + _nitpicker.destroy_view(_handle); + } + + View_handle handle() const { return _handle; } + + void stack(View_handle neighbor) + { + _nitpicker.enqueue<Command::To_front>(_handle, neighbor); + } + + void place(Rect rect, Point offset) + { + _nitpicker.enqueue<Command::Geometry>(_handle, rect); + _nitpicker.enqueue<Command::Offset>(_handle, offset); + } + }; + + /** + * Nitpicker used as a global namespace of view handles + */ + Nitpicker::Session_client &_nitpicker; + + Config const &_config; + + Color _base_color = _config.base_color(_title); + + /* + * 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<int> _r, _g, _b; + + Color _color() const { return Color(_r >> 4, _g >> 4, _b >> 4); } + + /** + * Nitpicker session that contains the upper and lower window + * decorations. + */ + Nitpicker::Connection _nitpicker_top_bottom; + Genode::Lazy_volatile_object<Nitpicker_buffer> _buffer_top_bottom; + + /** + * Nitpicker session that contains the left and right window + * decorations. + */ + Nitpicker::Connection _nitpicker_left_right; + Genode::Lazy_volatile_object<Nitpicker_buffer> _buffer_left_right; + + Nitpicker_view _bottom_view { _nitpicker, _nitpicker_top_bottom }, + _right_view { _nitpicker, _nitpicker_left_right }, + _left_view { _nitpicker, _nitpicker_left_right }, + _top_view { _nitpicker, _nitpicker_top_bottom }; + + Nitpicker_view _content_view { _nitpicker, (unsigned)id() }; + + void _reallocate_nitpicker_buffers() + { + Area const theme_size = _theme.background_size(); + bool const use_alpha = true; + + Area const inner_size = geometry().area(); + Area const outer_size = outer_geometry().area(); + + Area const size_top_bottom(outer_size.w(), + theme_size.h()); + + _nitpicker_top_bottom.buffer(Framebuffer::Mode(size_top_bottom.w(), + size_top_bottom.h(), + Framebuffer::Mode::RGB565), + use_alpha); + + _buffer_top_bottom.construct(_nitpicker_top_bottom, size_top_bottom, _ram); + + Area const size_left_right(outer_size.w() - inner_size.w(), + outer_size.h()); + + _nitpicker_left_right.buffer(Framebuffer::Mode(size_left_right.w(), + size_left_right.h(), + Framebuffer::Mode::RGB565), + use_alpha); + + _buffer_left_right.construct(_nitpicker_left_right, size_left_right, _ram); + } + + void _repaint_decorations(Nitpicker_buffer &buffer) + { + buffer.reset_surface(); + + _theme.draw_background(buffer.pixel_surface(), + buffer.alpha_surface(), + (int)_alpha); + + _theme.draw_title(buffer.pixel_surface(), buffer.alpha_surface(), + _title.string()); + + _for_each_element([&] (Element const &element) { + _theme.draw_element(buffer.pixel_surface(), buffer.alpha_surface(), + element.type, element.alpha); }); + + Color const tint_color = _color(); + if (tint_color != Color(0, 0, 0)) + Tint_painter::paint(buffer.pixel_surface(), + Rect(Point(0, 0), buffer.pixel_surface().size()), + tint_color); + + buffer.flush_surface(); + + buffer.nitpicker.framebuffer()->refresh(0, 0, buffer.size().w(), buffer.size().h()); + } + + void _assign_color(Color color) + { + _base_color = color; + + _r.dst(_base_color.r << 4, 20); + _g.dst(_base_color.g << 4, 20); + _b.dst(_base_color.b << 4, 20); + } + + public: + + Window(unsigned id, Nitpicker::Session_client &nitpicker, + Animator &animator, Genode::Ram_session &ram, + Theme const &theme, Config const &config) + : + Window_base(id), + Animator::Item(animator), + _ram(ram), _theme(theme), _animator(animator), + _nitpicker(nitpicker), _config(config) + { + _reallocate_nitpicker_buffers(); + _alpha.dst(_focused ? 256 : 200, 20); + animate(); + } + + void stack(Nitpicker::Session::View_handle neighbor) override + { + _neighbor = neighbor; + + _top_view.stack(neighbor); + _left_view.stack(_top_view.handle()); + _right_view.stack(_left_view.handle()); + _bottom_view.stack(_right_view.handle()); + _content_view.stack(_bottom_view.handle()); + } + + Nitpicker::Session::View_handle frontmost_view() const override + { + return _content_view.handle(); + } + + Rect _decor_geometry() const + { + Theme::Margins const decor = _theme.decor_margins(); + + return Rect(geometry().p1() - Point(decor.left, decor.top), + geometry().p2() + Point(decor.right, decor.bottom)); + } + + Rect outer_geometry() const override + { + Theme::Margins const aura = _theme.aura_margins(); + Theme::Margins const decor = _theme.decor_margins(); + + unsigned const left = aura.left + decor.left; + unsigned const right = aura.right + decor.right; + unsigned const top = aura.top + decor.top; + unsigned const bottom = aura.bottom + decor.bottom; + + return Rect(geometry().p1() - Point(left, top), + geometry().p2() + Point(right, bottom)); + } + + void border_rects(Rect *top, Rect *left, Rect *right, Rect *bottom) const + { + outer_geometry().cut(geometry(), top, left, right, bottom); + } + + bool is_in_front_of(Window_base const &neighbor) const override + { + return _neighbor == neighbor.frontmost_view(); + } + + void update_nitpicker_views() override + { + if (!_nitpicker_views_up_to_date) { + + Area const theme_size = _theme.background_size(); + + /* update view positions */ + Rect top, left, right, bottom; + border_rects(&top, &left, &right, &bottom); + + _content_view.place(geometry(), Point(0, 0)); + _top_view .place(top, Point(0, 0)); + _left_view .place(left, Point(0, -top.h())); + _right_view .place(right, Point(-right.w(), -top.h())); + _bottom_view .place(bottom, Point(0, -theme_size.h() + bottom.h())); + + _nitpicker_views_up_to_date = true; + } + _nitpicker.execute(); + } + + void draw(Canvas_base &, Rect, Draw_behind_fn const &) const override { } + + void adapt_to_changed_config() + { + _assign_color(_config.base_color(_title)); + animate(); + } + + bool update(Xml_node window_node) override + { + bool updated = 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) { + + _topped_cnt = topped_cnt; + + stack(Nitpicker::Session::View_handle()); + + updated = true; + } + + bool trigger_animation = false; + + Rect const old_geometry = geometry(); + + geometry(rect_attribute(window_node)); + + /* + * Detect position changes + */ + if (geometry().p1() != old_geometry.p1() + || geometry().p2() != old_geometry.p2()) { + + _nitpicker_views_up_to_date = false; + updated = true; + } + + /* + * Detect size changes + */ + if (geometry().w() != old_geometry.w() + || geometry().h() != old_geometry.h()) { + + _reallocate_nitpicker_buffers(); + + /* triggering the animation has the side effect of repainting */ + trigger_animation = true; + } + + bool focused = window_node.attribute_value("focused", false); + + if (_focused != focused) { + _focused = focused; + _alpha.dst(_focused ? 256 : 200, 20); + trigger_animation = true; + } + + Window_title title = Decorator::string_attribute(window_node, "title", + Window_title("<untitled>")); + + if (_title != title) { + _title = title; + trigger_animation = true; + } + + /* update color on title change as the title is used as policy selector */ + Color const base_color = _config.base_color(_title); + if (_base_color != base_color) { + _assign_color(base_color); + trigger_animation = true; + } + + _for_each_element([&] (Element &element) { + bool const present = window_node.attribute_value(element.attr, false); + if (present != element.present()) { + element.present(present); + trigger_animation = true; + } + }); + + Xml_node const highlight = window_node.has_sub_node("highlight") + ? window_node.sub_node("highlight") + : Xml_node("<highlight/>"); + + _for_each_element([&] (Element &element) { + bool const highlighted = highlight.has_sub_node(element.attr); + if (highlighted != element.highlighted()) { + element.highlighted(highlighted); + trigger_animation = true; + } + }); + + if (trigger_animation) { + updated = true; + + /* schedule animation */ + animate(); + } + + return updated; + } + + Hover hover(Point) const override; + + /** + * Window_base interface + */ + bool animated() const override + { + return (_alpha.dst() != (int)_alpha) + || _r != _r.dst() || _g != _g.dst() || _b != _b.dst() + || _closer.animated() || _maximizer.animated(); + + } + + /** + * Animator::Item interface + */ + void animate() override + { + _alpha.animate(); + _r.animate(); + _g.animate(); + _b.animate(); + + _for_each_element([&] (Element &element) { element.animate(); }); + + Animator::Item::animated(animated()); + + _repaint_decorations(*_buffer_top_bottom); + _repaint_decorations(*_buffer_left_right); + } +}; + +#endif /* _WINDOW_H_ */