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
This commit is contained in:
Norman Feske 2024-12-10 17:53:45 +01:00 committed by Christian Helmuth
parent 7fc060438a
commit 30b3fa45f1
5 changed files with 153 additions and 18 deletions

View File

@ -65,6 +65,23 @@
<press key="KEY_ENTER" action="toggle_fullscreen"/>
</press>
<press key="KEY_SCREEN" action="free_arrange">
<press key="BTN_LEFT" action="drag">
<release key="BTN_LEFT" action="drop"/>
<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"/>
<press key="KEY_4" action="screen" target="screen_4"/>
<press key="KEY_5" action="screen" target="screen_5"/>
<press key="KEY_6" action="screen" target="screen_6"/>
<press key="KEY_7" action="screen" target="screen_7"/>
<press key="KEY_8" action="screen" target="screen_8"/>
<press key="KEY_9" action="screen" target="screen_9"/>
<press key="KEY_0" action="screen" target="screen_0"/>
</press>
<release key="KEY_SCREEN" action="strict_arrange"/>
</press>
<press key="BTN_LEFT" action="drag">
<release key="BTN_LEFT" action="drop"/>
</press>

View File

@ -65,6 +65,23 @@
<press key="KEY_ENTER" action="toggle_fullscreen"/>
</press>
<press key="KEY_SCREEN" action="free_arrange">
<press key="BTN_LEFT" action="drag">
<release key="BTN_LEFT" action="drop"/>
<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"/>
<press key="KEY_4" action="screen" target="screen_4"/>
<press key="KEY_5" action="screen" target="screen_5"/>
<press key="KEY_6" action="screen" target="screen_6"/>
<press key="KEY_7" action="screen" target="screen_7"/>
<press key="KEY_8" action="screen" target="screen_8"/>
<press key="KEY_9" action="screen" target="screen_9"/>
<press key="KEY_0" action="screen" target="screen_0"/>
</press>
<release key="KEY_SCREEN" action="strict_arrange"/>
</press>
<press key="BTN_LEFT" action="drag">
<release key="BTN_LEFT" action="drop"/>
</press>

View File

@ -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;

View File

@ -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 = { };

View File

@ -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);