From 5d4e55dc101db83d22393ec7a6894e62eddfaeca Mon Sep 17 00:00:00 2001 From: Norman Feske Date: Tue, 10 Dec 2024 17:30:10 +0100 Subject: [PATCH] window_layouter: handle drag/drop as actions This patch moves the formerly hard-wired drag-and-drop handling to the configuration level by introducing the actions "drag" and "drop". To aid the robust handling of release events matching their corresponding press events, the patch refines the policy-matching of the current combination of keys against the hierarchy of and nodes. If no policy for a concrete combination exists, a release event also considers the policy of its matching node. This way, the regular drag-and-drop rules can be expressed as This also works when releasing BTN_LEFT while pressing additional keys, for which no policy exists. With this change, the layouter supports the matching of multiple key sequences instead of only one, thereby supporting multiple actions at once and allowing for decoupling different user interactions in the configuration. Issue #5403 --- .../gems/recipes/raw/motif_wm/layouter.config | 18 +++- .../window_layouter/window_layouter.config | 18 +++- repos/gems/src/app/window_layouter/command.h | 5 +- .../window_layouter/key_sequence_tracker.h | 65 +++++++---- .../gems/src/app/window_layouter/user_state.h | 101 +++++++++--------- 5 files changed, 129 insertions(+), 78 deletions(-) diff --git a/repos/gems/recipes/raw/motif_wm/layouter.config b/repos/gems/recipes/raw/motif_wm/layouter.config index a70155b558a..80bba521c84 100644 --- a/repos/gems/recipes/raw/motif_wm/layouter.config +++ b/repos/gems/recipes/raw/motif_wm/layouter.config @@ -19,19 +19,24 @@ - + - + + + + + + @@ -44,7 +49,6 @@ - @@ -57,6 +61,14 @@ + + + + + + + + diff --git a/repos/gems/recipes/raw/window_layouter/window_layouter.config b/repos/gems/recipes/raw/window_layouter/window_layouter.config index a70155b558a..80bba521c84 100644 --- a/repos/gems/recipes/raw/window_layouter/window_layouter.config +++ b/repos/gems/recipes/raw/window_layouter/window_layouter.config @@ -19,19 +19,24 @@ - + - + + + + + + @@ -44,7 +49,6 @@ - @@ -57,6 +61,14 @@ + + + + + + + + diff --git a/repos/gems/src/app/window_layouter/command.h b/repos/gems/src/app/window_layouter/command.h index 3de2cacb57f..ab0c572306a 100644 --- a/repos/gems/src/app/window_layouter/command.h +++ b/repos/gems/src/app/window_layouter/command.h @@ -22,7 +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 }; + NEXT_TAB, PREV_TAB, SCREEN, RELEASE_GRAB, PICK_UP, PLACE_DOWN, + DRAG, DROP }; Type type; Target::Name target; @@ -39,6 +40,8 @@ struct Window_layouter::Command if (string == "release_grab") return RELEASE_GRAB; if (string == "pick_up") return PICK_UP; if (string == "place_down") return PLACE_DOWN; + if (string == "drag") return DRAG; + if (string == "drop") return DROP; warning("cannot convert \"", string, "\" to action type"); return NONE; diff --git a/repos/gems/src/app/window_layouter/key_sequence_tracker.h b/repos/gems/src/app/window_layouter/key_sequence_tracker.h index f1b8be3a154..a11d9852a5c 100644 --- a/repos/gems/src/app/window_layouter/key_sequence_tracker.h +++ b/repos/gems/src/app/window_layouter/key_sequence_tracker.h @@ -110,7 +110,7 @@ class Window_layouter::Key_sequence_tracker bool done = false; /* process the first match only */ curr.for_each_sub_node(node_type, [&] (Xml_node const &node) { - if (!done && node.attribute_value("key", Key_name()) == key) { + if (node.attribute_value("key", Key_name()) == key) { fn(node); done = true; } }); @@ -118,9 +118,10 @@ class Window_layouter::Key_sequence_tracker no_match_fn(); } - void _with_match_rec(unsigned const pos, Xml_node const &node, auto const &fn) const + void _with_match_rec(unsigned const pos, unsigned const max_pos, + Xml_node const &node, auto const &fn) const { - if (pos == _stack.pos) { + if (pos == max_pos) { fn(node); return; } @@ -129,7 +130,7 @@ class Window_layouter::Key_sequence_tracker _with_matching_sub_node(node, _stack.entries[pos], [&] (Xml_node const &sub_node) { if (pos < _stack.pos) - _with_match_rec(pos + 1, sub_node, fn); }, + _with_match_rec(pos + 1, max_pos, sub_node, fn); }, [&] { }); }; @@ -142,7 +143,15 @@ class Window_layouter::Key_sequence_tracker */ void _with_xml_by_path(Xml_node const &config, auto const &fn) const { - _with_match_rec(0, config, fn); + _with_match_rec(0, _stack.pos, config, fn); + } + + void _with_xml_at_press(Xml_node const &config, Input::Keycode key, auto const &fn) const + { + for (unsigned i = 0; i < _stack.pos; i++) + if (_stack.entries[i].press && _stack.entries[i].key == key) { + _with_match_rec(0, i + 1, config, fn); + return; } } /** @@ -182,39 +191,59 @@ class Window_layouter::Key_sequence_tracker _stack.flush(Stack::Entry { .press = false, .key = key }); }); + Constructible new_entry { }; + _with_xml_by_path(config, [&] (Xml_node const &curr_node) { ev.handle_press([&] (Input::Keycode key, Codepoint) { - Stack::Entry const press { .press = true, .key = key }; - _with_matching_sub_node(curr_node, press, - [&] (Xml_node const &node) { _execute_command(node, fn); }, + [&] (Xml_node const &node) { + _execute_command(node, fn); }, [&] { }); - _stack.push(press); + new_entry.construct(press); }); ev.handle_release([&] (Input::Keycode key) { - Stack::Entry const release { .press = false, .key = key }; - /* * If there exists a specific path for the release event, - * follow the path. Otherwise, we remove the released key - * from the sequence. + * follow the path and record the release event. Otherwise, + * 'new_entry' will remain unconstructed so that the + * corresponding press event gets flushed from the stack. */ + Stack::Entry const release { .press = false, .key = key }; _with_matching_sub_node(curr_node, release, [&] (Xml_node const &next_node) { _execute_command(next_node, fn); - _stack.push(release); + if (next_node.num_sub_nodes()) + new_entry.construct(release); }, - [&] /* no match */ { - Stack::Entry const press { .press = true, .key = key }; - _stack.flush(press); - }); + [&] /* no match */ { }); }); }); + + if (new_entry.constructed()) { + _stack.push(*new_entry); + return; + } + + /* + * If no matching node exists for the current combination + * of keys, fall back to a node declared immediately + * inside the corresponding node. + */ + ev.handle_release([&] (Input::Keycode key) { + _with_xml_at_press(config, key, [&] (Xml_node const &press_node) { + _with_matching_sub_node(press_node, { .press = false, .key = key }, + [&] (Xml_node const &next_node) { + _execute_command(next_node, fn); }, + [&] { }); }); + + _stack.flush(Stack::Entry { .press = true, .key = key }); + _stack.flush(Stack::Entry { .press = false, .key = key }); + }); } }; diff --git a/repos/gems/src/app/window_layouter/user_state.h b/repos/gems/src/app/window_layouter/user_state.h index 6221a86e002..bd43f1f16df 100644 --- a/repos/gems/src/app/window_layouter/user_state.h +++ b/repos/gems/src/app/window_layouter/user_state.h @@ -243,59 +243,6 @@ void Window_layouter::User_state::_handle_event(Input::Event const &e, if (e.press()) _key_cnt++; if (e.release()) _key_cnt--; - /* handle pointer click */ - if (e.key_press(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.release() && _key_cnt == 0) { - - 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) - _action.close(_dragged_window_id); - - _action.finalize_drag(_dragged_window_id, _dragged_element, - _pointer_clicked, _pointer_curr); - } - } - /* handle key sequences */ if (_key(e)) { @@ -348,6 +295,54 @@ void Window_layouter::User_state::_handle_event(Input::Event const &e, } return; + case Command::DRAG: + + _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); + } + return; + + 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) + _action.close(_dragged_window_id); + + _action.finalize_drag(_dragged_window_id, _dragged_element, + _pointer_clicked, _pointer_curr); + } + return; + default: warning("command ", (int)command.type, " unhanded"); }