decorator: window colors and controls

This patch improves the decorator in two ways. First, it enables the
assignment of window colors depending on the window labels. This
configuration can be changed dynamically. Second, it adds the handling
of window controls for closing, maximizing, minimizing windows.

Issue #1689
Fixes #1688
This commit is contained in:
Norman Feske 2015-09-16 23:15:17 +02:00 committed by Christian Helmuth
parent 1460105f71
commit afac1e86bb
15 changed files with 1133 additions and 312 deletions

View File

@ -14,30 +14,34 @@
#ifndef _CANVAS_H_
#define _CANVAS_H_
/* Painters of the nitpicker and scout graphics backends */
#include <nitpicker_gfx/text_painter.h>
#include <nitpicker_gfx/box_painter.h>
#include <scout_gfx/icon_painter.h>
/* decorator includes */
#include <decorator/types.h>
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 <typename PT> 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<PT> const &texture =
static_cast<Genode::Texture<PT> const &>(texture_by_id(id));
unsigned const alpha = 255;
Icon_painter::paint(_surface, Rect(pos, texture.size()), texture, alpha);
}
};

Binary file not shown.

View File

@ -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 <os/session_policy.h>
#include <util/color.h>
/* decorator includes */
#include <decorator/types.h>
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_ */

View File

@ -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;
}

View File

@ -19,10 +19,6 @@
#include <os/attached_rom_dataspace.h>
#include <os/reporter.h>
/* Nitpicker graphics backend */
#include <nitpicker_gfx/text_painter.h>
#include <nitpicker_gfx/box_painter.h>
/* decorator includes */
#include <decorator/window_stack.h>
#include <decorator/xml_utils.h>
@ -104,11 +100,21 @@ struct Decorator::Main : Window_factory_base
Signal_dispatcher<Main> nitpicker_sync_dispatcher = {
sig_rec, *this, &Main::handle_nitpicker_sync };
Config config { *Genode::env()->heap() };
void handle_config(unsigned);
Signal_dispatcher<Main> 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 &>(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");
});
}
});

Binary file not shown.

Binary file not shown.

View File

@ -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)

View File

@ -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 <gems/chunky_texture.h>
#include <base/env.h>
#include <os/pixel_rgb565.h>
#include <os/texture_rgb565.h>
/* local includes */
#include "canvas.h"
template <typename PT>
class Icon_texture : public Chunky_texture<PT>
{
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<PT>(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<PT>::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<Genode::Pixel_rgb565> 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();
}

View File

@ -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("<untitled>"));
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());
}

View File

@ -14,11 +14,12 @@
#ifndef _WINDOW_H_
#define _WINDOW_H_
#include <util/lazy_value.h>
/* Genode includes */
#include <decorator/window.h>
/* gems includes */
#include <gems/animator.h>
/* 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<int> _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<unsigned> _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("<untitled>"));
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_ */

View File

@ -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 <util/lazy_value.h>
#include <util/color.h>
/* gems includes */
#include <gems/animator.h>
/* 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<int> _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_ */

Binary file not shown.

View File

@ -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;
}
};

View File

@ -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 <typename FUNC>
void for_each_window(FUNC const &func)
{
for (Window_base *win = _windows.first(); win; win = win->next())
func(*win);
}
void update_nitpicker_views()
{
/*