236f26f62e
When querying for an adjacent output we now need an absolute position in order to know which adjacent output that matches. (The position is either the current mouse position or the center of the currently focused container, depending on context.) If two outputs have one edge each that at least partially align with each other they now count as adjacent. Seamless mouse is affected by this and now properly moves and positions itself between outputs with "uneven" placement (as long as they have at least some part of the edge adjacent to each other). When focusing or moving a container in a specified direction the center of the current focused container decides where to look for an adjacent output. So if e.g. an output has two adjacent outputs to the right and a "focus right" command is issued then it's the placement of the currently focused container that decides which output actually gets focused. Also, if an output has at least one output adjacent in some direction but the entire edge is not covered (ie. it has "holes" with no outputs), then the algorithm will choose the output that is closest to the currently focused container (this does not apply to seamless mouse, the pointer will just stop at the edge in that case).
609 lines
17 KiB
C
609 lines
17 KiB
C
#include <stdlib.h>
|
|
#include <stdbool.h>
|
|
#include <wlc/wlc.h>
|
|
#include "layout.h"
|
|
#include "log.h"
|
|
#include "list.h"
|
|
#include "config.h"
|
|
#include "container.h"
|
|
#include "workspace.h"
|
|
#include "focus.h"
|
|
#include "output.h"
|
|
|
|
swayc_t root_container;
|
|
list_t *scratchpad;
|
|
|
|
int min_sane_h = 60;
|
|
int min_sane_w = 100;
|
|
|
|
void init_layout(void) {
|
|
root_container.type = C_ROOT;
|
|
root_container.layout = L_NONE;
|
|
root_container.children = create_list();
|
|
root_container.handle = -1;
|
|
root_container.visible = true;
|
|
scratchpad = create_list();
|
|
}
|
|
|
|
int index_child(const swayc_t *child) {
|
|
swayc_t *parent = child->parent;
|
|
int i, len;
|
|
if (!child->is_floating) {
|
|
len = parent->children->length;
|
|
for (i = 0; i < len; ++i) {
|
|
if (parent->children->items[i] == child) {
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
len = parent->floating->length;
|
|
for (i = 0; i < len; ++i) {
|
|
if (parent->floating->items[i] == child) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!sway_assert(i < len, "Stray container")) {
|
|
return -1;
|
|
}
|
|
return i;
|
|
}
|
|
|
|
void add_child(swayc_t *parent, swayc_t *child) {
|
|
sway_log(L_DEBUG, "Adding %p (%d, %fx%f) to %p (%d, %fx%f)", child, child->type,
|
|
child->width, child->height, parent, parent->type, parent->width, parent->height);
|
|
list_add(parent->children, child);
|
|
child->parent = parent;
|
|
// set focus for this container
|
|
if (!parent->focused) {
|
|
parent->focused = child;
|
|
}
|
|
}
|
|
|
|
void insert_child(swayc_t *parent, swayc_t *child, int index) {
|
|
if (index > parent->children->length) {
|
|
index = parent->children->length;
|
|
}
|
|
if (index < 0) {
|
|
index = 0;
|
|
}
|
|
list_insert(parent->children, index, child);
|
|
child->parent = parent;
|
|
if (!parent->focused) {
|
|
parent->focused = child;
|
|
}
|
|
}
|
|
|
|
void add_floating(swayc_t *ws, swayc_t *child) {
|
|
sway_log(L_DEBUG, "Adding %p (%d, %fx%f) to %p (%d, %fx%f)", child, child->type,
|
|
child->width, child->height, ws, ws->type, ws->width, ws->height);
|
|
if (!sway_assert(ws->type == C_WORKSPACE, "Must be of workspace type")) {
|
|
return;
|
|
}
|
|
list_add(ws->floating, child);
|
|
child->parent = ws;
|
|
child->is_floating = true;
|
|
if (!ws->focused) {
|
|
ws->focused = child;
|
|
}
|
|
}
|
|
|
|
swayc_t *add_sibling(swayc_t *sibling, swayc_t *child) {
|
|
swayc_t *parent = sibling->parent;
|
|
int i = index_child(sibling);
|
|
list_insert(parent->children, i+1, child);
|
|
child->parent = parent;
|
|
return child->parent;
|
|
}
|
|
|
|
swayc_t *replace_child(swayc_t *child, swayc_t *new_child) {
|
|
swayc_t *parent = child->parent;
|
|
if (parent == NULL) {
|
|
return NULL;
|
|
}
|
|
int i = index_child(child);
|
|
parent->children->items[i] = new_child;
|
|
|
|
// Set parent and focus for new_child
|
|
new_child->parent = child->parent;
|
|
if (child->parent->focused == child) {
|
|
child->parent->focused = new_child;
|
|
}
|
|
child->parent = NULL;
|
|
|
|
// Set geometry for new child
|
|
new_child->x = child->x;
|
|
new_child->y = child->y;
|
|
new_child->width = child->width;
|
|
new_child->height = child->height;
|
|
|
|
// reset geometry for child
|
|
child->width = 0;
|
|
child->height = 0;
|
|
|
|
// deactivate child
|
|
if (child->type == C_VIEW) {
|
|
wlc_view_set_state(child->handle, WLC_BIT_ACTIVATED, false);
|
|
}
|
|
return parent;
|
|
}
|
|
|
|
swayc_t *remove_child(swayc_t *child) {
|
|
int i;
|
|
swayc_t *parent = child->parent;
|
|
if (child->is_floating) {
|
|
// Special case for floating views
|
|
for (i = 0; i < parent->floating->length; ++i) {
|
|
if (parent->floating->items[i] == child) {
|
|
list_del(parent->floating, i);
|
|
break;
|
|
}
|
|
}
|
|
i = 0;
|
|
} else {
|
|
for (i = 0; i < parent->children->length; ++i) {
|
|
if (parent->children->items[i] == child) {
|
|
list_del(parent->children, i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
// Set focused to new container
|
|
if (parent->focused == child) {
|
|
if (parent->children->length > 0) {
|
|
parent->focused = parent->children->items[i ? i-1:0];
|
|
} else if (parent->floating && parent->floating->length) {
|
|
parent->focused = parent->floating->items[parent->floating->length - 1];
|
|
} else {
|
|
parent->focused = NULL;
|
|
}
|
|
}
|
|
child->parent = NULL;
|
|
// deactivate view
|
|
if (child->type == C_VIEW) {
|
|
wlc_view_set_state(child->handle, WLC_BIT_ACTIVATED, false);
|
|
}
|
|
return parent;
|
|
}
|
|
|
|
void swap_container(swayc_t *a, swayc_t *b) {
|
|
//TODO doesnt handle floating <-> tiling swap
|
|
if (!sway_assert(a&&b, "parameters must be non null") ||
|
|
!sway_assert(a->parent && b->parent, "containers must have parents")) {
|
|
return;
|
|
}
|
|
size_t a_index = index_child(a);
|
|
size_t b_index = index_child(b);
|
|
swayc_t *a_parent = a->parent;
|
|
swayc_t *b_parent = b->parent;
|
|
// Swap the pointers
|
|
a_parent->children->items[a_index] = b;
|
|
b_parent->children->items[b_index] = a;
|
|
a->parent = b_parent;
|
|
b->parent = a_parent;
|
|
if (a_parent->focused == a) {
|
|
a_parent->focused = b;
|
|
}
|
|
// dont want to double switch
|
|
if (b_parent->focused == b && a_parent != b_parent) {
|
|
b_parent->focused = a;
|
|
}
|
|
}
|
|
|
|
void swap_geometry(swayc_t *a, swayc_t *b) {
|
|
double x = a->x;
|
|
double y = a->y;
|
|
double w = a->width;
|
|
double h = a->height;
|
|
a->x = b->x;
|
|
a->y = b->y;
|
|
a->width = b->width;
|
|
a->height = b->height;
|
|
b->x = x;
|
|
b->y = y;
|
|
b->width = w;
|
|
b->height = h;
|
|
}
|
|
|
|
void move_container(swayc_t *container, enum movement_direction dir) {
|
|
enum swayc_layouts layout;
|
|
if (container->is_floating
|
|
|| (container->type != C_VIEW && container->type != C_CONTAINER)) {
|
|
return;
|
|
}
|
|
if (dir == MOVE_UP || dir == MOVE_DOWN) {
|
|
layout = L_VERT;
|
|
} else if (dir == MOVE_LEFT || dir == MOVE_RIGHT) {
|
|
layout = L_HORIZ;
|
|
} else {
|
|
return;
|
|
}
|
|
swayc_t *parent = container->parent;
|
|
swayc_t *child = container;
|
|
bool ascended = false;
|
|
while (true) {
|
|
sway_log(L_DEBUG, "container:%p, parent:%p, child %p,",
|
|
container,parent,child);
|
|
if (parent->layout == layout) {
|
|
int diff;
|
|
// If it has ascended (parent has moved up), no container is removed
|
|
// so insert it at index, or index+1.
|
|
// if it has not, the moved container is removed, so it needs to be
|
|
// inserted at index-1, or index+1
|
|
if (ascended) {
|
|
diff = dir == MOVE_LEFT || dir == MOVE_UP ? 0 : 1;
|
|
} else {
|
|
diff = dir == MOVE_LEFT || dir == MOVE_UP ? -1 : 1;
|
|
}
|
|
int desired = index_child(child) + diff;
|
|
// when it has ascended, legal insertion position is 0:len
|
|
// when it has not, legal insertion position is 0:len-1
|
|
if (desired >= 0 && desired - ascended < parent->children->length) {
|
|
if (!ascended) {
|
|
child = parent->children->items[desired];
|
|
// Move container into sibling container
|
|
if (child->type == C_CONTAINER) {
|
|
parent = child;
|
|
// Insert it in first/last if matching layout,otherwise
|
|
// inesrt it next to focused container
|
|
if (parent->layout == layout) {
|
|
desired = (diff < 0) * parent->children->length;
|
|
} else {
|
|
desired = index_child(child->focused);
|
|
}
|
|
//reset geometry
|
|
container->width = container->height = 0;
|
|
}
|
|
}
|
|
swayc_t *old_parent = remove_child(container);
|
|
insert_child(parent, container, desired);
|
|
destroy_container(old_parent);
|
|
sway_log(L_DEBUG,"Moving to %p %d",parent, desired);
|
|
break;
|
|
}
|
|
}
|
|
// Change parent layout if we need to
|
|
if (parent->children->length == 1 && parent->layout != layout) {
|
|
parent->layout = layout;
|
|
continue;
|
|
}
|
|
if (parent->type == C_WORKSPACE) {
|
|
// We simply cannot move any further.
|
|
if (parent->layout == layout) {
|
|
break;
|
|
}
|
|
// Create container around workspace to insert child into
|
|
parent = new_container(parent, layout);
|
|
}
|
|
ascended = true;
|
|
child = parent;
|
|
parent = child->parent;
|
|
}
|
|
// Dirty hack to fix a certain case
|
|
arrange_windows(parent, -1, -1);
|
|
arrange_windows(parent->parent, -1, -1);
|
|
set_focused_container_for(parent->parent, container);
|
|
}
|
|
|
|
void move_container_to(swayc_t* container, swayc_t* destination) {
|
|
if (container == destination || swayc_is_parent_of(container, destination)) {
|
|
return;
|
|
}
|
|
swayc_t *parent = remove_child(container);
|
|
// Send to new destination
|
|
if (container->is_floating) {
|
|
add_floating(swayc_active_workspace_for(destination), container);
|
|
} else if (destination->type == C_WORKSPACE) {
|
|
// reset container geometry
|
|
container->width = container->height = 0;
|
|
add_child(destination, container);
|
|
} else {
|
|
// reset container geometry
|
|
container->width = container->height = 0;
|
|
add_sibling(destination, container);
|
|
}
|
|
// Destroy old container if we need to
|
|
parent = destroy_container(parent);
|
|
// Refocus
|
|
swayc_t *op1 = swayc_parent_by_type(destination, C_OUTPUT);
|
|
swayc_t *op2 = swayc_parent_by_type(parent, C_OUTPUT);
|
|
set_focused_container(get_focused_view(op1));
|
|
arrange_windows(op1, -1, -1);
|
|
update_visibility(op1);
|
|
if (op1 != op2) {
|
|
set_focused_container(get_focused_view(op2));
|
|
arrange_windows(op2, -1, -1);
|
|
update_visibility(op2);
|
|
}
|
|
}
|
|
|
|
void move_workspace_to(swayc_t* workspace, swayc_t* destination) {
|
|
if (workspace == destination || swayc_is_parent_of(workspace, destination)) {
|
|
return;
|
|
}
|
|
swayc_t *src_op = remove_child(workspace);
|
|
// reset container geometry
|
|
workspace->width = workspace->height = 0;
|
|
add_child(destination, workspace);
|
|
// Refocus destination (change to new workspace)
|
|
set_focused_container(get_focused_view(workspace));
|
|
arrange_windows(destination, -1, -1);
|
|
update_visibility(destination);
|
|
|
|
// make sure source output has a workspace
|
|
if (src_op->children->length == 0) {
|
|
char *ws_name = workspace_next_name();
|
|
swayc_t *ws = new_workspace(src_op, ws_name);
|
|
ws->is_focused = true;
|
|
free(ws_name);
|
|
}
|
|
set_focused_container(get_focused_view(src_op));
|
|
update_visibility(src_op);
|
|
}
|
|
|
|
void update_geometry(swayc_t *container) {
|
|
if (container->type != C_VIEW) {
|
|
return;
|
|
}
|
|
swayc_t *ws = swayc_parent_by_type(container, C_WORKSPACE);
|
|
swayc_t *op = ws->parent;
|
|
int gap = container->is_floating ? 0 : swayc_gap(container);
|
|
|
|
struct wlc_geometry geometry = {
|
|
.origin = {
|
|
.x = container->x + gap/2 < op->width ? container->x + gap/2 : op->width-1,
|
|
.y = container->y + gap/2 < op->height ? container->y + gap/2 : op->height-1
|
|
},
|
|
.size = {
|
|
.w = container->width > gap ? container->width - gap : 1,
|
|
.h = container->height > gap ? container->height - gap : 1,
|
|
}
|
|
};
|
|
if (swayc_is_fullscreen(container)) {
|
|
geometry.origin.x = 0;
|
|
geometry.origin.y = 0;
|
|
geometry.size.w = op->width;
|
|
geometry.size.h = op->height;
|
|
if (op->focused == ws) {
|
|
wlc_view_bring_to_front(container->handle);
|
|
}
|
|
} else if (!config->edge_gaps && gap > 0) {
|
|
// Remove gap against the workspace edges. Because a pixel is not
|
|
// divisable, depending on gap size and the number of siblings our view
|
|
// might be at the workspace edge without being exactly so (thus test
|
|
// with gap, and align correctly).
|
|
if (container->x - gap <= ws->x) {
|
|
geometry.origin.x = ws->x;
|
|
geometry.size.w = container->width - gap/2;
|
|
}
|
|
if (container->y - gap <= ws->y) {
|
|
geometry.origin.y = ws->y;
|
|
geometry.size.h = container->height - gap/2;
|
|
}
|
|
if (container->x + container->width + gap >= ws->x + ws->width) {
|
|
geometry.size.w = ws->width - geometry.origin.x;
|
|
}
|
|
if (container->y + container->height + gap >= ws->y + ws->height) {
|
|
geometry.size.h = ws->height - geometry.origin.y;
|
|
}
|
|
}
|
|
wlc_view_set_geometry(container->handle, 0, &geometry);
|
|
}
|
|
|
|
static void arrange_windows_r(swayc_t *container, double width, double height) {
|
|
int i;
|
|
if (width == -1 || height == -1) {
|
|
swayc_log(L_DEBUG, container, "Arranging layout for %p", container);
|
|
width = container->width;
|
|
height = container->height;
|
|
}
|
|
|
|
sway_log(L_DEBUG, "Arranging layout for %p %s %fx%f+%f,%f", container,
|
|
container->name, container->width, container->height, container->x, container->y);
|
|
|
|
int x = 0, y = 0;
|
|
switch (container->type) {
|
|
case C_ROOT:
|
|
for (i = 0; i < container->children->length; ++i) {
|
|
swayc_t *child = container->children->items[i];
|
|
sway_log(L_DEBUG, "Arranging output at %d", x);
|
|
arrange_windows_r(child, -1, -1);
|
|
x += child->width;
|
|
}
|
|
return;
|
|
case C_OUTPUT:
|
|
container->width = width;
|
|
container->height = height;
|
|
x = 0, y = 0;
|
|
for (i = 0; i < container->children->length; ++i) {
|
|
swayc_t *child = container->children->items[i];
|
|
int gap = swayc_gap(child);
|
|
child->x = x + gap;
|
|
child->y = y + gap;
|
|
child->width = width - gap * 2;
|
|
child->height = height - gap * 2;
|
|
sway_log(L_DEBUG, "Arranging workspace #%d at %f, %f", i, child->x, child->y);
|
|
arrange_windows_r(child, -1, -1);
|
|
}
|
|
return;
|
|
case C_VIEW:
|
|
{
|
|
container->width = width;
|
|
container->height = height;
|
|
update_geometry(container);
|
|
sway_log(L_DEBUG, "Set view to %.f x %.f @ %.f, %.f", container->width,
|
|
container->height, container->x, container->y);
|
|
}
|
|
return;
|
|
default:
|
|
container->width = width;
|
|
container->height = height;
|
|
break;
|
|
}
|
|
|
|
x = y = 0;
|
|
double scale = 0;
|
|
switch (container->layout) {
|
|
case L_HORIZ:
|
|
default:
|
|
// Calculate total width
|
|
for (i = 0; i < container->children->length; ++i) {
|
|
double *old_width = &((swayc_t *)container->children->items[i])->width;
|
|
if (*old_width <= 0) {
|
|
if (container->children->length > 1) {
|
|
*old_width = width / (container->children->length - 1);
|
|
} else {
|
|
*old_width = width;
|
|
}
|
|
}
|
|
scale += *old_width;
|
|
}
|
|
// Resize windows
|
|
if (scale > 0.1) {
|
|
scale = width / scale;
|
|
sway_log(L_DEBUG, "Arranging %p horizontally", container);
|
|
for (i = 0; i < container->children->length; ++i) {
|
|
swayc_t *child = container->children->items[i];
|
|
sway_log(L_DEBUG, "Calculating arrangement for %p:%d (will scale %f by %f)", child, child->type, width, scale);
|
|
child->x = x + container->x;
|
|
child->y = y + container->y;
|
|
arrange_windows_r(child, child->width * scale, height);
|
|
x += child->width;
|
|
}
|
|
}
|
|
break;
|
|
case L_VERT:
|
|
// Calculate total height
|
|
for (i = 0; i < container->children->length; ++i) {
|
|
double *old_height = &((swayc_t *)container->children->items[i])->height;
|
|
if (*old_height <= 0) {
|
|
if (container->children->length > 1) {
|
|
*old_height = height / (container->children->length - 1);
|
|
} else {
|
|
*old_height = height;
|
|
}
|
|
}
|
|
scale += *old_height;
|
|
}
|
|
// Resize
|
|
if (scale > 0.1) {
|
|
scale = height / scale;
|
|
sway_log(L_DEBUG, "Arranging %p vertically", container);
|
|
for (i = 0; i < container->children->length; ++i) {
|
|
swayc_t *child = container->children->items[i];
|
|
sway_log(L_DEBUG, "Calculating arrangement for %p:%d (will scale %f by %f)", child, child->type, height, scale);
|
|
child->x = x + container->x;
|
|
child->y = y + container->y;
|
|
arrange_windows_r(child, width, child->height * scale);
|
|
y += child->height;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
// Arrage floating layouts for workspaces last
|
|
if (container->type == C_WORKSPACE) {
|
|
for (i = 0; i < container->floating->length; ++i) {
|
|
swayc_t *view = container->floating->items[i];
|
|
if (view->type == C_VIEW) {
|
|
update_geometry(view);
|
|
if (swayc_is_fullscreen(view)) {
|
|
wlc_view_bring_to_front(view->handle);
|
|
} else if (!container->focused
|
|
|| !swayc_is_fullscreen(container->focused)) {
|
|
wlc_view_bring_to_front(view->handle);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void arrange_windows(swayc_t *container, double width, double height) {
|
|
update_visibility(container);
|
|
arrange_windows_r(container, width, height);
|
|
layout_log(&root_container, 0);
|
|
}
|
|
|
|
swayc_t *get_swayc_in_direction_under(swayc_t *container, enum movement_direction dir, swayc_t *limit) {
|
|
swayc_t *parent = container->parent;
|
|
if (dir == MOVE_PARENT) {
|
|
if (parent->type == C_OUTPUT) {
|
|
return NULL;
|
|
} else {
|
|
return parent;
|
|
}
|
|
}
|
|
// If moving to an adjacent output we need a starting position (since this
|
|
// output might border to multiple outputs).
|
|
struct wlc_point abs_pos;
|
|
get_absolute_center_position(container, &abs_pos);
|
|
while (true) {
|
|
// Test if we can even make a difference here
|
|
bool can_move = false;
|
|
int diff = 0;
|
|
if (parent->type == C_ROOT) {
|
|
sway_log(L_DEBUG, "Moving between outputs");
|
|
return swayc_adjacent_output(container, dir, &abs_pos, true);
|
|
} else {
|
|
if (dir == MOVE_LEFT || dir == MOVE_RIGHT) {
|
|
if (parent->layout == L_HORIZ) {
|
|
can_move = true;
|
|
diff = dir == MOVE_LEFT ? -1 : 1;
|
|
}
|
|
} else {
|
|
if (parent->layout == L_VERT) {
|
|
can_move = true;
|
|
diff = dir == MOVE_UP ? -1 : 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (can_move) {
|
|
int desired = index_child(container) + diff;
|
|
if (desired < 0 || desired >= parent->children->length) {
|
|
can_move = false;
|
|
} else {
|
|
return parent->children->items[desired];
|
|
}
|
|
}
|
|
if (!can_move) {
|
|
container = parent;
|
|
parent = parent->parent;
|
|
if (!parent || container == limit) {
|
|
// Nothing we can do
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
swayc_t *get_swayc_in_direction(swayc_t *container, enum movement_direction dir) {
|
|
return get_swayc_in_direction_under(container, dir, NULL);
|
|
}
|
|
|
|
void recursive_resize(swayc_t *container, double amount, enum wlc_resize_edge edge) {
|
|
int i;
|
|
bool layout_match = true;
|
|
sway_log(L_DEBUG, "Resizing %p with amount: %f", container, amount);
|
|
if (edge == WLC_RESIZE_EDGE_LEFT || edge == WLC_RESIZE_EDGE_RIGHT) {
|
|
container->width += amount;
|
|
layout_match = container->layout == L_HORIZ;
|
|
} else if (edge == WLC_RESIZE_EDGE_TOP || edge == WLC_RESIZE_EDGE_BOTTOM) {
|
|
container->height += amount;
|
|
layout_match = container->layout == L_VERT;
|
|
}
|
|
if (container->type == C_VIEW) {
|
|
update_geometry(container);
|
|
return;
|
|
}
|
|
if (layout_match) {
|
|
for (i = 0; i < container->children->length; i++) {
|
|
recursive_resize(container->children->items[i], amount/container->children->length, edge);
|
|
}
|
|
} else {
|
|
for (i = 0; i < container->children->length; i++) {
|
|
recursive_resize(container->children->items[i], amount, edge);
|
|
}
|
|
}
|
|
}
|