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