Skip to content

Commit

Permalink
window_layouter: drag windows between displays
Browse files Browse the repository at this point in the history
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
  • Loading branch information
nfeske committed Jan 30, 2025
1 parent b7c97a6 commit ad57ca1
Show file tree
Hide file tree
Showing 8 changed files with 157 additions and 44 deletions.
10 changes: 4 additions & 6 deletions repos/gems/src/app/window_layouter/assign.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 { };
Expand All @@ -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;
Expand All @@ -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);
Expand Down Expand Up @@ -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
*/
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion repos/gems/src/app/window_layouter/assign_list.h
Original file line number Diff line number Diff line change
Expand Up @@ -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); });
}

Expand Down
51 changes: 39 additions & 12 deletions repos/gems/src/app/window_layouter/main.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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);

Expand All @@ -252,15 +252,15 @@ struct Window_layouter::Main : Operations,

void _handle_drop_timer()
{
_drag_state = Drag_state::IDLE;
_drag = { };

_gen_rules();

_window_list.for_each_window([&] (Window &window) {
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
Expand All @@ -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);
}

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


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

Expand Down
73 changes: 64 additions & 9 deletions repos/gems/src/app/window_layouter/target_list.h
Original file line number Diff line number Diff line change
Expand Up @@ -138,14 +138,18 @@ 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;
_targets.for_each([&] (Target const &target) {
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) {

Expand All @@ -155,19 +159,26 @@ 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();
xml.node("boundary", [&] {
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); }); });
});
});

Expand Down Expand Up @@ -214,15 +225,16 @@ 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;

/* iterate over layers, starting at top-most layer (0) */
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;
Expand Down Expand Up @@ -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_ */
28 changes: 24 additions & 4 deletions repos/gems/src/app/window_layouter/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -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; }

Expand Down Expand Up @@ -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_ */
5 changes: 2 additions & 3 deletions repos/gems/src/app/window_layouter/user_state.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
25 changes: 19 additions & 6 deletions repos/gems/src/app/window_layouter/window.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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; }
Expand All @@ -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;
}

/**
Expand Down
Loading

0 comments on commit ad57ca1

Please sign in to comment.