335 lines
9.0 KiB
C++
335 lines
9.0 KiB
C++
/*
|
|
* \brief Window-stack handling for decorator
|
|
* \author Norman Feske
|
|
* \date 2014-01-09
|
|
*/
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
|
|
#ifndef _INCLUDE__DECORATOR__WINDOW_STACK_H_
|
|
#define _INCLUDE__DECORATOR__WINDOW_STACK_H_
|
|
|
|
/* Genode includes */
|
|
#include <nitpicker_session/nitpicker_session.h>
|
|
#include <base/log.h>
|
|
|
|
/* local includes */
|
|
#include <decorator/types.h>
|
|
#include <decorator/window.h>
|
|
#include <decorator/window_factory.h>
|
|
#include <decorator/xml_utils.h>
|
|
|
|
namespace Decorator { class Window_stack; }
|
|
|
|
|
|
class Decorator::Window_stack : public Window_base::Draw_behind_fn
|
|
{
|
|
private:
|
|
|
|
List_model<Window_base> _windows { };
|
|
Window_factory_base &_window_factory;
|
|
Dirty_rect mutable _dirty_rect { };
|
|
|
|
unsigned long _front_most_id = ~0UL;
|
|
|
|
inline void _draw_rec(Canvas_base &canvas, Window_base const *win,
|
|
Rect rect) const;
|
|
|
|
static inline
|
|
Xml_node _xml_node_by_window_id(Genode::Xml_node node, unsigned id)
|
|
{
|
|
for (node = node.sub_node("window"); ; node = node.next()) {
|
|
|
|
if (node.has_type("window") && node.attribute_value("id", 0UL) == id)
|
|
return node;
|
|
|
|
if (node.last()) break;
|
|
}
|
|
|
|
throw Xml_node::Nonexistent_sub_node();
|
|
}
|
|
|
|
/**
|
|
* Generate window list in reverse order
|
|
*/
|
|
Reversed_windows _reversed_window_list()
|
|
{
|
|
Reversed_windows reversed { };
|
|
_windows.for_each([&] (Window_base &window) {
|
|
window.prepend_to_reverse_list(reversed); });
|
|
return reversed;
|
|
}
|
|
|
|
public:
|
|
|
|
Window_stack(Window_factory_base &window_factory)
|
|
:
|
|
_window_factory(window_factory)
|
|
{ }
|
|
|
|
void mark_as_dirty(Rect rect) { _dirty_rect.mark_as_dirty(rect); }
|
|
|
|
Dirty_rect draw(Canvas_base &canvas) const
|
|
{
|
|
Dirty_rect result = _dirty_rect;
|
|
|
|
_dirty_rect.flush([&] (Rect const &rect) {
|
|
_windows.apply_first([&] (Window_base const &first) {
|
|
_draw_rec(canvas, &first, rect); }); });
|
|
|
|
return result;
|
|
}
|
|
|
|
template <typename FN>
|
|
inline void update_model(Xml_node root_node, FN const &flush);
|
|
|
|
bool schedule_animated_windows()
|
|
{
|
|
bool redraw_needed = false;
|
|
|
|
_windows.for_each([&] (Window_base const &win) {
|
|
|
|
if (win.animated()) {
|
|
_dirty_rect.mark_as_dirty(win.outer_geometry());
|
|
redraw_needed = true;
|
|
}
|
|
});
|
|
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) { _windows.for_each(func); }
|
|
|
|
void update_nitpicker_views()
|
|
{
|
|
/*
|
|
* Update nitpicker views in reverse order (back-most first). The
|
|
* reverse order is important because the stacking position of a
|
|
* view is propagated by referring to the neighbor the view is in
|
|
* front of. By starting with the back-most view, we make sure that
|
|
* each view is always at its final stacking position when
|
|
* specified as neighbor of another view.
|
|
*/
|
|
Reversed_windows reversed = _reversed_window_list();
|
|
|
|
while (Genode::List_element<Window_base> *win = reversed.first()) {
|
|
win->object()->update_nitpicker_views();
|
|
reversed.remove(win);
|
|
}
|
|
}
|
|
|
|
Window_base::Hover hover(Point pos) const
|
|
{
|
|
Window_base::Hover result { };
|
|
|
|
_windows.for_each([&] (Window_base const &win) {
|
|
|
|
if (!result.window_id && win.outer_geometry().contains(pos)) {
|
|
|
|
Window_base::Hover const hover = win.hover(pos);
|
|
if (hover.window_id != 0)
|
|
result = hover;
|
|
}
|
|
});
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
/**************************************
|
|
** Window::Draw_behind_fn interface **
|
|
**************************************/
|
|
|
|
void draw_behind(Canvas_base &canvas, Window_base const &window, Rect clip) const override
|
|
{
|
|
_draw_rec(canvas, window.next(), clip);
|
|
}
|
|
};
|
|
|
|
|
|
void Decorator::Window_stack::_draw_rec(Decorator::Canvas_base &canvas,
|
|
Decorator::Window_base const *win,
|
|
Decorator::Rect rect) const
|
|
{
|
|
Rect clipped;
|
|
|
|
/* find next window that intersects with the rectangle */
|
|
for ( ; win && !(clipped = Rect::intersect(win->outer_geometry(), rect)).valid(); )
|
|
win = win->next();
|
|
|
|
/* check if we hit the bottom of the window stack */
|
|
if (!win) return;
|
|
|
|
/* draw areas around the current window */
|
|
if (Window_base const * const next = win->next()) {
|
|
Rect top, left, right, bottom;
|
|
rect.cut(clipped, &top, &left, &right, &bottom);
|
|
|
|
if (top.valid()) _draw_rec(canvas, next, top);
|
|
if (left.valid()) _draw_rec(canvas, next, left);
|
|
if (right.valid()) _draw_rec(canvas, next, right);
|
|
if (bottom.valid()) _draw_rec(canvas, next, bottom);
|
|
}
|
|
|
|
/* draw current window */
|
|
win->draw(canvas, clipped, *this);
|
|
}
|
|
|
|
|
|
template <typename FN>
|
|
void Decorator::Window_stack::update_model(Genode::Xml_node root_node,
|
|
FN const &flush_window_stack_changes)
|
|
{
|
|
Abandoned_windows _abandoned_windows { };
|
|
|
|
struct Update_policy : List_model<Window_base>::Update_policy
|
|
{
|
|
Abandoned_windows &_abandoned_windows;
|
|
Window_factory_base &_window_factory;
|
|
Dirty_rect &_dirty_rect;
|
|
|
|
Update_policy(Abandoned_windows &abandoned_windows,
|
|
Window_factory_base &window_factory,
|
|
Dirty_rect &dirty_rect)
|
|
:
|
|
_abandoned_windows(abandoned_windows),
|
|
_window_factory(window_factory),
|
|
_dirty_rect(dirty_rect)
|
|
{ }
|
|
|
|
void destroy_element(Window_base &window)
|
|
{
|
|
window.abandon(_abandoned_windows);
|
|
}
|
|
|
|
Window_base &create_element(Xml_node node)
|
|
{
|
|
return *_window_factory.create(node);
|
|
}
|
|
|
|
void update_element(Window_base &window, Xml_node node)
|
|
{
|
|
Rect const orig_geometry = window.outer_geometry();
|
|
|
|
if (window.update(node)) {
|
|
_dirty_rect.mark_as_dirty(orig_geometry);
|
|
_dirty_rect.mark_as_dirty(window.outer_geometry());
|
|
}
|
|
}
|
|
|
|
static bool element_matches_xml_node(Window_base const &elem, Xml_node node)
|
|
{
|
|
return elem.id() == node.attribute_value("id", ~0UL);
|
|
}
|
|
|
|
static bool node_is_element(Xml_node) { return true; }
|
|
};
|
|
|
|
Update_policy policy { _abandoned_windows, _window_factory, _dirty_rect };
|
|
|
|
_windows.update_from_xml(policy, root_node);
|
|
|
|
unsigned long new_front_most_id = ~0UL;
|
|
if (root_node.has_sub_node("window"))
|
|
new_front_most_id = root_node.sub_node("window").attribute_value("id", ~0UL);
|
|
|
|
/*
|
|
* Propagate changed stacking order to nitpicker
|
|
*
|
|
* First, we reverse the window list. The 'reversed' list starts with
|
|
* the back-most window. We then go throuh each window back to front
|
|
* and check if its neighbor is consistent with its position in the
|
|
* window list.
|
|
*/
|
|
Reversed_windows reversed = _reversed_window_list();
|
|
|
|
/* return true if window just came to front */
|
|
auto new_front_most_window = [&] (Window_base const &win) {
|
|
return (new_front_most_id != _front_most_id) && (win.id() == new_front_most_id); };
|
|
|
|
auto stack_back_most_window = [&] (Window_base &window) {
|
|
|
|
if (window.stacked())
|
|
return;
|
|
|
|
if (new_front_most_window(window))
|
|
window.stack_front_most();
|
|
else
|
|
window.stack_back_most();
|
|
|
|
_dirty_rect.mark_as_dirty(window.outer_geometry());
|
|
};
|
|
|
|
auto stack_window = [&] (Window_base &window, Window_base &neighbor) {
|
|
|
|
if (window.stacked() && window.in_front_of(neighbor))
|
|
return;
|
|
|
|
if (new_front_most_window(window))
|
|
window.stack_front_most();
|
|
else
|
|
window.stack(neighbor.frontmost_view());
|
|
|
|
_dirty_rect.mark_as_dirty(window.outer_geometry());
|
|
};
|
|
|
|
if (Genode::List_element<Window_base> *back_most = reversed.first()) {
|
|
|
|
/* handle back-most window */
|
|
reversed.remove(back_most);
|
|
Window_base &window = *back_most->object();
|
|
stack_back_most_window(window);
|
|
window.stacking_neighbor(Window_base::View_handle());
|
|
|
|
Window_base *neighbor = &window;
|
|
|
|
/* check consistency between window list order and view stacking */
|
|
while (Genode::List_element<Window_base> *elem = reversed.first()) {
|
|
|
|
reversed.remove(elem);
|
|
|
|
Window_base &window = *elem->object();
|
|
stack_window(window, *neighbor);
|
|
window.stacking_neighbor(neighbor->frontmost_view());
|
|
neighbor = &window;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Apply window-creation operations before destroying windows to prevent
|
|
* flickering.
|
|
*/
|
|
flush_window_stack_changes();
|
|
|
|
/*
|
|
* Destroy abandoned window objects
|
|
*
|
|
* This is done after all other operations to avoid flickering whenever one
|
|
* window is replaced by another one. If we first destroyed the original
|
|
* one, the window background would appear for a brief moment until the new
|
|
* window is created. By deferring the destruction of the old window to the
|
|
* point when the new one already exists, one of both windows is visible at
|
|
* all times.
|
|
*/
|
|
Genode::List_element<Window_base> *elem = _abandoned_windows.first(), *next = nullptr;
|
|
for (; elem; elem = next) {
|
|
next = elem->next();
|
|
_dirty_rect.mark_as_dirty(elem->object()->outer_geometry());
|
|
_window_factory.destroy(elem->object());
|
|
}
|
|
|
|
_front_most_id = new_front_most_id;
|
|
}
|
|
|
|
#endif /* _INCLUDE__DECORATOR__WINDOW_STACK_H_ */
|