genode/repos/os/include/decorator/window_stack.h

341 lines
8.4 KiB
C++

/*
* \brief Window-stack handling for decorator
* \author Norman Feske
* \date 2014-01-09
*/
/*
* 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 _INCLUDE__DECORATOR__WINDOW_STACK_H_
#define _INCLUDE__DECORATOR__WINDOW_STACK_H_
/* Genode includes */
#include <nitpicker_session/nitpicker_session.h>
#include <base/printf.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:
Window_list _windows;
Window_factory_base &_window_factory;
Dirty_rect mutable _dirty_rect;
inline void _draw_rec(Canvas_base &canvas, Window_base const *win,
Rect rect) const;
Window_base *_lookup_by_id(unsigned const id)
{
for (Window_base *win = _windows.first(); win; win = win->next())
if (win->id() == id)
return win;
return 0;
}
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") && attribute(node, "id", 0UL) == id)
return node;
if (node.is_last()) break;
}
throw Xml_node::Nonexistent_sub_node();
}
void _destroy(Window_base &window)
{
_windows.remove(&window);
_window_factory.destroy(&window);
}
/**
* Generate window list in reverse order
*
* After calling this method, the '_windows' list is empty.
*/
Window_list _reversed_window_list()
{
Window_list reversed;
while (Window_base *w = _windows.first()) {
_windows.remove(w);
reversed.insert(w);
}
return reversed;
}
public:
Window_stack(Window_factory_base &window_factory)
:
_window_factory(window_factory)
{ }
Dirty_rect draw(Canvas_base &canvas) const
{
Dirty_rect result = _dirty_rect;
_dirty_rect.flush([&] (Rect const &rect) {
_draw_rec(canvas, _windows.first(), rect); });
return result;
}
inline void update_model(Xml_node root_node);
bool schedule_animated_windows()
{
bool redraw_needed = false;
for (Window_base *win = _windows.first(); win; win = win->next()) {
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)
{
for (Window_base *win = _windows.first(); win; win = win->next())
func(*win);
}
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.
*/
Window_list reversed = _reversed_window_list();
while (Window_base *win = reversed.first()) {
win->update_nitpicker_views();
reversed.remove(win);
_windows.insert(win);
}
}
void flush()
{
while (Window_base *window = _windows.first())
_destroy(*window);
}
Window_base::Hover hover(Point pos) const
{
for (Window_base const *win = _windows.first(); win; win = win->next())
if (win->outer_geometry().contains(pos)) {
Window_base::Hover const hover = win->hover(pos);
if (hover.window_id != 0)
return hover;
}
return Window_base::Hover();
}
/**************************************
** 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);
}
void Decorator::Window_stack::update_model(Genode::Xml_node root_node)
{
/*
* Step 1: Remove windows that are no longer present.
*/
for (Window_base *window = _windows.first(), *next = 0; window; window = next) {
next = window->next();
try {
_xml_node_by_window_id(root_node, window->id());
}
catch (Xml_node::Nonexistent_sub_node) {
_dirty_rect.mark_as_dirty(window->outer_geometry());
_destroy(*window);
};
}
/*
* Step 2: Update window properties of already present windows.
*/
for (Window_base *window = _windows.first(); window; window = window->next()) {
/*
* After step 1, a Xml_node::Nonexistent_sub_node exception can no
* longer occur. All windows remaining in the window stack are present
* in the XML model.
*/
try {
Rect const orig_geometry = window->outer_geometry();
if (window->update(_xml_node_by_window_id(root_node, window->id()))) {
_dirty_rect.mark_as_dirty(orig_geometry);
_dirty_rect.mark_as_dirty(window->outer_geometry());
}
}
catch (Xml_node::Nonexistent_sub_node) {
PERR("could not look up window %ld in XML model", window->id()); }
}
/*
* Step 3: Add new appearing windows to the window stack.
*/
for_each_sub_node(root_node, "window", [&] (Xml_node window_node) {
unsigned long const id = attribute(window_node, "id", 0UL);
if (!_lookup_by_id(id)) {
Window_base *new_window = _window_factory.create(window_node);
if (new_window) {
new_window->update(window_node);
/*
* Insert new window in front of all other windows.
*
* Immediately propagate the new stacking position of the new
* window to nitpicker ('update_nitpicker_views'). Otherwise,
*/
new_window->stack(Nitpicker::Session::View_handle());
_windows.insert(new_window);
_dirty_rect.mark_as_dirty(new_window->outer_geometry());
}
}
});
/*
* Step 4: Adjust window order.
*/
Window_base *previous_window = 0;
Window_base *window = _windows.first();
for_each_sub_node(root_node, "window", [&] (Xml_node window_node) {
if (!window) {
PERR("unexpected end of window list during re-ordering");
return;
}
unsigned long const id = attribute(window_node, "id", 0UL);
if (window->id() != id) {
window = _lookup_by_id(id);
if (!window) {
PERR("window lookup unexpectedly failed during re-ordering");
return;
}
_windows.remove(window);
_windows.insert(window, previous_window);
_dirty_rect.mark_as_dirty(window->outer_geometry());
}
previous_window = window;
window = window->next();
});
/*
* 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.
*/
Window_list reversed = _reversed_window_list();
if (Window_base * const back_most = reversed.first()) {
/* keep back-most window as is */
reversed.remove(back_most);
_windows.insert(back_most);
/* check consistency between window list order and view stacking */
while (Window_base *w = reversed.first()) {
Window_base * const neighbor = _windows.first();
reversed.remove(w);
_windows.insert(w);
/* propagate change stacking order to nitpicker */
if (w->is_in_front_of(*neighbor))
continue;
w->stack(neighbor->frontmost_view());
}
}
}
#endif /* _INCLUDE__DECORATOR__WINDOW_STACK_H_ */