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 struct tileinfo *ti = ci->home->data;
68 tile_destroy(ci->home);
74 DEF_CMD(tile_refresh_size)
76 struct pane *p = ci->home;
77 struct tileinfo *ti = p->data;
79 if (ti->direction == Neither) {
88 struct pane *parent = ci->focus;
89 struct pane *p2, *child;
90 struct tileinfo *ti, *cti;
92 /* Clone a new 'tile' onto the parent, but only
93 * create a single tile, cloned from the focus pane
99 ti->direction = Neither;
101 ti->group = strdup(cti->group);
102 INIT_LIST_HEAD(&ti->tiles);
103 p2 = pane_register(parent, 0, &tile_handle.c, ti);
107 /* Remove borders as our children will provide their own. */
108 call("Window:border", p2);
109 attr_set_str(&p2->attrs, "borders", "BL");
110 while (!cti->leaf && child->focus) {
111 child = child->focus;
114 cti = list_next_entry(cti, tiles);
115 while (cti != child->data &&
116 (cti->name == NULL || strcmp(cti->name, "main") != 0))
117 cti = list_next_entry(cti, tiles);
120 ti->name = strdup(cti->name);
121 pane_clone_children(child, p2);
127 struct pane *display = ci->focus;
131 /* Remove borders as our children will provide their own. */
132 call("Window:border", display);
135 p = pane_register(display, 0, &tile_handle.c, ti);
140 ti->direction = Neither;
142 ti->group = strdup(ci->str);
144 ti->name = strdup(ci->str2);
145 INIT_LIST_HEAD(&ti->tiles);
146 attr_set_str(&p->attrs, "borders", "BL");
147 return comm_call(ci->comm2, "callback:attach", p);
150 static struct pane *tile_split(struct pane **pp safe, int horiz, int after,
153 /* Create a new pane near the given one, reducing its size,
154 * and possibly the size of other siblings.
155 * Return a new pane which is a sibling of the old, or NULL
156 * if there is no room for further splits.
157 * This may require creating a new parent and moving 'p' down
160 int space, new_space;
161 struct pane *p = safe_cast *pp;
163 struct tileinfo *ti = p->data;
164 struct tileinfo *ti2;
170 /* FIXME ask the leafs */
173 new_space = space / 2;
176 if (ti->direction != (horiz? Horiz : Vert)) {
177 /* This tile does not stack in the required direction, need
178 * to create an extra level.
180 struct pane *p2, *child;
182 /* ti2 will be tileinfo for p, new pane gets ti */
185 ti2->direction = ti->direction;
186 ti2->group = ti->group;
187 INIT_LIST_HEAD(&ti2->tiles);
190 p2 = pane_register(p, 0, &tile_handle.c, ti);
194 ti->direction = horiz ? Horiz : Vert;
195 /* All children of p must be moved to p2, except p2 */
196 list_for_each_entry_safe(child, t, &p->children, siblings)
198 pane_reparent(child, p2);
202 ti2->group = ti->group;
203 ti2->direction = ti->direction;
206 ti2->name = strdup(name);
207 /* FIXME if ti wasn't a leaf, this is wrong. Is that possible? */
209 list_add(&ti2->tiles, &ti->tiles);
211 list_add_tail(&ti2->tiles, &ti->tiles);
212 ret = pane_register(p->parent, 0, &tile_handle.c, ti2);
218 pane_move_after(ret, p);
219 else if (p == list_first_entry(&p->parent->children,
220 struct pane, siblings))
221 pane_move_after(ret, NULL);
223 pane_move_after(ret, list_prev_entry(p, siblings));
224 switch (!!horiz + 2 * !!after) {
225 case 0: /* vert before */
226 pane_resize(ret, p->x, p->y, p->w, new_space);
227 pane_resize(p, p->x, p->y + ret->h, p->w, space);
229 case 1: /* horiz before */
230 pane_resize(ret, p->x, p->y, new_space, p->h);
231 pane_resize(p, p->x + ret->w, p->y, space, p->h);
233 case 2: /* vert after */
234 pane_resize(ret, p->x, p->y + space, p->w, new_space);
235 pane_resize(p, p->x, p->y, p->w, space);
237 case 3: /* horiz after */
238 pane_resize(ret, p->x + space, p->y, new_space, p->h);
239 pane_resize(p, p->x, p->y, space, p->h);
248 static int tile_destroy(struct pane *p safe)
250 struct tileinfo *ti = p->data;
251 struct pane *prev = NULL, *next = NULL;
252 struct pane *t, *remain = NULL;
253 int pos, prevpos, nextpos;
256 if (ti->direction == Neither /* Root file a tile-set */
257 || p->parent == p /* subsumbed hust being destroyed */
258 || p->parent->handle != p->handle /* Some messed with parentage */
262 if (ti->direction == Vert)
266 prevpos = nextpos = -1;
267 list_for_each_entry(t, &p->parent->children, siblings) {
271 if (ti->direction == Vert)
275 if (pos2 < pos && (prev == NULL || prevpos < pos2))
277 if (pos2 > pos && (next == NULL || nextpos > pos2))
283 /* There is always a sibling of a non-root */
284 //ASSERT(remaining > 0);
285 if (prev == NULL /* FIXME redundant */ && next) {
286 /* next gets the space and focus*/
287 if (ti->direction == Horiz)
288 pane_resize(next, p->x, next->y,
289 p->w + next->w, next->h);
291 pane_resize(next, next->x, p->y,
292 next->w, p->h + next->h);
294 p->parent->focus = next;
295 } else if (next == NULL /* FIXME redundant */ && prev) {
296 /* prev gets the space and focus */
297 if (ti->direction == Horiz)
298 pane_resize(prev, prev->x, prev->y,
299 prev->w + p->w, prev->h);
301 pane_resize(prev, prev->x, prev->y,
302 prev->w, prev->h + p->h);
304 p->parent->focus = prev;
305 } else if (/*FIXME*/ next && prev) {
306 /* space to the smallest, else share the space */
307 /* Focus goes to prev, unless next is small */
308 p->parent->focus = prev;
309 if (ti->direction == Horiz) {
311 if (prev->w < next->w*2/3)
312 /* prev is much smaller, it gets all */
314 else if (next->w < prev->w*2/3) {
316 p->parent->focus = next;
318 pane_resize(prev, prev->x, prev->y, prev->w + w, prev->h);
320 pane_resize(next, prev->x + prev->w, next->y,
321 next->w + w, next->h);
324 if (prev->h < next->h*2/3)
325 /* prev is much smaller, it gets all */
327 else if (next->h < prev->h*2/3) {
329 p->parent->focus = next;
331 pane_resize(prev, prev->x, prev->y, prev->w, prev->h + h);
333 pane_resize(next, next->x, prev->y + prev->h,
334 next->w , next->h + h);
339 list_del(&ti->tiles);
340 if (remaining == 1 && remain && remain->parent != remain &&
341 remain->handle == p->handle) {
342 struct tileinfo *ti2;
344 /* Only one child left, must move it into parent.
345 * Cannot destroy the parent, so bring child into parent */
351 tmp = ti2->direction;
352 ti2->direction = ti->direction;
357 pane_subsume(remain, p);
362 static void tile_avail(struct pane *p safe, struct pane *ignore)
364 /* How much can we shrink this pane?
365 * If 'ignore' is set, it is a child of 'p', and we only
366 * consider other children.
367 * If stacking direction matches 'horiz' find sum of avail in children.
368 * if stacking direction doesn't match 'horiz', find minimum.
369 * If only one child, assume min of 4.
371 struct tileinfo *ti = p->data;
375 /* one or zero children */
376 if (ti->direction == Horiz) {
377 ti->avail_inline = p->w < 4 ? 0 : p->w - 4;
378 ti->avail_perp = p->h < 4 ? 0 : p->h - 4;
380 ti->avail_inline = p->h < 4 ? 0 : p->h - 4;
381 ti->avail_perp = p->w < 4 ? 0 : p->w - 4;
384 struct tileinfo *ti2;
385 int sum = 0, min = -1;
386 list_for_each_entry(t, &p->children, siblings) {
387 if (t == ignore || !mine(t))
391 if (min < 0 || min > ti2->avail_perp)
392 min = ti2->avail_perp;
393 sum += ti2->avail_inline;
395 ti->avail_perp = sum;
396 ti->avail_inline = min;
400 static void tile_adjust(struct pane *p safe)
402 /* Size of pane 'p' has changed and it is time to adjust
403 * the children, both their size and offset.
404 * For the non-stacking direction, offset must be zero and
405 * size is copied from p.
406 * For the stacking direction, we count used size, find the
407 * change required and distribute that. For shrinking, this
408 * relies on the fact that 'tile_avail' must have been run
409 * and it left avail space calculations around.
417 struct tileinfo *ti = p->data;
420 /* Children are responsible for themselves. */
423 list_for_each_entry(t, &p->children, siblings) {
428 if (ti->direction == Horiz) {
429 pane_resize(t, t->x, 0, t->w, p->h);
433 pane_resize(t, 0, t->y, p->w, t->h);
437 if (ti->avail_inline)
441 while (used < size || (used > size && avail_cnt)) {
443 int remain = used; /* size of panes still to be resized */
448 list_for_each_entry(t, &p->children, siblings) {
449 struct tileinfo *ti2 = t->data;
456 mysize = (ti2->direction == Horiz) ? t->w : t->h;
460 if (ti2->avail_inline == 0) {
464 diff = (((used - size) * mysize) +
465 (used%remain)) / remain;
466 if (diff > ti2->avail_inline)
467 diff = ti2->avail_inline;
468 ti2->avail_inline -= diff;
469 if (ti2->avail_inline)
470 /* Still space available if needed */
474 } else if (used == size)
477 diff = (((size - used) * mysize) +
478 (used%remain) )/ remain;
482 if (ti2->direction == Horiz)
483 pane_resize(t, t->x, t->y, t->w + diff, t->h);
485 pane_resize(t, t->x, t->y, t->w, t->h + diff);
494 list_for_each_entry(t, &p->children, siblings) {
495 struct tileinfo *ti2 = t->data;
498 if (ti2->direction == Horiz) {
499 pane_resize(t, pos, t->y, t->w, t->h);
502 pane_resize(t, t->x, pos, t->w, t->h);
509 static bool tile_grow(struct pane *p safe, int horiz, int size)
511 /* Try to grow the pane in given direction, or shrink if
513 * This is only done by shrinking other tiles, not by
514 * resizing the top level.
515 * If this pane isn't stacked in the right direction, or if
516 * neighbors are too small to shrink, the parent is resized.
517 * Then that propagates back down. Size of this pane is adjusted
518 * first to catch the propagations, then corrected after.
520 struct tileinfo *ti = p->data;
521 struct tileinfo *tip;
524 if (ti->direction == Neither)
525 /* Cannot grow/shrink the root */
528 /* Does this pane have room to shrink */
530 if (ti->direction == (horiz? Horiz : Vert))
531 avail = ti->avail_inline;
533 avail = ti->avail_perp;
537 if (ti->direction != (horiz ? Horiz : Vert)) {
538 /* need to ask parent to do this */
539 return tile_grow(p->parent, horiz, size);
542 /* OK, this stacks in the right direction. if shrinking we can commit */
544 struct pane *other = NULL;
547 list_for_each_entry(t, &p->parent->children, siblings) {
554 if (other && p_found)
559 /* Strange - there should have been two elements in list */
561 if (ti->direction == Horiz) {
562 pane_resize(p, p->x, p->y, p->w + size, p->h);
563 pane_resize(other, other->x, other->y,
564 other->w - size, other->h);
566 pane_resize(p, p->x, p->y, p->w, p->h + size);
567 pane_resize(other, other->x, other->y,
568 other->w, other->h - size);
570 tile_adjust(p->parent);
574 /* Hoping to grow if there is room for others to shrink */
575 tile_avail(p->parent, p);
576 tip = p->parent->data;
577 if (ti->direction == (horiz ? Horiz : Vert))
578 avail = tip->avail_inline;
580 avail = tip->avail_perp;
583 if (ti->direction == Horiz)
584 pane_resize(p, p->x, p->y, p->w + size, p->h);
586 pane_resize(p, p->x, p->y, p->w, p->h + size);
588 ti->avail_inline = 0; /* make sure this one doesn't suffer */
589 tile_adjust(p->parent);
593 static struct pane *next_child(struct pane *parent, struct pane *prev, bool popup)
596 list_for_each_entry(p2, &parent->children, siblings) {
603 if (mine(p2) == popup)
610 static struct tileinfo *tile_first(struct tileinfo *ti safe)
613 struct pane *p = next_child(ti->p, NULL, 0);
621 static bool tile_is_first(struct tileinfo *ti safe)
623 while (ti->direction != Neither) {
624 if (ti->p != next_child(ti->p->parent, NULL, 0))
626 ti = ti->p->parent->data;
631 static struct pane *tile_root_popup(struct tileinfo *ti safe)
633 while (ti->direction != Neither)
634 ti = ti->p->parent->data;
635 return next_child(ti->p, NULL, 1);
638 static struct tileinfo *safe tile_next_named(struct tileinfo *ti safe,
641 struct tileinfo *t = ti;
642 while ((t = list_next_entry(t, tiles)) != ti) {
645 if (!t->name || strcmp(t->name, name) != 0)
652 static bool wrong_pane(struct cmd_info const *ci safe)
654 struct tileinfo *ti = ci->home->data;
656 if (ci->str || ti->group) {
657 if (!ci->str || !ti->group)
659 if (strcmp(ci->str, ti->group) != 0)
661 /* same group - continue */
666 DEF_CMD(tile_window_next)
668 /* If currently on a popup, go to next popup if there is one, else
670 * If was not on a pop-up, go to next tile and if there is a popup,
673 struct pane *p = ci->home;
675 struct tileinfo *ti = p->data;
680 if (p->focus && p->focus->z) {
681 p2 = next_child(p, p->focus, 1);
685 } else if (ti->leaf) {
686 pane_take_focus(ti->content);
692 t2 = tile_next_named(ti, ci->str2);
693 if (tile_is_first(t2) &&
694 (p2 = tile_root_popup(t2)) != NULL) {
702 pane_take_focus(t2->p);
703 p2 = next_child(t2->p, NULL, 1);
710 DEF_CMD(tile_window_prev)
712 struct pane *p = ci->home;
713 struct tileinfo *ti = p->data;
718 t2 = list_prev_entry(ti, tiles);
719 pane_take_focus(t2->p);
723 DEF_CMD(tile_window_xplus)
725 struct pane *p = ci->home;
729 tile_grow(p, 1, RPT_NUM(ci));
733 DEF_CMD(tile_window_xminus)
735 struct pane *p = ci->home;
739 tile_grow(p, 1, -RPT_NUM(ci));
742 DEF_CMD(tile_window_yplus)
744 struct pane *p = ci->home;
748 tile_grow(p, 0, RPT_NUM(ci));
751 DEF_CMD(tile_window_yminus)
753 struct pane *p = ci->home;
757 tile_grow(p, 0, -RPT_NUM(ci));
761 DEF_CMD(tile_window_splitx)
763 struct pane *p = ci->home;
768 p2 = tile_split(&p, 1, 1, ci->str2);
769 pane_clone_children(p, p2);
773 DEF_CMD(tile_window_splity)
775 struct pane *p = ci->home;
780 p2 = tile_split(&p, 0, 1, ci->str2);
781 pane_clone_children(p, p2);
785 DEF_CMD(tile_window_close)
787 struct pane *p = ci->home;
788 struct tileinfo *ti = p->data;
792 if (ti->direction != Neither)
797 DEF_CMD(tile_window_bury)
799 /* Bury the document in this tile.
800 * Find some other document to display
807 /* First, push the doc to the end of the 'recently used' list */
808 call("doc:notify:doc:revisit", ci->focus, -1);
809 /* Now choose a replacement */
810 doc = call_ret(pane, "docs:choose", ci->home);
812 /* display that doc in this pane */
813 home_call(doc, "doc:attach-view", ci->home);
817 DEF_CMD(tile_window_close_others)
819 struct pane *p = ci->home;
820 struct pane *parent = p->parent;
821 struct tileinfo *ti = p->data;
826 /* close sibling panes until ->parent changes, or there aren't any */
827 while (found && p->parent == parent) {
831 list_for_each_entry(s, &parent->children, siblings)
838 return ti->direction != Neither ? 1 : Efalse;
843 /* Choose some other tile. If there aren't any, make one.
844 * Result is returned in ci->focus
846 * 1: if split is need, use 2 to determine direction, else default
847 * 2: if split needed, split horizontally, else vertically
848 * 4: if split needed use 8 to determine which is new, else default
849 * 8: if split is needed, new pane is to the right/down.
850 * 512: don't split, just return Efalse
852 struct pane *p = ci->home;
854 struct tileinfo *ti = p->data;
855 struct tileinfo *ti2;
858 if (ci->str || ti->group) {
859 if (!ci->str || !ti->group)
861 if (strcmp(ci->str, ti->group) != 0)
863 /* same group - continue */
866 /* probably coming from a pop-up. Just use first tile */
867 ti2 = tile_first(ti);
870 if (ci->str2 && ti2->name && strcmp(ci->str2, ti2->name) == 0)
872 return comm_call(ci->comm2, "callback:pane", ti2->p);
874 if (ci->str2 && ti->name && strcmp(ci->str2, ti->name) == 0)
877 ti2 = tile_next_named(ti, ci->str2);
879 return comm_call(ci->comm2, "callback:pane", ti2->p);
881 /* Need to create a tile. If wider than 120 (FIXME configurable?),
882 * horiz-split else vert
890 struct xy xy = pane_scale(p);
891 horiz = p->w * 1000 >= 1200 * xy.x;
897 p2 = tile_split(&p, horiz, after, ci->str2);
899 return comm_call(ci->comm2, "callback:pane", p2);
905 struct tileinfo *ti = ci->home->data;
907 if (ci->str || ti->group) {
908 if (!ci->str || !ti->group)
910 if (strcmp(ci->str, ti->group) != 0)
912 /* same group - continue */
915 /* There is no clear 'This', use first. */
919 if (ci->str2 && ti->name && strcmp(ci->str2, ti->name) == 0)
921 return comm_call(ci->comm2, "callback:pane", ti->p);
923 return comm_call(ci->comm2, "callback:pane", ci->home, 0,
929 /* Find the pane displaying given document, preferrably not
932 struct tileinfo *ti = ci->home->data;
936 if (ci->str || ti->group) {
937 if (!ci->str || !ti->group)
939 if (strcmp(ci->str, ti->group) != 0)
941 /* same group - continue */
943 /* Find where 'focus' is open */
944 name = pane_attr_get(ci->focus, "doc-name");
953 t = list_next_entry(t, tiles);
957 n = pane_attr_get(f, "doc-name");
958 if (n && strcmp(n, name) == 0)
959 return comm_call(ci->comm2, "callback:pane",
970 struct pane *p = ci->home;
971 struct tileinfo *ti = p->data;
973 if (ti->direction != Neither)
975 if (ci->str || ti->group) {
976 if (!ci->str || !ti->group)
978 if (strcmp(ci->str, ti->group) != 0)
980 /* same group - continue */
983 return comm_call(ci->comm2, "callback:pane", p);
986 DEF_CMD(tile_child_notify)
988 struct pane *p = ci->home;
989 struct tileinfo *ti = p->data;
990 struct pane *c = ci->focus;
994 if (ci->num > 0 && mine(c))
995 /* always accept my own children */
1000 /* Sorry, new children not permitted */
1007 /* Child closed, but we weren't, so find something else to display */
1009 c = call_ret(pane, "docs:choose", p);
1011 home_call(c, "doc:attach-view", p);
1012 else if (ti->direction != Neither)
1016 /* New pane, discard the old */
1020 pane_close(ti->content);
1026 /* Child moved away - hopefully to be replaced */
1030 /* Simple replacement */
1037 void edlib_init(struct pane *ed safe)
1039 tile_map = key_alloc();
1041 key_add(tile_map, "Window:next", &tile_window_next);
1042 key_add(tile_map, "Window:prev", &tile_window_prev);
1043 key_add(tile_map, "Window:x+", &tile_window_xplus);
1044 key_add(tile_map, "Window:x-", &tile_window_xminus);
1045 key_add(tile_map, "Window:y+", &tile_window_yplus);
1046 key_add(tile_map, "Window:y-", &tile_window_yminus);
1047 key_add(tile_map, "Window:split-x", &tile_window_splitx);
1048 key_add(tile_map, "Window:split-y", &tile_window_splity);
1049 key_add(tile_map, "Window:close", &tile_window_close);
1050 key_add(tile_map, "Window:close-others", &tile_window_close_others);
1051 key_add(tile_map, "Window:bury", &tile_window_bury);
1053 key_add(tile_map, "OtherPane", &tile_other);
1054 key_add(tile_map, "ThisPane", &tile_this);
1055 key_add(tile_map, "DocPane", &tile_doc);
1056 key_add(tile_map, "RootPane", &tile_root);
1057 key_add(tile_map, "Clone", &tile_clone);
1058 key_add(tile_map, "Child-Notify", &tile_child_notify);
1059 key_add(tile_map, "Close", &tile_close);
1060 key_add(tile_map, "Refresh:size", &tile_refresh_size);
1062 call_comm("global-set-command", ed, &tile_attach, 0, NULL, "attach-tile");