diff options
author | 2021-04-22 20:01:09 -0700 | |
---|---|---|
committer | 2021-05-06 00:24:55 -0400 | |
commit | f9a5c18c93c4c72fdefbecb34d30464fa6276b41 (patch) | |
tree | 4e9274ff6a6d0a50f3cf03b688340dde70705d90 | |
parent | Added scroll_factor input variable to ipc output (diff) | |
download | sway-f9a5c18c93c4c72fdefbecb34d30464fa6276b41.tar.gz sway-f9a5c18c93c4c72fdefbecb34d30464fa6276b41.tar.zst sway-f9a5c18c93c4c72fdefbecb34d30464fa6276b41.zip |
Add tab dragging functionality
Implements functionality described in [1]. Please see the issue for a
video with a demonstration of the new behavior.
An issue is that titlebars cover up a significant portion of the top
edge drop area. The solution is simply to change the edge drop area
hitbox to start at the contents instead of the container.
[1] https://github.com/swaywm/sway/issues/6218
-rw-r--r-- | sway/input/seatop_move_tiling.c | 132 |
1 files changed, 120 insertions, 12 deletions
diff --git a/sway/input/seatop_move_tiling.c b/sway/input/seatop_move_tiling.c index 446612c6..223c6c08 100644 --- a/sway/input/seatop_move_tiling.c +++ b/sway/input/seatop_move_tiling.c | |||
@@ -16,6 +16,10 @@ | |||
16 | // Thickness of the dropzone when dragging to the edge of a layout container | 16 | // Thickness of the dropzone when dragging to the edge of a layout container |
17 | #define DROP_LAYOUT_BORDER 30 | 17 | #define DROP_LAYOUT_BORDER 30 |
18 | 18 | ||
19 | // Thickness of indicator when dropping onto a titlebar. This should be a | ||
20 | // multiple of 2. | ||
21 | #define DROP_SPLIT_INDICATOR 10 | ||
22 | |||
19 | struct seatop_move_tiling_event { | 23 | struct seatop_move_tiling_event { |
20 | struct sway_container *con; | 24 | struct sway_container *con; |
21 | struct sway_node *target_node; | 25 | struct sway_node *target_node; |
@@ -23,6 +27,8 @@ struct seatop_move_tiling_event { | |||
23 | struct wlr_box drop_box; | 27 | struct wlr_box drop_box; |
24 | double ref_lx, ref_ly; // cursor's x/y at start of op | 28 | double ref_lx, ref_ly; // cursor's x/y at start of op |
25 | bool threshold_reached; | 29 | bool threshold_reached; |
30 | bool split_target; | ||
31 | bool insert_after_target; | ||
26 | }; | 32 | }; |
27 | 33 | ||
28 | static void handle_render(struct sway_seat *seat, | 34 | static void handle_render(struct sway_seat *seat, |
@@ -92,8 +98,76 @@ static void resize_box(struct wlr_box *box, enum wlr_edges edge, | |||
92 | } | 98 | } |
93 | } | 99 | } |
94 | 100 | ||
101 | static void split_border(double pos, int offset, int len, int n_children, | ||
102 | int avoid, int *out_pos, bool *out_after) { | ||
103 | int region = 2 * n_children * (pos - offset) / len; | ||
104 | // If the cursor is over the right side of a left-adjacent titlebar, or the | ||
105 | // left side of a right-adjacent titlebar, it's position when dropped will | ||
106 | // be the same. To avoid this, shift the region for adjacent containers. | ||
107 | if (avoid >= 0) { | ||
108 | if (region == 2 * avoid - 1 || region == 2 * avoid) { | ||
109 | region--; | ||
110 | } else if (region == 2 * avoid + 1 || region == 2 * avoid + 2) { | ||
111 | region++; | ||
112 | } | ||
113 | } | ||
114 | |||
115 | int child_index = (region + 1) / 2; | ||
116 | *out_after = region % 2; | ||
117 | // When dropping at the beginning or end of a container, show the drop | ||
118 | // region within the container boundary, otherwise show it on top of the | ||
119 | // border between two titlebars. | ||
120 | if (child_index == 0) { | ||
121 | *out_pos = offset; | ||
122 | } else if (child_index == n_children) { | ||
123 | *out_pos = offset + len - DROP_SPLIT_INDICATOR; | ||
124 | } else { | ||
125 | *out_pos = offset + child_index * len / n_children - | ||
126 | DROP_SPLIT_INDICATOR / 2; | ||
127 | } | ||
128 | } | ||
129 | |||
130 | static bool split_titlebar(struct sway_node *node, struct sway_container *avoid, | ||
131 | struct wlr_cursor *cursor, struct wlr_box *title_box, bool *after) { | ||
132 | struct sway_container *con = node->sway_container; | ||
133 | struct sway_node *parent = &con->pending.parent->node; | ||
134 | int title_height = container_titlebar_height(); | ||
135 | struct wlr_box box; | ||
136 | int n_children, avoid_index; | ||
137 | enum sway_container_layout layout = | ||
138 | parent ? node_get_layout(parent) : L_NONE; | ||
139 | if (layout == L_TABBED || layout == L_STACKED) { | ||
140 | node_get_box(parent, &box); | ||
141 | n_children = node_get_children(parent)->length; | ||
142 | avoid_index = list_find(node_get_children(parent), avoid); | ||
143 | } else { | ||
144 | node_get_box(node, &box); | ||
145 | n_children = 1; | ||
146 | avoid_index = -1; | ||
147 | } | ||
148 | if (layout == L_STACKED && cursor->y < box.y + title_height * n_children) { | ||
149 | // Drop into stacked titlebars. | ||
150 | title_box->width = box.width; | ||
151 | title_box->height = DROP_SPLIT_INDICATOR; | ||
152 | title_box->x = box.x; | ||
153 | split_border(cursor->y, box.y, title_height * n_children, | ||
154 | n_children, avoid_index, &title_box->y, after); | ||
155 | return true; | ||
156 | } else if (layout != L_STACKED && cursor->y < box.y + title_height) { | ||
157 | // Drop into side-by-side titlebars. | ||
158 | title_box->width = DROP_SPLIT_INDICATOR; | ||
159 | title_box->height = title_height; | ||
160 | title_box->y = box.y; | ||
161 | split_border(cursor->x, box.x, box.width, n_children, | ||
162 | avoid_index, &title_box->x, after); | ||
163 | return true; | ||
164 | } | ||
165 | return false; | ||
166 | } | ||
167 | |||
95 | static void handle_motion_postthreshold(struct sway_seat *seat) { | 168 | static void handle_motion_postthreshold(struct sway_seat *seat) { |
96 | struct seatop_move_tiling_event *e = seat->seatop_data; | 169 | struct seatop_move_tiling_event *e = seat->seatop_data; |
170 | e->split_target = false; | ||
97 | struct wlr_surface *surface = NULL; | 171 | struct wlr_surface *surface = NULL; |
98 | double sx, sy; | 172 | double sx, sy; |
99 | struct sway_cursor *cursor = seat->cursor; | 173 | struct sway_cursor *cursor = seat->cursor; |
@@ -127,27 +201,53 @@ static void handle_motion_postthreshold(struct sway_seat *seat) { | |||
127 | return; | 201 | return; |
128 | } | 202 | } |
129 | 203 | ||
204 | // Check if the cursor is over a tilebar only if the destination | ||
205 | // container is not a descendant of the source container. | ||
206 | if (!surface && !container_has_ancestor(con, e->con) && | ||
207 | split_titlebar(node, e->con, cursor->cursor, | ||
208 | &e->drop_box, &e->insert_after_target)) { | ||
209 | // Don't allow dropping over the source container's titlebar | ||
210 | // to give users a chance to cancel a drag operation. | ||
211 | if (con == e->con) { | ||
212 | e->target_node = NULL; | ||
213 | } else { | ||
214 | e->target_node = node; | ||
215 | e->split_target = true; | ||
216 | } | ||
217 | e->target_edge = WLR_EDGE_NONE; | ||
218 | return; | ||
219 | } | ||
220 | |||
130 | // Traverse the ancestors, trying to find a layout container perpendicular | 221 | // Traverse the ancestors, trying to find a layout container perpendicular |
131 | // to the edge. Eg. close to the top or bottom of a horiz layout. | 222 | // to the edge. Eg. close to the top or bottom of a horiz layout. |
223 | int thresh_top = con->pending.content_y + DROP_LAYOUT_BORDER; | ||
224 | int thresh_bottom = con->pending.content_y + | ||
225 | con->pending.content_height - DROP_LAYOUT_BORDER; | ||
226 | int thresh_left = con->pending.content_x + DROP_LAYOUT_BORDER; | ||
227 | int thresh_right = con->pending.content_x + | ||
228 | con->pending.content_width - DROP_LAYOUT_BORDER; | ||
132 | while (con) { | 229 | while (con) { |
133 | enum wlr_edges edge = WLR_EDGE_NONE; | 230 | enum wlr_edges edge = WLR_EDGE_NONE; |
134 | enum sway_container_layout layout = container_parent_layout(con); | 231 | enum sway_container_layout layout = container_parent_layout(con); |
135 | struct wlr_box parent; | 232 | struct wlr_box box; |
136 | con->pending.parent ? container_get_box(con->pending.parent, &parent) : | 233 | node_get_box(node_get_parent(&con->node), &box); |
137 | workspace_get_box(con->pending.workspace, &parent); | ||
138 | if (layout == L_HORIZ || layout == L_TABBED) { | 234 | if (layout == L_HORIZ || layout == L_TABBED) { |
139 | if (cursor->cursor->y < parent.y + DROP_LAYOUT_BORDER) { | 235 | if (cursor->cursor->y < thresh_top) { |
140 | edge = WLR_EDGE_TOP; | 236 | edge = WLR_EDGE_TOP; |
141 | } else if (cursor->cursor->y > parent.y + parent.height | 237 | box.height = thresh_top - box.y; |
142 | - DROP_LAYOUT_BORDER) { | 238 | } else if (cursor->cursor->y > thresh_bottom) { |
143 | edge = WLR_EDGE_BOTTOM; | 239 | edge = WLR_EDGE_BOTTOM; |
240 | box.height = box.y + box.height - thresh_bottom; | ||
241 | box.y = thresh_bottom; | ||
144 | } | 242 | } |
145 | } else if (layout == L_VERT || layout == L_STACKED) { | 243 | } else if (layout == L_VERT || layout == L_STACKED) { |
146 | if (cursor->cursor->x < parent.x + DROP_LAYOUT_BORDER) { | 244 | if (cursor->cursor->x < thresh_left) { |
147 | edge = WLR_EDGE_LEFT; | 245 | edge = WLR_EDGE_LEFT; |
148 | } else if (cursor->cursor->x > parent.x + parent.width | 246 | box.width = thresh_left - box.x; |
149 | - DROP_LAYOUT_BORDER) { | 247 | } else if (cursor->cursor->x > thresh_right) { |
150 | edge = WLR_EDGE_RIGHT; | 248 | edge = WLR_EDGE_RIGHT; |
249 | box.width = box.x + box.width - thresh_right; | ||
250 | box.x = thresh_right; | ||
151 | } | 251 | } |
152 | } | 252 | } |
153 | if (edge) { | 253 | if (edge) { |
@@ -156,8 +256,7 @@ static void handle_motion_postthreshold(struct sway_seat *seat) { | |||
156 | e->target_node = node_get_parent(e->target_node); | 256 | e->target_node = node_get_parent(e->target_node); |
157 | } | 257 | } |
158 | e->target_edge = edge; | 258 | e->target_edge = edge; |
159 | node_get_box(e->target_node, &e->drop_box); | 259 | e->drop_box = box; |
160 | resize_box(&e->drop_box, edge, DROP_LAYOUT_BORDER); | ||
161 | desktop_damage_box(&e->drop_box); | 260 | desktop_damage_box(&e->drop_box); |
162 | return; | 261 | return; |
163 | } | 262 | } |
@@ -241,7 +340,8 @@ static void finalize_move(struct sway_seat *seat) { | |||
241 | target_node->sway_workspace : target_node->sway_container->pending.workspace; | 340 | target_node->sway_workspace : target_node->sway_container->pending.workspace; |
242 | enum wlr_edges edge = e->target_edge; | 341 | enum wlr_edges edge = e->target_edge; |
243 | int after = edge != WLR_EDGE_TOP && edge != WLR_EDGE_LEFT; | 342 | int after = edge != WLR_EDGE_TOP && edge != WLR_EDGE_LEFT; |
244 | bool swap = edge == WLR_EDGE_NONE && target_node->type == N_CONTAINER; | 343 | bool swap = edge == WLR_EDGE_NONE && target_node->type == N_CONTAINER && |
344 | !e->split_target; | ||
245 | 345 | ||
246 | if (!swap) { | 346 | if (!swap) { |
247 | container_detach(con); | 347 | container_detach(con); |
@@ -250,6 +350,14 @@ static void finalize_move(struct sway_seat *seat) { | |||
250 | // Moving container into empty workspace | 350 | // Moving container into empty workspace |
251 | if (target_node->type == N_WORKSPACE && edge == WLR_EDGE_NONE) { | 351 | if (target_node->type == N_WORKSPACE && edge == WLR_EDGE_NONE) { |
252 | con = workspace_add_tiling(new_ws, con); | 352 | con = workspace_add_tiling(new_ws, con); |
353 | } else if (e->split_target) { | ||
354 | struct sway_container *target = target_node->sway_container; | ||
355 | enum sway_container_layout layout = container_parent_layout(target); | ||
356 | if (layout != L_TABBED && layout != L_STACKED) { | ||
357 | container_split(target, L_TABBED); | ||
358 | } | ||
359 | container_add_sibling(target, con, e->insert_after_target); | ||
360 | ipc_event_window(con, "move"); | ||
253 | } else if (target_node->type == N_CONTAINER) { | 361 | } else if (target_node->type == N_CONTAINER) { |
254 | // Moving container before/after another | 362 | // Moving container before/after another |
255 | struct sway_container *target = target_node->sway_container; | 363 | struct sway_container *target = target_node->sway_container; |