Skip to content

Commit

Permalink
window_layouter: handle drag/drop as actions
Browse files Browse the repository at this point in the history
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 <press> and
<release> nodes. If no policy for a concrete combination exists, a
release event also considers the policy of its matching <press> node.
This way, the regular drag-and-drop rules can be expressed as

  <press key="BTN_LEFT" action="drag">
     <release key="BTN_LEFT" action="drop"/>
  </press>

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 genodelabs#5403
  • Loading branch information
nfeske committed Dec 18, 2024
1 parent 9554c3c commit 5d4e55d
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 78 deletions.
18 changes: 15 additions & 3 deletions repos/gems/recipes/raw/motif_wm/layouter.config
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,24 @@
<assign label_prefix="" target="screen_1" xpos="any" ypos="any"/>
</rules>

<press key="KEY_SCREEN" action="release_grab">
<press key="KEY_SCREEN">
<press key="KEY_TAB" action="next_window">
<release key="KEY_TAB">
<release key="KEY_SCREEN" action="raise_window"/>
</release>
<release key="KEY_SCREEN" action="raise_window"/>
</press>
<press key="KEY_LEFTSHIFT" action="pick_up">
<press key="KEY_LEFTSHIFT">
<press key="KEY_TAB" action="prev_window">
<release key="KEY_TAB">
<release key="KEY_SCREEN" action="raise_window"/>
</release>
</press>
</press>
</press>

<press key="KEY_SCREEN">
<press key="KEY_LEFTSHIFT" action="pick_up">
<press key="KEY_1" action="screen" target="screen_1"/>
<press key="KEY_2" action="screen" target="screen_2"/>
<press key="KEY_3" action="screen" target="screen_3"/>
Expand All @@ -44,7 +49,6 @@
<press key="KEY_0" action="screen" target="screen_0"/>
<release key="KEY_LEFTSHIFT" action="place_down"/>
</press>
<press key="KEY_ENTER" action="toggle_fullscreen"/>
<press key="KEY_1" action="screen" target="screen_1"/>
<press key="KEY_2" action="screen" target="screen_2"/>
<press key="KEY_3" action="screen" target="screen_3"/>
Expand All @@ -57,6 +61,14 @@
<press key="KEY_0" action="screen" target="screen_0"/>
</press>

<press key="KEY_SCREEN" action="release_grab">
<press key="KEY_ENTER" action="toggle_fullscreen"/>
</press>

<press key="BTN_LEFT" action="drag">
<release key="BTN_LEFT" action="drop"/>
</press>

<!-- support switching screens while dragging a window -->
<press key="BTN_LEFT">
<press key="KEY_1" action="screen" target="screen_1"/>
Expand Down
18 changes: 15 additions & 3 deletions repos/gems/recipes/raw/window_layouter/window_layouter.config
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,24 @@
<assign label_prefix="" target="screen_1" xpos="any" ypos="any"/>
</rules>

<press key="KEY_SCREEN" action="release_grab">
<press key="KEY_SCREEN">
<press key="KEY_TAB" action="next_window">
<release key="KEY_TAB">
<release key="KEY_SCREEN" action="raise_window"/>
</release>
<release key="KEY_SCREEN" action="raise_window"/>
</press>
<press key="KEY_LEFTSHIFT" action="pick_up">
<press key="KEY_LEFTSHIFT">
<press key="KEY_TAB" action="prev_window">
<release key="KEY_TAB">
<release key="KEY_SCREEN" action="raise_window"/>
</release>
</press>
</press>
</press>

<press key="KEY_SCREEN">
<press key="KEY_LEFTSHIFT" action="pick_up">
<press key="KEY_1" action="screen" target="screen_1"/>
<press key="KEY_2" action="screen" target="screen_2"/>
<press key="KEY_3" action="screen" target="screen_3"/>
Expand All @@ -44,7 +49,6 @@
<press key="KEY_0" action="screen" target="screen_0"/>
<release key="KEY_LEFTSHIFT" action="place_down"/>
</press>
<press key="KEY_ENTER" action="toggle_fullscreen"/>
<press key="KEY_1" action="screen" target="screen_1"/>
<press key="KEY_2" action="screen" target="screen_2"/>
<press key="KEY_3" action="screen" target="screen_3"/>
Expand All @@ -57,6 +61,14 @@
<press key="KEY_0" action="screen" target="screen_0"/>
</press>

<press key="KEY_SCREEN" action="release_grab">
<press key="KEY_ENTER" action="toggle_fullscreen"/>
</press>

<press key="BTN_LEFT" action="drag">
<release key="BTN_LEFT" action="drop"/>
</press>

<!-- support switching screens while dragging a window -->
<press key="BTN_LEFT">
<press key="KEY_1" action="screen" target="screen_1"/>
Expand Down
5 changes: 4 additions & 1 deletion repos/gems/src/app/window_layouter/command.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down
65 changes: 47 additions & 18 deletions repos/gems/src/app/window_layouter/key_sequence_tracker.h
Original file line number Diff line number Diff line change
Expand Up @@ -110,17 +110,18 @@ 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; } });

if (!done)
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;
}
Expand All @@ -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); },
[&] { });
};

Expand All @@ -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; }
}

/**
Expand Down Expand Up @@ -182,39 +191,59 @@ class Window_layouter::Key_sequence_tracker
_stack.flush(Stack::Entry { .press = false, .key = key });
});

Constructible<Stack::Entry> 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 <release> node exists for the current combination
* of keys, fall back to a <release> node declared immediately
* inside the corresponding <press> 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 });
});
}
};

Expand Down
101 changes: 48 additions & 53 deletions repos/gems/src/app/window_layouter/user_state.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {

Expand Down Expand Up @@ -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");
}
Expand Down

0 comments on commit 5d4e55d

Please sign in to comment.