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 000000000..dc0e77377 Binary files /dev/null and b/repos/gems/src/app/themed_decorator/theme/closer.png differ 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 000000000..8836bb291 Binary files /dev/null and b/repos/gems/src/app/themed_decorator/theme/default.png differ diff --git a/repos/gems/src/app/themed_decorator/theme/font.tff b/repos/gems/src/app/themed_decorator/theme/font.tff new file mode 100644 index 000000000..f3e2eee82 Binary files /dev/null and b/repos/gems/src/app/themed_decorator/theme/font.tff differ diff --git a/repos/gems/src/app/themed_decorator/theme/maximizer.png b/repos/gems/src/app/themed_decorator/theme/maximizer.png new file mode 100644 index 000000000..cfc8bb78f Binary files /dev/null and b/repos/gems/src/app/themed_decorator/theme/maximizer.png differ 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_ */