home · contact · privacy
Overhauled large parts of window system to universalize scroll hints.
[plomrogue] / src / windows.c
1 /* windows.c */
2
3 #include "windows.h"
4 #include <stdint.h>    /* for uint8_t, uint16_t, uint32_t, UINT16_MAX */
5 #include <ncurses.h>   /* for typedefs WINDOW, chtype, wresize(), getmaxx(), */
6                        /* getmaxy(), delwin(), mvwaddch(), mvwaddstr(),      */
7                        /* newpad(), wnoutrefres(), erase(), werase(),        */
8                        /* pnoutrefresh(), doupdate(), getmaxyx()   */
9 #include <stdlib.h>    /* for malloc(), free() */
10 #include <string.h>    /* for strlen(), strnlen(), memcpy() */
11 #include "yx_uint16.h" /* for struct yx_uint16 */
12 #include "misc.h"      /* for center_offset() */
13
14
15
16 /* Fit virtual screen's width to minimum width demanded by current windows'
17  * geometries.
18  */
19 static uint8_t refit_pad(struct WinMeta * wmeta);
20
21
22
23 /* Update geometry (sizes, positions) of window "w" and its successors in the
24  * window chain. For the positioning algorithm place_win() is used.
25  */
26 static uint8_t update_wins(struct WinMeta * wmeta, struct Win * w);
27 static void place_win(struct WinMeta * wmeta, struct Win * w);
28
29
30
31 /* Draw scroll hint (a line stating that there are "dist" more elements of
32  * "unit" further into the direction symbolized by the "dir" char) into virtual
33  * screen pad, onto an appropriate edge of "frame": the left or right edge if
34  * "dir" is "<" or ">", or the upper or lower edge if it is "^" or "v". "start"
35  * should be either the start coordinate of "frame" if it describes a window or
36  * .y=, .x=wm->pad_offset if it describes the virtual screen. winscroll_hint()
37  * and padscroll_hint() are wrappers to simplify the use of scroll_hint().
38  */
39 static void scroll_hint(struct WinMeta * wm, struct Frame * frame, char dir,
40                         uint16_t dist, char * unit, struct yx_uint16 start);
41 static void winscroll_hint(struct WinMeta * wm, struct Win * w, char dir,
42                            uint16_t dist);
43 static void padscroll_hint(struct WinMeta * wm, char dir, uint16_t dist);
44
45
46
47 /* Draw contents of all windows in window chain from window "w" onwards. */
48 static uint8_t draw_wins(struct WinMeta * wm, struct Win * w);
49
50
51
52 /* draw_win_borderlines() draws the vertical and horizontal borders of window
53  * "w" sans corners into the virtual screen "pad", and draws the top border
54  * line as the windows' title bar (highlighted if the window is described
55  * active by "active" being == 1).
56  *
57  * draw_wins_borderlines() calls draw_win_borderlines() recursively on all
58  * windows from "w" on. "w_active" is a pointer to the one window that
59  * draw_win_borderlines() is supposed to handle as the active window.
60  *
61  * Finally, draw_wins_bordercorners() draws into "pad" the borders of window "w"
62  * and all its successors.
63  */
64 static void draw_win_borderlines(struct Win * w, char active, WINDOW * pad);
65 static void draw_wins_borderlines(struct Win * w, struct Win * w_active,
66                                   WINDOW * pad);
67 static void draw_wins_bordercorners(struct Win * w, WINDOW * pad);
68
69
70
71 /* Shift active window forwards / backwards in window chain. */
72 static void shift_win_forward(struct WinMeta * wmeta);
73 static void shift_win_backward(struct WinMeta * wmeta);
74
75
76
77 static uint8_t refit_pad(struct WinMeta * wmeta)
78 {
79     /* Determine rightmost window column. */
80     uint32_t lastwincol = 0;
81     struct Win * w_p = wmeta->chain_start;
82     while (w_p != 0)
83     {
84         if ((uint32_t) w_p->start.x + (uint32_t) w_p->frame.size.x
85             > lastwincol + 1)
86         {
87             lastwincol = (uint32_t) w_p->start.x
88                          + (uint32_t) w_p->frame.size.x - 1;
89         }
90         w_p = w_p->next;
91     }
92
93     /* Only resize the pad if the rightmost window column has changed. */
94     if (getmaxx(wmeta->padframe.curses_win) + 1 != lastwincol)
95     {
96         if (lastwincol + 2 > UINT16_MAX)
97         {
98             return 2;
99         }
100         return (ERR == wresize(wmeta->padframe.curses_win,
101                                getmaxy(wmeta->padframe.curses_win),
102                                lastwincol + 2));
103     }
104     return 0;
105 }
106
107
108
109 static uint8_t update_wins(struct WinMeta * wmeta, struct Win * w)
110 {
111     place_win(wmeta, w);
112     uint8_t test_refit = refit_pad(wmeta);
113     if (0 != test_refit)
114     {
115         return test_refit;
116     }
117     if (0 != w->next)
118     {
119         return update_wins(wmeta, w->next);
120     }
121     return 0;
122 }
123
124
125
126 static void place_win(struct WinMeta * wmeta, struct Win * w)
127 {
128     /* First window goes into the upper-left corner. */
129     w->start.x = 0;
130     w->start.y = 1;                             /* Leave space for title bar. */
131     if (0 != w->prev)
132     {
133
134         /* Non-first window fallbacks to: fit rightwards of rightmost border. */
135         struct Win * w_top = w->prev;
136         while (w_top->start.y != 1)
137         {
138             w_top = w_top->prev;
139         }
140         w->start.x = w_top->start.x + w_top->frame.size.x + 1;
141
142         /* Fit window below its predecessor if that one directly thrones over
143          * empty space wide and high enough.
144          */
145         uint16_t w_prev_maxy = w->prev->start.y + w->prev->frame.size.y;
146         if (   w->frame.size.x <= w->prev->frame.size.x
147             && w->frame.size.y <  wmeta->padframe.size.y - w_prev_maxy)
148         {
149             w->start.x = w->prev->start.x;
150             w->start.y = w_prev_maxy + 1;
151         }
152
153         /* Failing that, try to open a new sub column below the nearest
154          * predecessor window that thrones over enough empty space.
155          */
156         else
157         {
158             struct Win * w_up = w->prev;
159             struct Win * w_upup = w_up;
160             uint16_t widthdiff;
161             while (w_up != w_top)
162             {
163                 w_upup = w_up->prev;
164                 while (1)
165                 {
166                     if (w_up->start.y != w_upup->start.y)
167                     {
168                         break;
169                     }
170                     w_upup = w_upup->prev;
171                 }
172                 w_prev_maxy = w_upup->start.y + w_upup->frame.size.y;
173                 widthdiff = (w_upup->start.x + w_upup->frame.size.x)
174                             - (w_up->start.x + w_up->frame.size.x);
175                 if (   w->frame.size.y < wmeta->padframe.size.y - w_prev_maxy
176                     && w->frame.size.x < widthdiff)
177                 {
178                     w->start.x = w_up->start.x + w_up->frame.size.x + 1 ;
179                     w->start.y = w_prev_maxy + 1;
180                     break;
181                 }
182                 w_up = w_upup;
183             }
184         }
185     }
186 }
187
188
189
190 static void scroll_hint(struct WinMeta * wm, struct Frame * frame, char dir,
191                         uint16_t dist, char * unit, struct yx_uint16 start)
192 {
193     /* Decide on alignment (vertical/horizontal?), thereby hint text space. */
194     char * more = "more";
195     uint16_t dsc_space = frame->size.x;
196     if ('<' == dir || '>' == dir)
197     {
198         dsc_space = frame->size.y;
199     }                                  /* vv-- 10 = max strlen for uint16_t */
200     char scrolldsc[1 + strlen(more) + 1 + 10 + 1 + strlen(unit) + 1 + 1];
201     sprintf(scrolldsc, " %d %s %s ", dist, more, unit);
202
203     /* Decide on offset of the description text inside the scroll hint line. */
204     uint16_t dsc_offset = 1;
205     if (dsc_space > strlen(scrolldsc) + 1)
206     {
207         dsc_offset = (dsc_space - strlen(scrolldsc)) / 2;
208     }
209
210     /* Draw scroll hint line as dir symbols bracketing description text. */
211     uint16_t draw_offset = 0;
212     if      ('>' == dir)
213     {
214         draw_offset = frame->size.x - 1;
215     }
216     else if ('v' == dir)
217     {
218         draw_offset = frame->size.y - 1;
219     }
220     uint16_t q = 0;
221     for (; q < dsc_space; q++)
222     {
223         chtype symbol = dir | A_REVERSE;
224         if (q >= dsc_offset && q < strlen(scrolldsc) + dsc_offset)
225         {
226             symbol = scrolldsc[q - dsc_offset] | A_REVERSE;
227         }
228         if ('<' == dir || '>' == dir)
229         {
230             mvwaddch(wm->padframe.curses_win,
231                      start.y + q, start.x + draw_offset, symbol);
232         }
233         else
234         {
235             mvwaddch(wm->padframe.curses_win,
236                      start.y + draw_offset, start.x + q, symbol);
237         }
238     }
239 }
240
241
242 static void padscroll_hint(struct WinMeta * wm, char dir, uint16_t dist)
243 {
244     struct yx_uint16 start;
245     start.y = 0;
246     start.x = wm->pad_offset;
247     scroll_hint(wm, &wm->padframe, dir, dist, "columns", start);
248 }
249
250
251
252 static void winscroll_hint(struct WinMeta * wm, struct Win * w, char dir,
253                            uint16_t dist)
254 {
255     char * unit = "lines";
256     if ('<' == dir || '>' == dir)
257     {
258         unit = "columns";
259     }
260     struct yx_uint16 start = w->start;
261     scroll_hint(wm, &w->frame, dir, dist, unit, start);
262 }
263
264
265
266 static uint8_t draw_wins(struct WinMeta * wm, struct Win * w)
267 {
268     if (ERR == wresize(w->frame.curses_win, 1, 1))
269     {
270         return 1;
271     }
272     w->draw(w);
273     uint16_t y, x, size_y, size_x;
274     getmaxyx(w->frame.curses_win, size_y, size_x);
275     uint16_t offset_y = center_offset(w->center.y, size_y, w->frame.size.y);
276     uint16_t offset_x = center_offset(w->center.x, size_x, w->frame.size.x);
277     for (y = offset_y; y < w->frame.size.y + offset_y && y < size_y; y++)
278     {
279         for (x = offset_x; x < w->frame.size.x + offset_x && x < size_x; x++)
280         {
281             chtype ch = mvwinch(w->frame.curses_win, y, x);
282             mvwaddch(wm->padframe.curses_win, w->start.y + (y - offset_y),
283                                               w->start.x + (x - offset_x), ch);
284         }
285     }
286     if (offset_y > 0)
287     {
288         winscroll_hint(wm, w, '^', offset_y + 1);
289     }
290     if (size_y > offset_y + w->frame.size.y)
291     {
292         winscroll_hint(wm, w, 'v', size_y - ((offset_y + w->frame.size.y) - 1));
293     }
294     if (offset_x > 0)
295     {
296         winscroll_hint(wm, w, '<', offset_x + 1);
297     }
298     if (size_x > offset_x + w->frame.size.x)
299     {
300         winscroll_hint(wm, w, '>', size_x - ((offset_x + w->frame.size.x) - 1));
301     }
302     if (0 != w->next)
303     {
304         return draw_wins(wm, w->next);
305     }
306     return 0;
307 }
308
309
310
311 static void draw_win_borderlines(struct Win * w, char active, WINDOW * pad)
312 {
313     /* Draw vertical and horizontal border lines. */
314     uint16_t y, x;
315     for (y = w->start.y; y <= w->start.y + w->frame.size.y; y++)
316     {
317         mvwaddch(pad, y, w->start.x - 1,               '|');
318         mvwaddch(pad, y, w->start.x + w->frame.size.x, '|');
319     }
320     for (x = w->start.x; x <= w->start.x + w->frame.size.x; x++)
321     {
322         mvwaddch(pad, w->start.y - 1,               x, '-');
323         mvwaddch(pad, w->start.y + w->frame.size.y, x, '-');
324     }
325
326     /* Draw as much as possible of the title into center of top border line. */
327     char min_title_length_visible = 3;  /* min. 1 char + 2 padding/decoration */
328     if (w->frame.size.x >= min_title_length_visible)
329     {
330         uint16_t title_offset = 0;
331         if (w->frame.size.x > strlen(w->title) + 2)
332         {
333             title_offset = (w->frame.size.x - (strlen(w->title) + 2)) / 2;
334         }                                    /* +2 is for padding/decoration */
335         uint16_t length_visible = strnlen(w->title, w->frame.size.x - 2);
336         char title[length_visible + 3];
337         char decoration = ' ';
338         if (1 == active)
339         {
340             decoration = '$';
341         }
342         memcpy(title + 1, w->title, length_visible);
343         title[0] = title[length_visible + 1] = decoration;
344         title[length_visible + 2] = '\0';
345         mvwaddstr(pad, w->start.y - 1, w->start.x + title_offset, title);
346     }
347 }
348
349
350
351 static void draw_wins_borderlines(struct Win * w, struct Win * w_active,
352                                   WINDOW * pad)
353 {
354     char active = 0;
355     if (w == w_active)
356     {
357         active = 1;
358     }
359     draw_win_borderlines(w, active, pad);
360     if (0 != w->next)
361     {
362         draw_wins_borderlines(w->next, w_active, pad);
363     }
364 }
365
366
367
368 static void draw_wins_bordercorners(struct Win * w, WINDOW * pad)
369 {
370     mvwaddch(pad, w->start.y - 1, w->start.x - 1, '+');
371     mvwaddch(pad, w->start.y - 1, w->start.x + w->frame.size.x, '+');
372     mvwaddch(pad, w->start.y + w->frame.size.y, w->start.x - 1, '+');
373     mvwaddch(pad,
374              w->start.y + w->frame.size.y, w->start.x + w->frame.size.x, '+');
375     if (0 != w->next)
376     {
377         draw_wins_bordercorners(w->next, pad);
378     }
379 }
380
381
382
383 static void shift_win_forward(struct WinMeta * wmeta)
384 {
385     if (wmeta->active == wmeta->chain_end)
386     {
387         wmeta->chain_end = wmeta->active->prev;
388         wmeta->chain_end->next = 0;
389         wmeta->active->next = wmeta->chain_start;
390         wmeta->active->next->prev = wmeta->active;
391         wmeta->chain_start = wmeta->active;
392         wmeta->chain_start->prev = 0;
393     }
394     else
395     {
396         struct Win * old_prev = wmeta->active->prev;
397         struct Win * old_next = wmeta->active->next;
398         if (wmeta->chain_end == wmeta->active->next)
399         {
400             wmeta->chain_end = wmeta->active;
401             wmeta->active->next = 0;
402         }
403         else
404         {
405             wmeta->active->next = old_next->next;
406             wmeta->active->next->prev = wmeta->active;
407         }
408         if (wmeta->chain_start == wmeta->active)
409         {
410             wmeta->chain_start = old_next;
411         }
412         else
413         {
414             old_prev->next = old_next;
415         }
416         old_next->prev = old_prev;
417         old_next->next = wmeta->active;
418         wmeta->active->prev = old_next;
419     }
420 }
421
422
423
424 static void shift_win_backward(struct WinMeta * wmeta)
425 {
426     if (wmeta->active == wmeta->chain_start)
427     {
428         wmeta->chain_start = wmeta->active->next;
429         wmeta->chain_start->prev = 0;
430         wmeta->active->prev = wmeta->chain_end;
431         wmeta->active->prev->next = wmeta->active;
432         wmeta->chain_end = wmeta->active;
433         wmeta->chain_end->next = 0;
434     }
435     else
436     {
437         struct Win * old_prev = wmeta->active->prev;
438         struct Win * old_next = wmeta->active->next;
439         if (wmeta->chain_start == wmeta->active->prev)
440         {
441             wmeta->chain_start = wmeta->active;
442             wmeta->active->prev = 0;
443         }
444         else
445         {
446             wmeta->active->prev = old_prev->prev;
447             wmeta->active->prev->next = wmeta->active;
448         }
449         if (wmeta->chain_end == wmeta->active)
450         {
451             wmeta->chain_end = old_prev;
452         }
453         else
454         {
455             old_next->prev = old_prev;
456         }
457         old_prev->next = old_next;
458         old_prev->prev = wmeta->active;
459         wmeta->active->next = old_prev;
460     }
461 }
462
463
464
465 extern uint8_t init_win_meta(WINDOW * screen, struct WinMeta ** wmp)
466 {
467     struct WinMeta * wmeta = malloc(sizeof(struct WinMeta));
468     wmeta->screen              = screen;
469     uint32_t maxy_test         = getmaxy(screen);
470     uint32_t maxx_test         = getmaxx(screen);
471     if (maxy_test > UINT16_MAX || maxx_test > UINT16_MAX)
472     {
473         return 2;
474     }
475     wmeta->padframe.size.y     = maxy_test;
476     wmeta->padframe.size.x     = maxx_test;
477     wmeta->chain_start         = 0;
478     wmeta->chain_end           = 0;
479     wmeta->pad_offset          = 0;
480     WINDOW * pad_test          = newpad(wmeta->padframe.size.y, 1);
481     if (NULL == pad_test)
482     {
483         return 1;
484     }
485     wmeta->padframe.curses_win = pad_test;
486     wmeta->active              = 0;
487     *wmp = wmeta;
488     return 0;
489 }
490
491
492
493 extern uint8_t init_win(struct WinMeta * wmeta, struct Win ** wp, char * title,
494                         int16_t height, int16_t width,
495                         void * data, void * func)
496 {
497     struct Win * w = malloc(sizeof(struct Win));
498     if (NULL == w)
499     {
500         return 1;
501     }
502     w->prev             = 0;
503     w->next             = 0;
504     w->frame.curses_win = newpad(1, 1);
505     if (NULL == w->frame.curses_win)
506     {
507         return 1;
508     }
509     w->title            = malloc(strlen(title) + 1);
510     if (NULL == w->title)
511     {
512         return 1;
513     }
514     sprintf(w->title, "%s", title);
515     w->data             = data;
516     w->draw             = func;
517     w->center.y         = 0;
518     w->center.x         = 0;
519     if      (0 < width)
520     {
521         w->frame.size.x = width;
522     }
523     else if (0 >= width)
524     {
525         w->frame.size.x = wmeta->padframe.size.x + width;
526     }
527     if      (0 < height && height <= wmeta->padframe.size.y - 1)
528     {
529         w->frame.size.y = height;
530     }
531     else if (0 >= height && wmeta->padframe.size.y + (height - 1) > 0)
532     {
533         w->frame.size.y = wmeta->padframe.size.y + (height - 1);
534     }
535     *wp = w;
536     return 0;
537 }
538
539
540
541 extern void free_winmeta(struct WinMeta * wmeta)
542 {
543     delwin(wmeta->padframe.curses_win);
544     free(wmeta);
545 }
546
547
548
549 extern void free_win(struct Win * win)
550 {
551     delwin(win->frame.curses_win);
552     free(win->title);
553     free(win);
554 }
555
556
557
558 extern uint8_t append_win(struct WinMeta * wmeta, struct Win * w)
559 {
560     if (0 != wmeta->chain_start)
561     {
562         w->prev = wmeta->chain_end;
563         wmeta->chain_end->next = w;
564     }
565     else
566     {
567         wmeta->active = w;
568         wmeta->chain_start = w;
569     }
570     wmeta->chain_end = w;
571     return update_wins(wmeta, w);
572 }
573
574
575
576 extern uint8_t suspend_win(struct WinMeta * wmeta, struct Win * w)
577 {
578     if (wmeta->chain_start != w)
579     {
580         w->prev->next = w->next;
581     }
582     else
583     {
584         wmeta->chain_start = w->next;
585     }
586     char pad_refitted = 0;
587     if (wmeta->chain_end != w)
588     {
589         w->next->prev = w->prev;
590         if (wmeta->active == w)
591         {
592             wmeta->active = w->next;
593         }
594         uint8_t test = update_wins(wmeta, w->next); /* Positioning of         */
595         if (0 != test)                              /* successor windows may  */
596         {                                           /* be affected / need     */
597             return test;                            /* correction. Note that  */
598         }                                           /* update_wins() already  */
599         pad_refitted = 1;                           /* refits the pad, voiding*/
600     }                                               /* later need for that.   */
601     else
602     {
603         wmeta->chain_end = w->prev;
604         if (wmeta->active == w)
605         {
606             wmeta->active = w->prev;
607         }
608     }
609
610     w->prev = 0;
611     w->next = 0;
612
613     if (0 == pad_refitted)
614     {
615         return refit_pad(wmeta);
616     }
617     return 0;
618 }
619
620
621
622 extern void reset_pad_offset(struct WinMeta * wmeta, uint16_t new_offset)
623 {
624     if (new_offset >= 0
625         && (new_offset < wmeta->pad_offset
626             || new_offset + wmeta->padframe.size.x
627                < getmaxx(wmeta->padframe.curses_win)))
628     {
629         wmeta->pad_offset = new_offset;
630     }
631 }
632
633
634
635 extern uint8_t resize_active_win(struct WinMeta * wmeta, struct yx_uint16 size)
636 {
637     if (0 != wmeta->active
638         && size.x > 0 && size.y > 0
639         && size.y < wmeta->padframe.size.y)
640     {
641         wmeta->active->frame.size = size;
642         return update_wins(wmeta, wmeta->active); /* Positioning of following */
643     }                                             /* windows may be affected. */
644     return 0;
645 }
646
647
648
649 extern void cycle_active_win(struct WinMeta * wmeta, char dir)
650 {
651     if (0 != wmeta->active)
652     {
653         if ('f' == dir)
654         {
655             if (wmeta->active->next != 0)
656             {
657                 wmeta->active = wmeta->active->next;
658             }
659             else
660             {
661                 wmeta->active = wmeta->chain_start;
662             }
663         }
664         else
665         {
666             if (wmeta->active->prev != 0)
667             {
668                 wmeta->active = wmeta->active->prev;
669             }
670             else
671             {
672                 wmeta->active = wmeta->chain_end;
673             }
674         }
675     }
676 }
677
678
679
680 extern uint8_t shift_active_win(struct WinMeta * wmeta, char dir)
681 {
682     if (   0 == wmeta->active                        /* No shifting with < 2  */
683         || wmeta->chain_start == wmeta->chain_end)   /* windows visible.      */
684     {
685         return 0;
686     }
687     if ('f' == dir)
688     {
689         shift_win_forward(wmeta);
690     }
691     else
692     {
693         shift_win_backward(wmeta);
694     }
695     return update_wins(wmeta, wmeta->chain_start);
696 }
697
698
699
700 extern uint8_t draw_all_wins(struct WinMeta * wm)
701 {
702     /* Empty everything before filling it a-new. */
703     erase();
704     wnoutrefresh(wm->screen);
705     werase(wm->padframe.curses_win);
706     if (wm->chain_start)
707     {
708
709         /* Draw windows' borders first, then windows. */
710         draw_wins_borderlines(wm->chain_start, wm->active,
711                               wm->padframe.curses_win);
712         draw_wins_bordercorners(wm->chain_start, wm->padframe.curses_win);
713
714         if (1 == draw_wins(wm, wm->chain_start))
715         {
716             return 1;
717         }
718
719         /* Draw virtual screen scroll hints. */
720         if (wm->pad_offset > 0)
721         {
722             padscroll_hint(wm, '<', wm->pad_offset + 1);
723         }
724         uint16_t size_x = getmaxx(wm->padframe.curses_win);
725         uint16_t right_edge = wm->pad_offset + wm->padframe.size.x;
726         if (right_edge < size_x - 1)
727         {
728             padscroll_hint(wm, '>', size_x - right_edge);
729         }
730
731         /* Write pad segment to be shown on physical screen to screen buffer. */
732         pnoutrefresh(wm->padframe.curses_win, 0, wm->pad_offset, 0, 0,
733                      wm->padframe.size.y, wm->padframe.size.x - 1);
734     }
735
736     /* Only at the end write accumulated changes to the physical screen. */
737     doupdate();
738     return 0;
739 }