home · contact · privacy
Radically simplified drawing of window border corners.
[plomrogue] / src / windows.c
1 /* windows.c */
2
3 #include "windows.h"
4 #include <stdint.h>    /* for uint16_t, uint32_t */
5 #include <ncurses.h>   /* for LOTS of stuff */
6 #include <stdlib.h>    /* for malloc(), free() */
7 #include <string.h>    /* for strlen(), memcpy() */
8 #include "yx_uint16.h" /* for yx_uint16 coordinates */
9
10
11
12 /* Fit virtual screen's width to minimum width demanded by current windows'
13  * geometries.
14  */
15 static void refit_pad(struct WinMeta * wmeta);
16
17
18
19 /* Update geometry (sizes, positions) of window "w" and its successors in the
20  * window chain. For the positioning algorithm place_win() is used.
21  */
22 static void update_wins(struct WinMeta * wmeta, struct Win * w);
23 static void place_win(struct WinMeta * wmeta, struct Win * w);
24
25
26
27 /* Destroy window "w"'s ncurses window (and set w.Frame.curses_win to 0). */
28 static void destroy_win(struct Win * w);
29
30
31
32 /* Draw contents of all windows in window chain from window "w" onwards. */
33 static void draw_wins(struct Win * w);
34
35
36
37 /* draw_win_borderlines() draws the vertical and horizontal borders of window
38  * "w" sans corners, and draws the top border line as the windows' title bar
39  * (highlighted if the window is described active by "active" being set).
40  * draw_wins_borderlines().
41  *
42  * draw_wins_borderlines() calls draw_win_borderlines() recursively on all
43  * windows from "w" on. "w_active" is a pointer to the one window that
44  * draw_win_borderlines() is supposed to handle as the active window.
45  *
46  * Finally, draw_wins_bordercorners draws into "pad" the borders of window "w"
47  * and all its successors.
48  */
49 static void draw_win_borderlines(struct Win * w, char active);
50 static void draw_wins_borderlines(struct Win * w, struct Win * w_active);
51 static void draw_wins_bordercorners(struct Win * w, WINDOW * pad);
52
53
54
55 static void refit_pad(struct WinMeta * wmeta)
56 {
57     /* Determine rightmost window column. */
58     uint16_t lastwincol = 0;
59     struct Win * w_p = wmeta->chain_start;
60     while (w_p != 0)
61     {
62         if (w_p->start.x + w_p->frame.size.x > lastwincol + 1)
63         {
64             lastwincol = w_p->start.x + w_p->frame.size.x - 1;
65         }
66         w_p = w_p->next;
67     }
68
69     /* Only resize the pad if the rightmost window column has changed. */
70     if (getmaxx(wmeta->pad.curses_win) != lastwincol)
71     {
72         wresize(wmeta->pad.curses_win,
73                 getmaxy(wmeta->pad.curses_win), lastwincol + 2);
74     }
75 }
76
77
78
79 static void update_wins (struct WinMeta * wmeta, struct Win * w)
80 {
81     if (0 != w->frame.curses_win)
82     {
83         destroy_win (w);
84     }
85     place_win(wmeta, w);
86     refit_pad(wmeta);
87     w->frame.curses_win = subpad(wmeta->pad.curses_win,
88                                  w->frame.size.y, w->frame.size.x,
89                                  w->start.y, w->start.x);
90     if (0 != w->next)
91     {
92         update_wins (wmeta, w->next);
93     }
94 }
95
96
97
98 static void place_win (struct WinMeta * wmeta, struct Win * w)
99 {
100     /* First window goes into the upper-left corner. */
101     w->start.x = 0;
102     w->start.y = 1;                             /* Leave space for title bar. */
103     if (0 != w->prev)
104     {
105
106         /* Non-first window fallbacks to: fit rightwards of rightmost border. */
107         struct Win * w_top = w->prev;
108         while (w_top->start.y != 1)
109         {
110             w_top = w_top->prev;
111         }
112         w->start.x = w_top->start.x + w_top->frame.size.x + 1;
113
114         /* Fit window below its predecessor if that one directly thrones over
115          * empty space wide and high enough.
116          */
117         uint16_t w_prev_maxy = w->prev->start.y
118                                + getmaxy(w->prev->frame.curses_win);
119         if (   w->frame.size.x <= w->prev->frame.size.x
120             && w->frame.size.y <  wmeta->pad.size.y - w_prev_maxy)
121         {
122             w->start.x = w->prev->start.x;
123             w->start.y = w_prev_maxy + 1;
124         }
125
126         /* Failing that, try to open a new sub column below the nearest
127          * predecessor window that thrones over enough empty space.
128          */
129         else
130         {
131             struct Win * w_up = w->prev;
132             struct Win * w_upup = w_up;
133             uint16_t widthdiff;
134             while (w_up != w_top)
135             {
136                 w_upup = w_up->prev;
137                 while (1)
138                 {
139                     if (w_up->start.y != w_upup->start.y)
140                     {
141                         break;
142                     }
143                     w_upup = w_upup->prev;
144                 }
145                 w_prev_maxy = w_upup->start.y
146                               + getmaxy(w_upup->frame.curses_win);
147                 widthdiff = (w_upup->start.x + w_upup->frame.size.x)
148                             - (w_up->start.x + w_up->frame.size.x);
149                 if (   w->frame.size.y < wmeta->pad.size.y - w_prev_maxy
150                     && w->frame.size.x < widthdiff)
151                 {
152                     w->start.x = w_up->start.x + w_up->frame.size.x + 1 ;
153                     w->start.y = w_prev_maxy + 1;
154                     break;
155                 }
156                 w_up = w_upup;
157             }
158         }
159     }
160 }
161
162
163
164 static void destroy_win (struct Win * w)
165 {
166     delwin(w->frame.curses_win);
167     w->frame.curses_win = 0;
168 }
169
170
171
172 static void draw_wins (struct Win * w)
173 {
174     w->draw(w);
175     if (0 != w->next)
176     {
177         draw_wins (w->next);
178     }
179 }
180
181
182
183 static void draw_win_borderlines(struct Win * w, char active)
184 {
185     /* Draw vertical and horizontal border lines. */
186     uint16_t y, x;
187     for (y = w->start.y; y <= w->start.y + w->frame.size.y; y++)
188     {
189         mvwaddch(wgetparent(w->frame.curses_win), y, w->start.x - 1, '|');
190         mvwaddch(wgetparent(w->frame.curses_win),
191                  y, w->start.x + w->frame.size.x, '|');
192     }
193     for (x = w->start.x; x <= w->start.x + w->frame.size.x; x++)
194     {
195         mvwaddch(wgetparent(w->frame.curses_win), w->start.y - 1, x, '-');
196         mvwaddch(wgetparent(w->frame.curses_win),
197                  w->start.y + w->frame.size.y, x, '-');
198     }
199
200     /* Draw as much as possible of the title into center of top border line. */
201     char min_title_length_visible = 3;  /* min. 1 char + 2 padding/decoration */
202     if (w->frame.size.x >= min_title_length_visible)
203     {
204         uint16_t title_offset = 0;
205         if (w->frame.size.x > strlen(w->title) + 2)
206         {
207             title_offset = (w->frame.size.x - (strlen(w->title) + 2)) / 2;
208         }                                     /* +2 is for padding/decoration */
209         uint16_t length_visible = strnlen(w->title, w->frame.size.x - 2);
210         char title[length_visible + 3];
211         char decoration = ' ';
212         if (1 == active)
213         {
214             decoration = '$';
215         }
216         memcpy(title + 1, w->title, length_visible);
217         title[0] = title[length_visible + 1] = decoration;
218         title[length_visible + 2] = '\0';
219         mvwaddstr(wgetparent(w->frame.curses_win),
220                   w->start.y - 1, w->start.x + title_offset, title);
221     }
222 }
223
224
225
226 static void draw_wins_borderlines(struct Win * w, struct Win * w_active)
227 {
228     char active = 0;
229     if (w == w_active)
230     {
231         active = 1;
232     }
233     draw_win_borderlines(w, active);
234     if (0 != w->next)
235     {
236         draw_wins_borderlines (w->next, w_active);
237     }
238 }
239
240
241
242 static void draw_wins_bordercorners(struct Win * w, WINDOW * pad)
243 {
244     mvwaddch(pad, w->start.y - 1, w->start.x - 1, '+');
245     mvwaddch(pad, w->start.y - 1, w->start.x + w->frame.size.x, '+');
246     mvwaddch(pad, w->start.y + w->frame.size.y, w->start.x - 1, '+');
247     mvwaddch(pad,
248              w->start.y + w->frame.size.y, w->start.x + w->frame.size.x, '+');
249     if (0 != w->next)
250     {
251         draw_wins_bordercorners(w->next, pad);
252     }
253 }
254
255
256
257 extern struct WinMeta init_win_meta(WINDOW * screen)
258 {
259     struct WinMeta wmeta;
260     wmeta.screen         = screen;
261     wmeta.pad.size.y     = getmaxy(screen);
262     wmeta.pad.size.x     = getmaxx(screen);
263     wmeta.chain_start    = 0;
264     wmeta.chain_end      = 0;
265     wmeta.pad_offset     = 0;
266     wmeta.pad.curses_win = newpad(wmeta.pad.size.y, 1);
267     wmeta.active         = 0;
268     return wmeta;
269 }
270
271
272
273 extern struct Win init_win(struct WinMeta * wmeta, char * title,
274                            void * data, void * func)
275 {
276     struct Win w;
277     w.prev             = 0;
278     w.next             = 0;
279     w.frame.curses_win = 0;
280     w.title            = title;
281     w.frame.size.x     = 20;
282     w.frame.size.y     = wmeta->pad.size.y - 1;
283     w.data             = data;
284     w.draw             = func;
285     return w;
286 }
287
288
289
290 extern void append_win(struct WinMeta * wmeta, struct Win * w)
291 {
292     if (0 != wmeta->chain_start)
293     {
294         w->prev = wmeta->chain_end;
295         wmeta->chain_end->next = w;
296     }
297     else
298     {
299         wmeta->active = w;
300         wmeta->chain_start = w;
301     }
302     wmeta->chain_end = w;
303     update_wins(wmeta, w);
304 }
305
306
307
308 extern void suspend_win(struct WinMeta * wmeta, struct Win * w)
309 {
310     destroy_win(w);
311
312     if (wmeta->chain_start != w)
313     {
314         w->prev->next = w->next;
315     }
316     else
317     {
318         wmeta->chain_start = w->next;
319     }
320     char pad_refitted = 0;
321     if (wmeta->chain_end != w)
322     {
323         w->next->prev = w->prev;
324         if (wmeta->active == w)
325         {
326             wmeta->active = w->next;
327         }
328         update_wins(wmeta, w->next); /* Positioning of successor windows may  */
329         pad_refitted = 1;            /* be affected / need correction. Note   */
330     }                                /* that update_wins() already refits the */
331     else                             /* pad, voiding later need for that.     */
332     {
333         wmeta->chain_end = w->prev;
334         if (wmeta->active == w)
335         {
336             wmeta->active = w->prev;
337         }
338     }
339
340     w->prev = 0;
341     w->next = 0;
342
343     if (0 == pad_refitted)
344     {
345         refit_pad(wmeta);
346     }
347 }
348
349
350
351 extern void reset_pad_offset(struct WinMeta * wmeta, uint16_t new_offset)
352 {
353     if (new_offset >= 0
354         && (new_offset < wmeta->pad_offset
355             || new_offset + wmeta->pad.size.x < getmaxx(wmeta->pad.curses_win)))
356     {
357         wmeta->pad_offset = new_offset;
358     }
359 }
360
361
362
363 extern void resize_active_win(struct WinMeta * wmeta, struct yx_uint16 size)
364 {
365     if (0 != wmeta->active
366         && size.x > 0 && size.y > 0
367         && size.y < wmeta->pad.size.y)
368     {
369         wmeta->active->frame.size = size;
370         update_wins(wmeta, wmeta->chain_start);   /* Positioning of successor */
371     }                                             /* windows may be affected. */
372 }
373
374
375
376 extern void cycle_active_win(struct WinMeta * wmeta, char dir)
377 {
378     if (0 != wmeta->active)
379     {
380         if ('n' == dir)
381         {
382             if (wmeta->active->next != 0)
383             {
384                 wmeta->active = wmeta->active->next;
385             }
386             else
387             {
388                 wmeta->active = wmeta->chain_start;
389             }
390         }
391         else
392         {
393             if (wmeta->active->prev != 0)
394             {
395                 wmeta->active = wmeta->active->prev;
396             }
397             else
398             {
399                 wmeta->active = wmeta->chain_end;
400             }
401         }
402     }
403 }
404
405
406
407 extern void shift_active_win(struct WinMeta * wmeta, char dir)
408 {
409     if (0 != wmeta->active                        /* No shifting with less    */
410         && wmeta->chain_start != wmeta->chain_end /* than one window visible. */
411         && (dir == 'f' || dir == 'b'))
412     {
413         struct Win * w_shift = wmeta->active, * w_p, * w_p_next;
414
415         /* Check if shifting will lead to wrapping. */
416         char wrap = 0;
417         if (   (dir == 'f' && w_shift == wmeta->chain_end)
418             || (dir == 'b' && w_shift == wmeta->chain_start))
419         {
420             wrap = 1;
421         }
422
423         /* Suspend all visible windows, remember their order in wins[]. */
424         uint16_t i, i_max;
425         for (w_p  = wmeta->chain_start, i_max = 1;
426              w_p != wmeta->chain_end;
427              w_p  = w_p->next)
428         {
429             i_max++;
430         }
431         struct Win ** wins = malloc(i_max * sizeof(struct Win *));
432         for (i = 0, w_p = wmeta->chain_start;
433              i < i_max;
434              i++)
435         {
436             w_p_next = w_p->next;
437             suspend_win(wmeta, w_p);
438             wins[i] = w_p;
439             w_p = w_p_next;
440         }
441
442         /* Re-append all previously visible windows in the new order. */
443         if (wrap)
444         {
445             if (dir == 'f')
446             {
447                 append_win(wmeta, w_shift);
448                 for (i = 0; i < i_max - 1; i++)
449                 {
450                     append_win(wmeta, wins[i]);
451                 }
452             }
453             else
454             {
455                 for (i = 1; i < i_max; i++)
456                 {
457                     append_win(wmeta, wins[i]);
458                 }
459                 append_win(wmeta, w_shift);
460             }
461         }
462         else
463         {
464             for (i = 0; i < i_max; i++)
465             {
466                 if (   (dir == 'f' && w_shift == wins[i])
467                     || (dir == 'b' && w_shift == wins[i+1]))
468                 {
469                     append_win(wmeta, wins[i+1]);
470                     append_win(wmeta, wins[i]);
471                     i++;
472                 }
473                 else
474                 {
475                     append_win(wmeta, wins[i]);
476                 }
477             }
478         }
479         free(wins);
480
481         wmeta->active = w_shift;  /* Otherwise lastly appended win is active. */
482     }
483 }
484
485
486
487 extern void draw_all_wins(struct WinMeta * wmeta)
488 {
489     /* Empty everything before filling it a-new. */
490     erase();
491     wnoutrefresh(wmeta->screen);
492     werase(wmeta->pad.curses_win);
493     if (wmeta->chain_start)
494     {
495
496         /* Draw windows' contents first, then their borders. */
497         draw_wins(wmeta->chain_start);
498         draw_wins_borderlines(wmeta->chain_start, wmeta->active);
499         draw_wins_bordercorners(wmeta->chain_start, wmeta->pad.curses_win);
500
501         /* Draw virtual screen scroll hints. */
502         if (wmeta->pad_offset > 0)
503         {
504             draw_scroll_hint(&wmeta->pad,
505                              wmeta->pad_offset, wmeta->pad_offset + 1, '<');
506         }
507         if (wmeta->pad_offset + wmeta->pad.size.x
508             < getmaxx(wmeta->pad.curses_win) - 1)
509         {
510             draw_scroll_hint(&wmeta->pad,
511                              wmeta->pad_offset + wmeta->pad.size.x - 1,
512                              getmaxx(wmeta->pad.curses_win)
513                              - (wmeta->pad_offset + wmeta->pad.size.x), '>');
514         }
515
516         /* Write virtual screen segment to be shown on physical screen into */
517         /* ncurses screen buffer. */
518         pnoutrefresh(wmeta->pad.curses_win, 0, wmeta->pad_offset, 0, 0,
519                      wmeta->pad.size.y, wmeta->pad.size.x-1);
520     }
521
522     /* Only at the end write accumulated changes to the physical screen. */
523     doupdate();
524 }
525
526
527
528 extern void draw_scroll_hint(struct Frame * frame, uint16_t pos, uint32_t dist,
529                              char dir)
530 {
531     /* Decide on alignment (vertical/horizontal?), thereby scroll hint text. */
532     char * more = "more";
533     char * unit_cols = "columns";
534     char * unit_rows = "lines";
535     uint16_t dsc_space = frame->size.x;
536     char * unit = unit_rows;
537     if ('<' == dir || '>' == dir)
538     {
539         dsc_space = frame->size.y;
540         unit = unit_cols;
541     }
542     char * scrolldsc = malloc((4 * sizeof(char)) + strlen(more) + strlen(unit)
543                               + 10);                /* 10 = uint32 max strlen */
544     sprintf(scrolldsc, " %d %s %s ", dist, more, unit);
545
546     /* Decide on offset of the description text inside the scroll hint line. */
547     char offset = 1, q;
548     if (dsc_space > strlen(scrolldsc) + 1)
549     {
550         offset = (dsc_space - strlen(scrolldsc)) / 2;
551     }
552
553     /* Draw scroll hint line as dir symbols bracketing description text. */
554     chtype symbol;
555     for (q = 0; q < dsc_space; q++)
556     {
557         if (q >= offset && q < strlen(scrolldsc) + offset)
558         {
559             symbol = scrolldsc[q - offset] | A_REVERSE;
560         }
561         else
562         {
563             symbol = dir | A_REVERSE;
564         }
565         if ('<' == dir || '>' == dir)
566         {
567             mvwaddch(frame->curses_win, q, pos, symbol);
568         }
569         else
570         {
571             mvwaddch(frame->curses_win, pos, q, symbol);
572         }
573     }
574
575     free(scrolldsc);
576 }