diff options
Diffstat (limited to 'sway/input/seatop_move_tiling.c')
-rw-r--r-- | sway/input/seatop_move_tiling.c | 335 |
1 files changed, 335 insertions, 0 deletions
diff --git a/sway/input/seatop_move_tiling.c b/sway/input/seatop_move_tiling.c new file mode 100644 index 00000000..8b541f80 --- /dev/null +++ b/sway/input/seatop_move_tiling.c | |||
@@ -0,0 +1,335 @@ | |||
1 | #define _POSIX_C_SOURCE 200809L | ||
2 | #include <limits.h> | ||
3 | #include <wlr/types/wlr_cursor.h> | ||
4 | #include <wlr/util/edges.h> | ||
5 | #include "sway/desktop.h" | ||
6 | #include "sway/input/cursor.h" | ||
7 | #include "sway/input/seat.h" | ||
8 | #include "sway/output.h" | ||
9 | #include "sway/tree/arrange.h" | ||
10 | #include "sway/tree/node.h" | ||
11 | #include "sway/tree/view.h" | ||
12 | #include "sway/tree/workspace.h" | ||
13 | |||
14 | // Thickness of the dropzone when dragging to the edge of a layout container | ||
15 | #define DROP_LAYOUT_BORDER 30 | ||
16 | |||
17 | struct seatop_move_tiling_event { | ||
18 | struct sway_container *con; | ||
19 | struct sway_node *target_node; | ||
20 | enum wlr_edges target_edge; | ||
21 | struct wlr_box drop_box; | ||
22 | double ref_lx, ref_ly; // cursor's x/y at start of op | ||
23 | bool threshold_reached; | ||
24 | }; | ||
25 | |||
26 | static void handle_render(struct sway_seat *seat, | ||
27 | struct sway_output *output, pixman_region32_t *damage) { | ||
28 | struct seatop_move_tiling_event *e = seat->seatop_data; | ||
29 | if (!e->threshold_reached) { | ||
30 | return; | ||
31 | } | ||
32 | if (e->target_node && node_get_output(e->target_node) == output) { | ||
33 | float color[4]; | ||
34 | memcpy(&color, config->border_colors.focused.indicator, | ||
35 | sizeof(float) * 4); | ||
36 | premultiply_alpha(color, 0.5); | ||
37 | struct wlr_box box; | ||
38 | memcpy(&box, &e->drop_box, sizeof(struct wlr_box)); | ||
39 | scale_box(&box, output->wlr_output->scale); | ||
40 | render_rect(output->wlr_output, damage, &box, color); | ||
41 | } | ||
42 | } | ||
43 | |||
44 | static void handle_motion_prethreshold(struct sway_seat *seat) { | ||
45 | struct seatop_move_tiling_event *e = seat->seatop_data; | ||
46 | double cx = seat->cursor->cursor->x; | ||
47 | double cy = seat->cursor->cursor->y; | ||
48 | double sx = e->ref_lx; | ||
49 | double sy = e->ref_ly; | ||
50 | |||
51 | // Get the scaled threshold for the output. Even if the operation goes | ||
52 | // across multiple outputs of varying scales, just use the scale for the | ||
53 | // output that the cursor is currently on for simplicity. | ||
54 | struct wlr_output *wlr_output = wlr_output_layout_output_at( | ||
55 | root->output_layout, cx, cy); | ||
56 | double output_scale = wlr_output ? wlr_output->scale : 1; | ||
57 | double threshold = config->tiling_drag_threshold * output_scale; | ||
58 | threshold *= threshold; | ||
59 | |||
60 | // If the threshold has been exceeded, start the actual drag | ||
61 | if ((cx - sx) * (cx - sx) + (cy - sy) * (cy - sy) > threshold) { | ||
62 | e->threshold_reached = true; | ||
63 | cursor_set_image(seat->cursor, "grab", NULL); | ||
64 | } | ||
65 | } | ||
66 | |||
67 | static void resize_box(struct wlr_box *box, enum wlr_edges edge, | ||
68 | int thickness) { | ||
69 | switch (edge) { | ||
70 | case WLR_EDGE_TOP: | ||
71 | box->height = thickness; | ||
72 | break; | ||
73 | case WLR_EDGE_LEFT: | ||
74 | box->width = thickness; | ||
75 | break; | ||
76 | case WLR_EDGE_RIGHT: | ||
77 | box->x = box->x + box->width - thickness; | ||
78 | box->width = thickness; | ||
79 | break; | ||
80 | case WLR_EDGE_BOTTOM: | ||
81 | box->y = box->y + box->height - thickness; | ||
82 | box->height = thickness; | ||
83 | break; | ||
84 | case WLR_EDGE_NONE: | ||
85 | box->x += thickness; | ||
86 | box->y += thickness; | ||
87 | box->width -= thickness * 2; | ||
88 | box->height -= thickness * 2; | ||
89 | break; | ||
90 | } | ||
91 | } | ||
92 | |||
93 | static void handle_motion_postthreshold(struct sway_seat *seat) { | ||
94 | struct seatop_move_tiling_event *e = seat->seatop_data; | ||
95 | struct wlr_surface *surface = NULL; | ||
96 | double sx, sy; | ||
97 | struct sway_cursor *cursor = seat->cursor; | ||
98 | struct sway_node *node = node_at_coords(seat, | ||
99 | cursor->cursor->x, cursor->cursor->y, &surface, &sx, &sy); | ||
100 | // Damage the old location | ||
101 | desktop_damage_box(&e->drop_box); | ||
102 | |||
103 | if (!node) { | ||
104 | // Eg. hovered over a layer surface such as swaybar | ||
105 | e->target_node = NULL; | ||
106 | e->target_edge = WLR_EDGE_NONE; | ||
107 | return; | ||
108 | } | ||
109 | |||
110 | if (node->type == N_WORKSPACE) { | ||
111 | // Emtpy workspace | ||
112 | e->target_node = node; | ||
113 | e->target_edge = WLR_EDGE_NONE; | ||
114 | workspace_get_box(node->sway_workspace, &e->drop_box); | ||
115 | desktop_damage_box(&e->drop_box); | ||
116 | return; | ||
117 | } | ||
118 | |||
119 | // Deny moving within own workspace if this is the only child | ||
120 | struct sway_container *con = node->sway_container; | ||
121 | if (workspace_num_tiling_views(e->con->workspace) == 1 && | ||
122 | con->workspace == e->con->workspace) { | ||
123 | e->target_node = NULL; | ||
124 | e->target_edge = WLR_EDGE_NONE; | ||
125 | return; | ||
126 | } | ||
127 | |||
128 | // Traverse the ancestors, trying to find a layout container perpendicular | ||
129 | // to the edge. Eg. close to the top or bottom of a horiz layout. | ||
130 | while (con) { | ||
131 | enum wlr_edges edge = WLR_EDGE_NONE; | ||
132 | enum sway_container_layout layout = container_parent_layout(con); | ||
133 | struct wlr_box parent; | ||
134 | con->parent ? container_get_box(con->parent, &parent) : | ||
135 | workspace_get_box(con->workspace, &parent); | ||
136 | if (layout == L_HORIZ || layout == L_TABBED) { | ||
137 | if (cursor->cursor->y < parent.y + DROP_LAYOUT_BORDER) { | ||
138 | edge = WLR_EDGE_TOP; | ||
139 | } else if (cursor->cursor->y > parent.y + parent.height | ||
140 | - DROP_LAYOUT_BORDER) { | ||
141 | edge = WLR_EDGE_BOTTOM; | ||
142 | } | ||
143 | } else if (layout == L_VERT || layout == L_STACKED) { | ||
144 | if (cursor->cursor->x < parent.x + DROP_LAYOUT_BORDER) { | ||
145 | edge = WLR_EDGE_LEFT; | ||
146 | } else if (cursor->cursor->x > parent.x + parent.width | ||
147 | - DROP_LAYOUT_BORDER) { | ||
148 | edge = WLR_EDGE_RIGHT; | ||
149 | } | ||
150 | } | ||
151 | if (edge) { | ||
152 | e->target_node = node_get_parent(&con->node); | ||
153 | e->target_edge = edge; | ||
154 | node_get_box(e->target_node, &e->drop_box); | ||
155 | resize_box(&e->drop_box, edge, DROP_LAYOUT_BORDER); | ||
156 | desktop_damage_box(&e->drop_box); | ||
157 | return; | ||
158 | } | ||
159 | con = con->parent; | ||
160 | } | ||
161 | |||
162 | // Use the hovered view - but we must be over the actual surface | ||
163 | con = node->sway_container; | ||
164 | if (!con->view->surface || node == &e->con->node) { | ||
165 | e->target_node = NULL; | ||
166 | e->target_edge = WLR_EDGE_NONE; | ||
167 | return; | ||
168 | } | ||
169 | |||
170 | // Find the closest edge | ||
171 | size_t thickness = fmin(con->content_width, con->content_height) * 0.3; | ||
172 | size_t closest_dist = INT_MAX; | ||
173 | size_t dist; | ||
174 | e->target_edge = WLR_EDGE_NONE; | ||
175 | if ((dist = cursor->cursor->y - con->y) < closest_dist) { | ||
176 | closest_dist = dist; | ||
177 | e->target_edge = WLR_EDGE_TOP; | ||
178 | } | ||
179 | if ((dist = cursor->cursor->x - con->x) < closest_dist) { | ||
180 | closest_dist = dist; | ||
181 | e->target_edge = WLR_EDGE_LEFT; | ||
182 | } | ||
183 | if ((dist = con->x + con->width - cursor->cursor->x) < closest_dist) { | ||
184 | closest_dist = dist; | ||
185 | e->target_edge = WLR_EDGE_RIGHT; | ||
186 | } | ||
187 | if ((dist = con->y + con->height - cursor->cursor->y) < closest_dist) { | ||
188 | closest_dist = dist; | ||
189 | e->target_edge = WLR_EDGE_BOTTOM; | ||
190 | } | ||
191 | |||
192 | if (closest_dist > thickness) { | ||
193 | e->target_edge = WLR_EDGE_NONE; | ||
194 | } | ||
195 | |||
196 | e->target_node = node; | ||
197 | e->drop_box.x = con->content_x; | ||
198 | e->drop_box.y = con->content_y; | ||
199 | e->drop_box.width = con->content_width; | ||
200 | e->drop_box.height = con->content_height; | ||
201 | resize_box(&e->drop_box, e->target_edge, thickness); | ||
202 | desktop_damage_box(&e->drop_box); | ||
203 | } | ||
204 | |||
205 | static void handle_motion(struct sway_seat *seat, uint32_t time_msec) { | ||
206 | struct seatop_move_tiling_event *e = seat->seatop_data; | ||
207 | if (e->threshold_reached) { | ||
208 | handle_motion_postthreshold(seat); | ||
209 | } else { | ||
210 | handle_motion_prethreshold(seat); | ||
211 | } | ||
212 | } | ||
213 | |||
214 | static void handle_abort(struct sway_seat *seat) { | ||
215 | cursor_set_image(seat->cursor, "left_ptr", NULL); | ||
216 | } | ||
217 | |||
218 | static bool is_parallel(enum sway_container_layout layout, | ||
219 | enum wlr_edges edge) { | ||
220 | bool layout_is_horiz = layout == L_HORIZ || layout == L_TABBED; | ||
221 | bool edge_is_horiz = edge == WLR_EDGE_LEFT || edge == WLR_EDGE_RIGHT; | ||
222 | return layout_is_horiz == edge_is_horiz; | ||
223 | } | ||
224 | |||
225 | static void handle_finish(struct sway_seat *seat) { | ||
226 | struct seatop_move_tiling_event *e = seat->seatop_data; | ||
227 | |||
228 | if (!e->target_node) { | ||
229 | handle_abort(seat); | ||
230 | return; | ||
231 | } | ||
232 | |||
233 | struct sway_container *con = e->con; | ||
234 | struct sway_container *old_parent = con->parent; | ||
235 | struct sway_workspace *old_ws = con->workspace; | ||
236 | struct sway_node *target_node = e->target_node; | ||
237 | struct sway_workspace *new_ws = target_node->type == N_WORKSPACE ? | ||
238 | target_node->sway_workspace : target_node->sway_container->workspace; | ||
239 | enum wlr_edges edge = e->target_edge; | ||
240 | int after = edge != WLR_EDGE_TOP && edge != WLR_EDGE_LEFT; | ||
241 | |||
242 | container_detach(con); | ||
243 | |||
244 | // Moving container into empty workspace | ||
245 | if (target_node->type == N_WORKSPACE && edge == WLR_EDGE_NONE) { | ||
246 | workspace_add_tiling(new_ws, con); | ||
247 | } else if (target_node->type == N_CONTAINER) { | ||
248 | // Moving container before/after another | ||
249 | struct sway_container *target = target_node->sway_container; | ||
250 | enum sway_container_layout layout = container_parent_layout(target); | ||
251 | if (edge && !is_parallel(layout, edge)) { | ||
252 | enum sway_container_layout new_layout = edge == WLR_EDGE_TOP || | ||
253 | edge == WLR_EDGE_BOTTOM ? L_VERT : L_HORIZ; | ||
254 | container_split(target, new_layout); | ||
255 | } | ||
256 | container_add_sibling(target, con, after); | ||
257 | } else { | ||
258 | // Target is a workspace which requires splitting | ||
259 | enum sway_container_layout new_layout = edge == WLR_EDGE_TOP || | ||
260 | edge == WLR_EDGE_BOTTOM ? L_VERT : L_HORIZ; | ||
261 | workspace_split(new_ws, new_layout); | ||
262 | workspace_insert_tiling(new_ws, con, after); | ||
263 | } | ||
264 | |||
265 | if (old_parent) { | ||
266 | container_reap_empty(old_parent); | ||
267 | } | ||
268 | |||
269 | // This is a bit dirty, but we'll set the dimensions to that of a sibling. | ||
270 | // I don't think there's any other way to make it consistent without | ||
271 | // changing how we auto-size containers. | ||
272 | list_t *siblings = container_get_siblings(con); | ||
273 | if (siblings->length > 1) { | ||
274 | int index = list_find(siblings, con); | ||
275 | struct sway_container *sibling = index == 0 ? | ||
276 | siblings->items[1] : siblings->items[index - 1]; | ||
277 | con->width = sibling->width; | ||
278 | con->height = sibling->height; | ||
279 | } | ||
280 | |||
281 | arrange_workspace(old_ws); | ||
282 | if (new_ws != old_ws) { | ||
283 | arrange_workspace(new_ws); | ||
284 | } | ||
285 | |||
286 | cursor_set_image(seat->cursor, "left_ptr", NULL); | ||
287 | } | ||
288 | |||
289 | static void handle_unref(struct sway_seat *seat, struct sway_container *con) { | ||
290 | struct seatop_move_tiling_event *e = seat->seatop_data; | ||
291 | if (e->target_node == &con->node) { // Drop target | ||
292 | e->target_node = NULL; | ||
293 | } | ||
294 | if (e->con == con) { // The container being moved | ||
295 | seatop_abort(seat); | ||
296 | } | ||
297 | } | ||
298 | |||
299 | static const struct sway_seatop_impl seatop_impl = { | ||
300 | .motion = handle_motion, | ||
301 | .finish = handle_finish, | ||
302 | .abort = handle_abort, | ||
303 | .unref = handle_unref, | ||
304 | .render = handle_render, | ||
305 | }; | ||
306 | |||
307 | void seatop_begin_move_tiling_threshold(struct sway_seat *seat, | ||
308 | struct sway_container *con, uint32_t button) { | ||
309 | seatop_abort(seat); | ||
310 | |||
311 | struct seatop_move_tiling_event *e = | ||
312 | calloc(1, sizeof(struct seatop_move_tiling_event)); | ||
313 | if (!e) { | ||
314 | return; | ||
315 | } | ||
316 | e->con = con; | ||
317 | e->ref_lx = seat->cursor->cursor->x; | ||
318 | e->ref_ly = seat->cursor->cursor->y; | ||
319 | |||
320 | seat->seatop_impl = &seatop_impl; | ||
321 | seat->seatop_data = e; | ||
322 | seat->seatop_button = button; | ||
323 | |||
324 | container_raise_floating(con); | ||
325 | } | ||
326 | |||
327 | void seatop_begin_move_tiling(struct sway_seat *seat, | ||
328 | struct sway_container *con, uint32_t button) { | ||
329 | seatop_begin_move_tiling_threshold(seat, con, button); | ||
330 | struct seatop_move_tiling_event *e = seat->seatop_data; | ||
331 | if (e) { | ||
332 | e->threshold_reached = true; | ||
333 | cursor_set_image(seat->cursor, "grab", NULL); | ||
334 | } | ||
335 | } | ||