wm/decorator/layouter: window clipping

This patch changes the window-layout format to support the rectangular
clipping of windows at screen boundaries. The new <boundary> node defines
the clipping boundary for the windows listed within the node. Boundaries
are expected to be disjoint. In the example below, the "vbox" window is
placed partially outside the screen area of "screen_2".

<window_layout>
  <boundary name="screen_1" xpos="0" ypos="0" width="640" height="480">
    <window id="1" title="launchpad" xpos="10" ypos="140" width="400" height=">
  </boundary>
  <boundary name="screen_2" xpos="640" ypos="0" width="800" height="600">
    <window id="2" title="vbox"     xpos="520" ypos="52" width="800" height="600">
    <window id="3" title="terminal" xpos="650" ypos="72" width="500" height="400">
  </boundary>
</window_layout>

The layouter uses boundaries to restrict the visiblilty of windows to
their respective target areas.

Until now, Sculpt relied on the fact that the window-layout ROM had the
same structure as the resize-request ROM. With the addition of the
<boundary> nodes, this is no longer the case. Therefore, the Sculpt
manager generates a dedicated resize-request ROM now.

Issue #5390
This commit is contained in:
Norman Feske 2024-11-15 13:13:32 +01:00 committed by Christian Helmuth
parent b3d99960e7
commit 1638ee00c3
19 changed files with 587 additions and 310 deletions

View File

@ -77,79 +77,93 @@ install_config {
<sleep milliseconds="500" />
<inline description="open window 1">
<window_layout>
<window id="1" title="Genode Toolchain"
xpos="100" ypos="50" width="200" height="200"
focused="yes" />
<boundary width="1024" height="768">
<window id="1" title="Genode Toolchain"
xpos="100" ypos="50" width="200" height="200"
focused="yes" />
</boundary>
</window_layout>
</inline>
<sleep milliseconds="1000" />
<inline description="open window 2 behind window 1">
<window_layout>
<window id="1" title="Genode Toolchain"
xpos="100" ypos="50" width="200" height="200"
focused="yes" />
<window id="2" title="Arora (2)"
xpos="170" ypos="150" width="300" height="200" />
<boundary width="1024" height="768">
<window id="1" title="Genode Toolchain"
xpos="100" ypos="50" width="200" height="200"
focused="yes" />
<window id="2" title="Arora (2)"
xpos="170" ypos="150" width="300" height="200" />
</boundary>
</window_layout>
</inline>
<sleep milliseconds="1000" />
<inline description="open window 3 in front">
<window_layout>
<window id="3" title="Launchpad"
xpos="210" ypos="250" width="400" height="200" />
<window id="1" title="Genode Toolchain"
xpos="100" ypos="50" width="200" height="200"
focused="yes" />
<window id="2" title="Arora (2)"
xpos="170" ypos="150" width="300" height="200" />
<boundary width="1024" height="768">
<window id="3" title="Launchpad"
xpos="210" ypos="250" width="400" height="200" />
<window id="1" title="Genode Toolchain"
xpos="100" ypos="50" width="200" height="200"
focused="yes" />
<window id="2" title="Arora (2)"
xpos="170" ypos="150" width="300" height="200" />
</boundary>
</window_layout>
</inline>
<sleep milliseconds="1000" />
<inline description="bring window 1 to front">
<window_layout>
<window id="1" title="Genode Toolchain"
xpos="100" ypos="50" width="200" height="200"
focused="yes" />
<window id="3" title="Launchpad"
xpos="210" ypos="250" width="400" height="200" />
<window id="2" title="Arora (2)"
xpos="170" ypos="150" width="300" height="200" />
<boundary width="1024" height="768">
<window id="1" title="Genode Toolchain"
xpos="100" ypos="50" width="200" height="200"
focused="yes" />
<window id="3" title="Launchpad"
xpos="210" ypos="250" width="400" height="200" />
<window id="2" title="Arora (2)"
xpos="170" ypos="150" width="300" height="200" />
</boundary>
</window_layout>
</inline>
<sleep milliseconds="1000" />
<inline description="change title of window 1">
<window_layout>
<window id="1" title="Genode Toolchain (running)"
xpos="100" ypos="50" width="200" height="200"
focused="yes" />
<window id="3" title="Launchpad"
xpos="210" ypos="250" width="400" height="200" />
<window id="2" title="Arora (2)"
xpos="170" ypos="150" width="300" height="200" />
<boundary width="1024" height="768">
<window id="1" title="Genode Toolchain (running)"
xpos="100" ypos="50" width="200" height="200"
focused="yes" />
<window id="3" title="Launchpad"
xpos="210" ypos="250" width="400" height="200" />
<window id="2" title="Arora (2)"
xpos="170" ypos="150" width="300" height="200" />
</boundary>
</window_layout>
</inline>
<sleep milliseconds="1000" />
<inline description="change focus to window 3">
<window_layout>
<window id="1" title="Genode Toolchain (running)"
xpos="100" ypos="50" width="200" height="200" />
<window id="3" title="Launchpad"
xpos="210" ypos="250" width="400" height="200"
focused="yes" />
<window id="2" title="Arora (2)"
xpos="170" ypos="150" width="300" height="200" />
<boundary width="1024" height="768">
<window id="1" title="Genode Toolchain (running)"
xpos="100" ypos="50" width="200" height="200" />
<window id="3" title="Launchpad"
xpos="210" ypos="250" width="400" height="200"
focused="yes" />
<window id="2" title="Arora (2)"
xpos="170" ypos="150" width="300" height="200" />
</boundary>
</window_layout>
</inline>
<sleep milliseconds="1000" />
<inline description="move window 3">
<window_layout>
<window id="1" title="Genode Toolchain"
xpos="100" ypos="50" width="200" height="200" />
<window id="3" title="Launchpad"
xpos="310" ypos="300" width="500" height="300"
focused="yes" />
<window id="2" title="Arora (2)"
xpos="170" ypos="150" width="300" height="200" />
<boundary width="1024" height="768">
<window id="1" title="Genode Toolchain"
xpos="100" ypos="50" width="200" height="200" />
<window id="3" title="Launchpad"
xpos="310" ypos="300" width="500" height="300"
focused="yes" />
<window id="2" title="Arora (2)"
xpos="170" ypos="150" width="300" height="200" />
</boundary>
</window_layout>
</inline>
<sleep milliseconds="1000" />
@ -160,7 +174,7 @@ install_config {
</start>
<start name="decorator">
<resource name="RAM" quantum="4M"/>
<resource name="RAM" quantum="8M"/>
<route>
<service name="ROM" label="pointer">
<child name="report_rom" />

View File

@ -114,7 +114,7 @@
</provides>
<config verbose="no">
<policy label="decorator -> window_layout" report="manager -> window_layout"/>
<policy label="wm -> resize_request" report="manager -> window_layout"/>
<policy label="wm -> resize_request" report="manager -> resize_request"/>
<policy label="wm -> focus" report="manager -> wm_focus"/>
<policy label="decorator -> pointer" report="wm -> pointer"/>
<policy label="manager -> window_list" report="wm -> window_list"/>

View File

@ -40,6 +40,8 @@ struct Decorator::Main : Window_factory_base
{
Env &_env;
Heap _heap { _env.ram(), _env.rm() };
Timer::Connection _timer { _env };
/*
@ -120,7 +122,9 @@ struct Decorator::Main : Window_factory_base
_back_to_front(dirty);
}
Window_stack _window_stack = { *this };
Window_stack _window_stack { *this, _heap };
Windows _windows { };
/**
* Handler for responding to window-layout changes
@ -172,8 +176,6 @@ struct Decorator::Main : Window_factory_base
}
}
Heap _heap { _env.ram(), _env.rm() };
Attached_rom_dataspace _config { _env, "config" };
void _handle_config();
@ -222,19 +224,34 @@ struct Decorator::Main : Window_factory_base
/**
* Window_factory_base interface
*/
Window_base *create(Xml_node window_node) override
Window_base::Ref &create_ref(Xml_node const &window_node) override
{
return new (_heap)
Window(window_node.attribute_value("id", 0U),
_gui, _animator, _decorator_config);
Windows::Id const id { window_node.attribute_value("id", 0U) };
Window_base *window_ptr = nullptr;
_windows.apply<Window_base>(id,
[&] (Window_base &window) { window_ptr = &window; },
[&] /* missing */ {
window_ptr = new (_heap)
Window(_windows, id, _gui, _animator, _decorator_config); });
return *new (_heap) Window_base::Ref(*window_ptr);
}
/**
* Window_factory_base interface
*/
void destroy(Window_base *window) override
void destroy_ref(Window_base::Ref &ref) override
{
Genode::destroy(_heap, static_cast<Window *>(window));
destroy(_heap, &ref);
}
/**
* Window_factory_base interface
*/
void destroy_window(Window_base &window) override
{
destroy(_heap, &window);
}
};
@ -280,11 +297,11 @@ static void update_hover_report(Genode::Xml_node pointer_node,
Genode::Reporter::Xml_generator xml(hover_reporter, [&] ()
{
if (hover.window_id > 0) {
if (hover.window_id.value > 0) {
xml.node("window", [&] () {
xml.attribute("id", hover.window_id);
xml.attribute("id", hover.window_id.value);
if (hover.left_sizer) xml.node("left_sizer");
if (hover.right_sizer) xml.node("right_sizer");

View File

@ -15,9 +15,8 @@
#include "window.h"
void Decorator::Window::draw(Decorator::Canvas_base &canvas,
Decorator::Rect clip,
Draw_behind_fn const &draw_behind_fn) const
void Decorator::Window::draw(Decorator::Canvas_base &canvas, Ref const &win_ref,
Decorator::Rect clip, Draw_behind_fn const &draw_behind_fn) const
{
Clip_guard clip_guard(canvas, clip);
@ -28,7 +27,7 @@ void Decorator::Window::draw(Decorator::Canvas_base &canvas,
Point p2 = rect.p2();
if (_has_alpha)
draw_behind_fn.draw_behind(canvas, *this, canvas.clip());
draw_behind_fn.draw_behind(canvas, win_ref, canvas.clip());
_draw_corner(canvas, Rect(p1, corner), _border_size, true, true,
_window_elem_attr(Element::TOP_LEFT));

View File

@ -38,6 +38,8 @@ class Decorator::Window : public Window_base
*/
bool _gui_views_up_to_date = false;
Rect _clip { }; /* most recently used clipping rectangle */
struct Gui_view : Genode::Noncopyable
{
Gui::Connection &_gui;
@ -74,11 +76,18 @@ class Decorator::Window : public Window_base
void stack_back_most() { _gui.enqueue<Command::Back>(id()); }
void place(Rect rect)
void place_as_decor(Clip const &clip, Rect rect)
{
_gui.enqueue<Command::Geometry>(id(), rect);
Point offset = Point(0, 0) - rect.at;
_gui.enqueue<Command::Offset>(id(), offset);
Rect const intersection = Rect::intersect(clip, rect);
_gui.enqueue<Command::Geometry>(id(), intersection);
_gui.enqueue<Command::Offset>(id(), Point() - intersection.at);
}
void place_as_content(Clip const &clip, Rect rect)
{
Rect const intersection = Rect::intersect(clip, rect);
_gui.enqueue<Command::Geometry>(id(), intersection);
_gui.enqueue<Command::Offset>(id(), rect.at - intersection.at);
}
};
@ -87,7 +96,7 @@ class Decorator::Window : public Window_base
_left_view { _gui },
_top_view { _gui };
Gui_view _content_view { _gui, (unsigned)id() };
Gui_view _content_view { _gui, unsigned(id().value) };
static Border _init_border() {
return Border(_border_size + _title_height,
@ -419,10 +428,10 @@ class Decorator::Window : public Window_base
public:
Window(unsigned id, Gui::Connection &gui,
Window(Windows &windows, Windows::Id id, Gui::Connection &gui,
Animator &animator, Config const &config)
:
Window_base(id),
Window_base(windows, id),
_gui(gui),
_animator(animator), _config(config)
{ }
@ -465,18 +474,18 @@ class Decorator::Window : public Window_base
geometry().p2() + Point(_border.right, _border.bottom));
}
void update_gui_views() override
void update_gui_views(Clip const &clip) override
{
if (!_gui_views_up_to_date) {
if (!_gui_views_up_to_date || (clip != _clip)) {
/* update view positions */
auto const border = outer_geometry().cut(geometry());
_content_view.place(geometry());
_top_view .place(border.top);
_left_view .place(border.left);
_right_view .place(border.right);
_bottom_view .place(border.bottom);
_content_view.place_as_content(clip, geometry());
_top_view .place_as_decor (clip, border.top);
_left_view .place_as_decor (clip, border.left);
_right_view .place_as_decor (clip, border.right);
_bottom_view .place_as_decor (clip, border.bottom);
_gui_views_up_to_date = true;
}
@ -487,7 +496,7 @@ class Decorator::Window : public Window_base
_base_color = _config.base_color(_title);
}
void draw(Canvas_base &canvas, Rect clip, Draw_behind_fn const &) const override;
void draw(Canvas_base &canvas, Ref const &, Rect clip, Draw_behind_fn const &) const override;
bool update(Xml_node) override;

View File

@ -1621,8 +1621,9 @@ struct Sculpt::Main : Input_event_handler,
Rom_handler<Main> _decorator_margins {
_env, "decorator_margins", *this, &Main::_handle_window_layout_or_decorator_margins };
Expanding_reporter _wm_focus { _env, "focus", "wm_focus" };
Expanding_reporter _window_layout { _env, "window_layout", "window_layout" };
Expanding_reporter _wm_focus { _env, "focus", "wm_focus" };
Expanding_reporter _window_layout { _env, "window_layout", "window_layout" };
Expanding_reporter _resize_request { _env, "resize_request", "resize_request" };
template <size_t N>
void _with_window(Xml_node window_list, String<N> const &match, auto const &fn)
@ -1906,10 +1907,20 @@ void Sculpt::Main::_update_window_layout(Xml_node const &decorator_margins,
Point const inspect_p2(avail.x2() - margins.right - 1,
avail.y2() - margins.bottom - 1);
_window_layout.generate([&] (Xml_generator &xml) {
auto generate_within_screen_boundary = [&] (auto const &fn)
{
_resize_request.generate([&] (Xml_generator &resize_xml) {
_window_layout.generate([&] (Xml_generator &xml) {
xml.node("boundary", [&] {
xml.attribute("width", _screen_size.w);
xml.attribute("height", _screen_size.h);
fn(xml, resize_xml); }); }); });
};
generate_within_screen_boundary([&] (Xml_generator &xml, Xml_generator &resize_xml) {
auto gen_window = [&] (Xml_node const &win, Rect rect) {
if (rect.valid()) {
if (rect.valid())
xml.node("window", [&] {
xml.attribute("id", win.attribute_value("id", 0UL));
xml.attribute("xpos", rect.x1());
@ -1918,7 +1929,15 @@ void Sculpt::Main::_update_window_layout(Xml_node const &decorator_margins,
xml.attribute("height", rect.h());
xml.attribute("title", win.attribute_value("label", Label()));
});
}
};
auto gen_resize = [&] (Xml_node const &win, Area area) {
if (area.valid())
resize_xml.node("window", [&] {
resize_xml.attribute("id", win.attribute_value("id", 0UL));
resize_xml.attribute("width", area.w);
resize_xml.attribute("height", area.h);
});
};
/* window size limited to space unobstructed by the menu and log */
@ -1935,7 +1954,10 @@ void Sculpt::Main::_update_window_layout(Xml_node const &decorator_margins,
gen_window(win, panel); });
_with_window(window_list, Label("log"), [&] (Xml_node const &win) {
gen_window(win, Rect::compound(log_p1, log_p2)); });
Rect const rect = Rect::compound(log_p1, log_p2);
gen_window(win, rect);
gen_resize(win, rect.area);
});
int system_right_xpos = 0;
if (system_available()) {
@ -2058,8 +2080,12 @@ void Sculpt::Main::_update_window_layout(Xml_node const &decorator_margins,
}
_with_window(window_list, inspect_label, [&] (Xml_node const &win) {
if (_selected_tab == Panel_dialog::Tab::INSPECT)
gen_window(win, Rect::compound(inspect_p1, inspect_p2)); });
if (_selected_tab == Panel_dialog::Tab::INSPECT) {
Rect const rect = Rect::compound(inspect_p1, inspect_p2);
gen_window(win, rect);
gen_resize(win, rect.area);
}
});
/*
* Position runtime view centered within the inspect area, but allow

View File

@ -37,6 +37,8 @@ struct Decorator::Main : Window_factory_base
{
Env &_env;
Heap _heap { _env.ram(), _env.rm() };
Timer::Connection _timer { _env };
/*
@ -49,7 +51,9 @@ struct Decorator::Main : Window_factory_base
return { .cs = _timer.curr_time().trunc_to_plain_ms().value / 10 };
}
Window_stack _window_stack = { *this };
Window_stack _window_stack = { *this, _heap };
Windows _windows { };
/**
* Handler for responding to window-layout changes
@ -84,8 +88,6 @@ struct Decorator::Main : Window_factory_base
Animator _animator { };
Heap _heap { _env.ram(), _env.rm() };
Theme _theme { _env.ram(), _env.rm(), _heap };
Reporter _decorator_margins_reporter = { _env, "decorator_margins" };
@ -177,19 +179,35 @@ struct Decorator::Main : Window_factory_base
/**
* Window_factory_base interface
*/
Window_base *create(Xml_node window_node) override
Window_base::Ref &create_ref(Xml_node const &window_node) override
{
return new (_heap)
Window(_env, window_node.attribute_value("id", 0U),
_gui, _animator, _theme, _decorator_config);
Windows::Id const id { window_node.attribute_value("id", 0U) };
Window_base *window_ptr = nullptr;
_windows.apply<Window_base>(id,
[&] (Window_base &window) { window_ptr = &window; },
[&] /* missing */ {
window_ptr = new (_heap)
Window(_env, _windows, id, _gui, _animator, _theme,
_decorator_config); });
return *new (_heap) Window_base::Ref(*window_ptr);
}
/**
* Window_factory_base interface
*/
void destroy(Window_base *window) override
void destroy_ref(Window_base::Ref &ref) override
{
Genode::destroy(_heap, static_cast<Window *>(window));
destroy(_heap, &ref);
}
/**
* Window_factory_base interface
*/
void destroy_window(Window_base &window) override
{
destroy(_heap, &window);
}
};
@ -233,11 +251,11 @@ static void update_hover_report(Genode::Xml_node pointer_node,
Genode::Reporter::Xml_generator xml(hover_reporter, [&] ()
{
if (hover.window_id > 0) {
if (hover.window_id.value > 0) {
xml.node("window", [&] () {
xml.attribute("id", hover.window_id);
xml.attribute("id", hover.window_id.value);
if (hover.left_sizer) xml.node("left_sizer");
if (hover.right_sizer) xml.node("right_sizer");

View File

@ -161,7 +161,9 @@ element_geometry(Genode::Ram_allocator &ram, Genode::Region_map &rm,
Genode::Allocator &alloc, char const *sub_node_type,
Texture_id texture_id)
{
using namespace Decorator;
using Rect = Decorator::Rect;
using Point = Decorator::Point;
using Area = Decorator::Area;
static Genode::Xml_node const node = metadata(alloc);

View File

@ -19,6 +19,7 @@
#include <os/texture.h>
#include <os/pixel_alpha8.h>
#include <os/pixel_rgb888.h>
#include <decorator/types.h>
namespace Decorator {
@ -29,10 +30,6 @@ namespace Decorator {
using Pixel_surface = Genode::Surface<Pixel_rgb888>;
using Alpha_surface = Genode::Surface<Pixel_alpha8>;
using Area = Genode::Surface_base::Area;
using Point = Genode::Surface_base::Point;
using Rect = Genode::Surface_base::Rect;
}

View File

@ -52,6 +52,8 @@ class Decorator::Window : public Window_base, public Animator::Item
*/
bool _gui_views_up_to_date = false;
Rect _clip { }; /* most recently used clipping rectangle */
unsigned _topped_cnt = 0;
Window_title _title { };
@ -162,10 +164,11 @@ class Decorator::Window : public Window_base, public Animator::Item
void stack_back_most() { _gui.enqueue<Command::Back>(id()); }
void place(Rect rect, Point offset)
void place(Clip const &clip, Rect rect, Point offset)
{
_gui.enqueue<Command::Geometry>(id(), rect);
_gui.enqueue<Command::Offset>(id(), offset);
Rect const intersection = Rect::intersect(clip, rect);
_gui.enqueue<Command::Geometry>(id(), intersection);
_gui.enqueue<Command::Offset>(id(), offset + rect.at - intersection.at);
}
};
@ -263,7 +266,7 @@ class Decorator::Window : public Window_base, public Animator::Item
_left_view { _gui, _gui_left_right },
_top_view { _gui, _gui_top_bottom };
Content_view _content_view { _gui, (unsigned)id() };
Content_view _content_view { _gui, unsigned(id().value) };
void _repaint_decorations(Gui_buffer &buffer, Area area)
{
@ -352,10 +355,11 @@ class Decorator::Window : public Window_base, public Animator::Item
public:
Window(Genode::Env &env, unsigned id, Gui::Connection &gui,
Animator &animator, Theme const &theme, Config const &config)
Window(Genode::Env &env, Windows &windows, Windows::Id id,
Gui::Connection &gui, Animator &animator, Theme const &theme,
Config const &config)
:
Window_base(id),
Window_base(windows, id),
Animator::Item(animator),
_env(env), _theme(theme), _animator(animator),
_gui(gui), _config(config)
@ -433,11 +437,10 @@ class Decorator::Window : public Window_base, public Animator::Item
return _outer_from_inner_geometry(geometry());
}
void update_gui_views() override
void update_gui_views(Clip const &clip) override
{
bool const gui_view_rect_up_to_date =
_gui_view_rect.p1() == geometry().p1() &&
_gui_view_rect.p2() == geometry().p2();
bool const gui_view_rect_up_to_date = (_gui_view_rect == geometry())
&& (_clip == clip);
if (!_gui_views_up_to_date || !gui_view_rect_up_to_date) {
@ -448,20 +451,21 @@ class Decorator::Window : public Window_base, public Animator::Item
/* update view positions */
Rect::Cut_remainder const r = outer.cut(inner);
_content_view.place(inner, Point(0, 0));
_top_view .place(r.top, Point(0, 0));
_left_view .place(r.left, Point(0, -r.top.h()));
_right_view .place(r.right, Point(-r.right.w(), -r.top.h()));
_bottom_view .place(r.bottom, Point(0, -theme_size.h + r.bottom.h()));
_content_view.place(clip, inner, Point(0, 0));
_top_view .place(clip, r.top, Point(0, 0));
_left_view .place(clip, r.left, Point(0, -r.top.h()));
_right_view .place(clip, r.right, Point(-r.right.w(), -r.top.h()));
_bottom_view .place(clip, r.bottom, Point(0, -theme_size.h + r.bottom.h()));
_gui.execute();
_gui_view_rect = inner;
_gui_views_up_to_date = true;
_clip = clip;
}
}
void draw(Canvas_base &, Rect, Draw_behind_fn const &) const override { }
void draw(Canvas_base &, Ref const &, Rect, Draw_behind_fn const &) const override { }
void adapt_to_changed_config()
{

View File

@ -90,11 +90,24 @@ class Window_layouter::Assign_list : Noncopyable
return result;
}
template <typename FN>
void for_each(FN const &fn) { _assignments.for_each(fn); }
void for_each(auto const &fn) { _assignments.for_each(fn); }
void for_each(auto const &fn) const { _assignments.for_each(fn); }
template <typename FN>
void for_each(FN const &fn) const { _assignments.for_each(fn); }
void for_each_visible(auto const &target_name, auto const &fn) const
{
for_each([&] (Assign const &assign) {
if (assign.visible() && target_name == assign.target_name())
fn(assign); });
}
bool target_empty(auto const &target_name) const
{
bool result = true;
for_each_visible(target_name, [&] (Assign const &assign) {
assign.for_each_member([&] (Assign::Member const &) {
result = false; }); });
return result;
}
};
#endif /* _ASSIGN_LIST_H_ */

View File

@ -146,29 +146,28 @@ class Window_layouter::Target_list
if (target.layer() >= min_layer && target.layer() <= layer)
layer = target.layer(); });
/* visit all windows on the layer */
assignments.for_each([&] (Assign const &assign) {
/* search target by name */
_targets.for_each([&] (Target const &target) {
if (!assign.visible())
if (target.layer() != layer)
return;
Target::Name const target_name = assign.target_name();
if (!target.visible())
return;
/* search target by name */
_targets.for_each([&] (Target const &target) {
if (assignments.target_empty(target.name()))
return;
if (target.name() != target_name)
return;
Rect const boundary = target.geometry();
xml.node("boundary", [&] {
xml.attribute("name", target.name());
generate(xml, boundary);
if (target.layer() != layer)
return;
if (!target.visible())
return;
/* found target area, iterate though all assigned windows */
assign.for_each_member([&] (Assign::Member const &member) {
member.window.generate(xml, target.geometry()); });
/* visit all windows on the layer */
assignments.for_each_visible(target.name(), [&] (Assign const &assign) {
assign.for_each_member([&] (Assign::Member const &member) {
member.window.generate(xml, boundary); });
});
});
});

View File

@ -78,6 +78,14 @@ namespace Window_layouter {
from.for_each_sub_node([&] (Xml_node const &sub_node) {
copy_node(xml, sub_node, { max_depth.value - 1 }); }); });
}
static void generate(Xml_generator &xml, Rect const &rect)
{
xml.attribute("xpos", rect.x1());
xml.attribute("ypos", rect.y1());
xml.attribute("width", rect.w());
xml.attribute("height", rect.h());
}
}
#endif /* _TYPES_H_ */

View File

@ -64,6 +64,11 @@ struct Wm::Decorator_gui_session : Session_object<Gui::Session>,
Window_registry::Id win_id;
Rect geometry { };
Point offset { };
Rect content_geometry() const { return { geometry.p1() + offset, geometry.area }; }
Content_view_ref(Window_registry::Id win_id, Gui::View_ids &ids, View_id id)
: id(*this, ids, id), win_id(win_id) { }
};
@ -152,35 +157,33 @@ struct Wm::Decorator_gui_session : Session_object<Gui::Session>,
void _execute_command(Command const &cmd)
{
/*
* If the content view changes position, propagate the new position to
* the GUI service to properly transform absolute input coordinates.
*/
auto with_content_view_ref = [&] (View_id id, auto const &fn)
{
_content_view_ids.apply<Content_view_ref>(id,
[&] (Content_view_ref &ref) {
Rect const orig = ref.content_geometry();
fn(ref);
if (orig != ref.content_geometry())
_content_callback.content_geometry(ref.win_id,
ref.content_geometry()); },
[&] { });
};
switch (cmd.opcode) {
case Command::GEOMETRY:
/*
* If the content view changes position, propagate the new position
* to the GUI service to properly transform absolute input
* coordinates.
*/
_content_view_ids.apply<Content_view_ref const>(cmd.geometry.view,
[&] (Content_view_ref const &view_ref) {
_content_callback.content_geometry(view_ref.win_id, cmd.geometry.rect); },
[&] { });
with_content_view_ref(cmd.geometry.view, [&] (Content_view_ref &view_ref) {
view_ref.geometry = cmd.geometry.rect; });
/* forward command */
_real_gui.enqueue(cmd);
return;
case Command::OFFSET:
/*
* If non-content views change their offset (if the lookup
* fails), propagate the event
*/
_content_view_ids.apply<Content_view_ref const>(cmd.geometry.view,
[&] (Content_view_ref const &) { },
[&] { _real_gui.enqueue(cmd); });
return;
case Command::FRONT:
case Command::BACK:
case Command::FRONT_OF:
@ -192,7 +195,14 @@ struct Wm::Decorator_gui_session : Session_object<Gui::Session>,
_real_gui.execute();
_content_callback.update_content_child_views(view_ref.win_id); },
[&] { });
return;
case Command::OFFSET:
with_content_view_ref(cmd.offset.view, [&] (Content_view_ref &view_ref) {
view_ref.offset = cmd.offset.offset; });
_real_gui.enqueue(cmd);
return;
case Command::TITLE:

View File

@ -45,30 +45,34 @@ struct Param
};
void report_window_layout(Param param, Genode::Reporter &reporter)
void report_window_layout(Param param, Genode::Expanding_reporter &reporter)
{
float w = 1024;
float h = 768;
Genode::Reporter::Xml_generator xml(reporter, [&] ()
{
for (unsigned i = 1; i <= 10; i++) {
reporter.generate([&] (Genode::Xml_generator &xml) {
xml.node("window", [&] ()
{
xml.attribute("id", i);
xml.attribute("xpos", (long)(w * (0.25 + sin(param.angle[0])/5)));
xml.attribute("ypos", (long)(h * (0.25 + sin(param.angle[1])/5)));
xml.attribute("width", (long)(w * (0.25 + sin(param.angle[2])/5)));
xml.attribute("height", (long)(h * (0.25 + sin(param.angle[3])/5)));
xml.node("boundary", [&] {
xml.attribute("width", unsigned(w));
xml.attribute("height", unsigned(h));
if (i == 2)
xml.attribute("focused", "yes");
});
for (unsigned i = 1; i <= 10; i++) {
param = param + Param(2.2, 3.3, 4.4, 5.5);
}
xml.node("window", [&] {
xml.attribute("id", i);
xml.attribute("xpos", (long)(w * (0.25 + sin(param.angle[0])/5)));
xml.attribute("ypos", (long)(h * (0.25 + sin(param.angle[1])/5)));
xml.attribute("width", (long)(w * (0.25 + sin(param.angle[2])/5)));
xml.attribute("height", (long)(h * (0.25 + sin(param.angle[3])/5)));
if (i == 2)
xml.attribute("focused", "yes");
});
param = param + Param(2.2, 3.3, 4.4, 5.5);
}
});
});
}
@ -79,7 +83,8 @@ struct Main
Param _param { 0, 1, 2, 3 };
Genode::Reporter _window_layout_reporter { _env, "window_layout", "window_layout", 10*4096 };
Genode::Expanding_reporter _window_layout_reporter {
_env, "window_layout", "window_layout" };
Timer::Connection _timer { _env };
@ -95,7 +100,6 @@ struct Main
Main(Genode::Env &env) : _env(env)
{
_window_layout_reporter.enabled(true);
_timer.sigh(_timer_handler);
_timer.trigger_periodic(10*1000);
}

View File

@ -27,16 +27,12 @@
namespace Decorator {
using Point = Genode::Surface_base::Point;
using Area = Genode::Surface_base::Area;
using Rect = Genode::Surface_base::Rect;
using Dirty_rect = Genode::Dirty_rect<Rect, 3>;
using namespace Genode;
using Genode::size_t;
using Genode::Color;
using Genode::Xml_node;
using Genode::List_model;
using Genode::Interface;
using Point = Surface_base::Point;
using Area = Surface_base::Area;
using Rect = Surface_base::Rect;
using Dirty_rect = Genode::Dirty_rect<Rect, 3>;
}
#endif /* _INCLUDE__DECORATOR__TYPES_H_ */

View File

@ -20,6 +20,8 @@
#include <util/list_model.h>
#include <util/reconstructible.h>
#include <gui_session/client.h>
#include <base/registry.h>
#include <base/id_space.h>
/* decorator includes */
#include <decorator/types.h>
@ -30,15 +32,49 @@ namespace Decorator {
class Canvas_base;
class Window_base;
using Abandoned_windows = Genode::List<Genode::List_element<Window_base> >;
using Windows = Id_space<Window_base>;
using Abandoned_windows = Registry<Window_base>;
using Reversed_windows = Genode::List<Genode::List_element<Window_base> >;
}
class Decorator::Window_base : private Genode::List_model<Window_base>::Element
class Decorator::Window_base : private Windows::Element
{
public:
using Windows::Element::id;
struct Ref;
using Refs = List_model<Ref>;
/**
* Reference to a window
*
* The 'Ref' type decouples the lifetime of window objects from
* the lifetimes of their surrounding boundaries. If a window
* moves from one boundary to another, the old 'Ref' vanishes and
* a new 'Ref' is created but the window object stays intact.
*/
struct Ref : Refs::Element
{
Window_base &window;
Registry<Ref>::Element _registered;
inline Ref(Window_base &);
/**
* List_model::Element
*/
inline bool matches(Xml_node const &) const;
/**
* List_model::Element
*/
static bool type_matches(Xml_node const &) { return true; }
};
struct Border
{
unsigned top, left, right, bottom;
@ -59,7 +95,7 @@ class Decorator::Window_base : private Genode::List_model<Window_base>::Element
maximizer = false,
unmaximizer = false;
unsigned window_id = 0;
Windows::Id window_id { };
bool operator != (Hover const &other) const
{
@ -84,7 +120,7 @@ class Decorator::Window_base : private Genode::List_model<Window_base>::Element
*/
struct Draw_behind_fn : Interface
{
virtual void draw_behind(Canvas_base &, Window_base const &, Rect) const = 0;
virtual void draw_behind(Canvas_base &, Ref const &, Rect) const = 0;
};
private:
@ -93,16 +129,13 @@ class Decorator::Window_base : private Genode::List_model<Window_base>::Element
friend class Genode::List_model<Window_base>;
friend class Genode::List<Window_base>;
Registry<Ref> _refs { };
/*
* Geometry of content
*/
Rect _geometry { };
/*
* Unique window ID
*/
unsigned const _id;
bool _stacked = false;
/*
@ -110,30 +143,42 @@ class Decorator::Window_base : private Genode::List_model<Window_base>::Element
*/
Genode::Constructible<Gui::View_id> _neighbor { };
Genode::List_element<Window_base> _abandoned { this };
Constructible<Abandoned_windows::Element> _abandoned { };
Genode::List_element<Window_base> _reversed { this };
public:
Window_base(unsigned id) : _id(id) { }
Window_base(Windows &windows, Windows::Id id)
:
Windows::Element(*this, windows, id)
{ }
virtual ~Window_base() { }
void abandon(Abandoned_windows &abandoned_windows)
bool referenced() const
{
abandoned_windows.insert(&_abandoned);
bool result = false;
_refs.for_each([&] (Ref const &) { result = true; });
return result;
}
void consider_as_abandoned(Abandoned_windows &registry)
{
_abandoned.construct(registry, *this);
}
/**
* Revert 'consider_as_abandoned' after window was temporarily not referenced
*/
void dont_abandon() { _abandoned.destruct(); }
void prepend_to_reverse_list(Reversed_windows &window_list)
{
window_list.insert(&_reversed);
}
using List_model<Window_base>::Element::next;
unsigned id() const { return _id; }
Rect geometry() const { return _geometry; }
Rect geometry() const { return _geometry; }
void stacking_neighbor(Gui::View_id neighbor)
{
@ -169,11 +214,8 @@ class Decorator::Window_base : private Genode::List_model<Window_base>::Element
/**
* Draw window elements
*
* \param canvas graphics back end
* \param clip clipping area to apply
*/
virtual void draw(Canvas_base &canvas, Rect clip, Draw_behind_fn const &) const = 0;
virtual void draw(Canvas_base &, Ref const &, Rect clip, Draw_behind_fn const &) const = 0;
/**
* Update internal window representation from XML model
@ -187,7 +229,9 @@ class Decorator::Window_base : private Genode::List_model<Window_base>::Element
*/
virtual bool update(Xml_node window_node) = 0;
virtual void update_gui_views() { }
struct Clip : Rect { };
virtual void update_gui_views(Clip const &) { }
/**
* Report information about element at specified position
@ -207,7 +251,7 @@ class Decorator::Window_base : private Genode::List_model<Window_base>::Element
*/
bool matches(Xml_node const &node) const
{
return _id == node.attribute_value("id", ~0UL);
return id() == Windows::Id { node.attribute_value("id", ~0UL) };
}
/**
@ -216,4 +260,16 @@ class Decorator::Window_base : private Genode::List_model<Window_base>::Element
static bool type_matches(Xml_node const &) { return true; }
};
Decorator::Window_base::Ref::Ref(Window_base &window)
:
window(window), _registered(window._refs, *this)
{ }
bool Decorator::Window_base::Ref::matches(Xml_node const &node) const
{
return window.id() == Windows::Id { node.attribute_value("id", ~0UL) };
}
#endif /* _INCLUDE__DECORATOR__WINDOW_H_ */

View File

@ -24,8 +24,13 @@ namespace Decorator {
struct Decorator::Window_factory_base : Interface
{
virtual Window_base *create (Xml_node) = 0;
virtual void destroy (Window_base *) = 0;
using Win_ref = Window_base::Ref;
virtual Win_ref &create_ref(Xml_node const &) = 0;
virtual void destroy_ref(Win_ref &) = 0;
virtual void destroy_window(Window_base &) = 0;
};
#endif /* _INCLUDE__DECORATOR__WINDOW_FACTORY_H_ */

View File

@ -17,6 +17,7 @@
/* Genode includes */
#include <gui_session/gui_session.h>
#include <base/log.h>
#include <base/allocator.h>
/* local includes */
#include <decorator/types.h>
@ -31,13 +32,102 @@ 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 { };
struct Boundary;
using Boundaries = List_model<Boundary>;
unsigned long _front_most_id = ~0UL;
using Abandoned_boundaries = Registry<Boundary>;
inline void _draw_rec(Canvas_base &canvas, Window_base const *win,
using Win_ref = Window_base::Ref;
struct Boundary : Boundaries::Element
{
using Name = Genode::String<64>;
Name const _name;
Constructible<Registry<Boundary>::Element> _abandoned { };
Rect rect { };
List_model<Win_ref> win_refs { };
Boundary(Name const &name) : _name(name) { }
void update(Window_factory_base &factory,
Abandoned_windows &abandoned_windows,
Dirty_rect &dirty_rect,
Xml_node const &boundary)
{
rect = Rect::from_xml(boundary);
win_refs.update_from_xml(boundary,
[&] (Xml_node const &node) -> Win_ref & {
return factory.create_ref(node); },
[&] (Win_ref &ref) {
Window_base &window = ref.window;
factory.destroy_ref(ref);
if (!window.referenced())
window.consider_as_abandoned(abandoned_windows);
},
[&] (Win_ref &ref, Xml_node const &node) {
Rect const orig_geometry = ref.window.outer_geometry();
if (ref.window.update(node)) {
dirty_rect.mark_as_dirty(orig_geometry);
dirty_rect.mark_as_dirty(ref.window.outer_geometry());
}
}
);
}
void abandon(Registry<Boundary> &registry)
{
_abandoned.construct(registry, *this);
}
static Name name(Xml_node const &node)
{
return node.attribute_value("name", Name());
}
/**
* List_model::Element
*/
bool matches(Xml_node const &node) const
{
return _name == name(node);
}
/**
* List_model::Element
*/
static bool type_matches(Xml_node const &node)
{
return node.has_type("boundary");
}
/**
* Generate window list in reverse order
*/
Reversed_windows reversed_window_list()
{
Reversed_windows reversed { };
win_refs.for_each([&] (Win_ref &ref) {
ref.window.prepend_to_reverse_list(reversed); });
return reversed;
}
};
Boundaries _boundaries { };
Window_factory_base &_window_factory;
Allocator &_alloc;
Dirty_rect mutable _dirty_rect { };
Windows::Id _front_most_id { ~0UL };
inline void _draw_rec(Canvas_base &canvas, Win_ref const *,
Rect rect) const;
static inline
@ -54,33 +144,38 @@ class Decorator::Window_stack : public Window_base::Draw_behind_fn
throw Xml_node::Nonexistent_sub_node();
}
/**
* Generate window list in reverse order
*/
Reversed_windows _reversed_window_list()
void _for_each_window_const(auto const &fn) const
{
Reversed_windows reversed { };
_windows.for_each([&] (Window_base &window) {
window.prepend_to_reverse_list(reversed); });
return reversed;
_boundaries.for_each([&] (Boundary const &boundary) {
boundary.win_refs.for_each([&] (Win_ref const &ref) {
fn(ref.window); }); });
}
public:
Window_stack(Window_factory_base &window_factory)
Window_stack(Window_factory_base &window_factory, Allocator &alloc)
:
_window_factory(window_factory)
_window_factory(window_factory), _alloc(alloc)
{ }
void mark_as_dirty(Rect rect) { _dirty_rect.mark_as_dirty(rect); }
void for_each_window(auto const &fn)
{
_boundaries.for_each([&] (Boundary &boundary) {
boundary.win_refs.for_each([&] (Win_ref &ref) {
fn(ref.window); }); });
}
Dirty_rect draw(Canvas_base &canvas) const
{
Dirty_rect result = _dirty_rect;
_dirty_rect.flush([&] (Rect const &rect) {
_windows.with_first([&] (Window_base const &first) {
_draw_rec(canvas, &first, rect); }); });
_boundaries.for_each([&] (Boundary const &boundary) {
Rect const clipped = Rect::intersect(rect, boundary.rect);
boundary.win_refs.with_first([&] (Win_ref const &first) {
_draw_rec(canvas, &first, clipped); }); }); });
return result;
}
@ -91,8 +186,7 @@ class Decorator::Window_stack : public Window_base::Draw_behind_fn
{
bool redraw_needed = false;
_windows.for_each([&] (Window_base const &win) {
_for_each_window_const([&] (Window_base const &win) {
if (win.animated()) {
_dirty_rect.mark_as_dirty(win.outer_geometry());
redraw_needed = true;
@ -101,41 +195,37 @@ 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.
*/
void for_each_window(auto const &fn) { _windows.for_each(fn); }
void update_gui_views()
{
/*
* Update GUI 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();
_boundaries.for_each([&] (Boundary &boundary) {
while (Genode::List_element<Window_base> *win = reversed.first()) {
win->object()->update_gui_views();
reversed.remove(win);
}
/*
* Update GUI 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 = boundary.reversed_window_list();
while (Genode::List_element<Window_base> *win = reversed.first()) {
win->object()->update_gui_views({ boundary.rect });
reversed.remove(win);
}
});
}
Window_base::Hover hover(Point pos) const
{
Window_base::Hover result { };
_windows.for_each([&] (Window_base const &win) {
_for_each_window_const([&] (Window_base const &win) {
if (!result.window_id && win.outer_geometry().contains(pos)) {
if (!result.window_id.value && win.outer_geometry().contains(pos)) {
Window_base::Hover const hover = win.hover(pos);
if (hover.window_id != 0)
if (hover.window_id.value != 0)
result = hover;
}
});
@ -148,28 +238,27 @@ class Decorator::Window_stack : public Window_base::Draw_behind_fn
** Window::Draw_behind_fn interface **
**************************************/
void draw_behind(Canvas_base &canvas, Window_base const &window, Rect clip) const override
void draw_behind(Canvas_base &canvas, Win_ref const &ref, Rect clip) const override
{
_draw_rec(canvas, window.next(), clip);
_draw_rec(canvas, ref.next(), clip);
}
};
void Decorator::Window_stack::_draw_rec(Decorator::Canvas_base &canvas,
Decorator::Window_base const *win,
Decorator::Rect rect) const
void Decorator::Window_stack::_draw_rec(Canvas_base &canvas,
Win_ref const *ref, 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();
for ( ; ref && !(clipped = Rect::intersect(ref->window.outer_geometry(), rect)).valid(); )
ref = ref->next();
/* check if we hit the bottom of the window stack */
if (!win) return;
if (!ref) return;
/* draw areas around the current window */
if (Window_base const * const next = win->next()) {
if (Window_base::Ref const * const next = ref->next()) {
Rect::Cut_remainder const r = rect.cut(clipped);
@ -180,50 +269,41 @@ void Decorator::Window_stack::_draw_rec(Decorator::Canvas_base &canvas,
}
/* draw current window */
win->draw(canvas, clipped, *this);
ref->window.draw(canvas, *ref, clipped, *this);
}
void Decorator::Window_stack::update_model(Genode::Xml_node root_node,
auto const &flush_window_stack_changes_fn)
{
Abandoned_windows _abandoned_windows { };
Abandoned_boundaries abandoned_boundaries { };
Abandoned_windows abandoned_windows { };
_windows.update_from_xml(root_node,
_boundaries.update_from_xml(root_node,
[&] (Xml_node const &node) -> Window_base & {
return *_window_factory.create(node); },
[&] (Xml_node const &node) -> Boundary & {
return *new (_alloc) Boundary(Boundary::name(node)); },
[&] (Window_base &window) { window.abandon(_abandoned_windows); },
[&] (Boundary &boundary) {
boundary.update(_window_factory, abandoned_windows, _dirty_rect,
Xml_node("<empty/>"));
boundary.abandon(abandoned_boundaries);
},
[&] (Window_base &window, Xml_node const &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());
}
}
[&] (Boundary &boundary, Xml_node const &node) {
boundary.update(_window_factory, abandoned_windows, _dirty_rect, 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);
Windows::Id new_front_most_id { ~0UL };
/*
* Propagate changed stacking order to the GUI server
*
* 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();
_boundaries.with_first([&] (Boundary const &boundary) {
boundary.win_refs.with_first([&] (Window_base::Ref const &ref) {
new_front_most_id = ref.window.id(); }); });
/* 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); };
return (new_front_most_id.value != _front_most_id.value)
&& (win.id() == new_front_most_id); };
auto stack_back_most_window = [&] (Window_base &window) {
@ -251,27 +331,40 @@ void Decorator::Window_stack::update_model(Genode::Xml_node root_node,
_dirty_rect.mark_as_dirty(window.outer_geometry());
};
if (Genode::List_element<Window_base> *back_most = reversed.first()) {
_boundaries.for_each([&] (Boundary &boundary) {
/* handle back-most window */
reversed.remove(back_most);
Window_base &window = *back_most->object();
stack_back_most_window(window);
window.forget_neighbor();
/*
* Propagate changed stacking order to the GUI server
*
* 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 = boundary.reversed_window_list();
Window_base *neighbor = &window;
if (Genode::List_element<Window_base> *back_most = reversed.first()) {
/* check consistency between window list order and view stacking */
while (Genode::List_element<Window_base> *elem = reversed.first()) {
/* handle back-most window */
reversed.remove(back_most);
Window_base &window = *back_most->object();
stack_back_most_window(window);
window.forget_neighbor();
reversed.remove(elem);
Window_base *neighbor = &window;
Window_base &window = *elem->object();
stack_window(window, *neighbor);
window.stacking_neighbor(neighbor->frontmost_view());
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
@ -280,7 +373,7 @@ void Decorator::Window_stack::update_model(Genode::Xml_node root_node,
flush_window_stack_changes_fn();
/*
* Destroy abandoned window objects
* Destroy abandoned window and boundary 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
@ -289,12 +382,19 @@ void Decorator::Window_stack::update_model(Genode::Xml_node root_node,
* 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());
}
abandoned_boundaries.for_each([&] (Boundary &boundary) {
destroy(_alloc, &boundary); });
abandoned_windows.for_each([&] (Window_base &window) {
if (window.referenced()) {
window.dont_abandon();
} else {
Rect const rect = window.outer_geometry();
_window_factory.destroy_window(window);
mark_as_dirty(rect);
}
});
_front_most_id = new_front_most_id;
}