4 * Copyright (c) 2009 Nicholas Marriott <nicm@users.sourceforge.net>
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
15 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
16 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19 #include <sys/types.h>
26 * The window layout is a tree of cells each of which can be one of: a
27 * left-right container for a list of cells, a top-bottom container for a list
28 * of cells, or a container for a window pane.
30 * Each window has a pointer to the root of its layout tree (containing its
31 * panes), every pane has a pointer back to the cell containing it, and each
32 * cell a pointer to its parent cell.
35 int layout_resize_pane_grow(struct layout_cell
*, enum layout_type
, int);
36 int layout_resize_pane_shrink(struct layout_cell
*, enum layout_type
, int);
39 layout_create_cell(struct layout_cell
*lcparent
)
41 struct layout_cell
*lc
;
43 lc
= xmalloc(sizeof *lc
);
44 lc
->type
= LAYOUT_WINDOWPANE
;
45 lc
->parent
= lcparent
;
47 TAILQ_INIT(&lc
->cells
);
61 layout_free_cell(struct layout_cell
*lc
)
63 struct layout_cell
*lcchild
;
66 case LAYOUT_LEFTRIGHT
:
67 case LAYOUT_TOPBOTTOM
:
68 while (!TAILQ_EMPTY(&lc
->cells
)) {
69 lcchild
= TAILQ_FIRST(&lc
->cells
);
70 TAILQ_REMOVE(&lc
->cells
, lcchild
, entry
);
71 layout_free_cell(lcchild
);
74 case LAYOUT_WINDOWPANE
:
76 lc
->wp
->layout_cell
= NULL
;
84 layout_print_cell(struct layout_cell
*lc
, const char *hdr
, u_int n
)
86 struct layout_cell
*lcchild
;
89 "%s:%*s%p type %u [parent %p] wp=%p [%u,%u %ux%u]", hdr
, n
, " ", lc
,
90 lc
->type
, lc
->parent
, lc
->wp
, lc
->xoff
, lc
->yoff
, lc
->sx
, lc
->sy
);
92 case LAYOUT_LEFTRIGHT
:
93 case LAYOUT_TOPBOTTOM
:
94 TAILQ_FOREACH(lcchild
, &lc
->cells
, entry
)
95 layout_print_cell(lcchild
, hdr
, n
+ 1);
97 case LAYOUT_WINDOWPANE
:
104 struct layout_cell
*lc
, u_int sx
, u_int sy
, u_int xoff
, u_int yoff
)
114 layout_make_leaf(struct layout_cell
*lc
, struct window_pane
*wp
)
116 lc
->type
= LAYOUT_WINDOWPANE
;
118 TAILQ_INIT(&lc
->cells
);
120 wp
->layout_cell
= lc
;
125 layout_make_node(struct layout_cell
*lc
, enum layout_type type
)
127 if (type
== LAYOUT_WINDOWPANE
)
128 fatalx("bad layout type");
131 TAILQ_INIT(&lc
->cells
);
134 lc
->wp
->layout_cell
= NULL
;
138 /* Fix cell offsets based on their sizes. */
140 layout_fix_offsets(struct layout_cell
*lc
)
142 struct layout_cell
*lcchild
;
145 if (lc
->type
== LAYOUT_LEFTRIGHT
) {
147 TAILQ_FOREACH(lcchild
, &lc
->cells
, entry
) {
148 lcchild
->xoff
= xoff
;
149 lcchild
->yoff
= lc
->yoff
;
150 if (lcchild
->type
!= LAYOUT_WINDOWPANE
)
151 layout_fix_offsets(lcchild
);
152 xoff
+= lcchild
->sx
+ 1;
156 TAILQ_FOREACH(lcchild
, &lc
->cells
, entry
) {
157 lcchild
->xoff
= lc
->xoff
;
158 lcchild
->yoff
= yoff
;
159 if (lcchild
->type
!= LAYOUT_WINDOWPANE
)
160 layout_fix_offsets(lcchild
);
161 yoff
+= lcchild
->sy
+ 1;
166 /* Update pane offsets and sizes based on their cells. */
168 layout_fix_panes(struct window
*w
, u_int wsx
, u_int wsy
)
170 struct window_pane
*wp
;
171 struct layout_cell
*lc
;
174 TAILQ_FOREACH(wp
, &w
->panes
, entry
) {
175 if ((lc
= wp
->layout_cell
) == NULL
)
181 * Layout cells are limited by the smallest size of other cells
182 * within the same row or column; if this isn't the case
183 * resizing becomes difficult.
185 * However, panes do not have to take up their entire cell, so
186 * they can be cropped to the window edge if the layout
187 * overflows and they are partly visible.
189 * This stops cells being hidden unnecessarily.
193 * Work out the horizontal size. If the pane is actually
194 * outside the window or the entire pane is already visible,
197 if (lc
->xoff
>= wsx
|| lc
->xoff
+ lc
->sx
< wsx
)
206 * Similarly for the vertical size; the minimum vertical size
207 * is two because scroll regions cannot be one line.
209 if (lc
->yoff
>= wsy
|| lc
->yoff
+ lc
->sy
< wsy
)
217 window_pane_resize(wp
, sx
, sy
);
221 /* Calculate how much size is available to be removed from a cell. */
223 layout_resize_check(struct layout_cell
*lc
, enum layout_type type
)
225 struct layout_cell
*lcchild
;
226 u_int available
, minimum
;
228 if (lc
->type
== LAYOUT_WINDOWPANE
) {
229 /* Space available in this cell only. */
230 if (type
== LAYOUT_LEFTRIGHT
)
235 if (available
> PANE_MINIMUM
)
236 available
-= PANE_MINIMUM
;
239 } else if (lc
->type
== type
) {
240 /* Same type: total of available space in all child cells. */
242 TAILQ_FOREACH(lcchild
, &lc
->cells
, entry
)
243 available
+= layout_resize_check(lcchild
, type
);
245 /* Different type: minimum of available space in child cells. */
247 TAILQ_FOREACH(lcchild
, &lc
->cells
, entry
) {
248 available
= layout_resize_check(lcchild
, type
);
249 if (available
< minimum
)
259 * Adjust cell size evenly, including altering its children. This function
260 * expects the change to have already been bounded to the space available.
263 layout_resize_adjust(struct layout_cell
*lc
, enum layout_type type
, int change
)
265 struct layout_cell
*lcchild
;
267 /* Adjust the cell size. */
268 if (type
== LAYOUT_LEFTRIGHT
)
273 /* If this is a leaf cell, that is all that is necessary. */
274 if (type
== LAYOUT_WINDOWPANE
)
277 /* Child cell runs in a different direction. */
278 if (lc
->type
!= type
) {
279 TAILQ_FOREACH(lcchild
, &lc
->cells
, entry
)
280 layout_resize_adjust(lcchild
, type
, change
);
285 * Child cell runs in the same direction. Adjust each child equally
286 * until no further change is possible.
288 while (change
!= 0) {
289 TAILQ_FOREACH(lcchild
, &lc
->cells
, entry
) {
293 layout_resize_adjust(lcchild
, type
, 1);
297 if (layout_resize_check(lcchild
, type
) > 0) {
298 layout_resize_adjust(lcchild
, type
, -1);
306 layout_init(struct window
*w
)
308 struct layout_cell
*lc
;
310 lc
= w
->layout_root
= layout_create_cell(NULL
);
311 layout_set_size(lc
, w
->sx
, w
->sy
, 0, 0);
312 layout_make_leaf(lc
, TAILQ_FIRST(&w
->panes
));
314 layout_fix_panes(w
, w
->sx
, w
->sy
);
318 layout_free(struct window
*w
)
320 layout_free_cell(w
->layout_root
);
323 /* Resize the entire layout after window resize. */
325 layout_resize(struct window
*w
, u_int sx
, u_int sy
)
327 struct layout_cell
*lc
= w
->layout_root
;
328 int xlimit
, ylimit
, xchange
, ychange
;
331 * Adjust horizontally. Do not attempt to reduce the layout lower than
332 * the minimum (more than the amount returned by layout_resize_check).
334 * This can mean that the window size is smaller than the total layout
335 * size: redrawing this is handled at a higher level, but it does leave
336 * a problem with growing the window size here: if the current size is
337 * < the minimum, growing proportionately by adding to each pane is
338 * wrong as it would keep the layout size larger than the window size.
339 * Instead, spread the difference between the minimum and the new size
340 * out proportionately - this should leave the layout fitting the new
343 xchange
= sx
- w
->sx
;
344 xlimit
= layout_resize_check(lc
, LAYOUT_LEFTRIGHT
);
345 if (xchange
< 0 && xchange
< -xlimit
)
348 if (sx
<= lc
->sx
) /* lc->sx is minimum possible */
351 xchange
= sx
- lc
->sx
;
354 layout_resize_adjust(lc
, LAYOUT_LEFTRIGHT
, xchange
);
356 /* Adjust vertically in a similar fashion. */
357 ychange
= sy
- w
->sy
;
358 ylimit
= layout_resize_check(lc
, LAYOUT_TOPBOTTOM
);
359 if (ychange
< 0 && ychange
< -ylimit
)
362 if (sy
<= lc
->sy
) /* lc->sy is minimum possible */
365 ychange
= sy
- lc
->sy
;
368 layout_resize_adjust(lc
, LAYOUT_TOPBOTTOM
, ychange
);
370 /* Fix cell offsets. */
371 layout_fix_offsets(lc
);
372 layout_fix_panes(w
, sx
, sy
);
375 /* Resize a single pane within the layout. */
377 layout_resize_pane(struct window_pane
*wp
, enum layout_type type
, int change
)
379 struct layout_cell
*lc
, *lcparent
;
382 lc
= wp
->layout_cell
;
384 /* Find next parent of the same type. */
385 lcparent
= lc
->parent
;
386 while (lcparent
!= NULL
&& lcparent
->type
!= type
) {
388 lcparent
= lc
->parent
;
390 if (lcparent
== NULL
)
393 /* If this is the last cell, move back one. */
394 if (lc
== TAILQ_LAST(&lcparent
->cells
, layout_cells
))
395 lc
= TAILQ_PREV(lc
, layout_cells
, entry
);
397 /* Grow or shrink the cell. */
399 while (needed
!= 0) {
401 size
= layout_resize_pane_grow(lc
, type
, needed
);
404 size
= layout_resize_pane_shrink(lc
, type
, needed
);
408 if (size
== 0) /* no more change possible */
412 /* Fix cell offsets. */
413 layout_fix_offsets(wp
->window
->layout_root
);
414 layout_fix_panes(wp
->window
, wp
->window
->sx
, wp
->window
->sy
);
418 layout_resize_pane_grow(
419 struct layout_cell
*lc
, enum layout_type type
, int needed
)
421 struct layout_cell
*lcadd
, *lcremove
;
424 /* Growing. Always add to the current cell. */
427 /* Look towards the tail for a suitable cell for reduction. */
428 lcremove
= TAILQ_NEXT(lc
, entry
);
429 while (lcremove
!= NULL
) {
430 size
= layout_resize_check(lcremove
, type
);
433 lcremove
= TAILQ_NEXT(lcremove
, entry
);
436 /* If none found, look towards the head. */
437 if (lcremove
== NULL
) {
438 lcremove
= TAILQ_PREV(lc
, layout_cells
, entry
);
439 while (lcremove
!= NULL
) {
440 size
= layout_resize_check(lcremove
, type
);
443 lcremove
= TAILQ_PREV(lcremove
, layout_cells
, entry
);
445 if (lcremove
== NULL
)
449 /* Change the cells. */
450 if (size
> (u_int
) needed
)
452 layout_resize_adjust(lcadd
, type
, size
);
453 layout_resize_adjust(lcremove
, type
, -size
);
458 layout_resize_pane_shrink(
459 struct layout_cell
*lc
, enum layout_type type
, int needed
)
461 struct layout_cell
*lcadd
, *lcremove
;
464 /* Shrinking. Find cell to remove from by walking towards head. */
467 size
= layout_resize_check(lcremove
, type
);
470 lcremove
= TAILQ_PREV(lcremove
, layout_cells
, entry
);
471 } while (lcremove
!= NULL
);
472 if (lcremove
== NULL
)
475 /* And add onto the next cell (from the original cell). */
476 lcadd
= TAILQ_NEXT(lc
, entry
);
480 /* Change the cells. */
481 if (size
> (u_int
) -needed
)
483 layout_resize_adjust(lcadd
, type
, size
);
484 layout_resize_adjust(lcremove
, type
, -size
);
488 /* Assign window pane to newly split cell. */
490 layout_assign_pane(struct layout_cell
*lc
, struct window_pane
*wp
)
492 layout_make_leaf(lc
, wp
);
493 layout_fix_panes(wp
->window
, wp
->window
->sx
, wp
->window
->sy
);
497 * Split a pane into two. size is a hint, or -1 for default half/half
498 * split. This must be followed by layout_assign_pane before much else happens!
501 layout_split_pane(struct window_pane
*wp
, enum layout_type type
, int size
)
503 struct layout_cell
*lc
, *lcparent
, *lcnew
;
504 u_int sx
, sy
, xoff
, yoff
, size1
, size2
;
506 lc
= wp
->layout_cell
;
508 /* Copy the old cell size. */
514 /* Check there is enough space for the two new panes. */
516 case LAYOUT_LEFTRIGHT
:
517 if (sx
< PANE_MINIMUM
* 2 + 1)
520 case LAYOUT_TOPBOTTOM
:
521 if (sy
< PANE_MINIMUM
* 2 + 1)
525 fatalx("bad layout type");
528 if (lc
->parent
!= NULL
&& lc
->parent
->type
== type
) {
530 * If the parent exists and is of the same type as the split,
531 * create a new cell and insert it after this one.
534 /* Create the new child cell. */
535 lcnew
= layout_create_cell(lc
->parent
);
536 TAILQ_INSERT_AFTER(&lc
->parent
->cells
, lc
, lcnew
, entry
);
539 * Otherwise create a new parent and insert it.
542 /* Create and insert the replacement parent. */
543 lcparent
= layout_create_cell(lc
->parent
);
544 layout_make_node(lcparent
, type
);
545 layout_set_size(lcparent
, sx
, sy
, xoff
, yoff
);
546 if (lc
->parent
== NULL
)
547 wp
->window
->layout_root
= lcparent
;
549 TAILQ_REPLACE(&lc
->parent
->cells
, lc
, lcparent
, entry
);
551 /* Insert the old cell. */
552 lc
->parent
= lcparent
;
553 TAILQ_INSERT_HEAD(&lcparent
->cells
, lc
, entry
);
555 /* Create the new child cell. */
556 lcnew
= layout_create_cell(lcparent
);
557 TAILQ_INSERT_TAIL(&lcparent
->cells
, lcnew
, entry
);
560 /* Set new cell sizes. size is the target size or -1 for middle split,
561 * size1 is the size of the top/left and size2 the bottom/right.
564 case LAYOUT_LEFTRIGHT
:
566 size2
= ((sx
+ 1) / 2) - 1;
569 if (size2
< PANE_MINIMUM
)
570 size2
= PANE_MINIMUM
;
571 else if (size2
> sx
- 2)
573 size1
= sx
- 1 - size2
;
574 layout_set_size(lc
, size1
, sy
, xoff
, yoff
);
575 layout_set_size(lcnew
, size2
, sy
, xoff
+ lc
->sx
+ 1, yoff
);
577 case LAYOUT_TOPBOTTOM
:
579 size2
= ((sy
+ 1) / 2) - 1;
582 if (size2
< PANE_MINIMUM
)
583 size2
= PANE_MINIMUM
;
584 else if (size2
> sy
- 2)
586 size1
= sy
- 1 - size2
;
587 layout_set_size(lc
, sx
, size1
, xoff
, yoff
);
588 layout_set_size(lcnew
, sx
, size2
, xoff
, yoff
+ lc
->sy
+ 1);
591 fatalx("bad layout type");
594 /* Assign the panes. */
595 layout_make_leaf(lc
, wp
);
600 /* Destroy the layout associated with a pane and redistribute the space. */
602 layout_close_pane(struct window_pane
*wp
)
604 struct layout_cell
*lc
, *lcother
, *lcparent
;
606 lc
= wp
->layout_cell
;
607 lcparent
= lc
->parent
;
610 * If no parent, this is the last pane so window close is imminent and
611 * there is no need to resize anything.
613 if (lcparent
== NULL
) {
614 layout_free_cell(lc
);
615 wp
->window
->layout_root
= NULL
;
619 /* Merge the space into the previous or next cell. */
620 if (lc
== TAILQ_FIRST(&lcparent
->cells
))
621 lcother
= TAILQ_NEXT(lc
, entry
);
623 lcother
= TAILQ_PREV(lc
, layout_cells
, entry
);
624 if (lcparent
->type
== LAYOUT_LEFTRIGHT
)
625 layout_resize_adjust(lcother
, lcparent
->type
, lc
->sx
+ 1);
627 layout_resize_adjust(lcother
, lcparent
->type
, lc
->sy
+ 1);
629 /* Remove this from the parent's list. */
630 TAILQ_REMOVE(&lcparent
->cells
, lc
, entry
);
631 layout_free_cell(lc
);
634 * If the parent now has one cell, remove the parent from the tree and
635 * replace it by that cell.
637 lc
= TAILQ_FIRST(&lcparent
->cells
);
638 if (TAILQ_NEXT(lc
, entry
) == NULL
) {
639 TAILQ_REMOVE(&lcparent
->cells
, lc
, entry
);
641 lc
->parent
= lcparent
->parent
;
642 if (lc
->parent
== NULL
) {
643 lc
->xoff
= 0; lc
->yoff
= 0;
644 wp
->window
->layout_root
= lc
;
646 TAILQ_REPLACE(&lc
->parent
->cells
, lcparent
, lc
, entry
);
648 layout_free_cell(lcparent
);
651 /* Fix pane offsets and sizes. */
652 layout_fix_offsets(wp
->window
->layout_root
);
653 layout_fix_panes(wp
->window
, wp
->window
->sx
, wp
->window
->sy
);