window_layouter: drag windows between displays

This patch allows the user to drag windows from one target area (i.e.,
display) to another whereas the resizing of windows is restricted to
the window's original target area. The latter point is important to
ensure that the window's resize handles remain reachable at all times.

Issue #5390
This commit is contained in:
Norman Feske 2024-11-27 19:24:09 +01:00 committed by Christian Helmuth
parent 1638ee00c3
commit 8496d5b02a
8 changed files with 157 additions and 44 deletions

View File

@ -44,6 +44,8 @@ class Window_layouter::Assign : public List_model<Assign>::Element
using Label = String<80>;
Target::Name target_name { };
private:
Registry<Member> _members { };
@ -52,8 +54,6 @@ class Window_layouter::Assign : public List_model<Assign>::Element
Label const _label_prefix;
Label const _label_suffix;
Target::Name _target_name { };
bool _pos_defined = false;
bool _xpos_any = false;
bool _ypos_any = false;
@ -75,7 +75,7 @@ class Window_layouter::Assign : public List_model<Assign>::Element
void update(Xml_node assign)
{
_target_name = assign.attribute_value("target", Target::Name());
target_name = assign.attribute_value("target", Target::Name());
_pos_defined = assign.has_attribute("xpos") && assign.has_attribute("ypos");
_size_defined = assign.has_attribute("width") && assign.has_attribute("height");
_maximized = assign.attribute_value("maximized", false);
@ -160,8 +160,6 @@ class Window_layouter::Assign : public List_model<Assign>::Element
fn(_members);
}
Target::Name target_name() const { return _target_name; }
/**
* Used to generate <assign> nodes of windows captured via wildcard
*/
@ -200,7 +198,7 @@ class Window_layouter::Assign : public List_model<Assign>::Element
if (_label_prefix.valid()) xml.attribute("label_prefix", _label_prefix);
if (_label_suffix.valid()) xml.attribute("label_suffix", _label_suffix);
xml.attribute("target", _target_name);
xml.attribute("target", target_name);
}
void gen_geometry_attr(Xml_generator &xml) const

View File

@ -96,7 +96,7 @@ class Window_layouter::Assign_list : Noncopyable
void for_each_visible(auto const &target_name, auto const &fn) const
{
for_each([&] (Assign const &assign) {
if (assign.visible() && target_name == assign.target_name())
if (assign.visible() && target_name == assign.target_name)
fn(assign); });
}

View File

@ -48,9 +48,7 @@ struct Window_layouter::Main : Operations,
Timer::Connection _drop_timer { _env };
enum class Drag_state { IDLE, DRAGGING, SETTLING };
Drag_state _drag_state { Drag_state::IDLE };
Drag _drag { };
Signal_handler<Main> _drop_timer_handler {
_env.ep(), *this, &Main::_handle_drop_timer };
@ -106,7 +104,7 @@ struct Window_layouter::Main : Operations,
_assign_list.for_each([&] (Assign &assign) {
_target_list.for_each([&] (Target const &target) {
if (target.name() != assign.target_name())
if (target.name() != assign.target_name)
return;
assign.for_each_member([&] (Assign::Member &member) {
@ -222,10 +220,12 @@ struct Window_layouter::Main : Operations,
void drag(Window_id id, Window::Element element, Point clicked, Point curr) override
{
if (_drag_state == Drag_state::SETTLING)
if (_drag.state == Drag::State::SETTLING)
return;
_drag_state = Drag_state::DRAGGING;
_target_list.with_target_at(curr, [&] (Target const &pointed_target) {
bool const moving = (element.type == Window::Element::TITLE);
_drag = { Drag::State::DRAGGING, moving, id, curr, pointed_target.name() }; });
to_front(id);
@ -252,7 +252,7 @@ struct Window_layouter::Main : Operations,
void _handle_drop_timer()
{
_drag_state = Drag_state::IDLE;
_drag = { };
_gen_rules();
@ -260,7 +260,7 @@ struct Window_layouter::Main : Operations,
window.finalize_drag_operation(); });
}
void finalize_drag(Window_id, Window::Element, Point, Point) override
void finalize_drag(Window_id id, Window::Element element, Point, Point curr) override
{
/*
* Update window layout because highlighting may have changed after the
@ -271,8 +271,35 @@ struct Window_layouter::Main : Operations,
*/
_handle_hover();
_drag_state = Drag_state::SETTLING;
_drag = { };
_target_list.with_target_at(curr, [&] (Target const &pointed_target) {
bool const moving = (element.type == Window::Element::TITLE);
_drag = { Drag::State::SETTLING, moving, id, curr, pointed_target.name() }; });
/*
* Update the target of the assign rule of the dragged window
*/
auto with_target_change = [&] (auto const fn)
{
_target_list.with_target(_assign_list, id, [&] (Target const &from) {
_target_list.with_target(_drag.target, [&] (Target const &to) {
if (&from != &to)
fn(from, to); }); });
};
if (_drag.moving) {
with_target_change([&] (Target const &from, Target const &to) {
_assign_list.for_each([&] (Assign &assign) {
Window *window_ptr = nullptr;
assign.for_each_member([&] (Assign::Member &member) {
if (member.window.id() == id)
window_ptr = &member.window; });
if (window_ptr) {
assign.target_name = to.name();
window_ptr->warp(from.geometry().at - to.geometry().at);
}
});
});
}
_drop_timer.trigger_once(250*1000);
}
@ -417,7 +444,7 @@ void Window_layouter::Main::_gen_window_layout()
});
_window_layout_reporter.generate([&] (Xml_generator &xml) {
_target_list.gen_layout(xml, _assign_list); });
_target_list.gen_layout(xml, _assign_list, _drag); });
}
@ -470,7 +497,7 @@ void Window_layouter::Main::_gen_rules_assignments(Xml_generator &xml, FN const
xml.node("assign", [&] () {
xml.attribute("label", member.window.label());
xml.attribute("target", assign.target_name());
xml.attribute("target", assign.target_name);
gen_window_geometry(xml, assign, member.window);
});
};
@ -608,7 +635,7 @@ void Window_layouter::Main::_handle_hover()
try {
Xml_node const hover_window_xml = _hover.xml().sub_node("window");
_user_state.hover(hover_window_xml.attribute_value("id", 0U),
_user_state.hover({ hover_window_xml.attribute_value("id", 0U) },
_element_from_hover_model(hover_window_xml));
}

View File

@ -138,7 +138,8 @@ class Window_layouter::Target_list
* \return layer that was processed by the method
*/
unsigned _gen_top_most_layer(Xml_generator &xml, unsigned min_layer,
Assign_list const &assignments) const
Assign_list const &assignments,
Drag const &drag) const
{
/* search targets for next matching layer */
unsigned layer = MAX_LAYER;
@ -146,6 +147,9 @@ class Window_layouter::Target_list
if (target.layer() >= min_layer && target.layer() <= layer)
layer = target.layer(); });
Rect const drag_origin_boundary = drag.dragging()
? target_boundary(assignments, drag.window_id)
: Rect { };
/* search target by name */
_targets.for_each([&] (Target const &target) {
@ -155,7 +159,7 @@ class Window_layouter::Target_list
if (!target.visible())
return;
if (assignments.target_empty(target.name()))
if (assignments.target_empty(target.name()) && !drag.moving_at_target(target.name()))
return;
Rect const boundary = target.geometry();
@ -163,11 +167,18 @@ class Window_layouter::Target_list
xml.attribute("name", target.name());
generate(xml, boundary);
/* visit all windows on the layer */
/* in-flux window node for the currently dragged window */
if (drag.moving_at_target(target.name()))
assignments.for_each([&] (Assign const &assign) {
assign.for_each_member([&] (Assign::Member const &member) {
if (drag.moving_window(member.window.id()))
member.window.generate(xml, drag_origin_boundary); }); });
/* visit all windows on the layer, except for the dragged one */
assignments.for_each_visible(target.name(), [&] (Assign const &assign) {
assign.for_each_member([&] (Assign::Member const &member) {
member.window.generate(xml, boundary); });
});
if (!drag.moving_window(member.window.id()))
member.window.generate(xml, boundary); }); });
});
});
@ -214,7 +225,8 @@ class Window_layouter::Target_list
});
}
void gen_layout(Xml_generator &xml, Assign_list const &assignments) const
void gen_layout(Xml_generator &xml, Assign_list const &assignments,
Drag const &drag) const
{
unsigned min_layer = 0;
@ -222,7 +234,7 @@ class Window_layouter::Target_list
for (;;) {
unsigned const layer =
_gen_top_most_layer(xml, min_layer, assignments);
_gen_top_most_layer(xml, min_layer, assignments, drag);
if (layer == MAX_LAYER)
break;
@ -273,8 +285,51 @@ class Window_layouter::Target_list
});
}
template <typename FN>
void for_each(FN const &fn) const { _targets.for_each(fn); }
void for_each(auto const &fn) const { _targets.for_each(fn); }
void with_target(Name const &name, auto const &fn) const
{
Target const *ptr = nullptr;
for_each([&] (Target const &target) {
if (target.name() == name)
ptr = &target; });
if (ptr)
fn(*ptr);
}
void with_target_at(Point const at, auto const &fn) const
{
Target const *ptr = nullptr;
for_each([&] (Target const &target) {
if (target.visible() && target.geometry().contains(at))
ptr = &target; });
if (ptr)
fn(*ptr);
}
void with_target(Assign_list const &assignments, Window_id id, auto const &fn) const
{
Target const *ptr = nullptr;
_targets.for_each([&] (Target const &target) {
assignments.for_each_visible(target.name(), [&] (Assign const &assign) {
assign.for_each_member([&] (Assign::Member const &member) {
if (member.window.id() == id)
ptr = &target; }); }); });
if (ptr)
fn(*ptr);
}
/**
* Return the boundary of the target that displays the given window
*/
Rect target_boundary(Assign_list const &assignments, Window_id id) const
{
Rect result { };
with_target(assignments, id, [&] (Target const &target) {
result = target.geometry(); });
return result;
}
};
#endif /* _TARGET_LIST_H_ */

View File

@ -28,10 +28,7 @@ namespace Window_layouter {
struct Window_id
{
unsigned value = 0;
Window_id() { }
Window_id(unsigned value) : value(value) { }
unsigned value;
bool valid() const { return value != 0; }
@ -86,6 +83,29 @@ namespace Window_layouter {
xml.attribute("width", rect.w());
xml.attribute("height", rect.h());
}
struct Drag
{
enum class State { IDLE, DRAGGING, SETTLING };
State state;
bool moving; /* distiguish moving from resizing */
Window_id window_id;
Point curr_pos;
Name target;
bool dragging() const { return state == State::DRAGGING; }
bool moving_at_target(Name const &name) const
{
return dragging() && name == target && moving;
}
bool moving_window(Window_id id) const
{
return dragging() && id == window_id && moving;
}
};
}
#endif /* _TYPES_H_ */

View File

@ -284,9 +284,8 @@ void Window_layouter::User_state::_handle_event(Input::Event const &e,
/* detect end of drag operation */
if (e.release() && _key_cnt == 0) {
_drag_state = false;
if (_dragged_window_id.valid()) {
if (_drag_state && _dragged_window_id.valid()) {
_drag_state = false;
/*
* Issue resize to 0x0 when releasing the the window closer

View File

@ -227,12 +227,18 @@ class Window_layouter::Window : public List_model<Window>::Element
int x1 = _orig_geometry.x1(), y1 = _orig_geometry.y1(),
x2 = _orig_geometry.x2(), y2 = _orig_geometry.y2();
if (_drag_left_border) x1 = min(x1 + offset.x, x2);
if (_drag_right_border) x2 = max(x2 + offset.x, x1);
if (_drag_top_border) y1 = min(y1 + offset.y, y2);
if (_drag_bottom_border) y2 = max(y2 + offset.y, y1);
/* restrict resizing to the window's target area */
Rect const outer { { }, _target_area };
Rect const inner = _decorator_margins.inner_geometry(outer);
_drag_geometry = Rect::compound(Point(x1, y1), Point(x2, y2));
auto clamped = [] (int v, int lowest, int highest) { return min(max(v, lowest), highest); };
if (_drag_left_border) x1 = clamped(min(x1 + offset.x, x2), inner.x1(), outer.x2());
if (_drag_right_border) x2 = clamped(max(x2 + offset.x, x1), outer.x1(), inner.x2());
if (_drag_top_border) y1 = clamped(min(y1 + offset.y, y2), inner.y1(), outer.y2());
if (_drag_bottom_border) y2 = clamped(max(y2 + offset.y, y1), outer.y1(), inner.y2());
_drag_geometry = Rect::compound(Point { x1, y1 }, Point { x2, y2 });
_dragged_size = _drag_geometry.area;
}
@ -480,6 +486,13 @@ class Window_layouter::Window : public List_model<Window>::Element
void target_area(Area area) { _target_area = area; };
void warp(Point const rel)
{
_geometry.at = _geometry.at + rel;
_orig_geometry.at = _orig_geometry.at + rel;
_drag_geometry.at = _drag_geometry.at + rel;
}
bool maximized() const { return _maximized; }
void maximized(bool maximized) { _maximized = maximized; }
@ -505,7 +518,7 @@ class Window_layouter::Window : public List_model<Window>::Element
*/
bool matches(Xml_node const &node) const
{
return node.attribute_value("id", 0U) == _id;
return node.attribute_value("id", 0U) == _id.value;
}
/**

View File

@ -54,11 +54,12 @@ class Window_layouter::Window_list
[&] (Xml_node const &node) -> Window &
{
unsigned const id = node.attribute_value("id", 0U);
Area const initial_size = Area::from_xml(node);
Window_id const id { node.attribute_value("id", 0U) };
Area const initial_size = Area::from_xml(node);
Window::Label const label =
node.attribute_value("label",Window::Label());
node.attribute_value("label", Window::Label());
return *new (_alloc)
Window(id, label, initial_size,