xref: /openbsd-src/usr.bin/tmux/layout.c (revision 5df986e2a22d2f17634f183639549e2b44387c6d)
1*5df986e2Snicm /* $OpenBSD: layout.c,v 1.52 2024/12/17 08:40:24 nicm Exp $ */
2311827fbSnicm 
3311827fbSnicm /*
498ca8272Snicm  * Copyright (c) 2009 Nicholas Marriott <nicholas.marriott@gmail.com>
5824e896bSnicm  * Copyright (c) 2016 Stephen Kent <smkent@smkent.net>
6311827fbSnicm  *
7311827fbSnicm  * Permission to use, copy, modify, and distribute this software for any
8311827fbSnicm  * purpose with or without fee is hereby granted, provided that the above
9311827fbSnicm  * copyright notice and this permission notice appear in all copies.
10311827fbSnicm  *
11311827fbSnicm  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12311827fbSnicm  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13311827fbSnicm  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14311827fbSnicm  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15311827fbSnicm  * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
16311827fbSnicm  * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
17311827fbSnicm  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18311827fbSnicm  */
19311827fbSnicm 
20311827fbSnicm #include <sys/types.h>
21311827fbSnicm 
22af9e4c5dSnicm #include <stdlib.h>
23311827fbSnicm 
24311827fbSnicm #include "tmux.h"
25311827fbSnicm 
26311827fbSnicm /*
27af9e4c5dSnicm  * The window layout is a tree of cells each of which can be one of: a
28af9e4c5dSnicm  * left-right container for a list of cells, a top-bottom container for a list
29af9e4c5dSnicm  * of cells, or a container for a window pane.
30311827fbSnicm  *
31af9e4c5dSnicm  * Each window has a pointer to the root of its layout tree (containing its
32af9e4c5dSnicm  * panes), every pane has a pointer back to the cell containing it, and each
33af9e4c5dSnicm  * cell a pointer to its parent cell.
34311827fbSnicm  */
35311827fbSnicm 
3607b91187Snicm static u_int	layout_resize_check(struct window *, struct layout_cell *,
3707b91187Snicm 		    enum layout_type);
3807b91187Snicm static int	layout_resize_pane_grow(struct window *, struct layout_cell *,
39f4d5873aSnicm 		    enum layout_type, int, int);
4007b91187Snicm static int	layout_resize_pane_shrink(struct window *, struct layout_cell *,
41bc3b19faSnicm 		    enum layout_type, int);
42824e896bSnicm static u_int	layout_new_pane_size(struct window *, u_int,
43824e896bSnicm 		    struct layout_cell *, enum layout_type, u_int, u_int,
44824e896bSnicm 		    u_int);
45824e896bSnicm static int	layout_set_size_check(struct window *, struct layout_cell *,
46824e896bSnicm 		    enum layout_type, int);
47824e896bSnicm static void	layout_resize_child_cells(struct window *,
48824e896bSnicm 		    struct layout_cell *);
49311827fbSnicm 
50af9e4c5dSnicm struct layout_cell *
51af9e4c5dSnicm layout_create_cell(struct layout_cell *lcparent)
52311827fbSnicm {
53af9e4c5dSnicm 	struct layout_cell	*lc;
54311827fbSnicm 
55af9e4c5dSnicm 	lc = xmalloc(sizeof *lc);
56af9e4c5dSnicm 	lc->type = LAYOUT_WINDOWPANE;
57af9e4c5dSnicm 	lc->parent = lcparent;
58311827fbSnicm 
59af9e4c5dSnicm 	TAILQ_INIT(&lc->cells);
60311827fbSnicm 
61af9e4c5dSnicm 	lc->sx = UINT_MAX;
62af9e4c5dSnicm 	lc->sy = UINT_MAX;
63311827fbSnicm 
64af9e4c5dSnicm 	lc->xoff = UINT_MAX;
65af9e4c5dSnicm 	lc->yoff = UINT_MAX;
66311827fbSnicm 
67af9e4c5dSnicm 	lc->wp = NULL;
68af9e4c5dSnicm 
69af9e4c5dSnicm 	return (lc);
70311827fbSnicm }
71311827fbSnicm 
72311827fbSnicm void
73af9e4c5dSnicm layout_free_cell(struct layout_cell *lc)
74311827fbSnicm {
75af9e4c5dSnicm 	struct layout_cell	*lcchild;
76af9e4c5dSnicm 
77af9e4c5dSnicm 	switch (lc->type) {
78af9e4c5dSnicm 	case LAYOUT_LEFTRIGHT:
79af9e4c5dSnicm 	case LAYOUT_TOPBOTTOM:
80af9e4c5dSnicm 		while (!TAILQ_EMPTY(&lc->cells)) {
81af9e4c5dSnicm 			lcchild = TAILQ_FIRST(&lc->cells);
82af9e4c5dSnicm 			TAILQ_REMOVE(&lc->cells, lcchild, entry);
83af9e4c5dSnicm 			layout_free_cell(lcchild);
84af9e4c5dSnicm 		}
85af9e4c5dSnicm 		break;
86af9e4c5dSnicm 	case LAYOUT_WINDOWPANE:
87af9e4c5dSnicm 		if (lc->wp != NULL)
88af9e4c5dSnicm 			lc->wp->layout_cell = NULL;
89af9e4c5dSnicm 		break;
90af9e4c5dSnicm 	}
91af9e4c5dSnicm 
927d053cf9Snicm 	free(lc);
93311827fbSnicm }
94311827fbSnicm 
95311827fbSnicm void
96af9e4c5dSnicm layout_print_cell(struct layout_cell *lc, const char *hdr, u_int n)
97311827fbSnicm {
98af9e4c5dSnicm 	struct layout_cell	*lcchild;
9968eb3405Snicm 	const char		*type;
100af9e4c5dSnicm 
10168eb3405Snicm 	switch (lc->type) {
10268eb3405Snicm 	case LAYOUT_LEFTRIGHT:
10368eb3405Snicm 		type = "LEFTRIGHT";
10468eb3405Snicm 		break;
10568eb3405Snicm 	case LAYOUT_TOPBOTTOM:
10668eb3405Snicm 		type = "TOPBOTTOM";
10768eb3405Snicm 		break;
10868eb3405Snicm 	case LAYOUT_WINDOWPANE:
10968eb3405Snicm 		type = "WINDOWPANE";
11068eb3405Snicm 		break;
11168eb3405Snicm 	default:
11268eb3405Snicm 		type = "UNKNOWN";
11368eb3405Snicm 		break;
11468eb3405Snicm 	}
11568eb3405Snicm 	log_debug("%s:%*s%p type %s [parent %p] wp=%p [%u,%u %ux%u]", hdr, n,
11668eb3405Snicm 	    " ", lc, type, lc->parent, lc->wp, lc->xoff, lc->yoff, lc->sx,
117d5221700Snicm 	    lc->sy);
118af9e4c5dSnicm 	switch (lc->type) {
119af9e4c5dSnicm 	case LAYOUT_LEFTRIGHT:
120af9e4c5dSnicm 	case LAYOUT_TOPBOTTOM:
121af9e4c5dSnicm 		TAILQ_FOREACH(lcchild, &lc->cells, entry)
122af9e4c5dSnicm 		    	layout_print_cell(lcchild, hdr, n + 1);
123af9e4c5dSnicm 		break;
124af9e4c5dSnicm 	case LAYOUT_WINDOWPANE:
125af9e4c5dSnicm 		break;
126af9e4c5dSnicm 	}
127311827fbSnicm }
128311827fbSnicm 
1293d1607c5Snicm struct layout_cell *
1303d1607c5Snicm layout_search_by_border(struct layout_cell *lc, u_int x, u_int y)
1313d1607c5Snicm {
1323d1607c5Snicm 	struct layout_cell	*lcchild, *last = NULL;
1333d1607c5Snicm 
1343d1607c5Snicm 	TAILQ_FOREACH(lcchild, &lc->cells, entry) {
1353d1607c5Snicm 		if (x >= lcchild->xoff && x < lcchild->xoff + lcchild->sx &&
1363d1607c5Snicm 		    y >= lcchild->yoff && y < lcchild->yoff + lcchild->sy) {
1373d1607c5Snicm 			/* Inside the cell - recurse. */
1383d1607c5Snicm 			return (layout_search_by_border(lcchild, x, y));
1393d1607c5Snicm 		}
1403d1607c5Snicm 
1413d1607c5Snicm 		if (last == NULL) {
1423d1607c5Snicm 			last = lcchild;
1433d1607c5Snicm 			continue;
1443d1607c5Snicm 		}
1453d1607c5Snicm 
1463d1607c5Snicm 		switch (lc->type) {
1473d1607c5Snicm 		case LAYOUT_LEFTRIGHT:
1483d1607c5Snicm 			if (x < lcchild->xoff && x >= last->xoff + last->sx)
1493d1607c5Snicm 				return (last);
1503d1607c5Snicm 			break;
1513d1607c5Snicm 		case LAYOUT_TOPBOTTOM:
1523d1607c5Snicm 			if (y < lcchild->yoff && y >= last->yoff + last->sy)
1533d1607c5Snicm 				return (last);
1543d1607c5Snicm 			break;
1553d1607c5Snicm 		case LAYOUT_WINDOWPANE:
1563d1607c5Snicm 			break;
1573d1607c5Snicm 		}
1583d1607c5Snicm 
1593d1607c5Snicm 		last = lcchild;
1603d1607c5Snicm 	}
1613d1607c5Snicm 
1623d1607c5Snicm 	return (NULL);
1633d1607c5Snicm }
1643d1607c5Snicm 
165311827fbSnicm void
166d5221700Snicm layout_set_size(struct layout_cell *lc, u_int sx, u_int sy, u_int xoff,
167d5221700Snicm     u_int yoff)
168311827fbSnicm {
169af9e4c5dSnicm 	lc->sx = sx;
170af9e4c5dSnicm 	lc->sy = sy;
171311827fbSnicm 
172af9e4c5dSnicm 	lc->xoff = xoff;
173af9e4c5dSnicm 	lc->yoff = yoff;
174311827fbSnicm }
175311827fbSnicm 
176311827fbSnicm void
177af9e4c5dSnicm layout_make_leaf(struct layout_cell *lc, struct window_pane *wp)
178af9e4c5dSnicm {
179af9e4c5dSnicm 	lc->type = LAYOUT_WINDOWPANE;
180af9e4c5dSnicm 
181af9e4c5dSnicm 	TAILQ_INIT(&lc->cells);
182af9e4c5dSnicm 
183af9e4c5dSnicm 	wp->layout_cell = lc;
184af9e4c5dSnicm 	lc->wp = wp;
185af9e4c5dSnicm }
186af9e4c5dSnicm 
187af9e4c5dSnicm void
188af9e4c5dSnicm layout_make_node(struct layout_cell *lc, enum layout_type type)
189af9e4c5dSnicm {
190af9e4c5dSnicm 	if (type == LAYOUT_WINDOWPANE)
191af9e4c5dSnicm 		fatalx("bad layout type");
192af9e4c5dSnicm 	lc->type = type;
193af9e4c5dSnicm 
194af9e4c5dSnicm 	TAILQ_INIT(&lc->cells);
195af9e4c5dSnicm 
196af9e4c5dSnicm 	if (lc->wp != NULL)
197af9e4c5dSnicm 		lc->wp->layout_cell = NULL;
198af9e4c5dSnicm 	lc->wp = NULL;
199af9e4c5dSnicm }
200af9e4c5dSnicm 
2019bc2f29eSnicm /* Fix cell offsets for a child cell. */
20242fbd26aSnicm static void
20342fbd26aSnicm layout_fix_offsets1(struct layout_cell *lc)
204af9e4c5dSnicm {
205af9e4c5dSnicm 	struct layout_cell	*lcchild;
206af9e4c5dSnicm 	u_int			 xoff, yoff;
207af9e4c5dSnicm 
208af9e4c5dSnicm 	if (lc->type == LAYOUT_LEFTRIGHT) {
209af9e4c5dSnicm 		xoff = lc->xoff;
210af9e4c5dSnicm 		TAILQ_FOREACH(lcchild, &lc->cells, entry) {
211af9e4c5dSnicm 			lcchild->xoff = xoff;
212af9e4c5dSnicm 			lcchild->yoff = lc->yoff;
213af9e4c5dSnicm 			if (lcchild->type != LAYOUT_WINDOWPANE)
21442fbd26aSnicm 				layout_fix_offsets1(lcchild);
215af9e4c5dSnicm 			xoff += lcchild->sx + 1;
216af9e4c5dSnicm 		}
217af9e4c5dSnicm 	} else {
218af9e4c5dSnicm 		yoff = lc->yoff;
219af9e4c5dSnicm 		TAILQ_FOREACH(lcchild, &lc->cells, entry) {
220af9e4c5dSnicm 			lcchild->xoff = lc->xoff;
221af9e4c5dSnicm 			lcchild->yoff = yoff;
222af9e4c5dSnicm 			if (lcchild->type != LAYOUT_WINDOWPANE)
22342fbd26aSnicm 				layout_fix_offsets1(lcchild);
224af9e4c5dSnicm 			yoff += lcchild->sy + 1;
225af9e4c5dSnicm 		}
226af9e4c5dSnicm 	}
227af9e4c5dSnicm }
228af9e4c5dSnicm 
22942fbd26aSnicm /* Update cell offsets based on their sizes. */
23042fbd26aSnicm void
23142fbd26aSnicm layout_fix_offsets(struct window *w)
23242fbd26aSnicm {
23342fbd26aSnicm 	struct layout_cell      *lc = w->layout_root;
23442fbd26aSnicm 
23542fbd26aSnicm 	lc->xoff = 0;
23642fbd26aSnicm 	lc->yoff = 0;
23742fbd26aSnicm 
23842fbd26aSnicm 	layout_fix_offsets1(lc);
23942fbd26aSnicm }
24042fbd26aSnicm 
2419bc2f29eSnicm /* Is this a top cell? */
242bc3b19faSnicm static int
2439bc2f29eSnicm layout_cell_is_top(struct window *w, struct layout_cell *lc)
244bc3b19faSnicm {
245c1cd4700Snicm 	struct layout_cell	*next;
246bc3b19faSnicm 
2479bc2f29eSnicm 	while (lc != w->layout_root) {
2489bc2f29eSnicm 		next = lc->parent;
2499bc2f29eSnicm 		if (next->type == LAYOUT_TOPBOTTOM &&
2509bc2f29eSnicm 		    lc != TAILQ_FIRST(&next->cells))
251bc3b19faSnicm 			return (0);
2529bc2f29eSnicm 		lc = next;
253bc3b19faSnicm 	}
254bc3b19faSnicm 	return (1);
255bc3b19faSnicm }
256bc3b19faSnicm 
2579bc2f29eSnicm /* Is this a bottom cell? */
2589bc2f29eSnicm static int
2599bc2f29eSnicm layout_cell_is_bottom(struct window *w, struct layout_cell *lc)
2609bc2f29eSnicm {
2619bc2f29eSnicm 	struct layout_cell	*next;
2629bc2f29eSnicm 
2639bc2f29eSnicm 	while (lc != w->layout_root) {
2649bc2f29eSnicm 		next = lc->parent;
2659bc2f29eSnicm 		if (next->type == LAYOUT_TOPBOTTOM &&
2669bc2f29eSnicm 		    lc != TAILQ_LAST(&next->cells, layout_cells))
2679bc2f29eSnicm 			return (0);
2689bc2f29eSnicm 		lc = next;
2699bc2f29eSnicm 	}
2709bc2f29eSnicm 	return (1);
2719bc2f29eSnicm }
2729bc2f29eSnicm 
2739bc2f29eSnicm /*
2749bc2f29eSnicm  * Returns 1 if we need to add an extra line for the pane status line. This is
2759bc2f29eSnicm  * the case for the most upper or lower panes only.
2769bc2f29eSnicm  */
2779bc2f29eSnicm static int
278d2117533Snicm layout_add_horizontal_border(struct window *w, struct layout_cell *lc,
279d2117533Snicm     int status)
2809bc2f29eSnicm {
2819bc2f29eSnicm 	if (status == PANE_STATUS_TOP)
2829bc2f29eSnicm 		return (layout_cell_is_top(w, lc));
2839bc2f29eSnicm 	if (status == PANE_STATUS_BOTTOM)
2849bc2f29eSnicm 		return (layout_cell_is_bottom(w, lc));
2859bc2f29eSnicm 	return (0);
2869bc2f29eSnicm }
2879bc2f29eSnicm 
288af9e4c5dSnicm /* Update pane offsets and sizes based on their cells. */
289af9e4c5dSnicm void
290baddd6b2Snicm layout_fix_panes(struct window *w, struct window_pane *skip)
291311827fbSnicm {
292311827fbSnicm 	struct window_pane	*wp;
293af9e4c5dSnicm 	struct layout_cell	*lc;
294dd0df669Snicm 	int			 status, scrollbars, sb_pos, sb_w, sb_pad;
2958b458060Snicm 	u_int			 sx, sy;
296311827fbSnicm 
297bc3b19faSnicm 	status = options_get_number(w->options, "pane-border-status");
298d2117533Snicm 	scrollbars = options_get_number(w->options, "pane-scrollbars");
299d2117533Snicm 	sb_pos = options_get_number(w->options, "pane-scrollbars-position");
300d2117533Snicm 
301311827fbSnicm 	TAILQ_FOREACH(wp, &w->panes, entry) {
302baddd6b2Snicm 		if ((lc = wp->layout_cell) == NULL || wp == skip)
303af9e4c5dSnicm 			continue;
304bc3b19faSnicm 
305af9e4c5dSnicm 		wp->xoff = lc->xoff;
306af9e4c5dSnicm 		wp->yoff = lc->yoff;
307d2117533Snicm 		sx = lc->sx;
308d2117533Snicm 		sy = lc->sy;
309af9e4c5dSnicm 
310d2117533Snicm 		if (layout_add_horizontal_border(w, lc, status)) {
3119bc2f29eSnicm 			if (status == PANE_STATUS_TOP)
3129bc2f29eSnicm 				wp->yoff++;
313d2117533Snicm 			sy--;
314d2117533Snicm 		}
315d2117533Snicm 
3168b458060Snicm 		if (window_pane_show_scrollbar(wp, scrollbars)) {
317dd0df669Snicm 			sb_w = wp->scrollbar_style.width;
318dd0df669Snicm 			sb_pad = wp->scrollbar_style.pad;
319dd0df669Snicm 			if (sb_w < 1)
320dd0df669Snicm 				sb_w = 1;
321dd0df669Snicm 			if (sb_pad < 0)
322dd0df669Snicm 				sb_pad = 0;
323d2117533Snicm 			if (sb_pos == PANE_SCROLLBARS_LEFT) {
324dd0df669Snicm 				if ((int)sx - sb_w < PANE_MINIMUM) {
325dd0df669Snicm 					wp->xoff = wp->xoff +
326dd0df669Snicm 					    (int)sx - PANE_MINIMUM;
327dd0df669Snicm 					sx = PANE_MINIMUM;
328dd0df669Snicm 				} else {
329dd0df669Snicm 					sx = sx - sb_w - sb_pad;
330dd0df669Snicm 					wp->xoff = wp->xoff + sb_w + sb_pad;
331dd0df669Snicm 				}
332d2117533Snicm 			} else /* sb_pos == PANE_SCROLLBARS_RIGHT */
333dd0df669Snicm 				if ((int)sx - sb_w - sb_pad < PANE_MINIMUM)
334dd0df669Snicm 					sx = PANE_MINIMUM;
335dd0df669Snicm 				else
336dd0df669Snicm 					sx = sx - sb_w - sb_pad;
337d2117533Snicm 			wp->flags |= PANE_REDRAWSCROLLBAR;
338d2117533Snicm 		}
339d2117533Snicm 
340d2117533Snicm 		window_pane_resize(wp, sx, sy);
341311827fbSnicm 	}
342311827fbSnicm }
343311827fbSnicm 
344f4611a41Snicm /* Count the number of available cells in a layout. */
345f4611a41Snicm u_int
346f4611a41Snicm layout_count_cells(struct layout_cell *lc)
347f4611a41Snicm {
348f4611a41Snicm 	struct layout_cell	*lcchild;
349824e896bSnicm 	u_int			 count;
350f4611a41Snicm 
351f4611a41Snicm 	switch (lc->type) {
352f4611a41Snicm 	case LAYOUT_WINDOWPANE:
353f4611a41Snicm 		return (1);
354f4611a41Snicm 	case LAYOUT_LEFTRIGHT:
355f4611a41Snicm 	case LAYOUT_TOPBOTTOM:
356824e896bSnicm 		count = 0;
357f4611a41Snicm 		TAILQ_FOREACH(lcchild, &lc->cells, entry)
358824e896bSnicm 			count += layout_count_cells(lcchild);
359824e896bSnicm 		return (count);
360f4611a41Snicm 	default:
361f4611a41Snicm 		fatalx("bad layout type");
362f4611a41Snicm 	}
363f4611a41Snicm }
364f4611a41Snicm 
365af9e4c5dSnicm /* Calculate how much size is available to be removed from a cell. */
36607b91187Snicm static u_int
36707b91187Snicm layout_resize_check(struct window *w, struct layout_cell *lc,
36807b91187Snicm     enum layout_type type)
369311827fbSnicm {
370af9e4c5dSnicm 	struct layout_cell	*lcchild;
371dd0df669Snicm 	struct style		*sb_style = &w->active->scrollbar_style;
372af9e4c5dSnicm 	u_int			 available, minimum;
373d2117533Snicm 	int			 status, scrollbars;
374311827fbSnicm 
375781ff0fdSnicm 	status = options_get_number(w->options, "pane-border-status");
376d2117533Snicm 	scrollbars = options_get_number(w->options, "pane-scrollbars");
377d2117533Snicm 
378af9e4c5dSnicm 	if (lc->type == LAYOUT_WINDOWPANE) {
379af9e4c5dSnicm 		/* Space available in this cell only. */
3809bc2f29eSnicm 		if (type == LAYOUT_LEFTRIGHT) {
381af9e4c5dSnicm 			available = lc->sx;
382d2117533Snicm 			if (scrollbars)
383dd0df669Snicm 				minimum = PANE_MINIMUM + sb_style->width +
384dd0df669Snicm 				    sb_style->pad;
385d2117533Snicm 			else
3869bc2f29eSnicm 				minimum = PANE_MINIMUM;
3879bc2f29eSnicm 		} else {
388af9e4c5dSnicm 			available = lc->sy;
389d2117533Snicm 			if (layout_add_horizontal_border(w, lc, status))
3909bc2f29eSnicm 				minimum = PANE_MINIMUM + 1;
3919bc2f29eSnicm 			else
3929bc2f29eSnicm 				minimum = PANE_MINIMUM;
39307b91187Snicm 		}
39407b91187Snicm 		if (available > minimum)
39507b91187Snicm 			available -= minimum;
396311827fbSnicm 		else
397af9e4c5dSnicm 			available = 0;
398af9e4c5dSnicm 	} else if (lc->type == type) {
399af9e4c5dSnicm 		/* Same type: total of available space in all child cells. */
400af9e4c5dSnicm 		available = 0;
401af9e4c5dSnicm 		TAILQ_FOREACH(lcchild, &lc->cells, entry)
40207b91187Snicm 			available += layout_resize_check(w, lcchild, type);
403af9e4c5dSnicm 	} else {
404af9e4c5dSnicm 		/* Different type: minimum of available space in child cells. */
405af9e4c5dSnicm 		minimum = UINT_MAX;
406af9e4c5dSnicm 		TAILQ_FOREACH(lcchild, &lc->cells, entry) {
40707b91187Snicm 			available = layout_resize_check(w, lcchild, type);
408af9e4c5dSnicm 			if (available < minimum)
409af9e4c5dSnicm 				minimum = available;
410af9e4c5dSnicm 		}
411af9e4c5dSnicm 		available = minimum;
412311827fbSnicm 	}
413311827fbSnicm 
414af9e4c5dSnicm 	return (available);
415311827fbSnicm }
416311827fbSnicm 
417af9e4c5dSnicm /*
418af9e4c5dSnicm  * Adjust cell size evenly, including altering its children. This function
419af9e4c5dSnicm  * expects the change to have already been bounded to the space available.
420af9e4c5dSnicm  */
421311827fbSnicm void
42207b91187Snicm layout_resize_adjust(struct window *w, struct layout_cell *lc,
42307b91187Snicm     enum layout_type type, int change)
424311827fbSnicm {
425af9e4c5dSnicm 	struct layout_cell	*lcchild;
426311827fbSnicm 
427af9e4c5dSnicm 	/* Adjust the cell size. */
428af9e4c5dSnicm 	if (type == LAYOUT_LEFTRIGHT)
429af9e4c5dSnicm 		lc->sx += change;
430af9e4c5dSnicm 	else
431af9e4c5dSnicm 		lc->sy += change;
432af9e4c5dSnicm 
433af9e4c5dSnicm 	/* If this is a leaf cell, that is all that is necessary. */
434af9e4c5dSnicm 	if (type == LAYOUT_WINDOWPANE)
435311827fbSnicm 		return;
436311827fbSnicm 
437af9e4c5dSnicm 	/* Child cell runs in a different direction. */
438af9e4c5dSnicm 	if (lc->type != type) {
439af9e4c5dSnicm 		TAILQ_FOREACH(lcchild, &lc->cells, entry)
44007b91187Snicm 			layout_resize_adjust(w, lcchild, type, change);
441311827fbSnicm 		return;
442311827fbSnicm 	}
443311827fbSnicm 
444af9e4c5dSnicm 	/*
445af9e4c5dSnicm 	 * Child cell runs in the same direction. Adjust each child equally
446af9e4c5dSnicm 	 * until no further change is possible.
447af9e4c5dSnicm 	 */
448af9e4c5dSnicm 	while (change != 0) {
449af9e4c5dSnicm 		TAILQ_FOREACH(lcchild, &lc->cells, entry) {
450af9e4c5dSnicm 			if (change == 0)
451af9e4c5dSnicm 				break;
452af9e4c5dSnicm 			if (change > 0) {
45307b91187Snicm 				layout_resize_adjust(w, lcchild, type, 1);
454af9e4c5dSnicm 				change--;
455311827fbSnicm 				continue;
456311827fbSnicm 			}
45707b91187Snicm 			if (layout_resize_check(w, lcchild, type) > 0) {
45807b91187Snicm 				layout_resize_adjust(w, lcchild, type, -1);
459af9e4c5dSnicm 				change++;
460311827fbSnicm 			}
461af9e4c5dSnicm 		}
462311827fbSnicm 	}
463311827fbSnicm }
464311827fbSnicm 
465f4611a41Snicm /* Destroy a cell and redistribute the space. */
466f4611a41Snicm void
46707b91187Snicm layout_destroy_cell(struct window *w, struct layout_cell *lc,
46807b91187Snicm     struct layout_cell **lcroot)
469f4611a41Snicm {
470f4611a41Snicm 	struct layout_cell     *lcother, *lcparent;
471f4611a41Snicm 
472f4611a41Snicm 	/*
473f4611a41Snicm 	 * If no parent, this is the last pane so window close is imminent and
474f4611a41Snicm 	 * there is no need to resize anything.
475f4611a41Snicm 	 */
476f4611a41Snicm 	lcparent = lc->parent;
477f4611a41Snicm 	if (lcparent == NULL) {
478f4611a41Snicm 		layout_free_cell(lc);
479f4611a41Snicm 		*lcroot = NULL;
480f4611a41Snicm 		return;
481f4611a41Snicm 	}
482f4611a41Snicm 
483f4611a41Snicm 	/* Merge the space into the previous or next cell. */
484f4611a41Snicm 	if (lc == TAILQ_FIRST(&lcparent->cells))
485f4611a41Snicm 		lcother = TAILQ_NEXT(lc, entry);
486f4611a41Snicm 	else
487f4611a41Snicm 		lcother = TAILQ_PREV(lc, layout_cells, entry);
4889a0e05b8Snicm 	if (lcother != NULL && lcparent->type == LAYOUT_LEFTRIGHT)
48907b91187Snicm 		layout_resize_adjust(w, lcother, lcparent->type, lc->sx + 1);
4909a0e05b8Snicm 	else if (lcother != NULL)
49107b91187Snicm 		layout_resize_adjust(w, lcother, lcparent->type, lc->sy + 1);
492f4611a41Snicm 
493f4611a41Snicm 	/* Remove this from the parent's list. */
494f4611a41Snicm 	TAILQ_REMOVE(&lcparent->cells, lc, entry);
495f4611a41Snicm 	layout_free_cell(lc);
496f4611a41Snicm 
497f4611a41Snicm 	/*
498f4611a41Snicm 	 * If the parent now has one cell, remove the parent from the tree and
499f4611a41Snicm 	 * replace it by that cell.
500f4611a41Snicm 	 */
501f4611a41Snicm 	lc = TAILQ_FIRST(&lcparent->cells);
502f4611a41Snicm 	if (TAILQ_NEXT(lc, entry) == NULL) {
503f4611a41Snicm 		TAILQ_REMOVE(&lcparent->cells, lc, entry);
504f4611a41Snicm 
505f4611a41Snicm 		lc->parent = lcparent->parent;
506f4611a41Snicm 		if (lc->parent == NULL) {
507f4611a41Snicm 			lc->xoff = 0; lc->yoff = 0;
508f4611a41Snicm 			*lcroot = lc;
509f4611a41Snicm 		} else
510f4611a41Snicm 			TAILQ_REPLACE(&lc->parent->cells, lcparent, lc, entry);
511f4611a41Snicm 
512f4611a41Snicm 		layout_free_cell(lcparent);
513f4611a41Snicm 	}
514f4611a41Snicm }
515f4611a41Snicm 
516311827fbSnicm void
517b4a3311eSnicm layout_init(struct window *w, struct window_pane *wp)
518311827fbSnicm {
519af9e4c5dSnicm 	struct layout_cell	*lc;
520311827fbSnicm 
521af9e4c5dSnicm 	lc = w->layout_root = layout_create_cell(NULL);
522af9e4c5dSnicm 	layout_set_size(lc, w->sx, w->sy, 0, 0);
523b4a3311eSnicm 	layout_make_leaf(lc, wp);
524baddd6b2Snicm 	layout_fix_panes(w, NULL);
525311827fbSnicm }
526311827fbSnicm 
527af9e4c5dSnicm void
528af9e4c5dSnicm layout_free(struct window *w)
529af9e4c5dSnicm {
530af9e4c5dSnicm 	layout_free_cell(w->layout_root);
531af9e4c5dSnicm }
532af9e4c5dSnicm 
533af9e4c5dSnicm /* Resize the entire layout after window resize. */
534af9e4c5dSnicm void
535af9e4c5dSnicm layout_resize(struct window *w, u_int sx, u_int sy)
536af9e4c5dSnicm {
537af9e4c5dSnicm 	struct layout_cell	*lc = w->layout_root;
538af9e4c5dSnicm 	int			 xlimit, ylimit, xchange, ychange;
539af9e4c5dSnicm 
540af9e4c5dSnicm 	/*
541af9e4c5dSnicm 	 * Adjust horizontally. Do not attempt to reduce the layout lower than
542af9e4c5dSnicm 	 * the minimum (more than the amount returned by layout_resize_check).
543af9e4c5dSnicm 	 *
544af9e4c5dSnicm 	 * This can mean that the window size is smaller than the total layout
545af9e4c5dSnicm 	 * size: redrawing this is handled at a higher level, but it does leave
546af9e4c5dSnicm 	 * a problem with growing the window size here: if the current size is
547af9e4c5dSnicm 	 * < the minimum, growing proportionately by adding to each pane is
548af9e4c5dSnicm 	 * wrong as it would keep the layout size larger than the window size.
549af9e4c5dSnicm 	 * Instead, spread the difference between the minimum and the new size
550af9e4c5dSnicm 	 * out proportionately - this should leave the layout fitting the new
551af9e4c5dSnicm 	 * window size.
552af9e4c5dSnicm 	 */
553d4ddf7e1Snicm 	xchange = sx - lc->sx;
55407b91187Snicm 	xlimit = layout_resize_check(w, lc, LAYOUT_LEFTRIGHT);
555af9e4c5dSnicm 	if (xchange < 0 && xchange < -xlimit)
556af9e4c5dSnicm 		xchange = -xlimit;
557af9e4c5dSnicm 	if (xlimit == 0) {
558af9e4c5dSnicm 		if (sx <= lc->sx)	/* lc->sx is minimum possible */
559af9e4c5dSnicm 			xchange = 0;
560311827fbSnicm 		else
561af9e4c5dSnicm 			xchange = sx - lc->sx;
562af9e4c5dSnicm 	}
563af9e4c5dSnicm 	if (xchange != 0)
56407b91187Snicm 		layout_resize_adjust(w, lc, LAYOUT_LEFTRIGHT, xchange);
565311827fbSnicm 
566af9e4c5dSnicm 	/* Adjust vertically in a similar fashion. */
567d4ddf7e1Snicm 	ychange = sy - lc->sy;
56807b91187Snicm 	ylimit = layout_resize_check(w, lc, LAYOUT_TOPBOTTOM);
569af9e4c5dSnicm 	if (ychange < 0 && ychange < -ylimit)
570af9e4c5dSnicm 		ychange = -ylimit;
571af9e4c5dSnicm 	if (ylimit == 0) {
572af9e4c5dSnicm 		if (sy <= lc->sy)	/* lc->sy is minimum possible */
573af9e4c5dSnicm 			ychange = 0;
574af9e4c5dSnicm 		else
575af9e4c5dSnicm 			ychange = sy - lc->sy;
576af9e4c5dSnicm 	}
577af9e4c5dSnicm 	if (ychange != 0)
57807b91187Snicm 		layout_resize_adjust(w, lc, LAYOUT_TOPBOTTOM, ychange);
579af9e4c5dSnicm 
580af9e4c5dSnicm 	/* Fix cell offsets. */
58142fbd26aSnicm 	layout_fix_offsets(w);
582baddd6b2Snicm 	layout_fix_panes(w, NULL);
583311827fbSnicm }
584311827fbSnicm 
5853cfcdd36Snicm /* Resize a pane to an absolute size. */
5863cfcdd36Snicm void
5873cfcdd36Snicm layout_resize_pane_to(struct window_pane *wp, enum layout_type type,
5883cfcdd36Snicm     u_int new_size)
5893cfcdd36Snicm {
5903cfcdd36Snicm 	struct layout_cell     *lc, *lcparent;
5913cfcdd36Snicm 	int			change, size;
5923cfcdd36Snicm 
5933cfcdd36Snicm 	lc = wp->layout_cell;
5943cfcdd36Snicm 
5953cfcdd36Snicm 	/* Find next parent of the same type. */
5963cfcdd36Snicm 	lcparent = lc->parent;
5973cfcdd36Snicm 	while (lcparent != NULL && lcparent->type != type) {
5983cfcdd36Snicm 		lc = lcparent;
5993cfcdd36Snicm 		lcparent = lc->parent;
6003cfcdd36Snicm 	}
6013cfcdd36Snicm 	if (lcparent == NULL)
6023cfcdd36Snicm 		return;
6033cfcdd36Snicm 
6043cfcdd36Snicm 	/* Work out the size adjustment. */
6053cfcdd36Snicm 	if (type == LAYOUT_LEFTRIGHT)
6063cfcdd36Snicm 		size = lc->sx;
6073cfcdd36Snicm 	else
6083cfcdd36Snicm 		size = lc->sy;
6093cfcdd36Snicm 	if (lc == TAILQ_LAST(&lcparent->cells, layout_cells))
6103cfcdd36Snicm 		change = size - new_size;
6113cfcdd36Snicm 	else
6123cfcdd36Snicm 		change = new_size - size;
6133cfcdd36Snicm 
6143cfcdd36Snicm 	/* Resize the pane. */
615f4d5873aSnicm 	layout_resize_pane(wp, type, change, 1);
6163cfcdd36Snicm }
6173cfcdd36Snicm 
618af9e4c5dSnicm void
6193d1607c5Snicm layout_resize_layout(struct window *w, struct layout_cell *lc,
6203d1607c5Snicm     enum layout_type type, int change, int opposite)
621af9e4c5dSnicm {
622af9e4c5dSnicm 	int	needed, size;
623af9e4c5dSnicm 
624af9e4c5dSnicm 	/* Grow or shrink the cell. */
625af9e4c5dSnicm 	needed = change;
626af9e4c5dSnicm 	while (needed != 0) {
627af9e4c5dSnicm 		if (change > 0) {
628f4d5873aSnicm 			size = layout_resize_pane_grow(w, lc, type, needed,
629f4d5873aSnicm 			    opposite);
630af9e4c5dSnicm 			needed -= size;
631af9e4c5dSnicm 		} else {
63207b91187Snicm 			size = layout_resize_pane_shrink(w, lc, type, needed);
633af9e4c5dSnicm 			needed += size;
634af9e4c5dSnicm 		}
635af9e4c5dSnicm 
636af9e4c5dSnicm 		if (size == 0)	/* no more change possible */
637311827fbSnicm 			break;
638311827fbSnicm 	}
639af9e4c5dSnicm 
640af9e4c5dSnicm 	/* Fix cell offsets. */
64142fbd26aSnicm 	layout_fix_offsets(w);
642baddd6b2Snicm 	layout_fix_panes(w, NULL);
6433d1607c5Snicm 	notify_window("window-layout-changed", w);
6443d1607c5Snicm }
6453d1607c5Snicm 
6463d1607c5Snicm /* Resize a single pane within the layout. */
6473d1607c5Snicm void
6483d1607c5Snicm layout_resize_pane(struct window_pane *wp, enum layout_type type, int change,
6493d1607c5Snicm     int opposite)
6503d1607c5Snicm {
6513d1607c5Snicm 	struct layout_cell	*lc, *lcparent;
6523d1607c5Snicm 
6533d1607c5Snicm 	lc = wp->layout_cell;
6543d1607c5Snicm 
6553d1607c5Snicm 	/* Find next parent of the same type. */
6563d1607c5Snicm 	lcparent = lc->parent;
6573d1607c5Snicm 	while (lcparent != NULL && lcparent->type != type) {
6583d1607c5Snicm 		lc = lcparent;
6593d1607c5Snicm 		lcparent = lc->parent;
6603d1607c5Snicm 	}
6613d1607c5Snicm 	if (lcparent == NULL)
6623d1607c5Snicm 		return;
6633d1607c5Snicm 
6643d1607c5Snicm 	/* If this is the last cell, move back one. */
6653d1607c5Snicm 	if (lc == TAILQ_LAST(&lcparent->cells, layout_cells))
6663d1607c5Snicm 		lc = TAILQ_PREV(lc, layout_cells, entry);
6673d1607c5Snicm 
6683d1607c5Snicm 	layout_resize_layout(wp->window, lc, type, change, opposite);
669311827fbSnicm }
670af9e4c5dSnicm 
6713cfcdd36Snicm /* Helper function to grow pane. */
672bc3b19faSnicm static int
67307b91187Snicm layout_resize_pane_grow(struct window *w, struct layout_cell *lc,
674f4d5873aSnicm     enum layout_type type, int needed, int opposite)
675af9e4c5dSnicm {
676af9e4c5dSnicm 	struct layout_cell	*lcadd, *lcremove;
677f4d5873aSnicm 	u_int			 size = 0;
678af9e4c5dSnicm 
679af9e4c5dSnicm 	/* Growing. Always add to the current cell. */
680af9e4c5dSnicm 	lcadd = lc;
681af9e4c5dSnicm 
682af9e4c5dSnicm 	/* Look towards the tail for a suitable cell for reduction. */
683af9e4c5dSnicm 	lcremove = TAILQ_NEXT(lc, entry);
684af9e4c5dSnicm 	while (lcremove != NULL) {
68507b91187Snicm 		size = layout_resize_check(w, lcremove, type);
686af9e4c5dSnicm 		if (size > 0)
687af9e4c5dSnicm 			break;
688af9e4c5dSnicm 		lcremove = TAILQ_NEXT(lcremove, entry);
689af9e4c5dSnicm 	}
690af9e4c5dSnicm 
691af9e4c5dSnicm 	/* If none found, look towards the head. */
692f4d5873aSnicm 	if (opposite && lcremove == NULL) {
693af9e4c5dSnicm 		lcremove = TAILQ_PREV(lc, layout_cells, entry);
694af9e4c5dSnicm 		while (lcremove != NULL) {
69507b91187Snicm 			size = layout_resize_check(w, lcremove, type);
696af9e4c5dSnicm 			if (size > 0)
697af9e4c5dSnicm 				break;
698af9e4c5dSnicm 			lcremove = TAILQ_PREV(lcremove, layout_cells, entry);
699af9e4c5dSnicm 		}
700f4d5873aSnicm 	}
701af9e4c5dSnicm 	if (lcremove == NULL)
702af9e4c5dSnicm 		return (0);
703af9e4c5dSnicm 
704af9e4c5dSnicm 	/* Change the cells. */
705af9e4c5dSnicm 	if (size > (u_int) needed)
706af9e4c5dSnicm 		size = needed;
70707b91187Snicm 	layout_resize_adjust(w, lcadd, type, size);
70807b91187Snicm 	layout_resize_adjust(w, lcremove, type, -size);
709af9e4c5dSnicm 	return (size);
710af9e4c5dSnicm }
711af9e4c5dSnicm 
7123cfcdd36Snicm /* Helper function to shrink pane. */
713bc3b19faSnicm static int
71407b91187Snicm layout_resize_pane_shrink(struct window *w, struct layout_cell *lc,
71507b91187Snicm     enum layout_type type, int needed)
716af9e4c5dSnicm {
717af9e4c5dSnicm 	struct layout_cell	*lcadd, *lcremove;
718af9e4c5dSnicm 	u_int			 size;
719af9e4c5dSnicm 
720af9e4c5dSnicm 	/* Shrinking. Find cell to remove from by walking towards head. */
721af9e4c5dSnicm 	lcremove = lc;
722af9e4c5dSnicm 	do {
72307b91187Snicm 		size = layout_resize_check(w, lcremove, type);
724af9e4c5dSnicm 		if (size != 0)
725af9e4c5dSnicm 			break;
726af9e4c5dSnicm 		lcremove = TAILQ_PREV(lcremove, layout_cells, entry);
727af9e4c5dSnicm 	} while (lcremove != NULL);
728af9e4c5dSnicm 	if (lcremove == NULL)
729af9e4c5dSnicm 		return (0);
730af9e4c5dSnicm 
731af9e4c5dSnicm 	/* And add onto the next cell (from the original cell). */
732af9e4c5dSnicm 	lcadd = TAILQ_NEXT(lc, entry);
733af9e4c5dSnicm 	if (lcadd == NULL)
734af9e4c5dSnicm 		return (0);
735af9e4c5dSnicm 
736af9e4c5dSnicm 	/* Change the cells. */
737af9e4c5dSnicm 	if (size > (u_int) -needed)
738af9e4c5dSnicm 		size = -needed;
73907b91187Snicm 	layout_resize_adjust(w, lcadd, type, size);
74007b91187Snicm 	layout_resize_adjust(w, lcremove, type, -size);
741af9e4c5dSnicm 	return (size);
742af9e4c5dSnicm }
743af9e4c5dSnicm 
744572cd943Snicm /* Assign window pane to newly split cell. */
745572cd943Snicm void
746baddd6b2Snicm layout_assign_pane(struct layout_cell *lc, struct window_pane *wp,
747baddd6b2Snicm     int do_not_resize)
748572cd943Snicm {
749572cd943Snicm 	layout_make_leaf(lc, wp);
750baddd6b2Snicm 	if (do_not_resize)
751baddd6b2Snicm 		layout_fix_panes(wp->window, wp);
752baddd6b2Snicm 	else
753baddd6b2Snicm 		layout_fix_panes(wp->window, NULL);
754572cd943Snicm }
755572cd943Snicm 
756824e896bSnicm /* Calculate the new pane size for resized parent. */
757824e896bSnicm static u_int
758824e896bSnicm layout_new_pane_size(struct window *w, u_int previous, struct layout_cell *lc,
759824e896bSnicm     enum layout_type type, u_int size, u_int count_left, u_int size_left)
760824e896bSnicm {
761824e896bSnicm 	u_int	new_size, min, max, available;
762824e896bSnicm 
763824e896bSnicm 	/* If this is the last cell, it can take all of the remaining size. */
764824e896bSnicm 	if (count_left == 1)
765824e896bSnicm 		return (size_left);
766824e896bSnicm 
767824e896bSnicm 	/* How much is available in this parent? */
768824e896bSnicm 	available = layout_resize_check(w, lc, type);
769824e896bSnicm 
770824e896bSnicm 	/*
771824e896bSnicm 	 * Work out the minimum size of this cell and the new size
772824e896bSnicm 	 * proportionate to the previous size.
773824e896bSnicm 	 */
774824e896bSnicm 	min = (PANE_MINIMUM + 1) * (count_left - 1);
775824e896bSnicm 	if (type == LAYOUT_LEFTRIGHT) {
776824e896bSnicm 		if (lc->sx - available > min)
777824e896bSnicm 			min = lc->sx - available;
778824e896bSnicm 		new_size = (lc->sx * size) / previous;
779824e896bSnicm 	} else {
780824e896bSnicm 		if (lc->sy - available > min)
781824e896bSnicm 			min = lc->sy - available;
782824e896bSnicm 		new_size = (lc->sy * size) / previous;
783824e896bSnicm 	}
784824e896bSnicm 
785824e896bSnicm 	/* Check against the maximum and minimum size. */
786824e896bSnicm 	max = size_left - min;
787824e896bSnicm 	if (new_size > max)
788824e896bSnicm 		new_size = max;
789824e896bSnicm 	if (new_size < PANE_MINIMUM)
790824e896bSnicm 		new_size = PANE_MINIMUM;
791824e896bSnicm 	return (new_size);
792824e896bSnicm }
793824e896bSnicm 
794824e896bSnicm /* Check if the cell and all its children can be resized to a specific size. */
795824e896bSnicm static int
796824e896bSnicm layout_set_size_check(struct window *w, struct layout_cell *lc,
797824e896bSnicm     enum layout_type type, int size)
798824e896bSnicm {
799824e896bSnicm 	struct layout_cell	*lcchild;
800824e896bSnicm 	u_int			 new_size, available, previous, count, idx;
801824e896bSnicm 
802824e896bSnicm 	/* Cells with no children must just be bigger than minimum. */
803824e896bSnicm 	if (lc->type == LAYOUT_WINDOWPANE)
804824e896bSnicm 		return (size >= PANE_MINIMUM);
805824e896bSnicm 	available = size;
806824e896bSnicm 
807824e896bSnicm 	/* Count number of children. */
808824e896bSnicm 	count = 0;
809824e896bSnicm 	TAILQ_FOREACH(lcchild, &lc->cells, entry)
810824e896bSnicm 		count++;
811824e896bSnicm 
812824e896bSnicm 	/* Check new size will work for each child. */
813824e896bSnicm 	if (lc->type == type) {
814eb84caa4Snicm 		if (available < (count * 2) - 1)
815eb84caa4Snicm 			return (0);
816eb84caa4Snicm 
817824e896bSnicm 		if (type == LAYOUT_LEFTRIGHT)
818824e896bSnicm 			previous = lc->sx;
819824e896bSnicm 		else
820824e896bSnicm 			previous = lc->sy;
821824e896bSnicm 
822824e896bSnicm 		idx = 0;
823824e896bSnicm 		TAILQ_FOREACH(lcchild, &lc->cells, entry) {
824824e896bSnicm 			new_size = layout_new_pane_size(w, previous, lcchild,
825824e896bSnicm 			    type, size, count - idx, available);
826eb84caa4Snicm 			if (idx == count - 1) {
827824e896bSnicm 				if (new_size > available)
828824e896bSnicm 					return (0);
829eb84caa4Snicm 				available -= new_size;
830eb84caa4Snicm 			} else {
831eb84caa4Snicm 				if (new_size + 1 > available)
832eb84caa4Snicm 					return (0);
833eb84caa4Snicm 				available -= new_size + 1;
834eb84caa4Snicm 			}
835824e896bSnicm 			if (!layout_set_size_check(w, lcchild, type, new_size))
836824e896bSnicm 				return (0);
837824e896bSnicm 			idx++;
838824e896bSnicm 		}
839824e896bSnicm 	} else {
840824e896bSnicm 		TAILQ_FOREACH(lcchild, &lc->cells, entry) {
841824e896bSnicm 			if (lcchild->type == LAYOUT_WINDOWPANE)
842824e896bSnicm 				continue;
843824e896bSnicm 			if (!layout_set_size_check(w, lcchild, type, size))
844824e896bSnicm 				return (0);
845824e896bSnicm 		}
846824e896bSnicm 	}
847824e896bSnicm 
848824e896bSnicm 	return (1);
849824e896bSnicm }
850824e896bSnicm 
851824e896bSnicm /* Resize all child cells to fit within the current cell. */
852824e896bSnicm static void
853824e896bSnicm layout_resize_child_cells(struct window *w, struct layout_cell *lc)
854824e896bSnicm {
855824e896bSnicm 	struct layout_cell	*lcchild;
856824e896bSnicm 	u_int			 previous, available, count, idx;
857824e896bSnicm 
858824e896bSnicm 	if (lc->type == LAYOUT_WINDOWPANE)
859824e896bSnicm 		return;
860824e896bSnicm 
861824e896bSnicm 	/* What is the current size used? */
862824e896bSnicm 	count = 0;
863824e896bSnicm 	previous = 0;
864824e896bSnicm 	TAILQ_FOREACH(lcchild, &lc->cells, entry) {
865824e896bSnicm 		count++;
866824e896bSnicm 		if (lc->type == LAYOUT_LEFTRIGHT)
867824e896bSnicm 			previous += lcchild->sx;
868824e896bSnicm 		else if (lc->type == LAYOUT_TOPBOTTOM)
869824e896bSnicm 			previous += lcchild->sy;
870824e896bSnicm 	}
871824e896bSnicm 	previous += (count - 1);
872824e896bSnicm 
873824e896bSnicm 	/* And how much is available? */
874824e896bSnicm 	available = 0;
875824e896bSnicm 	if (lc->type == LAYOUT_LEFTRIGHT)
876824e896bSnicm 		available = lc->sx;
877824e896bSnicm 	else if (lc->type == LAYOUT_TOPBOTTOM)
878824e896bSnicm 		available = lc->sy;
879824e896bSnicm 
880824e896bSnicm 	/* Resize children into the new size. */
881824e896bSnicm 	idx = 0;
882824e896bSnicm 	TAILQ_FOREACH(lcchild, &lc->cells, entry) {
883824e896bSnicm 		if (lc->type == LAYOUT_TOPBOTTOM) {
884824e896bSnicm 			lcchild->sx = lc->sx;
885824e896bSnicm 			lcchild->xoff = lc->xoff;
886824e896bSnicm 		} else {
887824e896bSnicm 			lcchild->sx = layout_new_pane_size(w, previous, lcchild,
888824e896bSnicm 			    lc->type, lc->sx, count - idx, available);
889824e896bSnicm 			available -= (lcchild->sx + 1);
890824e896bSnicm 		}
891824e896bSnicm 		if (lc->type == LAYOUT_LEFTRIGHT)
892824e896bSnicm 			lcchild->sy = lc->sy;
893824e896bSnicm 		else {
894824e896bSnicm 			lcchild->sy = layout_new_pane_size(w, previous, lcchild,
895824e896bSnicm 			    lc->type, lc->sy, count - idx, available);
896824e896bSnicm 			available -= (lcchild->sy + 1);
897824e896bSnicm 		}
898824e896bSnicm 		layout_resize_child_cells(w, lcchild);
899824e896bSnicm 		idx++;
900824e896bSnicm 	}
901824e896bSnicm }
902824e896bSnicm 
903572cd943Snicm /*
904572cd943Snicm  * Split a pane into two. size is a hint, or -1 for default half/half
905572cd943Snicm  * split. This must be followed by layout_assign_pane before much else happens!
906824e896bSnicm  */
907572cd943Snicm struct layout_cell *
908d5221700Snicm layout_split_pane(struct window_pane *wp, enum layout_type type, int size,
909c26c4f79Snicm     int flags)
910af9e4c5dSnicm {
9119c03dbf0Snicm 	struct layout_cell	*lc, *lcparent, *lcnew, *lc1, *lc2;
912dd0df669Snicm 	struct style		*sb_style = &wp->scrollbar_style;
913e217846fSnicm 	u_int			 sx, sy, xoff, yoff, size1, size2, minimum;
914824e896bSnicm 	u_int			 new_size, saved_size, resize_first = 0;
915e217846fSnicm 	int			 full_size = (flags & SPAWN_FULLSIZE), status;
916d2117533Snicm 	int			 scrollbars;
917af9e4c5dSnicm 
918824e896bSnicm 	/*
919824e896bSnicm 	 * If full_size is specified, add a new cell at the top of the window
920824e896bSnicm 	 * layout. Otherwise, split the cell for the current pane.
921824e896bSnicm 	 */
922824e896bSnicm 	if (full_size)
923824e896bSnicm 		lc = wp->window->layout_root;
924824e896bSnicm 	else
925af9e4c5dSnicm 		lc = wp->layout_cell;
926e217846fSnicm 	status = options_get_number(wp->window->options, "pane-border-status");
927d2117533Snicm 	scrollbars = options_get_number(wp->window->options, "pane-scrollbars");
928af9e4c5dSnicm 
929af9e4c5dSnicm 	/* Copy the old cell size. */
930af9e4c5dSnicm 	sx = lc->sx;
931af9e4c5dSnicm 	sy = lc->sy;
932af9e4c5dSnicm 	xoff = lc->xoff;
933af9e4c5dSnicm 	yoff = lc->yoff;
934af9e4c5dSnicm 
935af9e4c5dSnicm 	/* Check there is enough space for the two new panes. */
936af9e4c5dSnicm 	switch (type) {
937af9e4c5dSnicm 	case LAYOUT_LEFTRIGHT:
938dd0df669Snicm 		if (scrollbars) {
939dd0df669Snicm 			minimum = PANE_MINIMUM * 2 + sb_style->width +
940dd0df669Snicm 			    sb_style->pad;
941dd0df669Snicm 		} else
942d2117533Snicm 			minimum = PANE_MINIMUM * 2 + 1;
943d2117533Snicm 		if (sx < minimum)
944572cd943Snicm 			return (NULL);
945af9e4c5dSnicm 		break;
946af9e4c5dSnicm 	case LAYOUT_TOPBOTTOM:
947d2117533Snicm 		if (layout_add_horizontal_border(wp->window, lc, status))
9489bc2f29eSnicm 			minimum = PANE_MINIMUM * 2 + 2;
9499bc2f29eSnicm 		else
950e217846fSnicm 			minimum = PANE_MINIMUM * 2 + 1;
951e217846fSnicm 		if (sy < minimum)
952572cd943Snicm 			return (NULL);
953af9e4c5dSnicm 		break;
954af9e4c5dSnicm 	default:
955af9e4c5dSnicm 		fatalx("bad layout type");
956af9e4c5dSnicm 	}
957af9e4c5dSnicm 
958824e896bSnicm 	/*
959824e896bSnicm 	 * Calculate new cell sizes. size is the target size or -1 for middle
960824e896bSnicm 	 * split, size1 is the size of the top/left and size2 the bottom/right.
961824e896bSnicm 	 */
962824e896bSnicm 	if (type == LAYOUT_LEFTRIGHT)
963824e896bSnicm 		saved_size = sx;
964824e896bSnicm 	else
965824e896bSnicm 		saved_size = sy;
966824e896bSnicm 	if (size < 0)
967824e896bSnicm 		size2 = ((saved_size + 1) / 2) - 1;
968c26c4f79Snicm 	else if (flags & SPAWN_BEFORE)
969824e896bSnicm 		size2 = saved_size - size - 1;
970824e896bSnicm 	else
971824e896bSnicm 		size2 = size;
972824e896bSnicm 	if (size2 < PANE_MINIMUM)
973824e896bSnicm 		size2 = PANE_MINIMUM;
974824e896bSnicm 	else if (size2 > saved_size - 2)
975824e896bSnicm 		size2 = saved_size - 2;
976824e896bSnicm 	size1 = saved_size - 1 - size2;
977824e896bSnicm 
978824e896bSnicm 	/* Which size are we using? */
979c26c4f79Snicm 	if (flags & SPAWN_BEFORE)
980824e896bSnicm 		new_size = size2;
981824e896bSnicm 	else
982824e896bSnicm 		new_size = size1;
983824e896bSnicm 
984824e896bSnicm 	/* Confirm there is enough space for full size pane. */
985824e896bSnicm 	if (full_size && !layout_set_size_check(wp->window, lc, type, new_size))
986824e896bSnicm 		return (NULL);
987824e896bSnicm 
988af9e4c5dSnicm 	if (lc->parent != NULL && lc->parent->type == type) {
989af9e4c5dSnicm 		/*
990af9e4c5dSnicm 		 * If the parent exists and is of the same type as the split,
991af9e4c5dSnicm 		 * create a new cell and insert it after this one.
992af9e4c5dSnicm 		 */
9939c03dbf0Snicm 		lcparent = lc->parent;
9949c03dbf0Snicm 		lcnew = layout_create_cell(lcparent);
995c26c4f79Snicm 		if (flags & SPAWN_BEFORE)
9969c03dbf0Snicm 			TAILQ_INSERT_BEFORE(lc, lcnew, entry);
9979c03dbf0Snicm 		else
9989c03dbf0Snicm 			TAILQ_INSERT_AFTER(&lcparent->cells, lc, lcnew, entry);
999824e896bSnicm 	} else if (full_size && lc->parent == NULL && lc->type == type) {
1000824e896bSnicm 		/*
1001824e896bSnicm 		 * If the new full size pane is the same type as the root
1002824e896bSnicm 		 * split, insert the new pane under the existing root cell
1003824e896bSnicm 		 * instead of creating a new root cell. The existing layout
1004824e896bSnicm 		 * must be resized before inserting the new cell.
1005824e896bSnicm 		 */
1006824e896bSnicm 		if (lc->type == LAYOUT_LEFTRIGHT) {
1007824e896bSnicm 			lc->sx = new_size;
1008824e896bSnicm 			layout_resize_child_cells(wp->window, lc);
1009824e896bSnicm 			lc->sx = saved_size;
1010824e896bSnicm 		} else if (lc->type == LAYOUT_TOPBOTTOM) {
1011824e896bSnicm 			lc->sy = new_size;
1012824e896bSnicm 			layout_resize_child_cells(wp->window, lc);
1013824e896bSnicm 			lc->sy = saved_size;
1014824e896bSnicm 		}
1015824e896bSnicm 		resize_first = 1;
1016824e896bSnicm 
1017824e896bSnicm 		/* Create the new cell. */
1018824e896bSnicm 		lcnew = layout_create_cell(lc);
1019f9ebce2cSnicm 		size = saved_size - 1 - new_size;
1020824e896bSnicm 		if (lc->type == LAYOUT_LEFTRIGHT)
1021f9ebce2cSnicm 			layout_set_size(lcnew, size, sy, 0, 0);
1022824e896bSnicm 		else if (lc->type == LAYOUT_TOPBOTTOM)
1023f9ebce2cSnicm 			layout_set_size(lcnew, sx, size, 0, 0);
1024c26c4f79Snicm 		if (flags & SPAWN_BEFORE)
1025824e896bSnicm 			TAILQ_INSERT_HEAD(&lc->cells, lcnew, entry);
1026824e896bSnicm 		else
1027824e896bSnicm 			TAILQ_INSERT_TAIL(&lc->cells, lcnew, entry);
1028af9e4c5dSnicm 	} else {
1029af9e4c5dSnicm 		/*
1030af9e4c5dSnicm 		 * Otherwise create a new parent and insert it.
1031af9e4c5dSnicm 		 */
1032af9e4c5dSnicm 
1033af9e4c5dSnicm 		/* Create and insert the replacement parent. */
1034af9e4c5dSnicm 		lcparent = layout_create_cell(lc->parent);
1035af9e4c5dSnicm 		layout_make_node(lcparent, type);
1036af9e4c5dSnicm 		layout_set_size(lcparent, sx, sy, xoff, yoff);
1037af9e4c5dSnicm 		if (lc->parent == NULL)
1038af9e4c5dSnicm 			wp->window->layout_root = lcparent;
1039af9e4c5dSnicm 		else
1040af9e4c5dSnicm 			TAILQ_REPLACE(&lc->parent->cells, lc, lcparent, entry);
1041af9e4c5dSnicm 
1042af9e4c5dSnicm 		/* Insert the old cell. */
1043af9e4c5dSnicm 		lc->parent = lcparent;
1044af9e4c5dSnicm 		TAILQ_INSERT_HEAD(&lcparent->cells, lc, entry);
1045af9e4c5dSnicm 
1046af9e4c5dSnicm 		/* Create the new child cell. */
1047af9e4c5dSnicm 		lcnew = layout_create_cell(lcparent);
1048c26c4f79Snicm 		if (flags & SPAWN_BEFORE)
10499c03dbf0Snicm 			TAILQ_INSERT_HEAD(&lcparent->cells, lcnew, entry);
10509c03dbf0Snicm 		else
1051af9e4c5dSnicm 			TAILQ_INSERT_TAIL(&lcparent->cells, lcnew, entry);
1052af9e4c5dSnicm 	}
1053c26c4f79Snicm 	if (flags & SPAWN_BEFORE) {
10549c03dbf0Snicm 		lc1 = lcnew;
10559c03dbf0Snicm 		lc2 = lc;
10569c03dbf0Snicm 	} else {
10579c03dbf0Snicm 		lc1 = lc;
10589c03dbf0Snicm 		lc2 = lcnew;
10599c03dbf0Snicm 	}
1060af9e4c5dSnicm 
1061824e896bSnicm 	/*
1062824e896bSnicm 	 * Set new cell sizes. size1 is the size of the top/left and size2 the
1063824e896bSnicm 	 * bottom/right.
1064af9e4c5dSnicm 	 */
1065824e896bSnicm 	if (!resize_first && type == LAYOUT_LEFTRIGHT) {
10669c03dbf0Snicm 		layout_set_size(lc1, size1, sy, xoff, yoff);
10679c03dbf0Snicm 		layout_set_size(lc2, size2, sy, xoff + lc1->sx + 1, yoff);
1068824e896bSnicm 	} else if (!resize_first && type == LAYOUT_TOPBOTTOM) {
10699c03dbf0Snicm 		layout_set_size(lc1, sx, size1, xoff, yoff);
10709c03dbf0Snicm 		layout_set_size(lc2, sx, size2, xoff, yoff + lc1->sy + 1);
1071af9e4c5dSnicm 	}
1072824e896bSnicm 	if (full_size) {
1073824e896bSnicm 		if (!resize_first)
1074824e896bSnicm 			layout_resize_child_cells(wp->window, lc);
107542fbd26aSnicm 		layout_fix_offsets(wp->window);
1076824e896bSnicm 	} else
1077af9e4c5dSnicm 		layout_make_leaf(lc, wp);
1078af9e4c5dSnicm 
1079572cd943Snicm 	return (lcnew);
1080af9e4c5dSnicm }
1081af9e4c5dSnicm 
1082f4611a41Snicm /* Destroy the cell associated with a pane. */
1083af9e4c5dSnicm void
1084af9e4c5dSnicm layout_close_pane(struct window_pane *wp)
1085af9e4c5dSnicm {
108607b91187Snicm 	struct window	*w = wp->window;
108707b91187Snicm 
1088f4611a41Snicm 	/* Remove the cell. */
108907b91187Snicm 	layout_destroy_cell(w, wp->layout_cell, &w->layout_root);
1090af9e4c5dSnicm 
1091af9e4c5dSnicm 	/* Fix pane offsets and sizes. */
109207b91187Snicm 	if (w->layout_root != NULL) {
109342fbd26aSnicm 		layout_fix_offsets(w);
1094baddd6b2Snicm 		layout_fix_panes(w, NULL);
1095af9e4c5dSnicm 	}
10962ae124feSnicm 	notify_window("window-layout-changed", w);
1097f4611a41Snicm }
1098967ee5b9Snicm 
1099967ee5b9Snicm int
1100967ee5b9Snicm layout_spread_cell(struct window *w, struct layout_cell *parent)
1101967ee5b9Snicm {
1102967ee5b9Snicm 	struct layout_cell	*lc;
1103dd0df669Snicm 	struct style		*sb_style = &w->active->scrollbar_style;
1104*5df986e2Snicm 	u_int			 number, each, size, this, remainder;
1105d2117533Snicm 	int			 change, changed, status, scrollbars;
1106967ee5b9Snicm 
1107967ee5b9Snicm 	number = 0;
1108967ee5b9Snicm 	TAILQ_FOREACH (lc, &parent->cells, entry)
1109967ee5b9Snicm 		number++;
1110967ee5b9Snicm 	if (number <= 1)
1111967ee5b9Snicm 		return (0);
1112e217846fSnicm 	status = options_get_number(w->options, "pane-border-status");
1113d2117533Snicm 	scrollbars = options_get_number(w->options, "pane-scrollbars");
1114967ee5b9Snicm 
1115d2117533Snicm 	if (parent->type == LAYOUT_LEFTRIGHT) {
1116d2117533Snicm 		if (scrollbars)
1117dd0df669Snicm 			size = parent->sx - sb_style->width + sb_style->pad;
1118d2117533Snicm 		else
1119967ee5b9Snicm 			size = parent->sx;
1120d2117533Snicm 	}
1121e217846fSnicm 	else if (parent->type == LAYOUT_TOPBOTTOM) {
1122d2117533Snicm 		if (layout_add_horizontal_border(w, parent, status))
11239bc2f29eSnicm 			size = parent->sy - 1;
11249bc2f29eSnicm 		else
1125967ee5b9Snicm 			size = parent->sy;
1126e217846fSnicm 	} else
1127e217846fSnicm 		return (0);
1128e217846fSnicm 	if (size < number - 1)
1129967ee5b9Snicm 		return (0);
1130967ee5b9Snicm 	each = (size - (number - 1)) / number;
1131e217846fSnicm 	if (each == 0)
1132e217846fSnicm 		return (0);
1133*5df986e2Snicm 	/*
1134*5df986e2Snicm 	 * Remaining space after assigning that which can be evenly
1135*5df986e2Snicm 	 * distributed.
1136*5df986e2Snicm 	 */
1137*5df986e2Snicm 	remainder = size - (number * (each + 1)) + 1;
1138967ee5b9Snicm 
1139967ee5b9Snicm 	changed = 0;
1140967ee5b9Snicm 	TAILQ_FOREACH (lc, &parent->cells, entry) {
1141967ee5b9Snicm 		change = 0;
1142967ee5b9Snicm 		if (parent->type == LAYOUT_LEFTRIGHT) {
1143967ee5b9Snicm 			change = each - (int)lc->sx;
1144*5df986e2Snicm 			if (remainder > 0) {
1145*5df986e2Snicm 				change++;
1146*5df986e2Snicm 				remainder--;
1147*5df986e2Snicm 			}
1148967ee5b9Snicm 			layout_resize_adjust(w, lc, LAYOUT_LEFTRIGHT, change);
1149967ee5b9Snicm 		} else if (parent->type == LAYOUT_TOPBOTTOM) {
1150d2117533Snicm 			if (layout_add_horizontal_border(w, lc, status))
11519bc2f29eSnicm 				this = each + 1;
11529bc2f29eSnicm 			else
1153e217846fSnicm 				this = each;
1154*5df986e2Snicm 			if (remainder > 0) {
1155*5df986e2Snicm 				this++;
1156*5df986e2Snicm 				remainder--;
1157*5df986e2Snicm 			}
1158e217846fSnicm 			change = this - (int)lc->sy;
1159967ee5b9Snicm 			layout_resize_adjust(w, lc, LAYOUT_TOPBOTTOM, change);
1160967ee5b9Snicm 		}
1161967ee5b9Snicm 		if (change != 0)
1162967ee5b9Snicm 			changed = 1;
1163967ee5b9Snicm 	}
1164967ee5b9Snicm 	return (changed);
1165967ee5b9Snicm }
1166967ee5b9Snicm 
1167967ee5b9Snicm void
1168967ee5b9Snicm layout_spread_out(struct window_pane *wp)
1169967ee5b9Snicm {
1170967ee5b9Snicm 	struct layout_cell	*parent;
1171967ee5b9Snicm 	struct window		*w = wp->window;
1172967ee5b9Snicm 
1173967ee5b9Snicm 	parent = wp->layout_cell->parent;
1174967ee5b9Snicm 	if (parent == NULL)
1175967ee5b9Snicm 		return;
1176967ee5b9Snicm 
1177967ee5b9Snicm 	do {
1178967ee5b9Snicm 		if (layout_spread_cell(w, parent)) {
117942fbd26aSnicm 			layout_fix_offsets(w);
1180baddd6b2Snicm 			layout_fix_panes(w, NULL);
1181967ee5b9Snicm 			break;
1182967ee5b9Snicm 		}
1183967ee5b9Snicm 	} while ((parent = parent->parent) != NULL);
1184967ee5b9Snicm }
1185