2 * Copyright Neil Brown ©2015-2023 <neil@brown.name>
3 * May be distributed under terms of GPLv2 - see file:COPYING
5 * Tile manager for edlib.
7 * Given a display pane, tile it with other panes which will be
8 * used by some other clients, probably text buffers.
9 * The owner of a pane can:
10 * - split it: above/below/left/right,
12 * - add/remove lines above/below/left/right
14 * Child panes are grouped either in rows or columns. Those panes
15 * can then be subdivided further.
22 /* The data can move between panes, so we must use a PTR type */
23 #define PANE_DATA_PTR_TYPE struct tileinfo *
28 /* If direction is Horiz, this and siblings are stacked
29 * left to right. Y co-ordinate is zero.
30 * If direction is Vert, siblings are stacked top to bottom.
31 * X co-ordinate is zero.
32 * The root of a tree of panes has direction of Neither. All
33 * other panes are either Horiz or Vert.
35 * avail_inline is how much this tile can shrink in the direction
36 * of stacking. Add these for parent.
37 * avail_perp is how much this tile can shrink perpendicular to direction.
38 * Min of these applies to parent.
40 enum dir {Neither, Horiz, Vert} direction;
44 struct list_head tiles; /* headless ordered list of all tiles
45 * in the tree. Used for next/prev
48 struct pane *content; /* if 'leaf' */
49 char *group; /* only allocate for root, other share */
50 char *name; /* name in group for this leaf */
52 #include "core-pane.h"
54 static struct map *tile_map safe;
55 static void tile_adjust(struct pane *p safe);
56 static void tile_avail(struct pane *p safe, struct pane *ignore);
57 static int tile_destroy(struct pane *p safe);
58 DEF_LOOKUP_CMD(tile_handle, tile_map);
60 static inline bool mine(struct pane *t safe)
62 return t->z == 0 && t->handle == &tile_handle.c;
65 DEF_CMD_CLOSED(tile_close)
67 struct tileinfo *ti = ci->home->data;
69 tile_destroy(ci->home);
75 DEF_CMD(tile_refresh_size)
77 struct pane *p = ci->home;
78 struct tileinfo *ti = p->data;
80 if (ti->direction == Neither) {
89 struct pane *parent = ci->focus;
90 struct pane *p2, *child;
91 struct tileinfo *ti, *cti;
93 /* Clone a new 'tile' onto the parent, but only
94 * create a single tile, cloned from the focus pane
100 ti->direction = Neither;
102 ti->group = strdup(cti->group);
103 INIT_LIST_HEAD(&ti->tiles);
104 p2 = pane_register(parent, 0, &tile_handle.c, ti);
108 /* Remove borders as our children will provide their own. */
109 call("Tile:border", p2);
110 attr_set_str(&p2->attrs, "borders", "BL");
111 while (!cti->leaf && child->focus) {
112 child = child->focus;
115 cti = list_next_entry(cti, tiles);
116 while (cti != child->data &&
117 (cti->name == NULL || strcmp(cti->name, "main") != 0))
118 cti = list_next_entry(cti, tiles);
121 ti->name = strdup(cti->name);
122 pane_clone_children(child, p2);
128 struct pane *display = ci->focus;
132 /* Remove borders as our children will provide their own. */
133 call("Tile:border", display);
136 p = pane_register(display, 0, &tile_handle.c, ti);
141 ti->direction = Neither;
143 ti->group = strdup(ci->str);
145 ti->name = strdup(ci->str2);
146 INIT_LIST_HEAD(&ti->tiles);
147 attr_set_str(&p->attrs, "borders", "BL");
148 return comm_call(ci->comm2, "callback:attach", p);
151 static struct pane *tile_split(struct pane **pp safe, int horiz, int after,
154 /* Create a new pane near the given one, reducing its size,
155 * and possibly the size of other siblings.
156 * Return a new pane which is a sibling of the old, or NULL
157 * if there is no room for further splits.
158 * This may require creating a new parent and moving 'p' down
161 int space, new_space;
162 struct pane *p = safe_cast *pp;
164 struct tileinfo *ti = p->data;
165 struct tileinfo *ti2;
171 /* FIXME ask the leafs */
174 new_space = space / 2;
177 if (ti->direction != (horiz? Horiz : Vert)) {
178 /* This tile does not stack in the required direction, need
179 * to create an extra level.
181 struct pane *p2, *child;
183 /* ti2 will be tileinfo for p, new pane gets ti */
186 ti2->direction = ti->direction;
187 ti2->group = ti->group;
188 INIT_LIST_HEAD(&ti2->tiles);
191 p2 = pane_register(p, 0, &tile_handle.c, ti);
195 ti->direction = horiz ? Horiz : Vert;
196 /* All children of p must be moved to p2, except p2 */
197 list_for_each_entry_safe(child, t, &p->children, siblings)
199 pane_reparent(child, p2);
203 ti2->group = ti->group;
204 ti2->direction = ti->direction;
207 ti2->name = strdup(name);
208 /* FIXME if ti wasn't a leaf, this is wrong. Is that possible? */
210 list_add(&ti2->tiles, &ti->tiles);
212 list_add_tail(&ti2->tiles, &ti->tiles);
213 ret = pane_register(p->parent, 0, &tile_handle.c, ti2);
219 pane_move_after(ret, p);
220 else if (p == list_first_entry(&p->parent->children,
221 struct pane, siblings))
222 pane_move_after(ret, NULL);
224 pane_move_after(ret, list_prev_entry(p, siblings));
225 switch (!!horiz + 2 * !!after) {
226 case 0: /* vert before */
227 pane_resize(ret, p->x, p->y, p->w, new_space);
228 pane_resize(p, p->x, p->y + ret->h, p->w, space);
230 case 1: /* horiz before */
231 pane_resize(ret, p->x, p->y, new_space, p->h);
232 pane_resize(p, p->x + ret->w, p->y, space, p->h);
234 case 2: /* vert after */
235 pane_resize(ret, p->x, p->y + space, p->w, new_space);
236 pane_resize(p, p->x, p->y, p->w, space);
238 case 3: /* horiz after */
239 pane_resize(ret, p->x + space, p->y, new_space, p->h);
240 pane_resize(p, p->x, p->y, space, p->h);
249 static int tile_destroy(struct pane *p safe)
251 struct tileinfo *ti = p->data;
252 struct pane *prev = NULL, *next = NULL;
253 struct pane *t, *remain = NULL;
254 int pos, prevpos, nextpos;
257 if (ti->direction == Neither /* Root file a tile-set */
258 || p->parent == p /* subsumbed hust being destroyed */
259 || p->parent->handle != p->handle /* Some messed with parentage */
263 if (ti->direction == Vert)
267 prevpos = nextpos = -1;
268 list_for_each_entry(t, &p->parent->children, siblings) {
272 if (ti->direction == Vert)
276 if (pos2 < pos && (prev == NULL || prevpos < pos2))
278 if (pos2 > pos && (next == NULL || nextpos > pos2))
284 /* There is always a sibling of a non-root */
285 //ASSERT(remaining > 0);
286 if (prev == NULL /* FIXME redundant */ && next) {
287 /* next gets the space and focus*/
288 if (ti->direction == Horiz)
289 pane_resize(next, p->x, next->y,
290 p->w + next->w, next->h);
292 pane_resize(next, next->x, p->y,
293 next->w, p->h + next->h);
295 p->parent->focus = next;
296 } else if (next == NULL /* FIXME redundant */ && prev) {
297 /* prev gets the space and focus */
298 if (ti->direction == Horiz)
299 pane_resize(prev, prev->x, prev->y,
300 prev->w + p->w, prev->h);
302 pane_resize(prev, prev->x, prev->y,
303 prev->w, prev->h + p->h);
305 p->parent->focus = prev;
306 } else if (/*FIXME*/ next && prev) {
307 /* space to the smallest, else share the space */
308 /* Focus goes to prev, unless next is small */
309 p->parent->focus = prev;
310 if (ti->direction == Horiz) {
312 if (prev->w < next->w*2/3)
313 /* prev is much smaller, it gets all */
315 else if (next->w < prev->w*2/3) {
317 p->parent->focus = next;
319 pane_resize(prev, prev->x, prev->y, prev->w + w, prev->h);
321 pane_resize(next, prev->x + prev->w, next->y,
322 next->w + w, next->h);
325 if (prev->h < next->h*2/3)
326 /* prev is much smaller, it gets all */
328 else if (next->h < prev->h*2/3) {
330 p->parent->focus = next;
332 pane_resize(prev, prev->x, prev->y, prev->w, prev->h + h);
334 pane_resize(next, next->x, prev->y + prev->h,
335 next->w , next->h + h);
340 list_del(&ti->tiles);
341 if (remaining == 1 && remain && remain->parent != remain &&
342 remain->handle == p->handle) {
343 struct tileinfo *ti2;
345 /* Only one child left, must move it into parent.
346 * Cannot destroy the parent, so bring child into parent */
352 tmp = ti2->direction;
353 ti2->direction = ti->direction;
358 pane_subsume(remain, p);
363 static void tile_avail(struct pane *p safe, struct pane *ignore)
365 /* How much can we shrink this pane?
366 * If 'ignore' is set, it is a child of 'p', and we only
367 * consider other children.
368 * If stacking direction matches 'horiz' find sum of avail in children.
369 * if stacking direction doesn't match 'horiz', find minimum.
370 * If only one child, assume min of 4.
372 struct tileinfo *ti = p->data;
376 /* one or zero children */
377 if (ti->direction == Horiz) {
378 ti->avail_inline = p->w < 4 ? 0 : p->w - 4;
379 ti->avail_perp = p->h < 4 ? 0 : p->h - 4;
381 ti->avail_inline = p->h < 4 ? 0 : p->h - 4;
382 ti->avail_perp = p->w < 4 ? 0 : p->w - 4;
385 struct tileinfo *ti2;
386 int sum = 0, min = -1;
387 list_for_each_entry(t, &p->children, siblings) {
388 if (t == ignore || !mine(t))
392 if (min < 0 || min > ti2->avail_perp)
393 min = ti2->avail_perp;
394 sum += ti2->avail_inline;
396 ti->avail_perp = sum;
397 ti->avail_inline = min;
401 static void tile_adjust(struct pane *p safe)
403 /* Size of pane 'p' has changed and it is time to adjust
404 * the children, both their size and offset.
405 * For the non-stacking direction, offset must be zero and
406 * size is copied from p.
407 * For the stacking direction, we count used size, find the
408 * change required and distribute that. For shrinking, this
409 * relies on the fact that 'tile_avail' must have been run
410 * and it left avail space calculations around.
418 struct tileinfo *ti = p->data;
421 /* Children are responsible for themselves. */
424 list_for_each_entry(t, &p->children, siblings) {
429 if (ti->direction == Horiz) {
430 pane_resize(t, t->x, 0, t->w, p->h);
434 pane_resize(t, 0, t->y, p->w, t->h);
438 if (ti->avail_inline)
442 while (used < size || (used > size && avail_cnt)) {
444 int remain = used; /* size of panes still to be resized */
449 list_for_each_entry(t, &p->children, siblings) {
450 struct tileinfo *ti2 = t->data;
457 mysize = (ti2->direction == Horiz) ? t->w : t->h;
461 if (ti2->avail_inline == 0) {
465 diff = (((used - size) * mysize) +
466 (used%remain)) / remain;
467 if (diff > ti2->avail_inline)
468 diff = ti2->avail_inline;
469 ti2->avail_inline -= diff;
470 if (ti2->avail_inline)
471 /* Still space available if needed */
475 } else if (used == size)
478 diff = (((size - used) * mysize) +
479 (used%remain) )/ remain;
483 if (ti2->direction == Horiz)
484 pane_resize(t, t->x, t->y, t->w + diff, t->h);
486 pane_resize(t, t->x, t->y, t->w, t->h + diff);
495 list_for_each_entry(t, &p->children, siblings) {
496 struct tileinfo *ti2 = t->data;
499 if (ti2->direction == Horiz) {
500 pane_resize(t, pos, t->y, t->w, t->h);
503 pane_resize(t, t->x, pos, t->w, t->h);
510 static bool tile_grow(struct pane *p safe, int horiz, int size)
512 /* Try to grow the pane in given direction, or shrink if
514 * This is only done by shrinking other tiles, not by
515 * resizing the top level.
516 * If this pane isn't stacked in the right direction, or if
517 * neighbors are too small to shrink, the parent is resized.
518 * Then that propagates back down. Size of this pane is adjusted
519 * first to catch the propagations, then corrected after.
521 struct tileinfo *ti = p->data;
522 struct tileinfo *tip;
525 if (ti->direction == Neither)
526 /* Cannot grow/shrink the root */
529 /* Does this pane have room to shrink */
531 if (ti->direction == (horiz? Horiz : Vert))
532 avail = ti->avail_inline;
534 avail = ti->avail_perp;
538 if (ti->direction != (horiz ? Horiz : Vert)) {
539 /* need to ask parent to do this */
540 return tile_grow(p->parent, horiz, size);
543 /* OK, this stacks in the right direction. if shrinking we can commit */
545 struct pane *other = NULL;
548 list_for_each_entry(t, &p->parent->children, siblings) {
555 if (other && p_found)
560 /* Strange - there should have been two elements in list */
562 if (ti->direction == Horiz) {
563 pane_resize(p, p->x, p->y, p->w + size, p->h);
564 pane_resize(other, other->x, other->y,
565 other->w - size, other->h);
567 pane_resize(p, p->x, p->y, p->w, p->h + size);
568 pane_resize(other, other->x, other->y,
569 other->w, other->h - size);
571 tile_adjust(p->parent);
575 /* Hoping to grow if there is room for others to shrink */
576 tile_avail(p->parent, p);
577 tip = p->parent->data;
578 if (ti->direction == (horiz ? Horiz : Vert))
579 avail = tip->avail_inline;
581 avail = tip->avail_perp;
584 if (ti->direction == Horiz)
585 pane_resize(p, p->x, p->y, p->w + size, p->h);
587 pane_resize(p, p->x, p->y, p->w, p->h + size);
589 ti->avail_inline = 0; /* make sure this one doesn't suffer */
590 tile_adjust(p->parent);
594 static struct pane *next_child(struct pane *parent, struct pane *prev, bool popup)
597 list_for_each_entry(p2, &parent->children, siblings) {
604 if (mine(p2) == popup)
611 static struct tileinfo *tile_first(struct tileinfo *ti safe)
614 struct pane *p = next_child(ti->p, NULL, 0);
622 static bool tile_is_first(struct tileinfo *ti safe)
624 while (ti->direction != Neither) {
625 if (ti->p != next_child(ti->p->parent, NULL, 0))
627 ti = ti->p->parent->data;
632 static struct pane *tile_root_popup(struct tileinfo *ti safe)
634 while (ti->direction != Neither)
635 ti = ti->p->parent->data;
636 return next_child(ti->p, NULL, 1);
639 static struct tileinfo *safe tile_next_named(struct tileinfo *ti safe,
642 struct tileinfo *t = ti;
643 while ((t = list_next_entry(t, tiles)) != ti) {
646 if (!t->name || strcmp(t->name, name) != 0)
653 static bool wrong_pane(struct cmd_info const *ci safe)
655 struct tileinfo *ti = ci->home->data;
657 if (ci->str || ti->group) {
658 if (!ci->str || !ti->group)
660 if (strcmp(ci->str, ti->group) != 0)
662 /* same group - continue */
667 DEF_CMD(tile_window_next)
669 /* If currently on a popup, go to next popup if there is one, else
671 * If was not on a pop-up, go to next tile and if there is a popup,
674 struct pane *p = ci->home;
676 struct tileinfo *ti = p->data;
681 if (p->focus && p->focus->z) {
682 p2 = next_child(p, p->focus, 1);
686 } else if (ti->leaf) {
687 pane_take_focus(ti->content);
693 t2 = tile_next_named(ti, ci->str2);
694 if (tile_is_first(t2) &&
695 (p2 = tile_root_popup(t2)) != NULL) {
703 pane_take_focus(t2->p);
704 p2 = next_child(t2->p, NULL, 1);
711 DEF_CMD(tile_window_prev)
713 struct pane *p = ci->home;
714 struct tileinfo *ti = p->data;
719 t2 = list_prev_entry(ti, tiles);
720 pane_take_focus(t2->p);
724 DEF_CMD(tile_window_xplus)
726 struct pane *p = ci->home;
730 tile_grow(p, 1, RPT_NUM(ci));
734 DEF_CMD(tile_window_xminus)
736 struct pane *p = ci->home;
740 tile_grow(p, 1, -RPT_NUM(ci));
743 DEF_CMD(tile_window_yplus)
745 struct pane *p = ci->home;
749 tile_grow(p, 0, RPT_NUM(ci));
752 DEF_CMD(tile_window_yminus)
754 struct pane *p = ci->home;
758 tile_grow(p, 0, -RPT_NUM(ci));
762 DEF_CMD(tile_window_splitx)
764 struct pane *p = ci->home;
769 p2 = tile_split(&p, 1, 1, ci->str2);
770 pane_clone_children(p, p2);
774 DEF_CMD(tile_window_splity)
776 struct pane *p = ci->home;
781 p2 = tile_split(&p, 0, 1, ci->str2);
782 pane_clone_children(p, p2);
786 DEF_CMD(tile_window_close)
788 struct pane *p = ci->home;
789 struct tileinfo *ti = p->data;
793 if (ti->direction != Neither)
798 DEF_CMD(tile_window_bury)
800 /* Bury the document in this tile.
801 * Find some other document to display
808 /* First, push the doc to the end of the 'recently used' list */
809 call("doc:notify:doc:revisit", ci->focus, -1);
810 /* Now choose a replacement */
811 doc = call_ret(pane, "docs:choose", ci->home);
813 /* display that doc in this pane */
814 home_call(doc, "doc:attach-view", ci->home);
818 DEF_CMD(tile_window_close_others)
820 struct pane *p = ci->home;
821 struct pane *parent = p->parent;
822 struct tileinfo *ti = p->data;
827 /* close sibling panes until ->parent changes, or there aren't any */
828 while (found && p->parent == parent) {
832 list_for_each_entry(s, &parent->children, siblings)
839 return ti->direction != Neither ? 1 : Efalse;
844 /* Choose some other tile. If there aren't any, make one.
845 * Result is returned in ci->focus
847 * 1: if split is need, use 2 to determine direction, else default
848 * 2: if split needed, split horizontally, else vertically
849 * 4: if split needed use 8 to determine which is new, else default
850 * 8: if split is needed, new pane is to the right/down.
851 * 512: don't split, just return Efalse
853 struct pane *p = ci->home;
855 struct tileinfo *ti = p->data;
856 struct tileinfo *ti2;
859 if (ci->str || ti->group) {
860 if (!ci->str || !ti->group)
862 if (strcmp(ci->str, ti->group) != 0)
864 /* same group - continue */
867 /* probably coming from a pop-up. Just use first tile */
868 ti2 = tile_first(ti);
871 if (ci->str2 && ti2->name && strcmp(ci->str2, ti2->name) == 0)
873 return comm_call(ci->comm2, "callback:pane", ti2->p);
875 if (ci->str2 && ti->name && strcmp(ci->str2, ti->name) == 0)
878 ti2 = tile_next_named(ti, ci->str2);
880 return comm_call(ci->comm2, "callback:pane", ti2->p);
882 /* Need to create a tile. If wider than 120 (FIXME configurable?),
883 * horiz-split else vert
891 struct xy xy = pane_scale(p);
892 horiz = p->w * 1000 >= 1200 * xy.x;
898 p2 = tile_split(&p, horiz, after, ci->str2);
900 return comm_call(ci->comm2, "callback:pane", p2);
906 struct tileinfo *ti = ci->home->data;
908 if (ci->str || ti->group) {
909 if (!ci->str || !ti->group)
911 if (strcmp(ci->str, ti->group) != 0)
913 /* same group - continue */
916 /* There is no clear 'This', use first. */
920 if (ci->str2 && ti->name && strcmp(ci->str2, ti->name) == 0)
922 return comm_call(ci->comm2, "callback:pane", ti->p);
924 return comm_call(ci->comm2, "callback:pane", ci->home, 0,
930 /* Find the pane displaying given document, preferrably not
933 struct tileinfo *ti = ci->home->data;
937 if (ci->str || ti->group) {
938 if (!ci->str || !ti->group)
940 if (strcmp(ci->str, ti->group) != 0)
942 /* same group - continue */
944 /* Find where 'focus' is open */
945 name = pane_attr_get(ci->focus, "doc-name");
954 t = list_next_entry(t, tiles);
958 n = pane_attr_get(f, "doc-name");
959 if (n && strcmp(n, name) == 0)
960 return comm_call(ci->comm2, "callback:pane",
971 struct pane *p = ci->home;
972 struct tileinfo *ti = p->data;
974 if (ti->direction != Neither)
976 if (ci->str || ti->group) {
977 if (!ci->str || !ti->group)
979 if (strcmp(ci->str, ti->group) != 0)
981 /* same group - continue */
984 return comm_call(ci->comm2, "callback:pane", p);
987 DEF_CMD(tile_child_notify)
989 struct pane *p = ci->home;
990 struct tileinfo *ti = p->data;
991 struct pane *c = ci->focus;
995 if (ci->num > 0 && mine(c))
996 /* always accept my own children */
1001 /* Sorry, new children not permitted */
1008 /* Child closed, but we weren't, so find something else to display */
1010 c = call_ret(pane, "docs:choose", p);
1012 home_call(c, "doc:attach-view", p);
1013 else if (ti->direction != Neither)
1017 /* New pane, discard the old */
1021 pane_close(ti->content);
1027 /* Child moved away - hopefully to be replaced */
1031 /* Simple replacement */
1038 void edlib_init(struct pane *ed safe)
1040 tile_map = key_alloc();
1042 key_add(tile_map, "Tile:next", &tile_window_next);
1043 key_add(tile_map, "Tile:prev", &tile_window_prev);
1044 key_add(tile_map, "Tile:x+", &tile_window_xplus);
1045 key_add(tile_map, "Tile:x-", &tile_window_xminus);
1046 key_add(tile_map, "Tile:y+", &tile_window_yplus);
1047 key_add(tile_map, "Tile:y-", &tile_window_yminus);
1048 key_add(tile_map, "Tile:split-x", &tile_window_splitx);
1049 key_add(tile_map, "Tile:split-y", &tile_window_splity);
1050 key_add(tile_map, "Tile:close", &tile_window_close);
1051 key_add(tile_map, "Tile:close-others", &tile_window_close_others);
1052 key_add(tile_map, "Tile:bury", &tile_window_bury);
1054 key_add(tile_map, "OtherPane", &tile_other);
1055 key_add(tile_map, "ThisPane", &tile_this);
1056 key_add(tile_map, "DocPane", &tile_doc);
1057 key_add(tile_map, "RootPane", &tile_root);
1058 key_add(tile_map, "Clone", &tile_clone);
1059 key_add(tile_map, "Child-Notify", &tile_child_notify);
1060 key_add(tile_map, "Close", &tile_close);
1061 key_add(tile_map, "Refresh:size", &tile_refresh_size);
1063 call_comm("global-set-command", ed, &tile_attach, 0, NULL, "attach-tile");