bdfca65be54f8771e99caa7121edfef4a093bcd4
[retroray] / src / rtk.c
1 #include <stdlib.h>
2 #include <string.h>
3 #include "imago2.h"
4 #include "rtk_impl.h"
5
6 static rtk_draw_ops gfx;
7
8 static void calc_widget_rect(rtk_widget *w, rtk_rect *rect);
9 static void draw_window(rtk_widget *w);
10 static void draw_button(rtk_widget *w);
11 static void draw_checkbox(rtk_widget *w);
12 static void draw_separator(rtk_widget *w);
13
14 void rtk_setup(rtk_draw_ops *drawop)
15 {
16         gfx = *drawop;
17 }
18
19 rtk_widget *rtk_create_widget(void)
20 {
21         rtk_widget *w;
22
23         if(!(w = calloc(1, sizeof *w))) {
24                 return 0;
25         }
26         w->any.flags = VISIBLE | ENABLED | GEOMCHG | DIRTY;
27         return w;
28 }
29
30 void rtk_free_widget(rtk_widget *w)
31 {
32         if(!w) return;
33
34         if(w->type == RTK_WIN) {
35                 while(w->win.clist) {
36                         rtk_widget *c = w->win.clist;
37                         w->win.clist = w->win.clist->any.next;
38                         rtk_free_widget(c);
39                 }
40         }
41
42         free(w->any.text);
43         free(w);
44 }
45
46 int rtk_type(rtk_widget *w)
47 {
48         return w->type;
49 }
50
51 void rtk_move(rtk_widget *w, int x, int y)
52 {
53         w->any.x = x;
54         w->any.y = y;
55         w->any.flags |= GEOMCHG;
56 }
57
58 void rtk_pos(rtk_widget *w, int *xptr, int *yptr)
59 {
60         *xptr = w->any.x;
61         *yptr = w->any.y;
62 }
63
64 void rtk_resize(rtk_widget *w, int xsz, int ysz)
65 {
66         w->any.width = xsz;
67         w->any.height = ysz;
68         w->any.flags |= GEOMCHG;
69 }
70
71 void rtk_size(rtk_widget *w, int *xptr, int *yptr)
72 {
73         *xptr = w->any.width;
74         *yptr = w->any.height;
75 }
76
77 int rtk_set_text(rtk_widget *w, const char *str)
78 {
79         rtk_rect rect;
80         char *s = strdup(str);
81         if(!s) return -1;
82
83         free(w->any.text);
84         w->any.text = s;
85
86         calc_widget_rect(w, &rect);
87         rtk_resize(w, rect.width, rect.height);
88         return 0;
89 }
90
91 const char *rtk_get_text(rtk_widget *w)
92 {
93         return w->any.text;
94 }
95
96 void rtk_set_value(rtk_widget *w, int val)
97 {
98         w->any.value = val;
99 }
100
101 int rtk_get_value(rtk_widget *w)
102 {
103         return w->any.value;
104 }
105
106 void rtk_set_callback(rtk_widget *w, rtk_callback cbfunc, void *cls)
107 {
108         w->any.cbfunc = cbfunc;
109         w->any.cbcls = cls;
110 }
111
112 void rtk_win_layout(rtk_widget *w, int layout)
113 {
114         w->win.layout = layout;
115 }
116
117 void rtk_win_clear(rtk_widget *w)
118 {
119         rtk_widget *tmp;
120
121         RTK_ASSERT_TYPE(w, RTK_WIN);
122
123         while(w->win.clist) {
124                 tmp = w->win.clist;
125                 w->win.clist = w->win.clist->any.next;
126                 rtk_free_widget(tmp);
127         }
128
129         w->win.clist = w->win.ctail = 0;
130 }
131
132 void rtk_win_add(rtk_widget *par, rtk_widget *child)
133 {
134         RTK_ASSERT_TYPE(par, RTK_WIN);
135
136         if(rtk_win_has(par, child)) {
137                 return;
138         }
139
140         if(child->any.par) {
141                 rtk_win_rm(child->any.par, child);
142         }
143
144         if(par->win.clist) {
145                 par->win.ctail->any.next = child;
146                 par->win.ctail = child;
147         } else {
148                 par->win.clist = par->win.ctail = child;
149         }
150         child->any.next = 0;
151
152         child->any.par = par;
153 }
154
155 void rtk_win_rm(rtk_widget *par, rtk_widget *child)
156 {
157         rtk_widget *prev, dummy;
158
159         RTK_ASSERT_TYPE(par, RTK_WIN);
160
161         dummy.any.next = par->win.clist;
162         prev = &dummy;
163         while(prev->any.next) {
164                 if(prev->any.next == child) {
165                         if(!child->any.next) {
166                                 par->win.ctail = prev;
167                         }
168                         prev->any.next = child->any.next;
169                         break;
170                 }
171                 prev = prev->any.next;
172         }
173         par->win.clist = dummy.any.next;
174 }
175
176 int rtk_win_has(rtk_widget *par, rtk_widget *child)
177 {
178         rtk_widget *w;
179
180         RTK_ASSERT_TYPE(par, RTK_WIN);
181
182         w = par->win.clist;
183         while(w) {
184                 if(w == child) {
185                         return 1;
186                 }
187                 w = w->any.next;
188         }
189         return 0;
190 }
191
192 /* --- button functions --- */
193
194 void rtk_bn_set_icon(rtk_widget *w, rtk_icon *icon)
195 {
196         rtk_rect rect;
197
198         RTK_ASSERT_TYPE(w, RTK_BUTTON);
199         w->bn.icon = icon;
200
201         calc_widget_rect(w, &rect);
202         rtk_resize(w, rect.width, rect.height);
203 }
204
205 rtk_icon *rtk_bn_get_icon(rtk_widget *w)
206 {
207         RTK_ASSERT_TYPE(w, RTK_BUTTON);
208         return w->bn.icon;
209 }
210
211 /* --- constructors --- */
212
213 rtk_widget *rtk_create_window(rtk_widget *par, const char *title, int x, int y, int width, int height)
214 {
215         rtk_widget *w;
216
217         if(!(w = rtk_create_widget())) {
218                 return 0;
219         }
220         w->type = RTK_WIN;
221         if(par) rtk_win_add(par, w);
222         rtk_set_text(w, title);
223         rtk_move(w, x, y);
224         rtk_resize(w, width, height);
225         return w;
226 }
227
228 rtk_widget *rtk_create_button(rtk_widget *par, const char *str, rtk_callback cbfunc)
229 {
230         rtk_widget *w;
231
232         if(!(w = rtk_create_widget())) {
233                 return 0;
234         }
235         w->type = RTK_BUTTON;
236         if(par) rtk_win_add(par, w);
237         rtk_set_text(w, str);
238         rtk_set_callback(w, cbfunc, 0);
239         return w;
240 }
241
242 rtk_widget *rtk_create_iconbutton(rtk_widget *par, rtk_icon *icon, rtk_callback cbfunc)
243 {
244         rtk_widget *w;
245
246         if(!(w = rtk_create_widget())) {
247                 return 0;
248         }
249         w->type = RTK_BUTTON;
250         if(par) rtk_win_add(par, w);
251         rtk_bn_set_icon(w, icon);
252         rtk_set_callback(w, cbfunc, 0);
253         return w;
254 }
255
256 rtk_widget *rtk_create_label(rtk_widget *par, const char *text)
257 {
258         rtk_widget *w;
259
260         if(!(w = rtk_create_widget())) {
261                 return 0;
262         }
263         w->type = RTK_LABEL;
264         if(par) rtk_win_add(par, w);
265         rtk_set_text(w, text);
266         return w;
267 }
268
269 rtk_widget *rtk_create_checkbox(rtk_widget *par, const char *text, int chk, rtk_callback cbfunc)
270 {
271         rtk_widget *w;
272
273         if(!(w = rtk_create_widget())) {
274                 return 0;
275         }
276         w->type = RTK_CHECKBOX;
277         if(par) rtk_win_add(par, w);
278         rtk_set_text(w, text);
279         rtk_set_value(w, chk ? 1 : 0);
280         rtk_set_callback(w, cbfunc, 0);
281         return w;
282 }
283
284 rtk_widget *rtk_create_separator(rtk_widget *par)
285 {
286         rtk_widget *w;
287
288         if(!(w = rtk_create_widget())) {
289                 return 0;
290         }
291         w->type = RTK_SEP;
292         if(par) rtk_win_add(par, w);
293         return w;
294 }
295
296
297 /* --- icon functions --- */
298 rtk_iconsheet *rtk_load_iconsheet(const char *fname)
299 {
300          rtk_iconsheet *is;
301
302         if(!(is = malloc(sizeof *is))) {
303                 return 0;
304         }
305         is->icons = 0;
306
307         if(!(is->pixels = img_load_pixels(fname, &is->width, &is->height, IMG_FMT_RGBA32))) {
308                 free(is);
309                 return 0;
310         }
311         return is;
312 }
313
314 void rtk_free_iconsheet(rtk_iconsheet *is)
315 {
316         rtk_icon *icon;
317
318         img_free_pixels(is->pixels);
319
320         while(is->icons) {
321                 icon = is->icons;
322                 is->icons = is->icons->next;
323                 free(icon->name);
324                 free(icon);
325         }
326         free(is);
327 }
328
329 rtk_icon *rtk_define_icon(rtk_iconsheet *is, const char *name, int x, int y, int w, int h)
330 {
331         rtk_icon *icon;
332
333         if(!(icon = malloc(sizeof *icon))) {
334                 return 0;
335         }
336         if(!(icon->name = strdup(name))) {
337                 free(icon);
338                 return 0;
339         }
340         icon->width = w;
341         icon->height = h;
342         icon->scanlen = is->width;
343         icon->pixels = is->pixels + y * is->width + x;
344         return icon;
345 }
346
347 #define BEVELSZ         1
348 #define PAD                     2
349 #define OFFS            (BEVELSZ + PAD)
350 #define CHKBOXSZ        (BEVELSZ * 2 + 8)
351
352 static void calc_widget_rect(rtk_widget *w, rtk_rect *rect)
353 {
354         rtk_rect txrect = {0};
355
356         rect->x = w->any.x;
357         rect->y = w->any.y;
358
359         if(w->any.text) {
360                 gfx.textrect(w->any.text, &txrect);
361         }
362
363         switch(w->type) {
364         case RTK_WIN:
365                 rect->width = w->any.width;
366                 rect->height = w->any.height;
367                 break;
368
369         case RTK_BUTTON:
370                 if(w->bn.icon) {
371                         rect->width = w->bn.icon->width + OFFS * 2;
372                         rect->height = w->bn.icon->height + OFFS * 2;
373                 } else {
374                         rect->width = txrect.width + OFFS * 2;
375                         rect->height = txrect.height + OFFS * 2;
376                 }
377                 break;
378
379         case RTK_CHECKBOX:
380                 rect->width = txrect.width + CHKBOXSZ + OFFS * 2 + PAD;
381                 rect->height = txrect.height + OFFS * 2;
382                 break;
383
384         case RTK_LABEL:
385                 rect->width = txrect.width + PAD * 2;
386                 rect->height = txrect.height + PAD * 2;
387                 break;
388
389         case RTK_SEP:
390                 if(w->any.par->win.layout == RTK_VBOX) {
391                         rect->width = w->any.par->any.width - PAD * 2;
392                         rect->height = PAD * 4 + BEVELSZ * 2;
393                 } else if(w->any.par->win.layout == RTK_HBOX) {
394                         rect->width = PAD * 4 + BEVELSZ * 2;
395                         rect->height = w->any.par->any.height - PAD * 2;
396                 } else {
397                         rect->width = rect->height = 0;
398                 }
399                 break;
400
401         default:
402                 rect->width = rect->height = 0;
403         }
404 }
405
406 static int need_relayout(rtk_widget *w)
407 {
408         rtk_widget *c;
409
410         if(w->any.flags & GEOMCHG) {
411                 return 1;
412         }
413
414         if(w->any.type == RTK_WIN) {
415                 c = w->win.clist;
416                 while(c) {
417                         if(need_relayout(c)) {
418                                 return 1;
419                         }
420                         c = c->any.next;
421                 }
422         }
423         return 0;
424 }
425
426 static void calc_layout(rtk_widget *w)
427 {
428         int x, y;
429         rtk_widget *c;
430         rtk_rect rect;
431
432         if(w->any.type == RTK_WIN && w->win.layout != RTK_NONE) {
433                 x = y = PAD;
434
435                 c = w->win.clist;
436                 while(c) {
437                         rtk_move(c, x, y);
438                         calc_layout(c);
439
440                         if(w->win.layout == RTK_VBOX) {
441                                 y += c->any.height + PAD * 2;
442                         } else {
443                                 x += c->any.width + PAD * 2;
444                         }
445
446                         c = c->any.next;
447                 }
448         }
449
450         calc_widget_rect(w, &rect);
451         w->any.width = rect.width;
452         w->any.height = rect.height;
453
454         w->any.flags = (w->any.flags & ~GEOMCHG) | DIRTY;
455 }
456
457 void rtk_draw_widget(rtk_widget *w)
458 {
459         if(need_relayout(w)) {
460                 calc_layout(w);
461         }
462
463         switch(w->any.type) {
464         case RTK_WIN:
465                 draw_window(w);
466                 break;
467
468         case RTK_BUTTON:
469                 draw_button(w);
470                 break;
471
472         case RTK_CHECKBOX:
473                 draw_checkbox(w);
474                 break;
475
476         case RTK_SEP:
477                 draw_separator(w);
478                 break;
479
480         default:
481                 break;
482         }
483
484         w->any.flags &= ~DIRTY;
485 }
486
487 static void widget_rect(rtk_widget *w, rtk_rect *rect)
488 {
489         rect->x = w->any.x;
490         rect->y = w->any.y;
491         rect->width = w->any.width;
492         rect->height = w->any.height;
493 }
494
495 static void abs_pos(rtk_widget *w, int *xpos, int *ypos)
496 {
497         int x, y, px, py;
498
499         x = w->any.x;
500         y = w->any.y;
501
502         if(w->any.par) {
503                 abs_pos(w->any.par, &px, &py);
504                 x += px;
505                 y += py;
506         }
507
508         *xpos = x;
509         *ypos = y;
510 }
511
512 #define COL_BG          0xff666666
513 #define COL_LBEV        0xffaaaaaa
514 #define COL_SBEV        0xff222222
515 #define COL_TEXT        0xff000000
516
517 static void hline(int x, int y, int sz, uint32_t col)
518 {
519         rtk_rect rect;
520         rect.x = x;
521         rect.y = y;
522         rect.width = sz;
523         rect.height = 1;
524         gfx.fill(&rect, col);
525 }
526
527 static void vline(int x, int y, int sz, uint32_t col)
528 {
529         rtk_rect rect;
530         rect.x = x;
531         rect.y = y;
532         rect.width = 1;
533         rect.height = sz;
534         gfx.fill(&rect, col);
535 }
536
537 enum {FRM_SOLID, FRM_OUTSET, FRM_INSET};
538
539 static void draw_frame(rtk_rect *rect, int type)
540 {
541         int tlcol, brcol;
542
543         switch(type) {
544         case FRM_SOLID:
545                 tlcol = brcol = 0xff000000;
546                 break;
547         case FRM_OUTSET:
548                 tlcol = COL_LBEV;
549                 brcol = COL_SBEV;
550                 break;
551         case FRM_INSET:
552                 tlcol = COL_SBEV;
553                 brcol = COL_LBEV;
554                 break;
555         default:
556                 break;
557         }
558
559         hline(rect->x, rect->y, rect->width, tlcol);
560         vline(rect->x, rect->y + 1, rect->height - 2, tlcol);
561         hline(rect->x, rect->y + rect->height - 1, rect->width, brcol);
562         vline(rect->x + rect->width - 1, rect->y + 1, rect->height - 2, brcol);
563 }
564
565 static void draw_window(rtk_widget *w)
566 {
567         rtk_rect rect;
568         rtk_widget *c;
569
570         widget_rect(w, &rect);
571         gfx.fill(&rect, COL_BG);
572
573         c = w->win.clist;
574         while(c) {
575                 rtk_draw_widget(c);
576                 c = c->any.next;
577         }
578 }
579
580 static void draw_button(rtk_widget *w)
581 {
582         rtk_rect rect;
583
584         widget_rect(w, &rect);
585         abs_pos(w, &rect.x, &rect.y);
586
587         if(rect.width > 2 && rect.height > 2) {
588                 draw_frame(&rect, FRM_OUTSET);
589
590                 rect.x++;
591                 rect.y++;
592                 rect.width -= 2;
593                 rect.height -= 2;
594         }
595
596         gfx.fill(&rect, COL_BG);
597         if(w->bn.icon) {
598                 gfx.blit(rect.x + OFFS, rect.y + OFFS, w->bn.icon);
599         } else {
600                 gfx.fill(&rect, 0xff802020);
601         }
602 }
603
604 static void draw_checkbox(rtk_widget *w)
605 {
606 }
607
608 static void draw_separator(rtk_widget *w)
609 {
610         rtk_widget *win = w->any.par;
611         rtk_rect rect;
612
613         if(!win) return;
614
615         widget_rect(w, &rect);
616         abs_pos(w, &rect.x, &rect.y);
617
618         switch(win->win.layout) {
619         case RTK_VBOX:
620                 rect.y += PAD * 2;
621                 rect.height = 2;
622                 break;
623
624         case RTK_HBOX:
625                 rect.x += PAD * 2;
626                 rect.width = 2;
627                 break;
628
629         default:
630                 break;
631         }
632
633         draw_frame(&rect, FRM_INSET);
634 }