From 146c34109b43dcccdc0c198e70aa6095b5dca27a Mon Sep 17 00:00:00 2001 From: Norman Feske Date: Tue, 10 Dec 2024 17:53:45 +0100 Subject: [PATCH] window_layouter: free-arrange mode This patch adds the feature of moving and resizing windows by clicking anywhere within a window while the global window-management key is held. Depending on the position within the window, the click is interpreted as click on the title (when clicking at inner 50% of the window, or as a click on the border (when clicking at an area nearby the window boundary). This mode of interaction requires more flexibility of the handling of key sequences. The formerly hard-wired handling of the drag and drop as response to BTN_LEFT events does not suffice. Therefore, this patch moves the driving of the drag-and-drop state to the config level by introducing the actions "drag" and "drop" Fixes #5403 --- .../gems/recipes/raw/motif_wm/layouter.config | 17 ++++ .../window_layouter/window_layouter.config | 17 ++++ repos/gems/src/app/window_layouter/command.h | 6 +- repos/gems/src/app/window_layouter/main.cc | 49 +++++++++++ .../gems/src/app/window_layouter/user_state.h | 82 +++++++++++++++---- 5 files changed, 153 insertions(+), 18 deletions(-) diff --git a/repos/gems/recipes/raw/motif_wm/layouter.config b/repos/gems/recipes/raw/motif_wm/layouter.config index 80bba521c84..2b1737473e2 100644 --- a/repos/gems/recipes/raw/motif_wm/layouter.config +++ b/repos/gems/recipes/raw/motif_wm/layouter.config @@ -65,6 +65,23 @@ + + + + + + + + + + + + + + + + + diff --git a/repos/gems/recipes/raw/window_layouter/window_layouter.config b/repos/gems/recipes/raw/window_layouter/window_layouter.config index 80bba521c84..2b1737473e2 100644 --- a/repos/gems/recipes/raw/window_layouter/window_layouter.config +++ b/repos/gems/recipes/raw/window_layouter/window_layouter.config @@ -65,6 +65,23 @@ + + + + + + + + + + + + + + + + + diff --git a/repos/gems/src/app/window_layouter/command.h b/repos/gems/src/app/window_layouter/command.h index ab0c572306a..a15ec3a1b01 100644 --- a/repos/gems/src/app/window_layouter/command.h +++ b/repos/gems/src/app/window_layouter/command.h @@ -22,8 +22,8 @@ namespace Window_layouter { class Command; } struct Window_layouter::Command { enum Type { NONE, NEXT_WINDOW, PREV_WINDOW, RAISE_WINDOW, TOGGLE_FULLSCREEN, - NEXT_TAB, PREV_TAB, SCREEN, RELEASE_GRAB, PICK_UP, PLACE_DOWN, - DRAG, DROP }; + SCREEN, RELEASE_GRAB, PICK_UP, PLACE_DOWN, + DRAG, DROP, FREE_ARRANGE, STRICT_ARRANGE }; Type type; Target::Name target; @@ -42,6 +42,8 @@ struct Window_layouter::Command if (string == "place_down") return PLACE_DOWN; if (string == "drag") return DRAG; if (string == "drop") return DROP; + if (string == "free_arrange") return FREE_ARRANGE; + if (string == "strict_arrange") return STRICT_ARRANGE; warning("cannot convert \"", string, "\" to action type"); return NONE; diff --git a/repos/gems/src/app/window_layouter/main.cc b/repos/gems/src/app/window_layouter/main.cc index e7598678b37..6cd75cb9786 100644 --- a/repos/gems/src/app/window_layouter/main.cc +++ b/repos/gems/src/app/window_layouter/main.cc @@ -358,6 +358,55 @@ struct Window_layouter::Main : User_state::Action, _gen_resize_request(); } + Window::Element free_arrange_element_at(Window_id id, Point const abs_at) override + { + using Element = Window::Element; + Element result { }; + + /* window geometry is relative to target */ + Point at { }; + _target_list.with_target(_assign_list, id, [&] (Target const &target) { + at = abs_at - target.rect.at; }); + + _window_list.with_window(id, [&] (Window &window) { + Rect const rect = window.outer_geometry(); + if (!rect.contains(at)) + return; + + int const x_percent = (100*(at.x - rect.x1()))/rect.w(), + y_percent = (100*(at.y - rect.y1()))/rect.h(); + + auto with_rel = [&] (int rel, auto const &lo_fn, auto const &mid_fn, auto const &hi_fn) + { + if (rel > 75) hi_fn(); else if (rel > 25) mid_fn(); else lo_fn(); + }; + + with_rel(x_percent, + [&] { + with_rel(y_percent, + [&] { result = { Element::TOP_LEFT }; }, + [&] { result = { Element::LEFT }; }, + [&] { result = { Element::BOTTOM_LEFT }; }); }, + [&] { + with_rel(y_percent, + [&] { result = { Element::TOP }; }, + [&] { result = { Element::TITLE }; }, + [&] { result = { Element::BOTTOM }; }); }, + [&] { + with_rel(y_percent, + [&] { result = { Element::TOP_RIGHT }; }, + [&] { result = { Element::RIGHT }; }, + [&] { result = { Element::BOTTOM_RIGHT }; }); } + ); + }); + return result; + } + + void free_arrange_hover_changed() override + { + _update_window_layout(); + } + void _handle_drop_timer() { _drag = { }; diff --git a/repos/gems/src/app/window_layouter/user_state.h b/repos/gems/src/app/window_layouter/user_state.h index bd43f1f16df..6c4a7571e46 100644 --- a/repos/gems/src/app/window_layouter/user_state.h +++ b/repos/gems/src/app/window_layouter/user_state.h @@ -37,6 +37,8 @@ class Window_layouter::User_state virtual void pick_up(Window_id) = 0; virtual void place_down() = 0; virtual void screen(Target::Name const &) = 0; + virtual void free_arrange_hover_changed() = 0; + virtual Window::Element free_arrange_element_at(Window_id, Point) = 0; }; struct Hover_state @@ -63,8 +65,9 @@ class Window_layouter::User_state Key_sequence_tracker _key_sequence_tracker { }; - Window::Element _hovered_element { }; - Window::Element _dragged_element { }; + Window::Element _strict_hovered_element { }; /* hovered window control */ + Window::Element _free_hovered_element { }; /* hovered window area */ + Window::Element _dragged_element { }; /* * True while drag operation in progress @@ -80,6 +83,17 @@ class Window_layouter::User_state bool _picked_up = false; + /* + * If true, the window element is determined by the sole relation of + * the pointer position to the window area, ignoring window controls. + */ + bool _free_arrange = false; + + Window::Element _hovered_element() const + { + return _free_arrange ? _free_hovered_element : _strict_hovered_element; + } + /* * Pointer position at the beginning of a drag operation */ @@ -118,7 +132,7 @@ class Window_layouter::User_state /* * Toggle maximized (fullscreen) state */ - if (_hovered_element.maximizer()) { + if (_strict_hovered_element.maximizer()) { _dragged_window_id = _hovered_window_id; _focused_window_id = _hovered_window_id; @@ -126,8 +140,8 @@ class Window_layouter::User_state _action.toggle_fullscreen(_hovered_window_id); - _hovered_element = { }; - _hovered_window_id = { }; + _strict_hovered_element = { }; + _hovered_window_id = { }; return; } @@ -147,6 +161,14 @@ class Window_layouter::User_state _pointer_clicked, _pointer_curr); } + void _update_free_hovered_element() + { + _free_hovered_element = { }; + if (_hovered_window_id.valid()) + _free_hovered_element = _action.free_arrange_element_at(_hovered_window_id, + _pointer_curr); + } + public: User_state(Action &action, Focus_history &focus_history) @@ -174,8 +196,10 @@ class Window_layouter::User_state { Window_id const orig_hovered_window_id = _hovered_window_id; - _hovered_window_id = window_id; - _hovered_element = element; + _hovered_window_id = window_id; + _strict_hovered_element = element; + + _update_free_hovered_element(); /* * Check if we have just received an update while already being in @@ -193,7 +217,7 @@ class Window_layouter::User_state * operation for the now-known window. */ if (_drag_state && !_drag_init_done && _hovered_window_id.valid()) - _initiate_drag(_hovered_window_id, _hovered_element); + _initiate_drag(_hovered_window_id, _strict_hovered_element); /* * Let focus follows the pointer, except while dragging or when @@ -214,21 +238,28 @@ class Window_layouter::User_state if (_drag_state) return; - _hovered_element = { }; - _hovered_window_id = { }; + _strict_hovered_element = { }; + _hovered_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 }; } + Hover_state hover_state() const + { + return { .window_id = _hovered_window_id, + .element = _hovered_element() }; + } }; void Window_layouter::User_state::_handle_event(Input::Event const &e, Xml_node config) { + Point const orig_pointer_curr = _pointer_curr; + bool const orig_free_arrange = _free_arrange; + e.handle_absolute_motion([&] (int x, int y) { _pointer_curr = Point(x, y); }); @@ -295,8 +326,20 @@ void Window_layouter::User_state::_handle_event(Input::Event const &e, } return; + case Command::FREE_ARRANGE: + _free_arrange = true; + return; + + case Command::STRICT_ARRANGE: + _free_arrange = false; + return; + case Command::DRAG: + /* ignore clicks outside of a window in free-arrange mode */ + if (_free_arrange && !_hovered_window_id.valid()) + return; + _drag_state = true; _pointer_clicked = _pointer_curr; @@ -311,7 +354,7 @@ void Window_layouter::User_state::_handle_event(Input::Event const &e, * model. */ - _initiate_drag(_hovered_window_id, _hovered_element); + _initiate_drag(_hovered_window_id, _hovered_element()); } else { @@ -329,26 +372,33 @@ void Window_layouter::User_state::_handle_event(Input::Event const &e, case Command::DROP: if (_drag_state && _dragged_window_id.valid()) { - _drag_state = false; /* * Issue resize to 0x0 when releasing the the window closer */ if (_dragged_element.closer()) - if (_dragged_element == _hovered_element) + if (_dragged_element == _hovered_element()) _action.close(_dragged_window_id); _action.finalize_drag(_dragged_window_id, _dragged_element, _pointer_clicked, _pointer_curr); } + _drag_state = false; return; - default: - warning("command ", (int)command.type, " unhanded"); + case Command::NONE: + return; } }); } + if (_free_arrange && (!orig_free_arrange || orig_pointer_curr != _pointer_curr)) { + Window::Element const orig_free_hovered_element = _free_hovered_element; + _update_free_hovered_element(); + if (orig_free_hovered_element != _free_hovered_element) + _action.free_arrange_hover_changed(); + } + /* update focus history after key/button action is completed */ if (e.release() && _key_cnt == 0) _focus_history.focus(_focused_window_id);