genode/repos/os/src/server/nitpicker/view_stack.cc
Norman Feske d3626bd84f nitpicker: fix transparency artifacts
The 'View_stack::draw_rec' method limited the redraw to parts of the
view that were explicitly marked as dirty. This does not produce the
desired result when stacking multiple transparent views. Here, the
background views must be drawn regardless of whether they are marked as
dirty or not.
2018-05-30 13:36:23 +02:00

388 lines
9.5 KiB
C++

/*
* \brief Nitpicker view stack implementation
* \author Norman Feske
* \date 2006-08-09
*/
/*
* Copyright (C) 2006-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.
*/
#include "view_stack.h"
#include "clip_guard.h"
using namespace Nitpicker;
template <typename VIEW>
VIEW *View_stack::_next_view(VIEW &view) const
{
for (VIEW *next_view = &view; ;) {
next_view = next_view->view_stack_next();
/* check if we hit the bottom of the view stack */
if (!next_view) return 0;
if (!next_view->owner().visible()) continue;
if (!next_view->background()) return next_view;
if (is_default_background(*next_view) || _focus.focused_background(*next_view))
return next_view;
/* view is a background view belonging to a non-focused session */
}
return 0;
}
Nitpicker::Rect View_stack::_outline(View_component const &view) const
{
Rect const rect = view.abs_geometry();
/* request thickness of view frame */
int const frame_size = view.frame_size(_focus);
return Rect(Point(rect.x1() - frame_size, rect.y1() - frame_size),
Point(rect.x2() + frame_size, rect.y2() + frame_size));
}
View_component const *View_stack::_target_stack_position(View_component const *neighbor, bool behind)
{
if (behind) {
if (!neighbor)
return nullptr;
/* find target position behind neighbor */
for (View_component const *cv = _first_view(); cv; cv = _next_view(*cv))
if (cv == neighbor)
return cv;
} else {
if (neighbor == _first_view())
return nullptr;
/* find target position in front of neighbor */
for (View_component const *cv = _first_view(), *next = nullptr; cv; cv = next) {
next = _next_view(*cv);
if (!next || next == neighbor || next->background())
return cv;
}
}
/* we should never reach this point */
return nullptr;
}
void View_stack::_optimize_label_rec(View_component const *cv, View_component const *lv,
Rect rect, Rect *optimal)
{
/* if label already fits in optimized rectangle, we are happy */
if (optimal->fits(lv->label_rect().area()))
return;
/* find next view that intersects with the rectangle or the target view */
Rect clipped;
while (cv && cv != lv && !(clipped = Rect::intersect(_outline(*cv), rect)).valid())
cv = _next_view(*cv);
/* reached end of view stack */
if (!cv) return;
if (cv != lv && _next_view(*cv)) {
/* cut current view from rectangle and go into sub rectangles */
Rect r[4];
rect.cut(clipped, &r[0], &r[1], &r[2], &r[3]);
for (int i = 0; i < 4; i++)
_optimize_label_rec(_next_view(*cv), lv, r[i], optimal);
return;
}
/*
* Now, cv equals lv and we must decide how to configure the
* optimal rectangle.
*/
/* stop if label does not fit vertically */
if (rect.h() < lv->label_rect().h())
return;
/*
* If label fits completely within current rectangle, we are done.
* If label's width is not fully visible, choose the widest rectangle.
*/
if (rect.fits(lv->label_rect().area()) || (rect.w() > optimal->w())) {
*optimal = rect;
return;
}
}
void View_stack::_place_labels(Rect rect)
{
/*
* XXX We may skip this if none of the domains have the labeling enabled.
*/
/* ignore mouse cursor */
View_component const *start = _next_view(*_first_view());
View_component *view = _next_view(*_first_view());
for (; view && _next_view(*view); view = _next_view(*view)) {
Rect const view_rect = view->abs_geometry();
if (Rect::intersect(view_rect, rect).valid()) {
Rect old = view->label_rect(), best;
/* calculate best visible label position */
Rect rect = Rect::intersect(Rect(Point(), _size), view_rect);
if (start) _optimize_label_rec(start, view, rect, &best);
/*
* If label is not fully visible, we ensure to display the first
* (most important) part. Otherwise, we center the label horizontally.
*/
int x = best.x1();
if (best.fits(view->label_rect().area()))
x += (best.w() - view->label_rect().w()) / 2;
view->label_pos(Point(x, best.y1()));
/* refresh old and new label positions */
refresh_view(*view, old);
refresh_view(*view, view->label_rect());
}
}
}
void View_stack::draw_rec(Canvas_base &canvas, Font const &font,
View_component const *view, Rect rect) const
{
Rect clipped;
/* find next view that intersects with the current clipping rectangle */
for ( ; view && !(clipped = Rect::intersect(_outline(*view), rect)).valid(); )
view = _next_view(*view);
/* check if we hit the bottom of the view stack */
if (!view) return;
Rect top, left, right, bottom;
rect.cut(clipped, &top, &left, &right, &bottom);
View_component const *next = _next_view(*view);
/* draw areas at the top/left of the current view */
if (next && top.valid()) draw_rec(canvas, font, next, top);
if (next && left.valid()) draw_rec(canvas, font, next, left);
/* draw current view */
view->dirty_rect().flush([&] (Rect const &dirty_rect) {
Clip_guard clip_guard(canvas, Rect::intersect(clipped, dirty_rect));
/* draw background if view is transparent */
if (view->uses_alpha())
draw_rec(canvas, font, _next_view(*view), clipped);
view->frame(canvas, _focus);
view->draw(canvas, font, _focus);
});
/* draw areas at the bottom/right of the current view */
if (next && right.valid()) draw_rec(canvas, font, next, right);
if (next && bottom.valid()) draw_rec(canvas, font, next, bottom);
}
void View_stack::refresh_view(View_component &view, Rect const rect)
{
/* rectangle constrained to view geometry */
Rect const view_rect = Rect::intersect(rect, _outline(view));
for (View_component *v = _first_view(); v; v = v->view_stack_next())
_mark_view_as_dirty(*v, view_rect);
view.for_each_child([&] (View_component &child) { refresh_view(child, rect); });
}
void View_stack::refresh(Rect const rect)
{
for (View_component *v = _first_view(); v; v = v->view_stack_next()) {
Rect const intersection = Rect::intersect(rect, _outline(*v));
if (intersection.valid())
refresh_view(*v, intersection);
}
}
void View_stack::geometry(View_component &view, Rect const rect)
{
Rect const old_outline = _outline(view);
/*
* Refresh area covered by the original view geometry.
*
* We specify the whole geometry to also cover the refresh of child
* views. The 'refresh_view' function takes care to constrain the
* refresh to the actual view geometry.
*/
refresh_view(view, Rect(Point(), _size));
/* change geometry */
view.geometry(Rect(rect));
/* refresh new view geometry */
refresh_view(view, Rect(Point(), _size));
Rect const compound = Rect::compound(old_outline, _outline(view));
/* update labels (except when moving the mouse cursor) */
if (&view != _first_view())
_place_labels(compound);
}
void View_stack::buffer_offset(View_component &view, Point const buffer_off)
{
view.buffer_off(buffer_off);
refresh_view(view, Rect(Point(), _size));
}
void View_stack::stack(View_component &view, View_component const *neighbor, bool behind)
{
_views.remove(&view);
_views.insert(&view, _target_stack_position(neighbor, behind));
/* enforce stacking constrains dictated by domain layers */
sort_views_by_layer();
_place_labels(view.abs_geometry());
refresh_view(view, _outline(view));
}
void View_stack::title(View_component &view, Font const &font, const char *title)
{
view.title(font, title);
_place_labels(view.abs_geometry());
_mark_view_as_dirty(view, _outline(view));
}
View_component *View_stack::find_view(Point p)
{
View_component *view = _first_view();
for ( ; view; view = _next_view(*view))
if (view->input_response_at(p))
return view;
return nullptr;
}
void View_stack::remove_view(View_component const &view, bool /* redraw */)
{
view.for_each_const_child([&] (View_component const &child) { remove_view(child); });
/* remember geometry of view to remove */
Rect rect = _outline(view);
/* exclude view from view stack */
_views.remove(&view);
refresh(rect);
}
void View_stack::sort_views_by_layer()
{
Genode::List<View_stack_elem> sorted;
/* last element of the sorted list */
View_stack_elem *at = nullptr;
while (_views.first()) {
/* find view with the lowest layer */
unsigned lowest_layer = ~0U;
View_stack_elem *lowest_view = nullptr;
for (View_stack_elem *v = _views.first(); v; v = v->next()) {
unsigned const layer = static_cast<View_component *>(v)->owner().layer();
if (layer < lowest_layer) {
lowest_layer = layer;
lowest_view = v;
}
}
if (!lowest_view)
lowest_view = _views.first();
/*
* Move lowest view from unsorted list to the end of the sorted
* list.
*/
_views.remove(lowest_view);
sorted.insert(lowest_view, at);
at = lowest_view;
}
/* replace empty source list by newly sorted list */
_views = sorted;
}
void View_stack::to_front(char const *selector)
{
/*
* Move all views that match the selector to the front while
* maintaining their ordering.
*/
View_component *at = nullptr;
for (View_component *v = _first_view(); v; v = v->view_stack_next()) {
if (!v->owner().matches_session_label(selector))
continue;
if (v->background())
continue;
/*
* Move view to behind the previous view that we moved to
* front. If 'v' is the first view that matches the selector,
* move it to the front ('at' argument of 'insert' is 0).
*/
_views.remove(v);
_views.insert(v, at);
at = v;
/* mark view geometry as to be redrawn */
refresh(_outline(*v));
}
/* reestablish domain layering */
sort_views_by_layer();
}