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 /* Note that we don't use PANE_DATA_TYPE because the tileinfo
23 * moves between panes sometimes.
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 */
53 static struct map *tile_map safe;
54 static void tile_adjust(struct pane *p safe);
55 static void tile_avail(struct pane *p safe, struct pane *ignore);
56 static int tile_destroy(struct pane *p safe);
57 DEF_LOOKUP_CMD(tile_handle, tile_map);
59 static inline bool mine(struct pane *t safe)
61 return t->z == 0 && t->handle == &tile_handle.c;
66 tile_destroy(ci->home);
72 struct tileinfo *ti = ci->home->data;
79 DEF_CMD(tile_refresh_size)
81 struct pane *p = ci->home;
82 struct tileinfo *ti = p->data;
84 if (ti->direction == Neither) {
93 struct pane *parent = ci->focus;
94 struct pane *p2, *child;
95 struct tileinfo *ti, *cti;
97 /* Clone a new 'tile' onto the parent, but only
98 * create a single tile, cloned from the focus pane
104 ti->direction = Neither;
106 ti->group = strdup(cti->group);
107 INIT_LIST_HEAD(&ti->tiles);
108 p2 = pane_register(parent, 0, &tile_handle.c, ti);
112 /* Remove borders as our children will provide their own. */
113 call("Window:border", p2);
114 attr_set_str(&p2->attrs, "borders", "BL");
115 while (!cti->leaf && child->focus) {
116 child = child->focus;
119 cti = list_next_entry(cti, tiles);
120 while (cti != child->data &&
121 (cti->name == NULL || strcmp(cti->name, "main") != 0))
122 cti = list_next_entry(cti, tiles);
125 ti->name = strdup(cti->name);
126 pane_clone_children(child, p2);
132 struct pane *display = ci->focus;
136 /* Remove borders as our children will provide their own. */
137 call("Window:border", display);
140 p = pane_register(display, 0, &tile_handle.c, ti);
145 ti->direction = Neither;
147 ti->group = strdup(ci->str);
149 ti->name = strdup(ci->str2);
150 INIT_LIST_HEAD(&ti->tiles);
151 attr_set_str(&p->attrs, "borders", "BL");
152 return comm_call(ci->comm2, "callback:attach", p);
155 static struct pane *tile_split(struct pane **pp safe, int horiz, int after,
158 /* Create a new pane near the given one, reducing its size,
159 * and possibly the size of other siblings.
160 * Return a new pane which is a sibling of the old, or NULL
161 * if there is no room for further splits.
162 * This may require creating a new parent and moving 'p' down
165 int space, new_space;
166 struct pane *p = safe_cast *pp;
168 struct tileinfo *ti = p->data;
169 struct tileinfo *ti2;
175 /* FIXME ask the leafs */
178 new_space = space / 2;
181 if (ti->direction != (horiz? Horiz : Vert)) {
182 /* This tile does not stack in the required direction, need
183 * to create an extra level.
185 struct pane *p2, *child;
187 /* ti2 will be tileinfo for p, new pane gets ti */
190 ti2->direction = ti->direction;
191 ti2->group = ti->group;
192 INIT_LIST_HEAD(&ti2->tiles);
195 p2 = pane_register(p, 0, &tile_handle.c, ti);
199 ti->direction = horiz ? Horiz : Vert;
200 /* All children of p must be moved to p2, except p2 */
201 list_for_each_entry_safe(child, t, &p->children, siblings)
203 pane_reparent(child, p2);
207 ti2->group = ti->group;
208 ti2->direction = ti->direction;
211 ti2->name = strdup(name);
212 /* FIXME if ti wasn't a leaf, this is wrong. Is that possible? */
214 list_add(&ti2->tiles, &ti->tiles);
216 list_add_tail(&ti2->tiles, &ti->tiles);
217 ret = pane_register(p->parent, 0, &tile_handle.c, ti2);
223 pane_move_after(ret, p);
224 else if (p == list_first_entry(&p->parent->children,
225 struct pane, siblings))
226 pane_move_after(ret, NULL);
228 pane_move_after(ret, list_prev_entry(p, siblings));
229 switch (!!horiz + 2 * !!after) {
230 case 0: /* vert before */
231 pane_resize(ret, p->x, p->y, p->w, new_space);
232 pane_resize(p, p->x, p->y + ret->h, p->w, space);
234 case 1: /* horiz before */
235 pane_resize(ret, p->x, p->y, new_space, p->h);
236 pane_resize(p, p->x + ret->w, p->y, space, p->h);
238 case 2: /* vert after */
239 pane_resize(ret, p->x, p->y + space, p->w, new_space);
240 pane_resize(p, p->x, p->y, p->w, space);
242 case 3: /* horiz after */
243 pane_resize(ret, p->x + space, p->y, new_space, p->h);
244 pane_resize(p, p->x, p->y, space, p->h);
253 static int tile_destroy(struct pane *p safe)
255 struct tileinfo *ti = p->data;
256 struct pane *prev = NULL, *next = NULL;
257 struct pane *t, *remain = NULL;
258 int pos, prevpos, nextpos;
261 if (ti->direction == Neither /* Root file a tile-set */
262 || p->parent == p /* subsumbed hust being destroyed */
263 || p->parent->handle != p->handle /* Some messed with parentage */
267 if (ti->direction == Vert)
271 prevpos = nextpos = -1;
272 list_for_each_entry(t, &p->parent->children, siblings) {
276 if (ti->direction == Vert)
280 if (pos2 < pos && (prev == NULL || prevpos < pos2))
282 if (pos2 > pos && (next == NULL || nextpos > pos2))
288 /* There is always a sibling of a non-root */
289 //ASSERT(remaining > 0);
290 if (prev == NULL /* FIXME redundant */ && next) {
291 /* next gets the space and focus*/
292 if (ti->direction == Horiz)
293 pane_resize(next, p->x, next->y,
294 p->w + next->w, next->h);
296 pane_resize(next, next->x, p->y,
297 next->w, p->h + next->h);
299 p->parent->focus = next;
300 } else if (next == NULL /* FIXME redundant */ && prev) {
301 /* prev gets the space and focus */
302 if (ti->direction == Horiz)
303 pane_resize(prev, prev->x, prev->y,
304 prev->w + p->w, prev->h);
306 pane_resize(prev, prev->x, prev->y,
307 prev->w, prev->h + p->h);
309 p->parent->focus = prev;
310 } else if (/*FIXME*/ next && prev) {
311 /* space to the smallest, else share the space */
312 /* Focus goes to prev, unless next is small */
313 p->parent->focus = prev;
314 if (ti->direction == Horiz) {
316 if (prev->w < next->w*2/3)
317 /* prev is much smaller, it gets all */
319 else if (next->w < prev->w*2/3) {
321 p->parent->focus = next;
323 pane_resize(prev, prev->x, prev->y, prev->w + w, prev->h);
325 pane_resize(next, prev->x + prev->w, next->y,
326 next->w + w, next->h);
329 if (prev->h < next->h*2/3)
330 /* prev is much smaller, it gets all */
332 else if (next->h < prev->h*2/3) {
334 p->parent->focus = next;
336 pane_resize(prev, prev->x, prev->y, prev->w, prev->h + h);
338 pane_resize(next, next->x, prev->y + prev->h,
339 next->w , next->h + h);
344 list_del(&ti->tiles);
345 if (remaining == 1 && remain && remain->parent != remain &&
346 remain->handle == p->handle) {
347 struct tileinfo *ti2;
349 /* Only one child left, must move it into parent.
350 * Cannot destroy the parent, so bring child into parent */
356 tmp = ti2->direction;
357 ti2->direction = ti->direction;
362 pane_subsume(remain, p);
367 static void tile_avail(struct pane *p safe, struct pane *ignore)
369 /* How much can we shrink this pane?
370 * If 'ignore' is set, it is a child of 'p', and we only
371 * consider other children.
372 * If stacking direction matches 'horiz' find sum of avail in children.
373 * if stacking direction doesn't match 'horiz', find minimum.
374 * If only one child, assume min of 4.
376 struct tileinfo *ti = p->data;
380 /* one or zero children */
381 if (ti->direction == Horiz) {
382 ti->avail_inline = p->w < 4 ? 0 : p->w - 4;
383 ti->avail_perp = p->h < 4 ? 0 : p->h - 4;
385 ti->avail_inline = p->h < 4 ? 0 : p->h - 4;
386 ti->avail_perp = p->w < 4 ? 0 : p->w - 4;
389 struct tileinfo *ti2;
390 int sum = 0, min = -1;
391 list_for_each_entry(t, &p->children, siblings) {
392 if (t == ignore || !mine(t))
396 if (min < 0 || min > ti2->avail_perp)
397 min = ti2->avail_perp;
398 sum += ti2->avail_inline;
400 ti->avail_perp = sum;
401 ti->avail_inline = min;
405 static void tile_adjust(struct pane *p safe)
407 /* Size of pane 'p' has changed and it is time to adjust
408 * the children, both their size and offset.
409 * For the non-stacking direction, offset must be zero and
410 * size is copied from p.
411 * For the stacking direction, we count used size, find the
412 * change required and distribute that. For shrinking, this
413 * relies on the fact that 'tile_avail' must have been run
414 * and it left avail space calculations around.
422 struct tileinfo *ti = p->data;
425 /* Children are responsible for themselves. */
428 list_for_each_entry(t, &p->children, siblings) {
433 if (ti->direction == Horiz) {
434 pane_resize(t, t->x, 0, t->w, p->h);
438 pane_resize(t, 0, t->y, p->w, t->h);
442 if (ti->avail_inline)
446 while (used < size || (used > size && avail_cnt)) {
448 int remain = used; /* size of panes still to be resized */
453 list_for_each_entry(t, &p->children, siblings) {
454 struct tileinfo *ti2 = t->data;
461 mysize = (ti2->direction == Horiz) ? t->w : t->h;
465 if (ti2->avail_inline == 0) {
469 diff = (((used - size) * mysize) +
470 (used%remain)) / remain;
471 if (diff > ti2->avail_inline)
472 diff = ti2->avail_inline;
473 ti2->avail_inline -= diff;
474 if (ti2->avail_inline)
475 /* Still space available if needed */
479 } else if (used == size)
482 diff = (((size - used) * mysize) +
483 (used%remain) )/ remain;
487 if (ti2->direction == Horiz)
488 pane_resize(t, t->x, t->y, t->w + diff, t->h);
490 pane_resize(t, t->x, t->y, t->w, t->h + diff);
499 list_for_each_entry(t, &p->children, siblings) {
500 struct tileinfo *ti2 = t->data;
503 if (ti2->direction == Horiz) {
504 pane_resize(t, pos, t->y, t->w, t->h);
507 pane_resize(t, t->x, pos, t->w, t->h);
514 static bool tile_grow(struct pane *p safe, int horiz, int size)
516 /* Try to grow the pane in given direction, or shrink if
518 * This is only done by shrinking other tiles, not by
519 * resizing the top level.
520 * If this pane isn't stacked in the right direction, or if
521 * neighbors are too small to shrink, the parent is resized.
522 * Then that propagates back down. Size of this pane is adjusted
523 * first to catch the propagations, then corrected after.
525 struct tileinfo *ti = p->data;
526 struct tileinfo *tip;
529 if (ti->direction == Neither)
530 /* Cannot grow/shrink the root */
533 /* Does this pane have room to shrink */
535 if (ti->direction == (horiz? Horiz : Vert))
536 avail = ti->avail_inline;
538 avail = ti->avail_perp;
542 if (ti->direction != (horiz ? Horiz : Vert)) {
543 /* need to ask parent to do this */
544 return tile_grow(p->parent, horiz, size);
547 /* OK, this stacks in the right direction. if shrinking we can commit */
549 struct pane *other = NULL;
552 list_for_each_entry(t, &p->parent->children, siblings) {
559 if (other && p_found)
564 /* Strange - there should have been two elements in list */
566 if (ti->direction == Horiz) {
567 pane_resize(p, p->x, p->y, p->w + size, p->h);
568 pane_resize(other, other->x, other->y,
569 other->w - size, other->h);
571 pane_resize(p, p->x, p->y, p->w, p->h + size);
572 pane_resize(other, other->x, other->y,
573 other->w, other->h - size);
575 tile_adjust(p->parent);
579 /* Hoping to grow if there is room for others to shrink */
580 tile_avail(p->parent, p);
581 tip = p->parent->data;
582 if (ti->direction == (horiz ? Horiz : Vert))
583 avail = tip->avail_inline;
585 avail = tip->avail_perp;
588 if (ti->direction == Horiz)
589 pane_resize(p, p->x, p->y, p->w + size, p->h);
591 pane_resize(p, p->x, p->y, p->w, p->h + size);
593 ti->avail_inline = 0; /* make sure this one doesn't suffer */
594 tile_adjust(p->parent);
598 static struct pane *next_child(struct pane *parent, struct pane *prev, bool popup)
601 list_for_each_entry(p2, &parent->children, siblings) {
608 if (mine(p2) == popup)
615 static struct tileinfo *tile_first(struct tileinfo *ti safe)
618 struct pane *p = next_child(ti->p, NULL, 0);
626 static bool tile_is_first(struct tileinfo *ti safe)
628 while (ti->direction != Neither) {
629 if (ti->p != next_child(ti->p->parent, NULL, 0))
631 ti = ti->p->parent->data;
636 static struct pane *tile_root_popup(struct tileinfo *ti safe)
638 while (ti->direction != Neither)
639 ti = ti->p->parent->data;
640 return next_child(ti->p, NULL, 1);
643 static struct tileinfo *safe tile_next_named(struct tileinfo *ti safe,
646 struct tileinfo *t = ti;
647 while ((t = list_next_entry(t, tiles)) != ti) {
650 if (!t->name || strcmp(t->name, name) != 0)
657 static bool wrong_pane(struct cmd_info const *ci safe)
659 struct tileinfo *ti = ci->home->data;
661 if (ci->str || ti->group) {
662 if (!ci->str || !ti->group)
664 if (strcmp(ci->str, ti->group) != 0)
666 /* same group - continue */
671 DEF_CMD(tile_window_next)
673 /* If currently on a popup, go to next popup if there is one, else
675 * If was not on a pop-up, go to next tile and if there is a popup,
678 struct pane *p = ci->home;
680 struct tileinfo *ti = p->data;
685 if (p->focus && p->focus->z) {
686 p2 = next_child(p, p->focus, 1);
690 } else if (ti->leaf) {
691 pane_focus(ti->content);
697 t2 = tile_next_named(ti, ci->str2);
698 if (tile_is_first(t2) &&
699 (p2 = tile_root_popup(t2)) != NULL) {
708 p2 = next_child(t2->p, NULL, 1);
715 DEF_CMD(tile_window_prev)
717 struct pane *p = ci->home;
718 struct tileinfo *ti = p->data;
723 t2 = list_prev_entry(ti, tiles);
728 DEF_CMD(tile_window_xplus)
730 struct pane *p = ci->home;
734 tile_grow(p, 1, RPT_NUM(ci));
738 DEF_CMD(tile_window_xminus)
740 struct pane *p = ci->home;
744 tile_grow(p, 1, -RPT_NUM(ci));
747 DEF_CMD(tile_window_yplus)
749 struct pane *p = ci->home;
753 tile_grow(p, 0, RPT_NUM(ci));
756 DEF_CMD(tile_window_yminus)
758 struct pane *p = ci->home;
762 tile_grow(p, 0, -RPT_NUM(ci));
766 DEF_CMD(tile_window_splitx)
768 struct pane *p = ci->home;
773 p2 = tile_split(&p, 1, 1, ci->str2);
774 pane_clone_children(p, p2);
778 DEF_CMD(tile_window_splity)
780 struct pane *p = ci->home;
785 p2 = tile_split(&p, 0, 1, ci->str2);
786 pane_clone_children(p, p2);
790 DEF_CMD(tile_window_close)
792 struct pane *p = ci->home;
793 struct tileinfo *ti = p->data;
797 if (ti->direction != Neither)
802 DEF_CMD(tile_window_bury)
804 /* Bury the document in this tile.
805 * Find some other document to display
812 /* First, push the doc to the end of the 'recently used' list */
813 call("doc:notify:doc:revisit", ci->focus, -1);
814 /* Now choose a replacement */
815 doc = call_ret(pane, "docs:choose", ci->home);
817 /* display that doc in this pane */
818 home_call(doc, "doc:attach-view", ci->home);
822 DEF_CMD(tile_window_close_others)
824 struct pane *p = ci->home;
825 struct pane *parent = p->parent;
826 struct tileinfo *ti = p->data;
831 /* close sibling panes until ->parent changes, or there aren't any */
832 while (found && p->parent == parent) {
836 list_for_each_entry(s, &parent->children, siblings)
843 return ti->direction != Neither ? 1 : Efalse;
848 /* Choose some other tile. If there aren't any, make one.
849 * Result is returned in ci->focus
851 * 1: if split is need, use 2 to determine direction, else default
852 * 2: if split needed, split horizontally, else vertically
853 * 4: if split needed use 8 to determine which is new, else default
854 * 8: if split is needed, new pane is to the right/down.
855 * 512: don't split, just return Efalse
857 struct pane *p = ci->home;
859 struct tileinfo *ti = p->data;
860 struct tileinfo *ti2;
863 if (ci->str || ti->group) {
864 if (!ci->str || !ti->group)
866 if (strcmp(ci->str, ti->group) != 0)
868 /* same group - continue */
871 /* probably coming from a pop-up. Just use first tile */
872 ti2 = tile_first(ti);
875 if (ci->str2 && ti2->name && strcmp(ci->str2, ti2->name) == 0)
877 return comm_call(ci->comm2, "callback:pane", ti2->p);
879 if (ci->str2 && ti->name && strcmp(ci->str2, ti->name) == 0)
882 ti2 = tile_next_named(ti, ci->str2);
884 return comm_call(ci->comm2, "callback:pane", ti2->p);
886 /* Need to create a tile. If wider than 120 (FIXME configurable?),
887 * horiz-split else vert
895 struct xy xy = pane_scale(p);
896 horiz = p->w * 1000 >= 1200 * xy.x;
902 p2 = tile_split(&p, horiz, after, ci->str2);
904 return comm_call(ci->comm2, "callback:pane", p2);
910 struct tileinfo *ti = ci->home->data;
912 if (ci->str || ti->group) {
913 if (!ci->str || !ti->group)
915 if (strcmp(ci->str, ti->group) != 0)
917 /* same group - continue */
920 /* There is no clear 'This', use first. */
924 if (ci->str2 && ti->name && strcmp(ci->str2, ti->name) == 0)
926 return comm_call(ci->comm2, "callback:pane", ti->p);
928 return comm_call(ci->comm2, "callback:pane", ci->home, 0,
934 /* Find the pane displaying given document, preferrably not
937 struct tileinfo *ti = ci->home->data;
941 if (ci->str || ti->group) {
942 if (!ci->str || !ti->group)
944 if (strcmp(ci->str, ti->group) != 0)
946 /* same group - continue */
948 /* Find where 'focus' is open */
949 name = pane_attr_get(ci->focus, "doc-name");
956 t = list_next_entry(t, tiles);
960 n = pane_attr_get(f, "doc-name");
961 if (name && n && strcmp(n, name) == 0)
962 return comm_call(ci->comm2, "callback:pane",
963 strcmp(ci->key, "DocLeaf") == 0
974 struct pane *p = ci->home;
975 struct tileinfo *ti = p->data;
977 if (ti->direction != Neither)
979 if (ci->str || ti->group) {
980 if (!ci->str || !ti->group)
982 if (strcmp(ci->str, ti->group) != 0)
984 /* same group - continue */
987 return comm_call(ci->comm2, "callback:pane", p);
990 DEF_CMD(tile_child_notify)
992 struct pane *p = ci->home;
993 struct tileinfo *ti = p->data;
994 struct pane *c = ci->focus;
998 if (ci->num > 0 && mine(c))
999 /* always accept my own children */
1002 if (ti->leaf != 1) {
1004 /* Sorry, new children not permitted */
1011 /* Child closed, but we weren't, so find something else to display */
1013 c = call_ret(pane, "docs:choose", p);
1015 home_call(c, "doc:attach-view", p);
1016 else if (ti->direction != Neither)
1020 /* New pane, discard the old */
1024 pane_close(ti->content);
1030 /* Child moved away - hopefully to be replaced */
1034 /* Simple replacement */
1041 void edlib_init(struct pane *ed safe)
1043 tile_map = key_alloc();
1045 key_add(tile_map, "Window:next", &tile_window_next);
1046 key_add(tile_map, "Window:prev", &tile_window_prev);
1047 key_add(tile_map, "Window:x+", &tile_window_xplus);
1048 key_add(tile_map, "Window:x-", &tile_window_xminus);
1049 key_add(tile_map, "Window:y+", &tile_window_yplus);
1050 key_add(tile_map, "Window:y-", &tile_window_yminus);
1051 key_add(tile_map, "Window:split-x", &tile_window_splitx);
1052 key_add(tile_map, "Window:split-y", &tile_window_splity);
1053 key_add(tile_map, "Window:close", &tile_window_close);
1054 key_add(tile_map, "Window:close-others", &tile_window_close_others);
1055 key_add(tile_map, "Window:bury", &tile_window_bury);
1057 key_add(tile_map, "OtherPane", &tile_other);
1058 key_add(tile_map, "ThisPane", &tile_this);
1059 key_add(tile_map, "DocPane", &tile_doc);
1060 key_add(tile_map, "DocLeaf", &tile_doc);
1061 key_add(tile_map, "RootPane", &tile_root);
1062 key_add(tile_map, "Clone", &tile_clone);
1063 key_add(tile_map, "Child-Notify", &tile_child_notify);
1064 key_add(tile_map, "Close", &tile_close);
1065 key_add(tile_map, "Free", &tile_free);
1066 key_add(tile_map, "Refresh:size", &tile_refresh_size);
1068 call_comm("global-set-command", ed, &tile_attach, 0, NULL, "attach-tile");