tile-pane and float-pane now use the new layout mechanics

The layout position of every cell is kept throughout the state changing actions, and can therefore be restored.
This commit is contained in:
Dane Jensen
2026-06-14 20:07:42 -07:00
committed by GitHub
5 changed files with 433 additions and 389 deletions

View File

@@ -38,10 +38,10 @@ static enum cmd_retval cmd_tile_pane_exec(struct cmd *, struct cmdq_item *);
const struct cmd_entry cmd_float_pane_entry = {
.name = "float-pane",
.alias = NULL,
.alias = "floatp",
.args = { "t:x:y:w:h:", 0, 0, NULL },
.usage = "[-h height] [-w width] [-x x] [-y y] "
.args = { "dt:x:X:y:Y:", 0, 0, NULL },
.usage = "[-x height] [-y width] [-X x-position] [-Y y-position] "
CMD_TARGET_PANE_USAGE,
.target = { 't', CMD_FIND_PANE, 0 },
@@ -52,9 +52,9 @@ const struct cmd_entry cmd_float_pane_entry = {
const struct cmd_entry cmd_tile_pane_entry = {
.name = "tile-pane",
.alias = NULL,
.alias = "tilep",
.args = { "t:", 0, 0, NULL },
.args = { "dt:", 0, 0, NULL },
.usage = CMD_TARGET_PANE_USAGE,
.target = { 't', CMD_FIND_PANE, 0 },
@@ -63,90 +63,6 @@ const struct cmd_entry cmd_tile_pane_entry = {
.exec = cmd_tile_pane_exec
};
/*
* Parse geometry arguments for float-pane.
* Returns 0 on success, -1 on error (error message already set on item).
* x/y/sx/sy are set to parsed values or cascade defaults.
* last_x/last_y are the static cascade counters; pass the address of the
* caller's statics.
*/
static int
cmd_float_pane_parse_geometry(struct args *args, struct cmdq_item *item,
struct window *w, int *out_x, int *out_y, u_int *out_sx, u_int *out_sy,
int *last_x, int *last_y)
{
char *cause = NULL;
int x, y;
u_int sx, sy;
/* Default size: half the window. */
sx = w->sx / 2;
sy = w->sy / 2;
if (args_has(args, 'w')) {
sx = args_strtonum_and_expand(args, 'w', 1, USHRT_MAX, item,
&cause);
if (cause != NULL) {
cmdq_error(item, "width %s", cause);
free(cause);
return (-1);
}
}
if (args_has(args, 'h')) {
sy = args_strtonum_and_expand(args, 'h', 1, USHRT_MAX, item,
&cause);
if (cause != NULL) {
cmdq_error(item, "height %s", cause);
free(cause);
return (-1);
}
}
/* Default position: cascade from (5,5), step +5, wrap at window edge. */
if (args_has(args, 'x')) {
x = args_strtonum_and_expand(args, 'x', SHRT_MIN, SHRT_MAX,
item, &cause);
if (cause != NULL) {
cmdq_error(item, "x %s", cause);
free(cause);
return (-1);
}
} else {
if (*last_x == 0) {
x = 5;
} else {
x = (*last_x += 5);
if (*last_x > (int)w->sx)
x = *last_x = 5;
}
}
if (args_has(args, 'y')) {
y = args_strtonum_and_expand(args, 'y', SHRT_MIN, SHRT_MAX,
item, &cause);
if (cause != NULL) {
cmdq_error(item, "y %s", cause);
free(cause);
return (-1);
}
} else {
if (*last_y == 0) {
y = 5;
} else {
y = (*last_y += 5);
if (*last_y > (int)w->sy)
y = *last_y = 5;
}
}
*last_x = x;
*last_y = y;
*out_x = x;
*out_y = y;
*out_sx = sx;
*out_sy = sy;
return (0);
}
static enum cmd_retval
cmd_float_pane_exec(struct cmd *self, struct cmdq_item *item)
{
@@ -154,16 +70,16 @@ cmd_float_pane_exec(struct cmd *self, struct cmdq_item *item)
struct cmd_find_state *target = cmdq_get_target(item);
struct window *w = target->wl->window;
struct window_pane *wp = target->wp;
static int last_x = 0, last_y = 0;
int x, y;
u_int sx, sy;
struct layout_cell *lc;
struct layout_cell *lc = wp->layout_cell;
u_int sx = lc->saved_sx, sy = lc->saved_sy;
int ox = lc->saved_xoff, oy = lc->saved_yoff;
char *cause = NULL;
if (window_pane_is_floating(wp)) {
cmdq_error(item, "pane is already floating");
return (CMD_RETURN_ERROR);
}
if (wp->flags & PANE_HIDDEN) {
if (window_pane_is_hidden(wp)) {
cmdq_error(item, "can't float a hidden pane");
return (CMD_RETURN_ERROR);
}
@@ -172,47 +88,22 @@ cmd_float_pane_exec(struct cmd *self, struct cmdq_item *item)
return (CMD_RETURN_ERROR);
}
/*
* If no geometry was given explicitly and we have a saved floating
* position from a previous tile-pane, restore it.
*/
if ((wp->flags & PANE_SAVED_FLOAT) &&
!args_has(args, 'x') && !args_has(args, 'y') &&
!args_has(args, 'w') && !args_has(args, 'h')) {
x = wp->saved_float_xoff;
y = wp->saved_float_yoff;
sx = wp->saved_float_sx;
sy = wp->saved_float_sy;
} else {
if (cmd_float_pane_parse_geometry(args, item, w, &x, &y, &sx,
&sy, &last_x, &last_y) != 0)
return (CMD_RETURN_ERROR);
layout_remove_tile(w, lc);
layout_cell_floating_args_parse(item, args, w, &sx, &sy, &ox, &oy, &cause);
if (cause != NULL) {
cmdq_error(item, "failed to float pane: %s", cause);
free(cause);
return (CMD_RETURN_ERROR);
}
/*
* Remove the pane from the tiled layout tree so neighbours reclaim
* the space. layout_close_pane calls layout_destroy_cell which frees
* the tiled layout_cell and sets wp->layout_cell = NULL via
* layout_free_cell. It also calls layout_fix_offsets/fix_panes and
* notify_window, which is fine to do here before we set up the
* floating cell.
*/
layout_close_pane(wp); /* wp->layout_cell is NULL afterwards */
/* Create a detached floating cell with the requested geometry. */
lc = layout_create_cell(NULL);
lc->xoff = x;
lc->yoff = y;
lc->sx = sx;
lc->sy = sy;
layout_make_leaf(lc, wp); /* sets wp->layout_cell = lc, lc->wp = wp */
layout_set_size(lc, sx, sy, ox, oy);
lc->flags |= LAYOUT_CELL_FLOATING;
TAILQ_REMOVE(&w->z_index, wp, zentry);
TAILQ_INSERT_HEAD(&w->z_index, wp, zentry);
if (w->layout_root != NULL)
layout_fix_offsets(w);
if (!args_has(args, 'd'))
window_set_active_pane(w, wp, 1);
layout_fix_offsets(w);
layout_fix_panes(w, NULL);
notify_window("window-layout-changed", w);
server_redraw_window(w);
@@ -223,138 +114,38 @@ cmd_float_pane_exec(struct cmd *self, struct cmdq_item *item)
static enum cmd_retval
cmd_tile_pane_exec(struct cmd *self, struct cmdq_item *item)
{
__attribute((unused)) struct args *args = cmd_get_args(self);
struct cmd_find_state *target = cmdq_get_target(item);
struct window *w = target->wl->window;
struct window_pane *wp = target->wp;
struct window_pane *target_wp, *wpiter;
struct layout_cell *float_lc, *lc;
int was_hidden;
__unused struct args *args = cmd_get_args(self);
struct cmd_find_state *target = cmdq_get_target(item);
struct window *w = target->wl->window;
struct window_pane *wp = target->wp;
struct layout_cell *lc = wp->layout_cell;
if (!window_pane_is_floating(wp)) {
cmdq_error(item, "pane is not floating");
return (CMD_RETURN_ERROR);
}
if (window_pane_is_hidden(wp)) {
cmdq_error(item, "can't tile a hidden pane");
return (CMD_RETURN_ERROR);
}
if (w->flags & WINDOW_ZOOMED) {
cmdq_error(item, "can't tile a pane while window is zoomed");
return (CMD_RETURN_ERROR);
}
was_hidden = (wp->flags & PANE_HIDDEN) != 0;
/*
* Save the floating geometry so we can restore it next time this pane
* is floated without an explicit position/size.
*/
float_lc = wp->layout_cell;
wp->saved_float_xoff = float_lc->xoff;
wp->saved_float_yoff = float_lc->yoff;
wp->saved_float_sx = float_lc->sx;
wp->saved_float_sy = float_lc->sy;
wp->flags |= PANE_SAVED_FLOAT;
/*
* If the pane is also hidden, clear saved_layout_cell before
* freeing the floating cell — otherwise the pointer would dangle.
*/
if (was_hidden)
wp->saved_layout_cell = NULL;
/*
* Free the detached floating cell. Clear its wp pointer first so
* layout_free_cell's WINDOWPANE case does not corrupt wp->layout_cell.
*/
float_lc->wp = NULL;
layout_free_cell(float_lc);
wp->layout_cell = NULL;
/*
* Find the best tiled pane to split after, prefer a visible (non-
* hidden) tiled pane. If all tiled panes are hidden, fall back
* to any tiled pane so the new pane enters the existing tree rather
* than becoming a disconnected root.
*/
target_wp = NULL;
if (w->active != NULL && !window_pane_is_floating(w->active) &&
!(w->active->flags & PANE_HIDDEN))
target_wp = w->active;
if (target_wp == NULL) {
TAILQ_FOREACH(wpiter, &w->last_panes, sentry) {
if (!(wpiter->flags & PANE_HIDDEN) &&
!window_pane_is_floating(wpiter) &&
window_pane_visible(wpiter)) {
target_wp = wpiter;
break;
}
}
}
if (target_wp == NULL) {
TAILQ_FOREACH(wpiter, &w->panes, entry) {
if (!(wpiter->flags & PANE_HIDDEN) &&
!window_pane_is_floating(wpiter) &&
window_pane_visible(wpiter)) {
target_wp = wpiter;
break;
}
}
}
/* Fall back to any tiled pane (even hidden) to stay in the tree. */
if (target_wp == NULL) {
TAILQ_FOREACH(wpiter, &w->panes, entry) {
if (!window_pane_is_floating(wpiter)) {
target_wp = wpiter;
break;
}
}
}
if (target_wp != NULL) {
lc = layout_split_pane(target_wp, LAYOUT_TOPBOTTOM, -1, 0);
if (lc == NULL)
lc = layout_split_pane(target_wp, LAYOUT_LEFTRIGHT,
-1, 0);
if (lc == NULL) {
cmdq_error(item, "not enough space to tile pane");
return (CMD_RETURN_ERROR);
}
layout_assign_pane(lc, wp, 0);
/*
* Redistribute space equally among all visible panes at this
* level, so the new pane gets an equal share rather than just
* half of the split target.
*/
if (wp->layout_cell != NULL && wp->layout_cell->parent != NULL)
layout_redistribute_cells(w, wp->layout_cell->parent,
wp->layout_cell->parent->type);
} else {
/*
* No tiled panes at all: make this pane the sole tiled pane
* (new layout root).
*/
lc = layout_create_cell(NULL);
lc->sx = w->sx;
lc->sy = w->sy;
lc->xoff = 0;
lc->yoff = 0;
w->layout_root = lc;
layout_make_leaf(lc, wp);
}
/*
* If the pane was hidden while floating, record its new tiled cell
* as the saved cell so 'show' can restore it correctly.
*/
if (was_hidden)
wp->saved_layout_cell = wp->layout_cell;
layout_save_size(lc);
lc->flags &= ~LAYOUT_CELL_FLOATING;
if (layout_insert_tile(w, lc) == 0) {
cmdq_error(item, "can't tile a pane that is already tiled");
return (CMD_RETURN_ERROR);
}
TAILQ_REMOVE(&w->z_index, wp, zentry);
TAILQ_INSERT_TAIL(&w->z_index, wp, zentry);
if (!(wp->flags & PANE_HIDDEN))
if (!args_has(args, 'd'))
window_set_active_pane(w, wp, 1);
if (w->layout_root != NULL)
layout_fix_offsets(w);
layout_fix_offsets(w);
layout_fix_panes(w, NULL);
notify_window("window-layout-changed", w);
server_redraw_window(w);

437
layout.c
View File

@@ -26,11 +26,18 @@
/*
* The window layout is a tree of cells each of which can be one of: a
* left-right container for a list of cells, a top-bottom container for a list
* of cells, or a container for a window pane.
* of cells, or a container for a window pane. 'Node' will be used to refer to
* a cell which contains a list of cells, and 'leaf' to refer to a cell that
* contains a window pane. A leaf is considered to be tiled if it is to be drawn
* as a part of the tiled layout. A 'neighbour' is a sibling that is also tiled.
* A cell's 'split' size refers to the side that is shortened when splitting it,
* determined by the parent's type.
*
* Each window has a pointer to the root of its layout tree (containing its
* panes), every pane has a pointer back to the cell containing it, and each
* cell a pointer to its parent cell.
* cell a pointer to its parent cell. Every cell has a position in the root
* layout tree. This position is retained through cell state changes such as
* floating or hiding.
*/
static u_int layout_resize_check(struct window *, struct layout_cell *,
@@ -44,6 +51,7 @@ static u_int layout_new_pane_size(struct window *, u_int,
u_int);
static int layout_set_size_check(struct window *, struct layout_cell *,
enum layout_type, int);
static int layout_cell_has_tiled_child(struct layout_cell *);
static void layout_resize_child_cells(struct window *,
struct layout_cell *);
@@ -66,6 +74,12 @@ layout_create_cell(struct layout_cell *lcparent)
lc->xoff = INT_MAX;
lc->yoff = INT_MAX;
lc->saved_sx = UINT_MAX;
lc->saved_sy = UINT_MAX;
lc->saved_xoff = INT_MAX;
lc->saved_yoff = INT_MAX;
lc->wp = NULL;
return (lc);
@@ -190,6 +204,16 @@ layout_set_size(struct layout_cell *lc, u_int sx, u_int sy, int xoff, int yoff)
lc->yoff = yoff;
}
void
layout_save_size(struct layout_cell *lc)
{
lc->saved_sx = lc->sx;
lc->saved_sy = lc->sy;
lc->saved_xoff = lc->xoff;
lc->saved_yoff = lc->yoff;
}
/* Make a cell a leaf cell. */
void
layout_make_leaf(struct layout_cell *lc, struct window_pane *wp)
@@ -250,7 +274,8 @@ layout_fix_offsets1(struct layout_cell *lc)
if (lc->type == LAYOUT_LEFTRIGHT) {
xoff = lc->xoff;
TAILQ_FOREACH(lcchild, &lc->cells, entry) {
if (!layout_cell_is_tiled(lcchild))
if (!layout_cell_is_tiled(lcchild) &&
!layout_cell_has_tiled_child(lcchild))
continue;
lcchild->xoff = xoff;
lcchild->yoff = lc->yoff;
@@ -261,7 +286,8 @@ layout_fix_offsets1(struct layout_cell *lc)
} else {
yoff = lc->yoff;
TAILQ_FOREACH(lcchild, &lc->cells, entry) {
if (!layout_cell_is_tiled(lcchild))
if (!layout_cell_is_tiled(lcchild) &&
!layout_cell_has_tiled_child(lcchild))
continue;
lcchild->xoff = lc->xoff;
lcchild->yoff = yoff;
@@ -290,13 +316,34 @@ layout_fix_offsets(struct window *w)
/*
* Not all cells are drawn within the tiled grid of a layout. This predicate
* isolates that logic.
* isolates that logic. Nodes are not considered tiled.
*/
int
layout_cell_is_tiled(struct layout_cell *lc)
{
return ((~lc->flags & LAYOUT_CELL_HIDDEN) &&
(~lc->flags & LAYOUT_CELL_FLOATING));
int is_leaf, is_floating, is_hidden;
is_leaf = lc->type == LAYOUT_WINDOWPANE;
is_floating = lc->flags & LAYOUT_CELL_FLOATING;
is_hidden = lc->flags & LAYOUT_CELL_HIDDEN;
return is_leaf && !(is_floating || is_hidden);
}
static int
layout_cell_has_tiled_child(struct layout_cell *lc)
{
struct layout_cell *lcchild;
if (lc->type == LAYOUT_WINDOWPANE)
return (0);
TAILQ_FOREACH(lcchild, &lc->cells, entry) {
if (layout_cell_is_tiled(lcchild) ||
layout_cell_has_tiled_child(lcchild))
return (1);
}
return (0);
}
static int
@@ -308,7 +355,8 @@ layout_cell_is_first_tiled(struct layout_cell *lc)
return (layout_cell_is_tiled(lc));
TAILQ_FOREACH(lcchild, &lcparent->cells, entry) {
if (layout_cell_is_tiled(lcchild))
if (layout_cell_is_tiled(lcchild) ||
layout_cell_has_tiled_child(lcchild))
break;
}
@@ -319,7 +367,7 @@ layout_cell_is_first_tiled(struct layout_cell *lc)
static int
layout_cell_is_top(struct window *w, struct layout_cell *lc)
{
struct layout_cell *next, *edge;
struct layout_cell *next;
while (lc != w->layout_root) {
next = lc->parent;
@@ -525,13 +573,20 @@ layout_resize_adjust(struct window *w, struct layout_cell *lc,
/* Child cell runs in a different direction. */
if (lc->type != type) {
TAILQ_FOREACH(lcchild, &lc->cells, entry) {
if (!layout_cell_is_tiled(lcchild))
if (!layout_cell_is_tiled(lcchild) &&
!layout_cell_has_tiled_child(lcchild))
continue;
layout_resize_adjust(w, lcchild, type, change);
}
return;
}
/*
* If a node doesn't contain any tiled cells, there is nothing to do.
*/
if (!layout_cell_has_tiled_child(lc))
return;
/*
* Child cell runs in the same direction. Adjust each child equally
* until no further change is possible.
@@ -540,7 +595,8 @@ layout_resize_adjust(struct window *w, struct layout_cell *lc,
TAILQ_FOREACH(lcchild, &lc->cells, entry) {
if (change == 0)
break;
if (!layout_cell_is_tiled(lcchild))
if (!layout_cell_is_tiled(lcchild) &&
!layout_cell_has_tiled_child(lcchild))
continue;
if (change > 0) {
layout_resize_adjust(w, lcchild, type, 1);
@@ -555,6 +611,20 @@ layout_resize_adjust(struct window *w, struct layout_cell *lc,
}
}
/* Resizes a cell to a specified size */
void
layout_resize_set_size(struct window *w, struct layout_cell *lc,
enum layout_type type, u_int size)
{
int change;
if (type == LAYOUT_LEFTRIGHT)
change = size - lc->sx;
else
change = size - lc->sy;
layout_resize_adjust(w, lc, type, change);
}
/*
* Redistribute space equally among all visible (non-hidden WINDOWPANE)
* children of lcparent in the given direction. Hidden WINDOWPANE leaves
@@ -611,40 +681,41 @@ layout_redistribute_cells(struct window *w, struct layout_cell *lcparent,
}
}
/* Helper function for layout_cell_get_neighbour. */
/* Find and return the nearest neighbour to a cell in a specific direction. */
static struct layout_cell *
layout_cell_get_neighbour_direction(struct layout_cell *lc, int direction)
{
struct layout_cell *lcother = lc;
struct layout_cell *lcneighbour = lc;
while (1) {
if (direction)
lcother = TAILQ_NEXT(lcother, entry);
lcneighbour = TAILQ_NEXT(lcneighbour, entry);
else
lcother = TAILQ_PREV(lcother, layout_cells, entry);
lcneighbour = TAILQ_PREV(lcneighbour, layout_cells,
entry);
if (lcother == NULL || layout_cell_is_tiled(lcother))
return (lcother);
if (lcneighbour == NULL || layout_cell_is_tiled(lcneighbour) ||
layout_cell_has_tiled_child(lcneighbour))
return (lcneighbour);
}
}
/*
* Finds the nearest visible neighbour. A neighbour is a sibling cell drawn
* within the tiled layout. Prefers cells "before" the specified cell.
* This behavior defines how cell dimensions are redistributed when a cell is
* hidden/shown and floated/tiled.
* Find and return the nearest neighbour. Prefers cells "after" the specified
* cell. This behavior defines how cell dimensions are redistributed when a cell
* is hidden/shown and floated/tiled.
*/
struct layout_cell *
layout_cell_get_neighbour(struct layout_cell *lc)
{
struct layout_cell *lcother, *lcparent = lc->parent;
int direction = 0;
int direction = 1;
if (lcparent == NULL)
return (NULL);
if (lc == TAILQ_FIRST(&lcparent->cells))
direction = 1;
if (lc == TAILQ_LAST(&lcparent->cells, layout_cells))
direction = !direction;
lcother = layout_cell_get_neighbour_direction(lc, direction);
if (lcother == NULL)
@@ -653,8 +724,10 @@ layout_cell_get_neighbour(struct layout_cell *lc)
return lcother;
}
/* Destroy a cell and redistribute the space if the cell was tiled. */
/*
* Destroy a cell and redistribute the space if the cell was tiled. Assumes
* to be called on a leaf cell.
*/
void
layout_destroy_cell(struct window *w, struct layout_cell *lc,
struct layout_cell **lcroot)
@@ -689,7 +762,7 @@ layout_destroy_cell(struct window *w, struct layout_cell *lc,
val = lc->sy + 1;
layout_resize_adjust(w, lcother, lcparent->type, val);
} else
layout_hide_cell(w, lcparent);
layout_remove_tile(w, lcparent);
/* Remove this from the parent's list. */
TAILQ_REMOVE(&lcparent->cells, lc, entry);
@@ -705,6 +778,7 @@ out:
TAILQ_REMOVE(&lcparent->cells, lc, entry);
lc->parent = lcparent->parent;
if (lc->parent == NULL) {
if (layout_cell_is_tiled(lc))
layout_set_size(lc, w->sx, w->sy, 0, 0);
@@ -820,7 +894,7 @@ layout_resize(struct window *w, u_int sx, u_int sy)
* out proportionately - this should leave the layout fitting the new
* window size.
*/
if (lc->type == LAYOUT_WINDOWPANE && !layout_cell_is_tiled(lc))
if (!layout_cell_is_tiled(lc))
return;
xchange = sx - lc->sx;
xlimit = layout_resize_check(w, lc, LAYOUT_LEFTRIGHT);
@@ -1195,7 +1269,8 @@ layout_resize_child_cells(struct window *w, struct layout_cell *lc)
count = 0;
previous = 0;
TAILQ_FOREACH(lcchild, &lc->cells, entry) {
if (!layout_cell_is_tiled(lcchild))
if (!layout_cell_is_tiled(lcchild) &&
!layout_cell_has_tiled_child(lcchild))
continue;
count++;
if (lc->type == LAYOUT_LEFTRIGHT)
@@ -1215,7 +1290,8 @@ layout_resize_child_cells(struct window *w, struct layout_cell *lc)
/* Resize children into the new size. */
idx = 0;
TAILQ_FOREACH(lcchild, &lc->cells, entry) {
if (!layout_cell_is_tiled(lcchild))
if (!layout_cell_is_tiled(lcchild) &&
!layout_cell_has_tiled_child(lcchild))
continue;
if (lc->type == LAYOUT_TOPBOTTOM) {
lcchild->sx = lc->sx;
@@ -1238,6 +1314,73 @@ layout_resize_child_cells(struct window *w, struct layout_cell *lc)
}
}
/* Checks if there is enough space for two new panes. */
int
layout_split_check_space(struct window_pane *wp, struct layout_cell *lc,
enum layout_type type)
{
struct style *sb_style = &wp->scrollbar_style;
u_int minimum, sx = lc->sx, sy = lc->sy;
int scrollbars, status;
status = options_get_number(wp->window->options, "pane-border-status");
scrollbars = options_get_number(wp->window->options, "pane-scrollbars");
switch (type) {
case LAYOUT_LEFTRIGHT:
if (scrollbars) {
minimum = PANE_MINIMUM * 2 + sb_style->width +
sb_style->pad;
} else
minimum = PANE_MINIMUM * 2 + 1;
if (sx < minimum)
return (0);
break;
case LAYOUT_TOPBOTTOM:
if (layout_add_horizontal_border(wp->window, lc, status))
minimum = PANE_MINIMUM * 2 + 2;
else
minimum = PANE_MINIMUM * 2 + 1;
if (sy < minimum)
return (0);
break;
default:
fatalx("bad layout type");
}
return (1);
}
/* Calculates the new cell sizes when splitting a pane. */
void
layout_split_sizes(struct layout_cell *lc, int size, int before,
enum layout_type type, u_int *size1, u_int *size2, u_int *saved_size)
{
u_int s1, s2, ss;
u_int sx = lc->sx, sy = lc->sy;
if (type == LAYOUT_LEFTRIGHT)
ss = sx;
else
ss = sy;
if (size < 0)
s2 = ((ss + 1) / 2) - 1;
else if (before)
s2 = ss - size - 1;
else
s2 = size;
if (s2 < PANE_MINIMUM)
s2 = PANE_MINIMUM;
else if (s2 > sx - 2)
s2 = ss - 2;
s1 = ss - 1 - s2;
*size1 = s1;
*size2 = s2;
*saved_size = ss;
}
/*
* Split a pane into two. size is a hint, or -1 for default half/half
* split. This must be followed by layout_assign_pane before much else happens!
@@ -1247,11 +1390,10 @@ layout_split_pane(struct window_pane *wp, enum layout_type type, int size,
int flags)
{
struct layout_cell *lc, *lcparent, *lcnew, *lc1, *lc2;
struct style *sb_style = &wp->scrollbar_style;
u_int sx, sy, xoff, yoff, size1, size2, minimum;
u_int sx, sy, xoff, yoff, size1, size2;
u_int new_size, saved_size, resize_first = 0;
int full_size = (flags & SPAWN_FULLSIZE), status;
int scrollbars;
int full_size = (flags & SPAWN_FULLSIZE);
int before = (flags & SPAWN_BEFORE);
/*
* If full_size is specified, add a new cell at the top of the window
@@ -1261,8 +1403,6 @@ layout_split_pane(struct window_pane *wp, enum layout_type type, int size,
lc = wp->window->layout_root;
else
lc = wp->layout_cell;
status = window_get_pane_status(wp->window);
scrollbars = options_get_number(wp->window->options, "pane-scrollbars");
/* Copy the old cell size. */
sx = lc->sx;
@@ -1271,47 +1411,14 @@ layout_split_pane(struct window_pane *wp, enum layout_type type, int size,
yoff = lc->yoff;
/* Check there is enough space for the two new panes. */
switch (type) {
case LAYOUT_LEFTRIGHT:
if (scrollbars) {
minimum = PANE_MINIMUM * 2 + sb_style->width +
sb_style->pad;
} else
minimum = PANE_MINIMUM * 2 + 1;
if (sx < minimum)
return (NULL);
break;
case LAYOUT_TOPBOTTOM:
if (layout_add_horizontal_border(wp->window, lc, status))
minimum = PANE_MINIMUM * 2 + 2;
else
minimum = PANE_MINIMUM * 2 + 1;
if (sy < minimum)
return (NULL);
break;
default:
fatalx("bad layout type");
}
if (!layout_split_check_space(wp, lc, type))
return (NULL);
/*
* Calculate new cell sizes. size is the target size or -1 for middle
* split, size1 is the size of the top/left and size2 the bottom/right.
*/
if (type == LAYOUT_LEFTRIGHT)
saved_size = sx;
else
saved_size = sy;
if (size < 0)
size2 = ((saved_size + 1) / 2) - 1;
else if (flags & SPAWN_BEFORE)
size2 = saved_size - size - 1;
else
size2 = size;
if (size2 < PANE_MINIMUM)
size2 = PANE_MINIMUM;
else if (size2 > saved_size - 2)
size2 = saved_size - 2;
size1 = saved_size - 1 - size2;
layout_split_sizes(lc, size, before, type, &size1, &size2, &saved_size);
/* Which size are we using? */
if (flags & SPAWN_BEFORE)
@@ -1433,8 +1540,8 @@ layout_floating_pane(struct window *w, struct window_pane *wp, u_int sx,
if (lcparent == NULL) {
/*
* Adding a pane to a root that doesn't have a container. Must
* create and insert a new root.
* Adding a pane to a root that isn't node. Must create and
* insert a new root.
*/
lcparent = layout_create_cell(NULL);
layout_make_node(lcparent, LAYOUT_TOPBOTTOM);
@@ -1619,51 +1726,40 @@ layout_get_tiled_cell(struct cmdq_item *item, struct args *args,
return (lc);
}
/* Get a new floating cell. */
struct layout_cell *
layout_get_floating_cell(struct cmdq_item *item, struct args *args,
struct window *w, struct window_pane *wp, char **cause)
void
layout_cell_floating_args_parse(struct cmdq_item *item, struct args *args,
struct window *w, u_int *sxp, u_int *syp, int *oxp, int *oyp, char **cause)
{
struct layout_cell *lcnew;
u_int sx = w->sx / 2, sy = w->sy / 4;
int ox = INT_MAX, oy = INT_MAX;
char *error;
int sx, sy, ox, oy;
sx = *sxp == UINT_MAX ? w->sx / 2 : *sxp;
sy = *syp == UINT_MAX ? w->sy / 4 : *syp;
ox = *oxp == INT_MAX ? INT_MAX : *oxp;
oy = *oyp == INT_MAX ? INT_MAX : *oyp;
if (args_has(args, 'x')) {
sx = args_percentage_and_expand(args, 'x', 0, w->sx - 1, w->sx,
item, &error);
if (error != NULL) {
xasprintf(cause, "position %s", error);
free(error);
return (NULL);
}
item, cause);
if (*cause != NULL)
return;
}
if (args_has(args, 'y')) {
sy = args_percentage_and_expand(args, 'y', 0, w->sy - 1, w->sy,
item, &error);
if (error != NULL) {
xasprintf(cause, "position %s", error);
free(error);
return (NULL);
}
item, cause);
if (*cause != NULL)
return;
}
if (args_has(args, 'X')) {
ox = args_percentage_and_expand(args, 'X', -sx, w->sx,
w->sx, item, &error);
if (error != NULL) {
xasprintf(cause, "size %s", error);
free(error);
return (NULL);
}
ox = args_percentage_and_expand(args, 'X', -(int)w->sx, w->sx,
w->sx, item, cause);
if (*cause != NULL)
return;
}
if (args_has(args, 'Y')) {
oy = args_percentage_and_expand(args, 'Y', -sy, w->sy,
w->sy, item, &error);
if (error != NULL) {
xasprintf(cause, "size %s", error);
free(error);
return (NULL);
}
oy = args_percentage_and_expand(args, 'Y', -(int)w->sy, w->sy,
w->sy, item, cause);
if (*cause != NULL)
return;
}
if (ox == INT_MAX) {
@@ -1686,6 +1782,25 @@ layout_get_floating_cell(struct cmdq_item *item, struct args *args,
}
w->last_new_pane_y = oy;
}
*sxp = sx;
*syp = sy;
*oxp = ox;
*oyp = oy;
}
/* Get a new floating cell. */
struct layout_cell *
layout_get_floating_cell(struct cmdq_item *item, struct args *args,
struct window *w, struct window_pane *wp, char **cause)
{
struct layout_cell *lcnew;
u_int sx, sy;
int ox, oy;
layout_cell_floating_args_parse(item, args, w, &sx, &sy, &ox, &oy, cause);
if (cause != NULL) {
return (NULL);
}
if (sx < PANE_MINIMUM || sx > PANE_MAXIMUM) {
*cause = xstrdup("invalid width");
@@ -1699,3 +1814,109 @@ layout_get_floating_cell(struct cmdq_item *item, struct args *args,
lcnew = layout_floating_pane(w, wp, sx, sy, ox, oy);
return (lcnew);
}
/*
* Removes a cell from the tiled layout by giving half the cells space to the
* nearest neighbour.
*/
int
layout_remove_tile(struct window *w, struct layout_cell *lc)
{
struct layout_cell *lcneighbour, *lcparent;
enum layout_type type;
int change;
if (lc->flags & LAYOUT_CELL_FLOATING)
return (0);
lcneighbour = layout_cell_get_neighbour(lc);
if (lcneighbour == NULL) {
if (lc->parent != NULL)
layout_remove_tile(w, lc->parent);
} else if ((lcparent = lcneighbour->parent) != NULL) {
type = lcparent->type;
/*
* Adding the size of the layout cell plus its border to the
* neighbour.
*/
if (type == LAYOUT_TOPBOTTOM)
change = lc->sy + 1;
else
change = lc->sx + 1;
layout_resize_adjust(w, lcneighbour, type, change);
}
/* Zeroing out the cell geometry until the cell is retiled. */
layout_set_size(lc, 0, 0, 0, 0);
return (1);
}
/*
* Inserts a cell back into the tiled layout by taking half the space from its
* nearest neighbour.
*/
int
layout_insert_tile(struct window *w, struct layout_cell *lc)
{
struct window_pane *wp;
struct layout_cell *lcneighbour, *lcparent = lc->parent;
enum layout_type type;
u_int size1, size2, saved_size;
if (lc == NULL)
fatalx("layout cell cannot be null when tiling");
if (lcparent == NULL) {
/* Only pane in the layout. */
layout_set_size(lc, w->sx, w->sy, 0, 0);
return (1);
}
type = lcparent->type;
lcneighbour = layout_cell_get_neighbour(lc);
if (lcneighbour == NULL) {
/*
* This will become the only visible cell in the parent.
* Tile the parent, then set the child's 'split' size.
*/
layout_insert_tile(w, lcparent);
if (type == LAYOUT_LEFTRIGHT)
size1 = lcparent->sx;
else
size1 = lcparent->sy;
layout_resize_set_size(w, lc, type, size1);
} else {
/*
* In order to determine if there is enough space to retile the
* pane, information is needed from window and window pane
* options. First get a neightbour window pane...
*/
if (~lcneighbour->type & LAYOUT_WINDOWPANE)
wp = TAILQ_FIRST(&lcneighbour->cells)->wp;
else
wp = lcneighbour->wp;
/*
* ...and then check if there is enough room to tile.
*/
if (!layout_split_check_space(wp, lcneighbour, type))
return (0);
layout_split_sizes(lcneighbour, -1, 0, type, &size1, &size2,
&saved_size);
layout_resize_set_size(w, lc, type, size1);
layout_resize_set_size(w, lcneighbour, type, size2);
}
/* Setting opposite of the 'split' size to that of the parent. */
if (lcparent != NULL) {
if (lcparent->type == LAYOUT_LEFTRIGHT) {
size1 = lcparent->sy;
type = LAYOUT_TOPBOTTOM;
} else {
size1 = lcparent->sx;
type = LAYOUT_LEFTRIGHT;
}
layout_resize_set_size(w, lc, type, size1);
}
return (1);
}

69
tmux.1
View File

@@ -3119,37 +3119,43 @@ The
command works only if at least one client is attached.
.Tg floatp
.It Xo Ic float\-pane
.Op Fl h Ar height
.Op Fl w Ar width
.Op Fl x Ar x
.Op Fl y Ar y
.Op Fl d
.Op Fl x Ar width
.Op Fl y Ar height
.Op Fl X Ar x\-position
.Op Fl y Ar y\-position
.Op Fl t Ar target\-pane
.Xc
.D1 Pq alias: Ic floatp
Lift
.Ar target\-pane
out of the tiled layout and make it a floating pane.
The
.Fl w
and
.Fl h
options set the width and height of the floating pane in columns and lines
respectively; the default is half the window width and height.
out of the tiled layout and make it floating.
The
.Fl x
and
.Fl y
options set the position of the upper-left corner of the pane;
if omitted, new floating panes are cascaded from the top-left of the window.
If
options set the width and height of the floating pane in columns and lines
respectively.
The default is half the window width and a quarter the window height.
The
.Fl X
and
.Fl Y
options set the position of the upper-left corner of the pane.
If omitted, new floating panes are cascaded from the top-left of the window.
.Pp
If the pane had previously been floating, the position and sizes are restored
from the saved values not specified by the
.Fl x ,
.Fl y ,
.Fl w ,
.Fl X ,
and
.Fl h
are all omitted and the pane was previously returned to the tiled layout
with
.Ic tile\-pane ,
its last floating position and size are restored.
.Fl Y
options.
.Pp
If
.Fl d
is given, the active pane is not changed.
The pane must not already be floating or hidden, and the window must not
be zoomed.
.Tg joinp
@@ -4064,18 +4070,23 @@ is omitted and a marked pane is present (see
the window containing the marked pane is used rather than the current window.
.Tg tilep
.It Xo Ic tile\-pane
.Op Fl d
.Op Fl t Ar target\-pane
.Xc
Insert a floating
.D1 Pq alias: Ic tilep
Return a floating
.Ar target\-pane
back into the tiled layout.
The pane is placed by splitting the active tiled pane (or the most recently
active tiled pane, or any visible tiled pane if none is active).
The current floating position and size are saved so they can be restored by
a subsequent
.Ic float\-pane
command with no geometry options.
The pane must be floating and the window must not be zoomed.
back to the tiled layout.
The location in the layout is preserved.
Half the space from the nearest tiled pane in the layout is given to
.Ar target\-pane .
The floating position and sizes are saved so they can be restored by future
calls to
.Ic float\-pane .
If
.Fl d
is given, the active pane is not changed.
The pane must be floating, not hidden, and the window must not be zoomed.
.Tg showp
.It Xo Ic show\-pane
.Op Fl t Ar target\-pane

23
tmux.h
View File

@@ -1290,7 +1290,6 @@ struct window_pane {
#define PANE_UNSEENCHANGES 0x4000
#define PANE_REDRAWSCROLLBAR 0x8000
#define PANE_HIDDEN 0x20000
#define PANE_SAVED_FLOAT 0x80000 /* saved_float_* fields are valid */
/* Last floating position/size, saved when the pane is tiled. */
int saved_float_xoff;
@@ -1497,11 +1496,11 @@ struct layout_cell {
struct layout_cell *parent;
u_int sx;
u_int sy;
u_int sx, saved_sx;
u_int sy, saved_sy;
int xoff;
int yoff;
int xoff, saved_xoff;
int yoff, saved_yoff;
struct window_pane *wp;
struct layout_cells cells;
@@ -3535,6 +3534,7 @@ int window_get_pane_status(struct window *);
struct style_range *window_pane_status_get_range(struct window_pane *, u_int,
u_int);
int window_pane_is_floating(struct window_pane *);
int window_pane_is_hidden(struct window_pane *);
/* layout.c */
u_int layout_count_cells(struct layout_cell *);
@@ -3551,14 +3551,16 @@ void layout_resize_layout(struct window *, struct layout_cell *,
enum layout_type, int, int);
struct layout_cell *layout_search_by_border(struct layout_cell *, u_int, u_int);
void layout_set_size(struct layout_cell *, u_int, u_int, int, int);
void layout_save_size(struct layout_cell *);
void layout_make_leaf(struct layout_cell *, struct window_pane *);
void layout_make_node(struct layout_cell *, enum layout_type);
void layout_fix_zindexes(struct window *, struct layout_cell *);
void layout_fix_offsets(struct window *);
int layout_cell_is_tiled(struct layout_cell *);
void layout_fix_panes(struct window *, struct window_pane *);
void layout_resize_adjust(struct window *, struct layout_cell *,
enum layout_type, int);
void layout_resize_set_size(struct window *, struct layout_cell *,
enum layout_type, u_int);
struct layout_cell *layout_cell_get_neighbour(struct layout_cell *);
void layout_init(struct window *, struct window_pane *);
void layout_free(struct window *);
@@ -3573,6 +3575,10 @@ void layout_resize_floating_pane_to(struct window_pane *,
enum layout_type, u_int, char **);
void layout_assign_pane(struct layout_cell *, struct window_pane *,
int);
int layout_split_check_space(struct window_pane *,
struct layout_cell *, enum layout_type);
void layout_split_sizes(struct layout_cell *, int, int,
enum layout_type, u_int *, u_int *, u_int *);
struct layout_cell *layout_split_pane(struct window_pane *, enum layout_type,
int, int);
struct layout_cell *layout_floating_pane(struct window *, struct window_pane *,
@@ -3580,10 +3586,15 @@ struct layout_cell *layout_floating_pane(struct window *, struct window_pane *,
void layout_close_pane(struct window_pane *);
int layout_spread_cell(struct window *, struct layout_cell *);
void layout_spread_out(struct window_pane *);
void layout_cell_floating_args_parse(struct cmdq_item *, struct args *,
struct window *, u_int *, u_int *, int *, int *, char **);
struct layout_cell *layout_get_floating_cell(struct cmdq_item *, struct args *,
struct window *, struct window_pane *, char **);
struct layout_cell *layout_get_tiled_cell(struct cmdq_item *, struct args *,
struct window *, struct window_pane *, int, char **);
int layout_cell_is_tiled(struct layout_cell *);
int layout_remove_tile(struct window *, struct layout_cell *);
int layout_insert_tile(struct window *, struct layout_cell *);
/* layout-custom.c */
char *layout_dump(struct window *, struct layout_cell *);

View File

@@ -2208,3 +2208,13 @@ window_pane_is_floating(struct window_pane *wp)
return (0);
return (1);
}
int
window_pane_is_hidden(struct window_pane *wp)
{
struct layout_cell *lc = wp->layout_cell;
if ((lc->flags & LAYOUT_CELL_HIDDEN) == 0)
return (0);
return (1);
}