mirror of
https://github.com/swaywm/sway.git
synced 2024-11-29 19:31:29 +00:00
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
This commit is contained in:
parent
d65e67face
commit
f9a5c18c93
|
@ -16,6 +16,10 @@
|
||||||
// Thickness of the dropzone when dragging to the edge of a layout container
|
// Thickness of the dropzone when dragging to the edge of a layout container
|
||||||
#define DROP_LAYOUT_BORDER 30
|
#define DROP_LAYOUT_BORDER 30
|
||||||
|
|
||||||
|
// Thickness of indicator when dropping onto a titlebar. This should be a
|
||||||
|
// multiple of 2.
|
||||||
|
#define DROP_SPLIT_INDICATOR 10
|
||||||
|
|
||||||
struct seatop_move_tiling_event {
|
struct seatop_move_tiling_event {
|
||||||
struct sway_container *con;
|
struct sway_container *con;
|
||||||
struct sway_node *target_node;
|
struct sway_node *target_node;
|
||||||
|
@ -23,6 +27,8 @@ struct seatop_move_tiling_event {
|
||||||
struct wlr_box drop_box;
|
struct wlr_box drop_box;
|
||||||
double ref_lx, ref_ly; // cursor's x/y at start of op
|
double ref_lx, ref_ly; // cursor's x/y at start of op
|
||||||
bool threshold_reached;
|
bool threshold_reached;
|
||||||
|
bool split_target;
|
||||||
|
bool insert_after_target;
|
||||||
};
|
};
|
||||||
|
|
||||||
static void handle_render(struct sway_seat *seat,
|
static void handle_render(struct sway_seat *seat,
|
||||||
|
@ -92,8 +98,76 @@ static void resize_box(struct wlr_box *box, enum wlr_edges edge,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void split_border(double pos, int offset, int len, int n_children,
|
||||||
|
int avoid, int *out_pos, bool *out_after) {
|
||||||
|
int region = 2 * n_children * (pos - offset) / len;
|
||||||
|
// If the cursor is over the right side of a left-adjacent titlebar, or the
|
||||||
|
// left side of a right-adjacent titlebar, it's position when dropped will
|
||||||
|
// be the same. To avoid this, shift the region for adjacent containers.
|
||||||
|
if (avoid >= 0) {
|
||||||
|
if (region == 2 * avoid - 1 || region == 2 * avoid) {
|
||||||
|
region--;
|
||||||
|
} else if (region == 2 * avoid + 1 || region == 2 * avoid + 2) {
|
||||||
|
region++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int child_index = (region + 1) / 2;
|
||||||
|
*out_after = region % 2;
|
||||||
|
// When dropping at the beginning or end of a container, show the drop
|
||||||
|
// region within the container boundary, otherwise show it on top of the
|
||||||
|
// border between two titlebars.
|
||||||
|
if (child_index == 0) {
|
||||||
|
*out_pos = offset;
|
||||||
|
} else if (child_index == n_children) {
|
||||||
|
*out_pos = offset + len - DROP_SPLIT_INDICATOR;
|
||||||
|
} else {
|
||||||
|
*out_pos = offset + child_index * len / n_children -
|
||||||
|
DROP_SPLIT_INDICATOR / 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool split_titlebar(struct sway_node *node, struct sway_container *avoid,
|
||||||
|
struct wlr_cursor *cursor, struct wlr_box *title_box, bool *after) {
|
||||||
|
struct sway_container *con = node->sway_container;
|
||||||
|
struct sway_node *parent = &con->pending.parent->node;
|
||||||
|
int title_height = container_titlebar_height();
|
||||||
|
struct wlr_box box;
|
||||||
|
int n_children, avoid_index;
|
||||||
|
enum sway_container_layout layout =
|
||||||
|
parent ? node_get_layout(parent) : L_NONE;
|
||||||
|
if (layout == L_TABBED || layout == L_STACKED) {
|
||||||
|
node_get_box(parent, &box);
|
||||||
|
n_children = node_get_children(parent)->length;
|
||||||
|
avoid_index = list_find(node_get_children(parent), avoid);
|
||||||
|
} else {
|
||||||
|
node_get_box(node, &box);
|
||||||
|
n_children = 1;
|
||||||
|
avoid_index = -1;
|
||||||
|
}
|
||||||
|
if (layout == L_STACKED && cursor->y < box.y + title_height * n_children) {
|
||||||
|
// Drop into stacked titlebars.
|
||||||
|
title_box->width = box.width;
|
||||||
|
title_box->height = DROP_SPLIT_INDICATOR;
|
||||||
|
title_box->x = box.x;
|
||||||
|
split_border(cursor->y, box.y, title_height * n_children,
|
||||||
|
n_children, avoid_index, &title_box->y, after);
|
||||||
|
return true;
|
||||||
|
} else if (layout != L_STACKED && cursor->y < box.y + title_height) {
|
||||||
|
// Drop into side-by-side titlebars.
|
||||||
|
title_box->width = DROP_SPLIT_INDICATOR;
|
||||||
|
title_box->height = title_height;
|
||||||
|
title_box->y = box.y;
|
||||||
|
split_border(cursor->x, box.x, box.width, n_children,
|
||||||
|
avoid_index, &title_box->x, after);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
static void handle_motion_postthreshold(struct sway_seat *seat) {
|
static void handle_motion_postthreshold(struct sway_seat *seat) {
|
||||||
struct seatop_move_tiling_event *e = seat->seatop_data;
|
struct seatop_move_tiling_event *e = seat->seatop_data;
|
||||||
|
e->split_target = false;
|
||||||
struct wlr_surface *surface = NULL;
|
struct wlr_surface *surface = NULL;
|
||||||
double sx, sy;
|
double sx, sy;
|
||||||
struct sway_cursor *cursor = seat->cursor;
|
struct sway_cursor *cursor = seat->cursor;
|
||||||
|
@ -127,27 +201,53 @@ static void handle_motion_postthreshold(struct sway_seat *seat) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if the cursor is over a tilebar only if the destination
|
||||||
|
// container is not a descendant of the source container.
|
||||||
|
if (!surface && !container_has_ancestor(con, e->con) &&
|
||||||
|
split_titlebar(node, e->con, cursor->cursor,
|
||||||
|
&e->drop_box, &e->insert_after_target)) {
|
||||||
|
// Don't allow dropping over the source container's titlebar
|
||||||
|
// to give users a chance to cancel a drag operation.
|
||||||
|
if (con == e->con) {
|
||||||
|
e->target_node = NULL;
|
||||||
|
} else {
|
||||||
|
e->target_node = node;
|
||||||
|
e->split_target = true;
|
||||||
|
}
|
||||||
|
e->target_edge = WLR_EDGE_NONE;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Traverse the ancestors, trying to find a layout container perpendicular
|
// Traverse the ancestors, trying to find a layout container perpendicular
|
||||||
// to the edge. Eg. close to the top or bottom of a horiz layout.
|
// to the edge. Eg. close to the top or bottom of a horiz layout.
|
||||||
|
int thresh_top = con->pending.content_y + DROP_LAYOUT_BORDER;
|
||||||
|
int thresh_bottom = con->pending.content_y +
|
||||||
|
con->pending.content_height - DROP_LAYOUT_BORDER;
|
||||||
|
int thresh_left = con->pending.content_x + DROP_LAYOUT_BORDER;
|
||||||
|
int thresh_right = con->pending.content_x +
|
||||||
|
con->pending.content_width - DROP_LAYOUT_BORDER;
|
||||||
while (con) {
|
while (con) {
|
||||||
enum wlr_edges edge = WLR_EDGE_NONE;
|
enum wlr_edges edge = WLR_EDGE_NONE;
|
||||||
enum sway_container_layout layout = container_parent_layout(con);
|
enum sway_container_layout layout = container_parent_layout(con);
|
||||||
struct wlr_box parent;
|
struct wlr_box box;
|
||||||
con->pending.parent ? container_get_box(con->pending.parent, &parent) :
|
node_get_box(node_get_parent(&con->node), &box);
|
||||||
workspace_get_box(con->pending.workspace, &parent);
|
|
||||||
if (layout == L_HORIZ || layout == L_TABBED) {
|
if (layout == L_HORIZ || layout == L_TABBED) {
|
||||||
if (cursor->cursor->y < parent.y + DROP_LAYOUT_BORDER) {
|
if (cursor->cursor->y < thresh_top) {
|
||||||
edge = WLR_EDGE_TOP;
|
edge = WLR_EDGE_TOP;
|
||||||
} else if (cursor->cursor->y > parent.y + parent.height
|
box.height = thresh_top - box.y;
|
||||||
- DROP_LAYOUT_BORDER) {
|
} else if (cursor->cursor->y > thresh_bottom) {
|
||||||
edge = WLR_EDGE_BOTTOM;
|
edge = WLR_EDGE_BOTTOM;
|
||||||
|
box.height = box.y + box.height - thresh_bottom;
|
||||||
|
box.y = thresh_bottom;
|
||||||
}
|
}
|
||||||
} else if (layout == L_VERT || layout == L_STACKED) {
|
} else if (layout == L_VERT || layout == L_STACKED) {
|
||||||
if (cursor->cursor->x < parent.x + DROP_LAYOUT_BORDER) {
|
if (cursor->cursor->x < thresh_left) {
|
||||||
edge = WLR_EDGE_LEFT;
|
edge = WLR_EDGE_LEFT;
|
||||||
} else if (cursor->cursor->x > parent.x + parent.width
|
box.width = thresh_left - box.x;
|
||||||
- DROP_LAYOUT_BORDER) {
|
} else if (cursor->cursor->x > thresh_right) {
|
||||||
edge = WLR_EDGE_RIGHT;
|
edge = WLR_EDGE_RIGHT;
|
||||||
|
box.width = box.x + box.width - thresh_right;
|
||||||
|
box.x = thresh_right;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (edge) {
|
if (edge) {
|
||||||
|
@ -156,8 +256,7 @@ static void handle_motion_postthreshold(struct sway_seat *seat) {
|
||||||
e->target_node = node_get_parent(e->target_node);
|
e->target_node = node_get_parent(e->target_node);
|
||||||
}
|
}
|
||||||
e->target_edge = edge;
|
e->target_edge = edge;
|
||||||
node_get_box(e->target_node, &e->drop_box);
|
e->drop_box = box;
|
||||||
resize_box(&e->drop_box, edge, DROP_LAYOUT_BORDER);
|
|
||||||
desktop_damage_box(&e->drop_box);
|
desktop_damage_box(&e->drop_box);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -241,7 +340,8 @@ static void finalize_move(struct sway_seat *seat) {
|
||||||
target_node->sway_workspace : target_node->sway_container->pending.workspace;
|
target_node->sway_workspace : target_node->sway_container->pending.workspace;
|
||||||
enum wlr_edges edge = e->target_edge;
|
enum wlr_edges edge = e->target_edge;
|
||||||
int after = edge != WLR_EDGE_TOP && edge != WLR_EDGE_LEFT;
|
int after = edge != WLR_EDGE_TOP && edge != WLR_EDGE_LEFT;
|
||||||
bool swap = edge == WLR_EDGE_NONE && target_node->type == N_CONTAINER;
|
bool swap = edge == WLR_EDGE_NONE && target_node->type == N_CONTAINER &&
|
||||||
|
!e->split_target;
|
||||||
|
|
||||||
if (!swap) {
|
if (!swap) {
|
||||||
container_detach(con);
|
container_detach(con);
|
||||||
|
@ -250,6 +350,14 @@ static void finalize_move(struct sway_seat *seat) {
|
||||||
// Moving container into empty workspace
|
// Moving container into empty workspace
|
||||||
if (target_node->type == N_WORKSPACE && edge == WLR_EDGE_NONE) {
|
if (target_node->type == N_WORKSPACE && edge == WLR_EDGE_NONE) {
|
||||||
con = workspace_add_tiling(new_ws, con);
|
con = workspace_add_tiling(new_ws, con);
|
||||||
|
} else if (e->split_target) {
|
||||||
|
struct sway_container *target = target_node->sway_container;
|
||||||
|
enum sway_container_layout layout = container_parent_layout(target);
|
||||||
|
if (layout != L_TABBED && layout != L_STACKED) {
|
||||||
|
container_split(target, L_TABBED);
|
||||||
|
}
|
||||||
|
container_add_sibling(target, con, e->insert_after_target);
|
||||||
|
ipc_event_window(con, "move");
|
||||||
} else if (target_node->type == N_CONTAINER) {
|
} else if (target_node->type == N_CONTAINER) {
|
||||||
// Moving container before/after another
|
// Moving container before/after another
|
||||||
struct sway_container *target = target_node->sway_container;
|
struct sway_container *target = target_node->sway_container;
|
||||||
|
|
Loading…
Reference in a new issue