genode/repos/gems/src/app/decorator/window.cc
Norman Feske c60604062c decorator: improve robustness of window restacking
This patch improves the detection of new appearing top-most windows.
Such a window should prompt the decorator to bring the corresponding
nitpicker view(s) to the front of the view stack. The original
implementation relied on hints provided by the layouter (the 'topped'
attribute). With the patch, the decorator tracks the top-most window by
itself, which improves the robustness.

As a second improvement, the patch defers the destruction of windows to
the point when all other window operations are completed. This hides
intermediate states when replacing one window by another in one step,
which is typical for console-like scenarios. Hence, this patch should
eliminate flickering artifacts when switching from one virtual console
to another.

Issue #3031
2018-11-16 14:53:26 +01:00

394 lines
11 KiB
C++

/*
* \brief Example window decorator that mimics the Motif look
* \author Norman Feske
* \date 2014-01-10
*/
/*
* Copyright (C) 2014-2017 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
/* 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().string_width(text).decimal(),
default_font().bounding_box().h());
/*
* 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);
}
}
bool Decorator::Window::update(Genode::Xml_node window_node, bool new_top_most)
{
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 || new_top_most) {
_topped_cnt = topped_cnt;
stack(Nitpicker::Session::View_handle());
updated |= true;
}
/*
* Detect geometry changes
*/
Rect new_geometry = rect_attribute(window_node);
if (new_geometry.p1() != geometry().p1()
|| new_geometry.p2() != geometry().p2()) {
geometry(new_geometry);
_nitpicker_views_up_to_date = false;
updated |= true;
}
_focused = window_node.attribute_value( "focused", false);
_has_alpha = window_node.attribute_value("has_alpha", false);
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 (window_node.attribute_value(attr, false))
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 + _border_size, 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 + _border_size, 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());
}