From 37044eaad8423ae929d2af9e79a3d0b9c174c1f0 Mon Sep 17 00:00:00 2001 From: Norman Feske Date: Fri, 4 Dec 2015 16:17:22 +0100 Subject: [PATCH] window layouter: internal restructuring This patch splits the implementation of the window layouter into several headers to ease the upcoming addition of new functionality. --- .../src/app/floating_window_layouter/main.cc | 753 ++++-------------- .../app/floating_window_layouter/operations.h | 33 + .../src/app/floating_window_layouter/types.h | 54 ++ .../app/floating_window_layouter/user_state.h | 266 +++++++ .../src/app/floating_window_layouter/window.h | 334 ++++++++ 5 files changed, 851 insertions(+), 589 deletions(-) create mode 100644 repos/gems/src/app/floating_window_layouter/operations.h create mode 100644 repos/gems/src/app/floating_window_layouter/types.h create mode 100644 repos/gems/src/app/floating_window_layouter/user_state.h create mode 100644 repos/gems/src/app/floating_window_layouter/window.h diff --git a/repos/gems/src/app/floating_window_layouter/main.cc b/repos/gems/src/app/floating_window_layouter/main.cc index 97f558481..cfa4a08ad 100644 --- a/repos/gems/src/app/floating_window_layouter/main.cc +++ b/repos/gems/src/app/floating_window_layouter/main.cc @@ -24,30 +24,22 @@ #include #include +/* local includes */ +#include "window.h" +#include "user_state.h" +#include "operations.h" namespace Floating_window_layouter { - using namespace Genode; struct Main; - class Window; - typedef Decorator::Point Point; - typedef Decorator::Area Area; - typedef Decorator::Rect Rect; - - using Decorator::attribute; - using Decorator::string_attribute; - using Decorator::area_attribute; - using Decorator::point_attribute; - - - static Xml_node xml_lookup_window_by_id(Xml_node node, unsigned const id) + static Xml_node xml_lookup_window_by_id(Xml_node node, Window_id const id) { char const *tag = "window"; char const *id_attr = "id"; for (node = node.sub_node(tag); ; node = node.next(tag)) - if (attribute(node, id_attr, 0UL) == id) + if (attribute(node, id_attr, 0UL) == id.value) return node; throw Xml_node::Nonexistent_sub_node(); @@ -58,323 +50,21 @@ namespace Floating_window_layouter { * Return true if compound XML node contains a sub node with ID */ static bool xml_contains_window_node_with_id(Xml_node node, - unsigned const id) + Window_id const id) { - try { xml_lookup_window_by_id(node, id); return true; } + try { xml_lookup_window_by_id(node, id.value); return true; } catch (Xml_node::Nonexistent_sub_node) { return false; } } } -class Floating_window_layouter::Window : public List::Element -{ - public: - - typedef String<256> Title; - typedef String<256> Label; - - struct Element - { - enum Type { UNDEFINED, TITLE, LEFT, RIGHT, TOP, BOTTOM, - TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT, - CLOSER, MAXIMIZER, MINIMIZER }; - - Type type; - - char const *name() const - { - switch (type) { - case UNDEFINED: return ""; - case TITLE: return "title"; - case LEFT: return "left"; - case RIGHT: return "right"; - case TOP: return "top"; - case BOTTOM: return "bottom"; - case TOP_LEFT: return "top_left"; - case TOP_RIGHT: return "top_right"; - case BOTTOM_LEFT: return "bottom_left"; - case BOTTOM_RIGHT: return "bottom_right"; - case CLOSER: return "closer"; - case MAXIMIZER: return "maximizer"; - case MINIMIZER: return "minimizer"; - } - return ""; - } - - Element(Type type) : type(type) { } - - bool operator != (Element const &other) const { return other.type != type; } - bool operator == (Element const &other) const { return other.type == type; } - }; - - private: - - unsigned const _id = 0; - - Title _title; - - Label _label; - - Rect _geometry; - - /** - * Window geometry at the start of the current drag operation - */ - Rect _orig_geometry; - - /** - * Size as desired by the user during resize drag operations - */ - Area _requested_size; - - /** - * Backup of the original geometry while the window is maximized - */ - Rect _unmaximized_geometry; - - Rect const _maximized_geometry; - - /** - * Window may be partially transparent - */ - bool _has_alpha = false; - - /** - * Window is temporarily not visible - */ - bool _is_hidden = false; - - bool _is_resizeable = false; - - bool _is_maximized = false; - - /* - * Number of times the window has been topped. This value is used by - * the decorator to detect the need for bringing the window to the - * front of nitpicker's global view stack even if the stacking order - * stays the same within the decorator instance. This is important in - * the presence of more than a single decorator. - */ - unsigned _topped_cnt = 0; - - bool _drag_left_border = false; - bool _drag_right_border = false; - bool _drag_top_border = false; - bool _drag_bottom_border = false; - - public: - - Window(unsigned id, Rect maximized_geometry) - : - _id(id), _maximized_geometry(maximized_geometry) - { } - - bool has_id(unsigned id) const { return id == _id; } - - unsigned id() const { return _id; } - - void title(Title const &title) { _title = title; } - - void label(Label const &label) { _label = label; } - - void geometry(Rect geometry) { _geometry = geometry; } - - Point position() const { return _geometry.p1(); } - - void position(Point pos) { _geometry = Rect(pos, _geometry.area()); } - - void has_alpha(bool has_alpha) { _has_alpha = has_alpha; } - - void is_hidden(bool is_hidden) { _is_hidden = is_hidden; } - - void is_resizeable(bool is_resizeable) { _is_resizeable = is_resizeable; } - - bool label_matches(Label const &label) const { return label == _label; } - - /** - * Return true if user drags a window border - */ - bool _drag_border() const - { - return _drag_left_border || _drag_right_border - || _drag_top_border || _drag_bottom_border; - } - - /** - * Define window size - * - * This function is called when the window-list model changes. - */ - void size(Area size) - { - if (_is_maximized) { - _geometry = Rect(_maximized_geometry.p1(), size); - return; - } - - if (!_drag_border()) { - _geometry = Rect(_geometry.p1(), size); - return; - } - - Point p1 = _geometry.p1(), p2 = _geometry.p2(); - - if (_drag_left_border) - p1 = Point(p2.x() - size.w() + 1, p1.y()); - - if (_drag_right_border) - p2 = Point(p1.x() + size.w() - 1, p2.y()); - - if (_drag_top_border) - p1 = Point(p1.x(), p2.y() - size.h() + 1); - - if (_drag_bottom_border) - p2 = Point(p2.x(), p1.y() + size.h() - 1); - - _geometry = Rect(p1, p2); - } - - Area size() const { return _geometry.area(); } - - Area requested_size() const { return _requested_size; } - - void serialize(Xml_generator &xml, bool focused, Element highlight) - { - /* omit window from the layout if hidden */ - if (_is_hidden) - return; - - xml.node("window", [&]() { - - xml.attribute("id", _id); - - /* present concatenation of label and title in the window's title bar */ - { - bool const has_title = Genode::strlen(_title.string()) > 0; - - char buf[Label::capacity()]; - Genode::snprintf(buf, sizeof(buf), "%s%s%s", - _label.string(), - has_title ? " " : "", - _title.string()); - - xml.attribute("title", buf); - } - - xml.attribute("xpos", _geometry.x1()); - xml.attribute("ypos", _geometry.y1()); - xml.attribute("width", _geometry.w()); - xml.attribute("height", _geometry.h()); - xml.attribute("topped", _topped_cnt); - - if (focused) - xml.attribute("focused", "yes"); - - if (highlight.type != Element::UNDEFINED) { - xml.node("highlight", [&] () { - xml.node(highlight.name()); - }); - } - - if (_has_alpha) - xml.attribute("has_alpha", "yes"); - - if (_is_resizeable) { - xml.attribute("maximizer", "yes"); - xml.attribute("closer", "yes"); - } - }); - } - - /** - * Called when the user starts dragging a window element - */ - void initiate_drag_operation(Window::Element element) - { - _drag_left_border = (element.type == Window::Element::LEFT) - || (element.type == Window::Element::TOP_LEFT) - || (element.type == Window::Element::BOTTOM_LEFT); - - _drag_right_border = (element.type == Window::Element::RIGHT) - || (element.type == Window::Element::TOP_RIGHT) - || (element.type == Window::Element::BOTTOM_RIGHT); - - _drag_top_border = (element.type == Window::Element::TOP) - || (element.type == Window::Element::TOP_LEFT) - || (element.type == Window::Element::TOP_RIGHT); - - _drag_bottom_border = (element.type == Window::Element::BOTTOM) - || (element.type == Window::Element::BOTTOM_LEFT) - || (element.type == Window::Element::BOTTOM_RIGHT); - - _orig_geometry = _geometry; - - _requested_size = _geometry.area(); - } - - void apply_drag_operation(Point offset) - { - if (!_drag_border()) - position(_orig_geometry.p1() + offset); - - int requested_w = _orig_geometry.w(), - requested_h = _orig_geometry.h(); - - if (_drag_left_border) requested_w -= offset.x(); - if (_drag_right_border) requested_w += offset.x(); - if (_drag_top_border) requested_h -= offset.y(); - if (_drag_bottom_border) requested_h += offset.y(); - - _requested_size = Area(max(1, requested_w), max(1, requested_h)); - } - - void finalize_drag_operation() - { - _requested_size = _geometry.area(); - } - - void topped() { _topped_cnt++; } - - void close() { _requested_size = Area(0, 0); } - - bool is_maximized() const { return _is_maximized; } - - void is_maximized(bool is_maximized) - { - /* enter maximized state */ - if (!_is_maximized && is_maximized) { - _unmaximized_geometry = _geometry; - _requested_size = _maximized_geometry.area(); - } - - /* leave maximized state */ - if (_is_maximized && !is_maximized) { - _requested_size = _unmaximized_geometry.area(); - _geometry = Rect(_unmaximized_geometry.p1(), _geometry.area()); - } - - _is_maximized = is_maximized; - } -}; - - -struct Floating_window_layouter::Main +struct Floating_window_layouter::Main : Operations { Signal_receiver &sig_rec; List windows; - unsigned hovered_window_id = 0; - unsigned focused_window_id = 0; - unsigned key_cnt = 0; - - Window::Element hovered_element = Window::Element::UNDEFINED; - Window::Element hovered_element_now = Window::Element::UNDEFINED; - - bool drag_state = false; - bool drag_init_done = true; - - Window *lookup_window_by_id(unsigned id) + Window *lookup_window_by_id(Window_id const id) { for (Window *w = windows.first(); w; w = w->next()) if (w->has_id(id)) @@ -383,6 +73,119 @@ struct Floating_window_layouter::Main return nullptr; } + Window const *lookup_window_by_id(Window_id const id) const + { + for (Window const *w = windows.first(); w; w = w->next()) + if (w->has_id(id)) + return w; + + return nullptr; + } + + /** + * Apply functor to each window + * + * The functor is called with 'Window const &' as argument. + */ + template + void for_each_window(FUNC const &fn) const + { + for (Window const *w = windows.first(); w; w = w->next()) + fn(*w); + } + + User_state _user_state { *this }; + + + /************************** + ** Operations interface ** + **************************/ + + void close(Window_id id) override + { + Window *window = lookup_window_by_id(id); + if (!window) + return; + + window->close(); + generate_resize_request_model(); + generate_focus_model(); + } + + void to_front(Window_id id) override + { + Window *window = lookup_window_by_id(id); + if (!window) + return; + + if (window != windows.first()) { + windows.remove(window); + windows.insert(window); + window->topped(); + generate_window_layout_model(); + } + } + + void focus(Window_id id) override + { + generate_focus_model(); + } + + void toggle_fullscreen(Window_id id) override + { + /* make sure that the specified window is the front-most one */ + to_front(id); + + Window *window = lookup_window_by_id(id); + if (!window) + return; + + window->is_maximized(!window->is_maximized()); + + generate_resize_request_model(); + } + + void drag(Window_id id, Window::Element element, Point clicked, Point curr) override + { + to_front(id); + + Window *window = lookup_window_by_id(id); + if (!window) + return; + + /* + * The drag operation may result in a change of the window geometry. + * We detect such a change be comparing the original geometry with + * the geometry with the drag operation applied. + */ + Point const last_pos = window->position(); + Area const last_requested_size = window->requested_size(); + + window->drag(element, clicked, curr); + + if (last_pos != window->position()) + generate_window_layout_model(); + + if (last_requested_size != window->requested_size()) + generate_resize_request_model(); + } + + void finalize_drag(Window_id id, Window::Element, Point clicked, Point final) + { + Window *window = lookup_window_by_id(id); + if (!window) + return; + + window->finalize_drag_operation(); + + /* + * Update window layout because highlighting may have changed after the + * drag operation. E.g., if the window has not kept up with the + * dragging of a resize handle, the resize handle is no longer hovered. + */ + generate_window_layout_model(); + } + /** * Install handler for responding to window-list changes @@ -420,10 +223,16 @@ struct Floating_window_layouter::Main Attached_rom_dataspace hover { "hover" }; + /** * Install handler for responding to user input */ - void handle_input(unsigned); + void handle_input(unsigned) + { + while (input.is_pending()) + _user_state.handle_input(input_ds.local_addr(), + input.flush()); + } Signal_dispatcher
input_dispatcher = { sig_rec, *this, &Main::handle_input }; @@ -440,17 +249,17 @@ struct Floating_window_layouter::Main Reporter resize_request_reporter = { "resize_request" }; Reporter focus_reporter = { "focus" }; - unsigned dragged_window_id = 0; - Point pointer_clicked; - Point pointer_last; - Point pointer_curr; + bool focused_window_is_maximized() const + { + Window const *w = lookup_window_by_id(_user_state.focused_window_id()); + return w && w->is_maximized(); + } void import_window_list(Xml_node); void generate_window_layout_model(); void generate_resize_request_model(); void generate_focus_model(); - void initiate_window_drag(Window &window); /** * Constructor @@ -553,11 +362,11 @@ void Floating_window_layouter::Main::generate_window_layout_model() { for (Window *w = windows.first(); w; w = w->next()) { - bool const is_hovered = w->has_id(hovered_window_id); - bool const is_focused = w->has_id(focused_window_id); + bool const is_hovered = w->has_id(_user_state.hover_state().window_id); + bool const is_focused = w->has_id(_user_state.focused_window_id()); Window::Element const highlight = - is_hovered ? hovered_element : Window::Element::UNDEFINED; + is_hovered ? _user_state.hover_state().element : Window::Element::UNDEFINED; w->serialize(xml, is_focused, highlight); } @@ -569,18 +378,17 @@ void Floating_window_layouter::Main::generate_resize_request_model() { Reporter::Xml_generator xml(resize_request_reporter, [&] () { - Window const *dragged_window = lookup_window_by_id(dragged_window_id); - if (dragged_window) { + for_each_window([&] (Window const &window) { - Area const requested_size = dragged_window->requested_size(); - if (requested_size != dragged_window->size()) { + Area const requested_size = window.requested_size(); + if (requested_size != window.size()) { xml.node("window", [&] () { - xml.attribute("id", dragged_window_id); + xml.attribute("id", window.id().value); xml.attribute("width", requested_size.w()); xml.attribute("height", requested_size.h()); }); } - } + }); }); } @@ -590,7 +398,7 @@ void Floating_window_layouter::Main::generate_focus_model() Reporter::Xml_generator xml(focus_reporter, [&] () { xml.node("window", [&] () { - xml.attribute("id", focused_window_id); + xml.attribute("id", _user_state.focused_window_id().value); }); }); } @@ -625,27 +433,10 @@ element_from_hover_model(Genode::Xml_node hover_window_xml) if (hover_window_xml.has_sub_node("maximizer")) return Type::MAXIMIZER; if (hover_window_xml.has_sub_node("minimizer")) return Type::MINIMIZER; - return Type::UNDEFINED; } -void Floating_window_layouter::Main::initiate_window_drag(Window &window) -{ - window.initiate_drag_operation(hovered_element); - - drag_init_done = true; - - /* bring focused window to front */ - if (&window != windows.first()) { - windows.remove(&window); - windows.insert(&window); - } - - window.topped(); -} - - void Floating_window_layouter::Main::handle_window_list_update(unsigned) { window_list.update(); @@ -701,7 +492,7 @@ void Floating_window_layouter::Main::_apply_focus_request() if (at == nullptr) { w->topped(); - focused_window_id = w->id(); + _user_state.focused_window_id(w->id()); generate_focus_model(); } @@ -733,249 +524,33 @@ void Floating_window_layouter::Main::handle_hover_update(unsigned) try { Xml_node const hover_xml(hover.local_addr()); - Xml_node const hover_window_xml = hover_xml.sub_node("window"); - unsigned const id = attribute(hover_window_xml, "id", 0UL); + _user_state.hover(attribute(hover_window_xml, "id", 0UL), + element_from_hover_model(hover_window_xml)); - hovered_element_now = element_from_hover_model(hover_window_xml); + } + + /* + * An exception may occur during the 'Xml_node' construction if the hover + * model is missing or malformed. Under this condition, we invalidate + * the hover state. + */ + catch (...) { + + _user_state.reset_hover(); /* - * Check if we have just received an update while already being in - * dragged state. - * - * This can happen when the user selects a new nitpicker domain by - * clicking on a window decoration. Prior the click, the new session is - * not aware of the current mouse position. So the hover model is not - * up to date. As soon as nitpicker assigns the focus to the new - * session and delivers the corresponding press event, we enter the - * drag state (in the 'handle_input' function. But we don't know which - * window is dragged until the decorator updates the hover model. Now, - * when the model is updated and we are still in dragged state, we can - * finally initiate the window-drag operation for the now-known window. + * Don't generate a focus-model update here. In a situation where the + * pointer has moved over a native nitpicker view (outside the realm of + * the window manager), the hover model as generated by the decorator + * naturally becomes empty. If we posted a focus update, this would + * steal the focus away from the native nitpicker view. */ - if (id && !drag_init_done && dragged_window_id == 0) - { - dragged_window_id = id; - hovered_window_id = id; - focused_window_id = id; - - Window *window = lookup_window_by_id(id); - if (window) { - initiate_window_drag(*window); - generate_window_layout_model(); - generate_focus_model(); - } - } - - if (!drag_state && (id != hovered_window_id || hovered_element_now != hovered_element)) { - - hovered_window_id = id; - hovered_element = hovered_element_now; - - /* XXX read from config */ - bool const focus_follows_pointer = true; - if (id && focus_follows_pointer) { - focused_window_id = id; - generate_focus_model(); - } - - generate_window_layout_model(); - } - } catch (...) { - - /* reset focused window if pointer does not hover over any window */ - if (!drag_state) { - hovered_element = Window::Element::UNDEFINED; - hovered_window_id = 0; - generate_window_layout_model(); - - /* - * Don't generate a focus-model update here. In a situation where - * the pointer has moved over a native nitpicker view (outside - * the realm of the window manager), the hover model as generated - * by the decorator naturally becomes empty. If we posted a - * focus update, this would steal the focus away from the native - * nitpicker view. - */ - } - } -} - - -void Floating_window_layouter::Main::handle_input(unsigned) -{ - bool need_regenerate_window_layout_model = false; - bool need_regenerate_resize_request_model = false; - - Window *hovered_window = lookup_window_by_id(hovered_window_id); - - while (input.is_pending()) { - - size_t const num_events = input.flush(); - - Input::Event const * const ev = input_ds.local_addr(); - - for (size_t i = 0; i < num_events; i++) { - - Input::Event e = ev[i]; - - if (e.type() == Input::Event::MOTION - || e.type() == Input::Event::FOCUS) - pointer_curr = Point(e.ax(), e.ay()); - - /* track number of pressed buttons/keys */ - if (e.type() == Input::Event::PRESS) key_cnt++; - if (e.type() == Input::Event::RELEASE) key_cnt--; - - if (e.type() == Input::Event::PRESS - && e.keycode() == Input::BTN_LEFT) { - - /* - * Toggle maximized state - */ - if (hovered_element == Window::Element::MAXIMIZER) { - - if (hovered_window) { - - dragged_window_id = hovered_window_id; - hovered_window->is_maximized(!hovered_window->is_maximized()); - - /* bring focused window to front */ - if (hovered_window != windows.first()) { - windows.remove(hovered_window); - windows.insert(hovered_window); - } - hovered_window->topped(); - - focused_window_id = hovered_window_id; - - need_regenerate_window_layout_model = true; - need_regenerate_resize_request_model = true; - } - } - - bool const hovered_window_is_maximized = - hovered_window ? hovered_window->is_maximized() : false; - - /* - * Change window geometry unless the window is in maximized - * state. - */ - if (hovered_element != Window::Element::MAXIMIZER) { - - if (!hovered_window_is_maximized) { - - drag_state = true; - drag_init_done = false; - dragged_window_id = hovered_window_id; - pointer_clicked = pointer_curr; - pointer_last = pointer_clicked; - - /* - * If the hovered window is known at the time of the press - * event, we can initiate the drag operation immediately. - * Otherwise, we the initiation is deferred to the next - * update of the hover model. - */ - if (hovered_window) - initiate_window_drag(*hovered_window); - } - - if (hovered_window) { - if (focused_window_id != hovered_window_id) { - focused_window_id = hovered_window_id; - - /* bring focused window to front */ - if (hovered_window != windows.first()) { - windows.remove(hovered_window); - windows.insert(hovered_window); - } - - hovered_window->topped(); - - generate_focus_model(); - } - need_regenerate_window_layout_model = true; - } - } - } - - /* detect end of drag operation */ - if (e.type() == Input::Event::RELEASE) { - if (key_cnt == 0) { - drag_state = false; - generate_focus_model(); - - bool const manipulate_geometry = - hovered_element != Window::Element::CLOSER; - - Window *dragged_window = lookup_window_by_id(dragged_window_id); - - if (dragged_window && manipulate_geometry) { - - Area const last_requested_size = dragged_window->requested_size(); - dragged_window->finalize_drag_operation(); - - if (last_requested_size != dragged_window->requested_size()) - need_regenerate_resize_request_model = true; - - /* - * Update window layout because highlighting may have - * changed after the drag operation. E.g., if the - * window has not kept up with the dragging of a - * resize handle, the resize handle is no longer - * hovered. - */ - } - - /** - * Issue resize to 0x0 when releasing the the window closer - */ - if (dragged_window && hovered_element == Window::Element::CLOSER) { - - if (hovered_element_now == hovered_element) { - dragged_window->close(); - need_regenerate_resize_request_model = true; - } - } - - if (dragged_window) { - handle_hover_update(0); - } - } - } - } } - if (drag_state && (pointer_curr != pointer_last)) { - - pointer_last = pointer_curr; - - bool const manipulate_geometry = - hovered_element != Window::Element::CLOSER; - - Window *dragged_window = lookup_window_by_id(dragged_window_id); - if (dragged_window && manipulate_geometry) { - - Point const last_pos = dragged_window->position(); - Area const last_requested_size = dragged_window->requested_size(); - - dragged_window->apply_drag_operation(pointer_curr - pointer_clicked); - - if (last_pos != dragged_window->position()) - need_regenerate_window_layout_model = true; - - if (last_requested_size != dragged_window->requested_size()) - need_regenerate_resize_request_model = true; - } - } - - if (need_regenerate_window_layout_model) - generate_window_layout_model(); - - if (need_regenerate_resize_request_model) - generate_resize_request_model(); + /* propagate changed hovering to the decorator */ + generate_window_layout_model(); } diff --git a/repos/gems/src/app/floating_window_layouter/operations.h b/repos/gems/src/app/floating_window_layouter/operations.h new file mode 100644 index 000000000..75f2dd820 --- /dev/null +++ b/repos/gems/src/app/floating_window_layouter/operations.h @@ -0,0 +1,33 @@ +/* + * \brief Floating window layouter + * \author Norman Feske + * \date 2015-12-31 + */ + +/* + * Copyright (C) 2015 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 _OPERATIONS_H_ +#define _OPERATIONS_H_ + +/* local includes */ +#include "window.h" + +namespace Floating_window_layouter { struct Operations; } + + +struct Floating_window_layouter::Operations +{ + virtual void close(Window_id) = 0; + virtual void toggle_fullscreen(Window_id) = 0; + virtual void focus(Window_id) = 0; + virtual void to_front(Window_id) = 0; + virtual void drag(Window_id, Window::Element, Point clicked, Point curr) = 0; + virtual void finalize_drag(Window_id, Window::Element, Point clicked, Point final) = 0; +}; + +#endif /* _OPERATIONS_H_ */ diff --git a/repos/gems/src/app/floating_window_layouter/types.h b/repos/gems/src/app/floating_window_layouter/types.h new file mode 100644 index 000000000..e6136d4c1 --- /dev/null +++ b/repos/gems/src/app/floating_window_layouter/types.h @@ -0,0 +1,54 @@ +/* + * \brief Floating window layouter + * \author Norman Feske + * \date 2015-12-31 + */ + +/* + * Copyright (C) 2015 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 _TYPES_H_ +#define _TYPES_H_ + +/* Genode includes */ +#include + +namespace Floating_window_layouter { + + using namespace Genode; + + typedef Decorator::Point Point; + typedef Decorator::Area Area; + typedef Decorator::Rect Rect; + + using Decorator::attribute; + using Decorator::string_attribute; + using Decorator::area_attribute; + using Decorator::point_attribute; + + struct Window_id + { + unsigned value = 0; + + Window_id() { } + Window_id(unsigned value) : value(value) { } + + bool valid() const { return value != 0; } + + bool operator != (Window_id const &other) const + { + return other.value != value; + } + + bool operator == (Window_id const &other) const + { + return other.value == value; + } + }; +} + +#endif /* _TYPES_H_ */ diff --git a/repos/gems/src/app/floating_window_layouter/user_state.h b/repos/gems/src/app/floating_window_layouter/user_state.h new file mode 100644 index 000000000..eea1038ac --- /dev/null +++ b/repos/gems/src/app/floating_window_layouter/user_state.h @@ -0,0 +1,266 @@ +/* + * \brief Floating window layouter + * \author Norman Feske + * \date 2013-02-14 + */ + +/* + * Copyright (C) 2013-2015 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 _USER_STATE_H_ +#define _USER_STATE_H_ + +/* local includes */ +#include "operations.h" + +namespace Floating_window_layouter { class User_state; } + + +class Floating_window_layouter::User_state +{ + public: + + struct Hover_state + { + Window_id window_id; + Window::Element element { Window::Element::UNDEFINED }; + + Hover_state(Window_id id, Window::Element element) + : + window_id(id), element(element) + { } + }; + + private: + + Window_id _hovered_window_id; + Window_id _focused_window_id; + Window_id _dragged_window_id; + + unsigned _key_cnt = 0; + + Window::Element _hovered_element = Window::Element::UNDEFINED; + Window::Element _dragged_element = Window::Element::UNDEFINED; + + /* + * True while drag operation in progress + */ + bool _drag_state = false; + + /* + * False if the hover state (hovered window and element) was not known + * at the initial click of a drag operation. In this case, the drag + * operation is initiated as soon as the hover state becomes known. + */ + bool _drag_init_done = false; + + /* + * Pointer position at the beginning of a drag operation + */ + Point _pointer_clicked; + + /* + * Current pointer position + */ + Point _pointer_curr; + + Operations &_operations; + + inline void _handle_event(Input::Event const &); + + void _initiate_drag(Window_id hovered_window_id, + Window::Element hovered_element) + { + /* + * This function must never be called without the hover state to be + * defined. This assertion checks this precondition. + */ + if (!hovered_window_id.valid()) { + struct Drag_with_undefined_hover_state { }; + throw Drag_with_undefined_hover_state(); + } + + _drag_init_done = true; + _dragged_window_id = hovered_window_id; + _dragged_element = hovered_element; + + /* + * Toggle maximized (fullscreen) state + */ + if (_hovered_element == Window::Element::MAXIMIZER) { + + _dragged_window_id = _hovered_window_id; + _focused_window_id = _hovered_window_id; + + _operations.toggle_fullscreen(_hovered_window_id); + return; + } + + /* + * Bring hovered window to front when clicked + */ + if (_focused_window_id != _hovered_window_id) { + + _focused_window_id = _hovered_window_id; + + _operations.to_front(_hovered_window_id); + _operations.focus(_hovered_window_id); + } + + _operations.drag(_dragged_window_id, _dragged_element, + _pointer_clicked, _pointer_curr); + } + + public: + + User_state(Operations &operations) : _operations(operations) { } + + void handle_input(Input::Event const events[], unsigned num_events) + { + Point const pointer_last = _pointer_curr; + + for (size_t i = 0; i < num_events; i++) + _handle_event(events[i]); + + /* + * Issue drag operation when in dragged state + */ + if (_drag_state && _drag_init_done && (_pointer_curr != pointer_last)) + _operations.drag(_dragged_window_id, _dragged_element, + _pointer_clicked, _pointer_curr); + } + + void hover(Window_id window_id, Window::Element element) + { + Window_id const last_hovered_window_id = _hovered_window_id; + + _hovered_window_id = window_id; + _hovered_element = element; + + /* + * Check if we have just received an update while already being in + * dragged state. + * + * This can happen when the user selects a new nitpicker domain by + * clicking on a window decoration. Prior the click, the new + * session is not aware of the current mouse position. So the hover + * model is not up to date. As soon as nitpicker assigns the focus + * to the new session and delivers the corresponding press event, + * we enter the drag state (in the 'handle_input' function. But we + * don't know which window is dragged until the decorator updates + * the hover model. Now, when the model is updated and we are still + * in dragged state, we can finally initiate the window-drag + * operation for the now-known window. + */ + if (_drag_state && !_drag_init_done && _hovered_window_id.valid()) + _initiate_drag(_hovered_window_id, _hovered_element); + + /* + * Let focus follows the pointer + * + * XXX obtain policy from config + */ + if (!_drag_state && _hovered_window_id.valid() + && _hovered_window_id != last_hovered_window_id) { + + _focused_window_id = _hovered_window_id; + _operations.focus(_focused_window_id); + } + } + + void reset_hover() + { + /* ignore hover resets when in drag state */ + if (_drag_state) + return; + + _hovered_element = Window::Element::UNDEFINED; + _hovered_window_id = Window_id(); + } + + Window_id focused_window_id() const { return _focused_window_id; } + + void focused_window_id(Window_id id) { _focused_window_id = id; } + + Hover_state hover_state() const { return { _hovered_window_id, _hovered_element }; } +}; + + +void Floating_window_layouter::User_state::_handle_event(Input::Event const &e) +{ + if (e.type() == Input::Event::MOTION + || e.type() == Input::Event::FOCUS) { + + _pointer_curr = Point(e.ax(), e.ay()); + + if (_drag_state && _drag_init_done) + _operations.drag(_dragged_window_id, _dragged_element, + _pointer_clicked, _pointer_curr); + } + + /* track number of pressed buttons/keys */ + if (e.type() == Input::Event::PRESS) _key_cnt++; + if (e.type() == Input::Event::RELEASE) _key_cnt--; + + if (e.type() == Input::Event::PRESS + && e.keycode() == Input::BTN_LEFT + && _key_cnt == 1) { + + /* + * Initiate drag operation if possible + */ + _drag_state = true; + _pointer_clicked = _pointer_curr; + + if (_hovered_window_id.valid()) { + + /* + * Initiate drag operation + * + * If the hovered window is known at the time of the press event, + * we can initiate the drag operation immediately. Otherwise, + * the initiation is deferred to the next update of the hover + * model. + */ + + _initiate_drag(_hovered_window_id, _hovered_element); + + } else { + + /* + * If the hovering state is undefined at the time of the click, + * we defer the drag handling until the next update of the hover + * state. This intermediate state is captured by '_drag_init_done'. + */ + _drag_init_done = false; + _dragged_window_id = Window_id(); + _dragged_element = Window::Element(Window::Element::UNDEFINED); + } + } + + /* detect end of drag operation */ + if (e.type() == Input::Event::RELEASE + && _key_cnt == 0 + && _dragged_window_id.valid()) { + + _drag_state = false; + + /* + * Issue resize to 0x0 when releasing the the window closer + */ + if (_dragged_element == Window::Element::CLOSER) { + + if (_dragged_element == _hovered_element) + _operations.close(_dragged_window_id); + } + + _operations.finalize_drag(_dragged_window_id, _dragged_element, + _pointer_clicked, _pointer_curr); + } +} + +#endif /* _USER_STATE_H_ */ diff --git a/repos/gems/src/app/floating_window_layouter/window.h b/repos/gems/src/app/floating_window_layouter/window.h new file mode 100644 index 000000000..0a10ee46c --- /dev/null +++ b/repos/gems/src/app/floating_window_layouter/window.h @@ -0,0 +1,334 @@ +/* + * \brief Floating window layouter + * \author Norman Feske + * \date 2013-02-14 + */ + +/* + * Copyright (C) 2013-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 _WINDOW_H_ +#define _WINDOW_H_ + +/* local includes */ +#include "types.h" + +namespace Floating_window_layouter { class Window; } + + +class Floating_window_layouter::Window : public List::Element +{ + public: + + typedef String<256> Title; + typedef String<256> Label; + + struct Element + { + enum Type { UNDEFINED, TITLE, LEFT, RIGHT, TOP, BOTTOM, + TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT, + CLOSER, MAXIMIZER, MINIMIZER }; + + Type type; + + char const *name() const + { + switch (type) { + case UNDEFINED: return ""; + case TITLE: return "title"; + case LEFT: return "left"; + case RIGHT: return "right"; + case TOP: return "top"; + case BOTTOM: return "bottom"; + case TOP_LEFT: return "top_left"; + case TOP_RIGHT: return "top_right"; + case BOTTOM_LEFT: return "bottom_left"; + case BOTTOM_RIGHT: return "bottom_right"; + case CLOSER: return "closer"; + case MAXIMIZER: return "maximizer"; + case MINIMIZER: return "minimizer"; + } + return ""; + } + + Element(Type type) : type(type) { } + + bool operator != (Element const &other) const { return other.type != type; } + bool operator == (Element const &other) const { return other.type == type; } + }; + + private: + + Window_id const _id; + + Title _title; + + Label _label; + + Rect _geometry; + + /** + * Window geometry at the start of the current drag operation + */ + Rect _orig_geometry; + + /** + * Size as desired by the user during resize drag operations + */ + Area _requested_size; + + /** + * Backup of the original geometry while the window is maximized + */ + Rect _unmaximized_geometry; + + Rect const _maximized_geometry; + + /** + * Window may be partially transparent + */ + bool _has_alpha = false; + + /** + * Window is temporarily not visible + */ + bool _is_hidden = false; + + bool _is_resizeable = false; + + bool _is_maximized = false; + + bool _is_dragged = false; + + /* + * Number of times the window has been topped. This value is used by + * the decorator to detect the need for bringing the window to the + * front of nitpicker's global view stack even if the stacking order + * stays the same within the decorator instance. This is important in + * the presence of more than a single decorator. + */ + unsigned _topped_cnt = 0; + + bool _drag_left_border = false; + bool _drag_right_border = false; + bool _drag_top_border = false; + bool _drag_bottom_border = false; + + /** + * Called when the user starts dragging a window element + */ + void _initiate_drag_operation(Window::Element element) + { + _drag_left_border = (element.type == Window::Element::LEFT) + || (element.type == Window::Element::TOP_LEFT) + || (element.type == Window::Element::BOTTOM_LEFT); + + _drag_right_border = (element.type == Window::Element::RIGHT) + || (element.type == Window::Element::TOP_RIGHT) + || (element.type == Window::Element::BOTTOM_RIGHT); + + _drag_top_border = (element.type == Window::Element::TOP) + || (element.type == Window::Element::TOP_LEFT) + || (element.type == Window::Element::TOP_RIGHT); + + _drag_bottom_border = (element.type == Window::Element::BOTTOM) + || (element.type == Window::Element::BOTTOM_LEFT) + || (element.type == Window::Element::BOTTOM_RIGHT); + + _orig_geometry = _geometry; + + _requested_size = _geometry.area(); + + _is_dragged = true; + } + + /** + * Called each time the pointer moves while the window is dragged + */ + void _apply_drag_operation(Point offset) + { + if (!_drag_border()) + position(_orig_geometry.p1() + offset); + + int requested_w = _orig_geometry.w(), + requested_h = _orig_geometry.h(); + + if (_drag_left_border) requested_w -= offset.x(); + if (_drag_right_border) requested_w += offset.x(); + if (_drag_top_border) requested_h -= offset.y(); + if (_drag_bottom_border) requested_h += offset.y(); + + _requested_size = Area(max(1, requested_w), max(1, requested_h)); + } + + /** + * Return true if user drags a window border + */ + bool _drag_border() const + { + return _drag_left_border || _drag_right_border + || _drag_top_border || _drag_bottom_border; + } + + public: + + Window(Window_id id, Rect maximized_geometry) + : + _id(id), _maximized_geometry(maximized_geometry) + { } + + bool has_id(Window_id id) const { return id == _id; } + + Window_id id() const { return _id; } + + void title(Title const &title) { _title = title; } + + void label(Label const &label) { _label = label; } + + void geometry(Rect geometry) { _geometry = geometry; } + + Point position() const { return _geometry.p1(); } + + void position(Point pos) { _geometry = Rect(pos, _geometry.area()); } + + void has_alpha(bool has_alpha) { _has_alpha = has_alpha; } + + void is_hidden(bool is_hidden) { _is_hidden = is_hidden; } + + void is_resizeable(bool is_resizeable) { _is_resizeable = is_resizeable; } + + bool label_matches(Label const &label) const { return label == _label; } + + /** + * Define window size + * + * This function is called when the window-list model changes. + */ + void size(Area size) + { + if (_is_maximized) { + _geometry = Rect(_maximized_geometry.p1(), size); + return; + } + + if (!_drag_border()) { + _geometry = Rect(_geometry.p1(), size); + return; + } + + Point p1 = _geometry.p1(), p2 = _geometry.p2(); + + if (_drag_left_border) + p1 = Point(p2.x() - size.w() + 1, p1.y()); + + if (_drag_right_border) + p2 = Point(p1.x() + size.w() - 1, p2.y()); + + if (_drag_top_border) + p1 = Point(p1.x(), p2.y() - size.h() + 1); + + if (_drag_bottom_border) + p2 = Point(p2.x(), p1.y() + size.h() - 1); + + _geometry = Rect(p1, p2); + } + + Area size() const { return _geometry.area(); } + + Area requested_size() const { return _requested_size; } + + void serialize(Xml_generator &xml, bool focused, Element highlight) + { + /* omit window from the layout if hidden */ + if (_is_hidden) + return; + + xml.node("window", [&]() { + + xml.attribute("id", _id.value); + + /* present concatenation of label and title in the window's title bar */ + { + bool const has_title = Genode::strlen(_title.string()) > 0; + + char buf[Label::capacity()]; + Genode::snprintf(buf, sizeof(buf), "%s%s%s", + _label.string(), + has_title ? " " : "", + _title.string()); + + xml.attribute("title", buf); + } + + xml.attribute("xpos", _geometry.x1()); + xml.attribute("ypos", _geometry.y1()); + xml.attribute("width", _geometry.w()); + xml.attribute("height", _geometry.h()); + xml.attribute("topped", _topped_cnt); + + if (focused) + xml.attribute("focused", "yes"); + + if (highlight.type != Element::UNDEFINED) { + xml.node("highlight", [&] () { + xml.node(highlight.name()); + }); + } + + if (_has_alpha) + xml.attribute("has_alpha", "yes"); + + if (_is_resizeable) { + xml.attribute("maximizer", "yes"); + xml.attribute("closer", "yes"); + } + }); + } + + void drag(Window::Element element, Point clicked, Point curr) + { + /* prevent maximized windows from being dragged */ + if (is_maximized()) + return; + + if (!_is_dragged) + _initiate_drag_operation(element); + + _apply_drag_operation(curr - clicked); + } + + void finalize_drag_operation() + { + _requested_size = _geometry.area(); + _is_dragged = false; + } + + void topped() { _topped_cnt++; } + + void close() { _requested_size = Area(0, 0); } + + bool is_maximized() const { return _is_maximized; } + + void is_maximized(bool is_maximized) + { + /* enter maximized state */ + if (!_is_maximized && is_maximized) { + _unmaximized_geometry = _geometry; + _requested_size = _maximized_geometry.area(); + } + + /* leave maximized state */ + if (_is_maximized && !is_maximized) { + _requested_size = _unmaximized_geometry.area(); + _geometry = Rect(_unmaximized_geometry.p1(), _geometry.area()); + } + + _is_maximized = is_maximized; + } +}; + +#endif /* _WINDOW_H_ */