From b7c97a61c01102fc5310c04a9b405a6760523718 Mon Sep 17 00:00:00 2001 From: Norman Feske Date: Fri, 15 Nov 2024 13:13:32 +0100 Subject: [PATCH] wm/decorator/layouter: window clipping This patch changes the window-layout format to support the rectangular clipping of windows at screen boundaries. The new 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". 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 nodes, this is no longer the case. Therefore, the Sculpt manager generates a dedicated resize-request ROM now. Issue #5390 --- repos/gems/run/decorator.run | 102 +++--- repos/gems/sculpt/leitzentrale/default | 2 +- repos/gems/src/app/decorator/main.cc | 39 ++- repos/gems/src/app/decorator/window.cc | 7 +- repos/gems/src/app/decorator/window.h | 39 ++- repos/gems/src/app/sculpt_manager/main.cc | 42 ++- repos/gems/src/app/themed_decorator/main.cc | 40 ++- repos/gems/src/app/themed_decorator/theme.cc | 4 +- repos/gems/src/app/themed_decorator/theme.h | 5 +- repos/gems/src/app/themed_decorator/window.h | 38 ++- .../src/app/window_layouter/assign_list.h | 21 +- .../src/app/window_layouter/target_list.h | 33 +- repos/gems/src/app/window_layouter/types.h | 8 + repos/gems/src/server/wm/decorator_gui.h | 50 +-- repos/gems/src/test/decorator_stress/main.cc | 46 +-- repos/os/include/decorator/types.h | 14 +- repos/os/include/decorator/window.h | 102 ++++-- repos/os/include/decorator/window_factory.h | 9 +- repos/os/include/decorator/window_stack.h | 304 ++++++++++++------ 19 files changed, 591 insertions(+), 314 deletions(-) diff --git a/repos/gems/run/decorator.run b/repos/gems/run/decorator.run index 0366a904d79..6f3025733eb 100644 --- a/repos/gems/run/decorator.run +++ b/repos/gems/run/decorator.run @@ -77,79 +77,93 @@ install_config { - + + + - - + + + + - - - + + + + + - - - + + + + + - - - + + + + + - - - + + + + + - - - + + + + + @@ -160,7 +174,7 @@ install_config { - + diff --git a/repos/gems/sculpt/leitzentrale/default b/repos/gems/sculpt/leitzentrale/default index 39d841370c6..e4be530b530 100644 --- a/repos/gems/sculpt/leitzentrale/default +++ b/repos/gems/sculpt/leitzentrale/default @@ -114,7 +114,7 @@ - + diff --git a/repos/gems/src/app/decorator/main.cc b/repos/gems/src/app/decorator/main.cc index 7ec8beca583..84fe8a3a16b 100644 --- a/repos/gems/src/app/decorator/main.cc +++ b/repos/gems/src/app/decorator/main.cc @@ -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 + { + Windows::Id const id { window_node.attribute_value("id", 0U) }; + + Window_base *window_ptr = nullptr; + _windows.apply(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_ref(Window_base::Ref &ref) override { - return new (_heap) - Window(window_node.attribute_value("id", 0U), - _gui, _animator, _decorator_config); + destroy(_heap, &ref); } /** * Window_factory_base interface */ - void destroy(Window_base *window) override + void destroy_window(Window_base &window) override { - Genode::destroy(_heap, static_cast(window)); + 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"); diff --git a/repos/gems/src/app/decorator/window.cc b/repos/gems/src/app/decorator/window.cc index c133b8b7d3f..d2b1e8db3b9 100644 --- a/repos/gems/src/app/decorator/window.cc +++ b/repos/gems/src/app/decorator/window.cc @@ -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)); diff --git a/repos/gems/src/app/decorator/window.h b/repos/gems/src/app/decorator/window.h index 393489d565a..5fd2753779f 100644 --- a/repos/gems/src/app/decorator/window.h +++ b/repos/gems/src/app/decorator/window.h @@ -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(id()); } - void place(Rect rect) + void place_as_decor(Clip const &clip, Rect rect) + { + Rect const intersection = Rect::intersect(clip, rect); + _gui.enqueue(id(), intersection); + _gui.enqueue(id(), Point() - intersection.at); + } + + void place_as_content(Clip const &clip, Rect rect) { - _gui.enqueue(id(), rect); - Point offset = Point(0, 0) - rect.at; - _gui.enqueue(id(), offset); + Rect const intersection = Rect::intersect(clip, rect); + _gui.enqueue(id(), intersection); + _gui.enqueue(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; diff --git a/repos/gems/src/app/sculpt_manager/main.cc b/repos/gems/src/app/sculpt_manager/main.cc index 3d2f0d25b52..c0ff8b8154d 100644 --- a/repos/gems/src/app/sculpt_manager/main.cc +++ b/repos/gems/src/app/sculpt_manager/main.cc @@ -1621,8 +1621,9 @@ struct Sculpt::Main : Input_event_handler, Rom_handler
_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 void _with_window(Xml_node window_list, String 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 diff --git a/repos/gems/src/app/themed_decorator/main.cc b/repos/gems/src/app/themed_decorator/main.cc index edf44d3bca6..57c84d650c6 100644 --- a/repos/gems/src/app/themed_decorator/main.cc +++ b/repos/gems/src/app/themed_decorator/main.cc @@ -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 + { + Windows::Id const id { window_node.attribute_value("id", 0U) }; + + Window_base *window_ptr = nullptr; + _windows.apply(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_ref(Window_base::Ref &ref) override { - return new (_heap) - Window(_env, window_node.attribute_value("id", 0U), - _gui, _animator, _theme, _decorator_config); + destroy(_heap, &ref); } /** * Window_factory_base interface */ - void destroy(Window_base *window) override + void destroy_window(Window_base &window) override { - Genode::destroy(_heap, static_cast(window)); + 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"); diff --git a/repos/gems/src/app/themed_decorator/theme.cc b/repos/gems/src/app/themed_decorator/theme.cc index 3570c4efbd8..807e1940cc9 100644 --- a/repos/gems/src/app/themed_decorator/theme.cc +++ b/repos/gems/src/app/themed_decorator/theme.cc @@ -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); diff --git a/repos/gems/src/app/themed_decorator/theme.h b/repos/gems/src/app/themed_decorator/theme.h index 32988982f6b..600ea1c0cc8 100644 --- a/repos/gems/src/app/themed_decorator/theme.h +++ b/repos/gems/src/app/themed_decorator/theme.h @@ -19,6 +19,7 @@ #include #include #include +#include namespace Decorator { @@ -29,10 +30,6 @@ namespace Decorator { using Pixel_surface = Genode::Surface; using Alpha_surface = Genode::Surface; - - using Area = Genode::Surface_base::Area; - using Point = Genode::Surface_base::Point; - using Rect = Genode::Surface_base::Rect; } diff --git a/repos/gems/src/app/themed_decorator/window.h b/repos/gems/src/app/themed_decorator/window.h index 7abdf8fb027..b82cb70e408 100644 --- a/repos/gems/src/app/themed_decorator/window.h +++ b/repos/gems/src/app/themed_decorator/window.h @@ -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(id()); } - void place(Rect rect, Point offset) + void place(Clip const &clip, Rect rect, Point offset) { - _gui.enqueue(id(), rect); - _gui.enqueue(id(), offset); + Rect const intersection = Rect::intersect(clip, rect); + _gui.enqueue(id(), intersection); + _gui.enqueue(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() { diff --git a/repos/gems/src/app/window_layouter/assign_list.h b/repos/gems/src/app/window_layouter/assign_list.h index 8d4ed67dbbe..7ad88647f00 100644 --- a/repos/gems/src/app/window_layouter/assign_list.h +++ b/repos/gems/src/app/window_layouter/assign_list.h @@ -90,11 +90,24 @@ class Window_layouter::Assign_list : Noncopyable return result; } - template - 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 - 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_ */ diff --git a/repos/gems/src/app/window_layouter/target_list.h b/repos/gems/src/app/window_layouter/target_list.h index 69a5b72893c..b65fa2d4763 100644 --- a/repos/gems/src/app/window_layouter/target_list.h +++ b/repos/gems/src/app/window_layouter/target_list.h @@ -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(); - - /* search target by name */ - _targets.for_each([&] (Target const &target) { - - if (target.name() != target_name) - return; + if (!target.visible()) + return; - if (target.layer() != layer) - return; + if (assignments.target_empty(target.name())) + return; - if (!target.visible()) - return; + Rect const boundary = target.geometry(); + xml.node("boundary", [&] { + xml.attribute("name", target.name()); + generate(xml, boundary); - /* 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); }); + }); }); }); diff --git a/repos/gems/src/app/window_layouter/types.h b/repos/gems/src/app/window_layouter/types.h index 43728ac1e02..d647949928d 100644 --- a/repos/gems/src/app/window_layouter/types.h +++ b/repos/gems/src/app/window_layouter/types.h @@ -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_ */ diff --git a/repos/gems/src/server/wm/decorator_gui.h b/repos/gems/src/server/wm/decorator_gui.h index 067606c2dce..e59ef3688fc 100644 --- a/repos/gems/src/server/wm/decorator_gui.h +++ b/repos/gems/src/server/wm/decorator_gui.h @@ -64,6 +64,11 @@ struct Wm::Decorator_gui_session : Session_object, 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, 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(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(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(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, _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: diff --git a/repos/gems/src/test/decorator_stress/main.cc b/repos/gems/src/test/decorator_stress/main.cc index 3f129d0b0a6..289a0a70c39 100644 --- a/repos/gems/src/test/decorator_stress/main.cc +++ b/repos/gems/src/test/decorator_stress/main.cc @@ -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++) { - - 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); - } + reporter.generate([&] (Genode::Xml_generator &xml) { + + xml.node("boundary", [&] { + xml.attribute("width", unsigned(w)); + xml.attribute("height", unsigned(h)); + + for (unsigned i = 1; i <= 10; i++) { + + 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); } diff --git a/repos/os/include/decorator/types.h b/repos/os/include/decorator/types.h index cd5c0c6a34c..ad9df150fcc 100644 --- a/repos/os/include/decorator/types.h +++ b/repos/os/include/decorator/types.h @@ -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; + 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; } #endif /* _INCLUDE__DECORATOR__TYPES_H_ */ diff --git a/repos/os/include/decorator/window.h b/repos/os/include/decorator/window.h index bc097c9de80..97461635747 100644 --- a/repos/os/include/decorator/window.h +++ b/repos/os/include/decorator/window.h @@ -20,6 +20,8 @@ #include #include #include +#include +#include /* decorator includes */ #include @@ -30,15 +32,49 @@ namespace Decorator { class Canvas_base; class Window_base; - using Abandoned_windows = Genode::List >; + using Windows = Id_space; + using Abandoned_windows = Registry; using Reversed_windows = Genode::List >; } -class Decorator::Window_base : private Genode::List_model::Element +class Decorator::Window_base : private Windows::Element { public: + using Windows::Element::id; + + struct Ref; + + using Refs = List_model; + + /** + * 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::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::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::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::Element friend class Genode::List_model; friend class Genode::List; + Registry _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::Element */ Genode::Constructible _neighbor { }; - Genode::List_element _abandoned { this }; + Constructible _abandoned { }; Genode::List_element _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 + { + bool result = false; + _refs.for_each([&] (Ref const &) { result = true; }); + return result; + } + + void consider_as_abandoned(Abandoned_windows ®istry) { - abandoned_windows.insert(&_abandoned); + _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::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::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::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::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::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_ */ diff --git a/repos/os/include/decorator/window_factory.h b/repos/os/include/decorator/window_factory.h index 967bba4003e..a64ef456729 100644 --- a/repos/os/include/decorator/window_factory.h +++ b/repos/os/include/decorator/window_factory.h @@ -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_ */ diff --git a/repos/os/include/decorator/window_stack.h b/repos/os/include/decorator/window_stack.h index e305a84d493..99c0a683010 100644 --- a/repos/os/include/decorator/window_stack.h +++ b/repos/os/include/decorator/window_stack.h @@ -17,6 +17,7 @@ /* Genode includes */ #include #include +#include /* local includes */ #include @@ -31,13 +32,102 @@ class Decorator::Window_stack : public Window_base::Draw_behind_fn { private: - List_model _windows { }; - Window_factory_base &_window_factory; - Dirty_rect mutable _dirty_rect { }; + struct Boundary; + using Boundaries = List_model; - unsigned long _front_most_id = ~0UL; + using Abandoned_boundaries = Registry; - 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::Element> _abandoned { }; + + Rect rect { }; + + List_model 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 ®istry) + { + _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(); - - while (Genode::List_element *win = reversed.first()) { - win->object()->update_gui_views(); - reversed.remove(win); - } + _boundaries.for_each([&] (Boundary &boundary) { + + /* + * 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 *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("")); + 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 *back_most = reversed.first()) { + _boundaries.for_each([&] (Boundary &boundary) { + + /* + * 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(); + + if (Genode::List_element *back_most = reversed.first()) { - /* handle back-most window */ - reversed.remove(back_most); - Window_base &window = *back_most->object(); - stack_back_most_window(window); - window.forget_neighbor(); + /* handle back-most window */ + reversed.remove(back_most); + Window_base &window = *back_most->object(); + stack_back_most_window(window); + window.forget_neighbor(); - Window_base *neighbor = &window; + Window_base *neighbor = &window; - /* check consistency between window list order and view stacking */ - while (Genode::List_element *elem = reversed.first()) { + /* check consistency between window list order and view stacking */ + while (Genode::List_element *elem = reversed.first()) { - reversed.remove(elem); + reversed.remove(elem); - Window_base &window = *elem->object(); - stack_window(window, *neighbor); - window.stacking_neighbor(neighbor->frontmost_view()); - neighbor = &window; + 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 *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; }