fixed logging and more
[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 void rtk_bn_mode(rtk_widget *w, int mode)
198 {
199         RTK_ASSERT_TYPE(w, RTK_BUTTON);
200         w->bn.mode = mode;
201 }
202
203 void rtk_bn_set_icon(rtk_widget *w, rtk_icon *icon)
204 {
205         rtk_rect rect;
206
207         RTK_ASSERT_TYPE(w, RTK_BUTTON);
208         w->bn.icon = icon;
209
210         calc_widget_rect(w, &rect);
211         rtk_resize(w, rect.width, rect.height);
212 }
213
214 rtk_icon *rtk_bn_get_icon(rtk_widget *w)
215 {
216         RTK_ASSERT_TYPE(w, RTK_BUTTON);
217         return w->bn.icon;
218 }
219
220 /* --- constructors --- */
221
222 rtk_widget *rtk_create_window(rtk_widget *par, const char *title, int x, int y, int width, int height)
223 {
224         rtk_widget *w;
225
226         if(!(w = rtk_create_widget())) {
227                 return 0;
228         }
229         w->type = RTK_WIN;
230         if(par) rtk_win_add(par, w);
231         rtk_set_text(w, title);
232         rtk_move(w, x, y);
233         rtk_resize(w, width, height);
234         return w;
235 }
236
237 rtk_widget *rtk_create_button(rtk_widget *par, const char *str, rtk_callback cbfunc)
238 {
239         rtk_widget *w;
240
241         if(!(w = rtk_create_widget())) {
242                 return 0;
243         }
244         w->type = RTK_BUTTON;
245         if(par) rtk_win_add(par, w);
246         rtk_set_text(w, str);
247         rtk_set_callback(w, cbfunc, 0);
248         return w;
249 }
250
251 rtk_widget *rtk_create_iconbutton(rtk_widget *par, rtk_icon *icon, rtk_callback cbfunc)
252 {
253         rtk_widget *w;
254
255         if(!(w = rtk_create_widget())) {
256                 return 0;
257         }
258         w->type = RTK_BUTTON;
259         if(par) rtk_win_add(par, w);
260         rtk_bn_set_icon(w, icon);
261         rtk_set_callback(w, cbfunc, 0);
262         return w;
263 }
264
265 rtk_widget *rtk_create_label(rtk_widget *par, const char *text)
266 {
267         rtk_widget *w;
268
269         if(!(w = rtk_create_widget())) {
270                 return 0;
271         }
272         w->type = RTK_LABEL;
273         if(par) rtk_win_add(par, w);
274         rtk_set_text(w, text);
275         return w;
276 }
277
278 rtk_widget *rtk_create_checkbox(rtk_widget *par, const char *text, int chk, rtk_callback cbfunc)
279 {
280         rtk_widget *w;
281
282         if(!(w = rtk_create_widget())) {
283                 return 0;
284         }
285         w->type = RTK_CHECKBOX;
286         if(par) rtk_win_add(par, w);
287         rtk_set_text(w, text);
288         rtk_set_value(w, chk ? 1 : 0);
289         rtk_set_callback(w, cbfunc, 0);
290         return w;
291 }
292
293 rtk_widget *rtk_create_separator(rtk_widget *par)
294 {
295         rtk_widget *w;
296
297         if(!(w = rtk_create_widget())) {
298                 return 0;
299         }
300         w->type = RTK_SEP;
301         if(par) rtk_win_add(par, w);
302         return w;
303 }
304
305
306 /* --- icon functions --- */
307 rtk_iconsheet *rtk_load_iconsheet(const char *fname)
308 {
309          rtk_iconsheet *is;
310
311         if(!(is = malloc(sizeof *is))) {
312                 return 0;
313         }
314         is->icons = 0;
315
316         if(!(is->pixels = img_load_pixels(fname, &is->width, &is->height, IMG_FMT_RGBA32))) {
317                 free(is);
318                 return 0;
319         }
320         return is;
321 }
322
323 void rtk_free_iconsheet(rtk_iconsheet *is)
324 {
325         rtk_icon *icon;
326
327         img_free_pixels(is->pixels);
328
329         while(is->icons) {
330                 icon = is->icons;
331                 is->icons = is->icons->next;
332                 free(icon->name);
333                 free(icon);
334         }
335         free(is);
336 }
337
338 rtk_icon *rtk_define_icon(rtk_iconsheet *is, const char *name, int x, int y, int w, int h)
339 {
340         rtk_icon *icon;
341
342         if(!(icon = malloc(sizeof *icon))) {
343                 return 0;
344         }
345         if(!(icon->name = strdup(name))) {
346                 free(icon);
347                 return 0;
348         }
349         icon->width = w;
350         icon->height = h;
351         icon->scanlen = is->width;
352         icon->pixels = is->pixels + y * is->width + x;
353         return icon;
354 }
355
356 #define BEVELSZ         1
357 #define PAD                     2
358 #define OFFS            (BEVELSZ + PAD)
359 #define CHKBOXSZ        (BEVELSZ * 2 + 8)
360
361 static void calc_widget_rect(rtk_widget *w, rtk_rect *rect)
362 {
363         rtk_rect txrect = {0};
364
365         rect->x = w->any.x;
366         rect->y = w->any.y;
367
368         if(w->any.text) {
369                 gfx.textrect(w->any.text, &txrect);
370         }
371
372         switch(w->type) {
373         case RTK_WIN:
374                 rect->width = w->any.width;
375                 rect->height = w->any.height;
376                 break;
377
378         case RTK_BUTTON:
379                 if(w->bn.icon) {
380                         rect->width = w->bn.icon->width + OFFS * 2;
381                         rect->height = w->bn.icon->height + OFFS * 2;
382                 } else {
383                         rect->width = txrect.width + OFFS * 2;
384                         rect->height = txrect.height + OFFS * 2;
385                 }
386                 break;
387
388         case RTK_CHECKBOX:
389                 rect->width = txrect.width + CHKBOXSZ + OFFS * 2 + PAD;
390                 rect->height = txrect.height + OFFS * 2;
391                 break;
392
393         case RTK_LABEL:
394                 rect->width = txrect.width + PAD * 2;
395                 rect->height = txrect.height + PAD * 2;
396                 break;
397
398         case RTK_SEP:
399                 if(w->any.par->win.layout == RTK_VBOX) {
400                         rect->width = w->any.par->any.width - PAD * 2;
401                         rect->height = PAD * 4 + BEVELSZ * 2;
402                 } else if(w->any.par->win.layout == RTK_HBOX) {
403                         rect->width = PAD * 4 + BEVELSZ * 2;
404                         rect->height = w->any.par->any.height - PAD * 2;
405                 } else {
406                         rect->width = rect->height = 0;
407                 }
408                 break;
409
410         default:
411                 rect->width = rect->height = 0;
412         }
413 }
414
415 static int need_relayout(rtk_widget *w)
416 {
417         rtk_widget *c;
418
419         if(w->any.flags & GEOMCHG) {
420                 return 1;
421         }
422
423         if(w->any.type == RTK_WIN) {
424                 c = w->win.clist;
425                 while(c) {
426                         if(need_relayout(c)) {
427                                 return 1;
428                         }
429                         c = c->any.next;
430                 }
431         }
432         return 0;
433 }
434
435 static void calc_layout(rtk_widget *w)
436 {
437         int x, y;
438         rtk_widget *c;
439         rtk_rect rect;
440
441         if(w->any.type == RTK_WIN && w->win.layout != RTK_NONE) {
442                 x = y = PAD;
443
444                 c = w->win.clist;
445                 while(c) {
446                         rtk_move(c, x, y);
447                         calc_layout(c);
448
449                         if(w->win.layout == RTK_VBOX) {
450                                 y += c->any.height + PAD * 2;
451                         } else {
452                                 x += c->any.width + PAD * 2;
453                         }
454
455                         c = c->any.next;
456                 }
457         }
458
459         calc_widget_rect(w, &rect);
460         w->any.width = rect.width;
461         w->any.height = rect.height;
462
463         w->any.flags = (w->any.flags & ~GEOMCHG) | DIRTY;
464 }
465
466 void rtk_draw_widget(rtk_widget *w)
467 {
468         if(need_relayout(w)) {
469                 calc_layout(w);
470         }
471
472         switch(w->any.type) {
473         case RTK_WIN:
474                 draw_window(w);
475                 break;
476
477         case RTK_BUTTON:
478                 draw_button(w);
479                 break;
480
481         case RTK_CHECKBOX:
482                 draw_checkbox(w);
483                 break;
484
485         case RTK_SEP:
486                 draw_separator(w);
487                 break;
488
489         default:
490                 break;
491         }
492
493         w->any.flags &= ~DIRTY;
494 }
495
496 static void widget_rect(rtk_widget *w, rtk_rect *rect)
497 {
498         rect->x = w->any.x;
499         rect->y = w->any.y;
500         rect->width = w->any.width;
501         rect->height = w->any.height;
502 }
503
504 static void abs_pos(rtk_widget *w, int *xpos, int *ypos)
505 {
506         int x, y, px, py;
507
508         x = w->any.x;
509         y = w->any.y;
510
511         if(w->any.par) {
512                 abs_pos(w->any.par, &px, &py);
513                 x += px;
514                 y += py;
515         }
516
517         *xpos = x;
518         *ypos = y;
519 }
520
521 #define COL_BG          0xff666666
522 #define COL_BGHL        0xff808080
523 #define COL_LBEV        0xffaaaaaa
524 #define COL_SBEV        0xff222222
525 #define COL_TEXT        0xff000000
526
527 static void hline(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 = sz;
533         rect.height = 1;
534         gfx.fill(&rect, col);
535 }
536
537 static void vline(int x, int y, int sz, uint32_t col)
538 {
539         rtk_rect rect;
540         rect.x = x;
541         rect.y = y;
542         rect.width = 1;
543         rect.height = sz;
544         gfx.fill(&rect, col);
545 }
546
547 enum {FRM_SOLID, FRM_OUTSET, FRM_INSET};
548
549 static void draw_frame(rtk_rect *rect, int type)
550 {
551         int tlcol, brcol;
552
553         switch(type) {
554         case FRM_SOLID:
555                 tlcol = brcol = 0xff000000;
556                 break;
557         case FRM_OUTSET:
558                 tlcol = COL_LBEV;
559                 brcol = COL_SBEV;
560                 break;
561         case FRM_INSET:
562                 tlcol = COL_SBEV;
563                 brcol = COL_LBEV;
564                 break;
565         default:
566                 break;
567         }
568
569         hline(rect->x, rect->y, rect->width, tlcol);
570         vline(rect->x, rect->y + 1, rect->height - 2, tlcol);
571         hline(rect->x, rect->y + rect->height - 1, rect->width, brcol);
572         vline(rect->x + rect->width - 1, rect->y + 1, rect->height - 2, brcol);
573 }
574
575 static void draw_window(rtk_widget *w)
576 {
577         rtk_rect rect;
578         rtk_widget *c;
579
580         widget_rect(w, &rect);
581         gfx.fill(&rect, COL_BG);
582
583         c = w->win.clist;
584         while(c) {
585                 rtk_draw_widget(c);
586                 c = c->any.next;
587         }
588 }
589
590 static void draw_button(rtk_widget *w)
591 {
592         int pressed;
593         rtk_rect rect;
594
595         widget_rect(w, &rect);
596         abs_pos(w, &rect.x, &rect.y);
597
598         if(w->bn.mode == RTK_TOGGLEBN) {
599                 pressed = w->any.value;
600         } else {
601                 pressed = w->any.flags & PRESS;
602         }
603
604         if(rect.width > 2 && rect.height > 2) {
605                 draw_frame(&rect, pressed ? FRM_INSET : FRM_OUTSET);
606
607                 rect.x++;
608                 rect.y++;
609                 rect.width -= 2;
610                 rect.height -= 2;
611         }
612
613         gfx.fill(&rect, w->any.flags & HOVER ? COL_BGHL : COL_BG);
614         if(w->bn.icon) {
615                 int offs = w->any.flags & PRESS ? PAD + 1 : PAD;
616                 gfx.blit(rect.x + offs, rect.y + offs, w->bn.icon);
617         } else {
618                 gfx.fill(&rect, 0xff802020);
619         }
620 }
621
622 static void draw_checkbox(rtk_widget *w)
623 {
624 }
625
626 static void draw_separator(rtk_widget *w)
627 {
628         rtk_widget *win = w->any.par;
629         rtk_rect rect;
630
631         if(!win) return;
632
633         widget_rect(w, &rect);
634         abs_pos(w, &rect.x, &rect.y);
635
636         switch(win->win.layout) {
637         case RTK_VBOX:
638                 rect.y += PAD * 2;
639                 rect.height = 2;
640                 break;
641
642         case RTK_HBOX:
643                 rect.x += PAD * 2;
644                 rect.width = 2;
645                 break;
646
647         default:
648                 break;
649         }
650
651         draw_frame(&rect, FRM_INSET);
652 }
653
654
655 static int hittest(rtk_widget *w, int x, int y)
656 {
657         if(x < w->any.x || y < w->any.y) return 0;
658         if(x >= w->any.x + w->any.width) return 0;
659         if(y >= w->any.y + w->any.height) return 0;
660         return 1;
661 }
662
663 static void sethover(rtk_widget *w)
664 {
665         if(hover == w) return;
666
667         if(hover) {
668                 hover->any.flags &= ~HOVER;
669         }
670         hover = w;
671         if(w) {
672                 w->any.flags |= HOVER;
673         }
674 }
675
676 static void setpress(rtk_widget *w)
677 {
678         if(pressed == w) return;
679
680         if(pressed) {
681                 pressed->any.flags &= ~PRESS;
682         }
683         pressed = w;
684         if(w) {
685                 w->any.flags |= PRESS;
686         }
687 }
688
689 static void click(rtk_widget *w, int x, int y)
690 {
691         switch(w->type) {
692         case RTK_BUTTON:
693                 if(w->bn.mode == RTK_TOGGLEBN) {
694         case RTK_CHECKBOX:
695                         w->any.value ^= 1;
696                 }
697                 if(w->any.cbfunc) {
698                         w->any.cbfunc(w, w->any.cbcls);
699                 }
700                 break;
701
702         default:
703                 break;
704         }
705 }
706
707 int rtk_input_key(rtk_widget *w, int key, int press)
708 {
709         return 0;
710 }
711
712 int rtk_input_mbutton(rtk_widget *w, int bn, int press, int x, int y)
713 {
714         if(!hittest(w, x, y)) {
715                 return 0;
716         }
717
718         if(press) {
719                 if(hover && hittest(hover, x, y)) {
720                         setpress(hover);
721                 }
722         } else {
723                 if(pressed && hittest(pressed, x, y)) {
724                         click(pressed, x, y);
725                 }
726                 setpress(0);
727         }
728
729         return 1;
730 }
731
732 int rtk_input_mmotion(rtk_widget *w, int x, int y)
733 {
734         rtk_widget *c;
735
736         if(!hittest(w, x, y)) {
737                 int res = hover ? 1 : 0;
738                 sethover(0);
739                 return res;
740         }
741
742         if(w->type == RTK_WIN) {
743                 c = w->win.clist;
744                 while(c) {
745                         if(hittest(c, x, y)) {
746                                 return rtk_input_mmotion(c, x, y);
747                         }
748                         c = c->any.next;
749                 }
750         }
751
752         if(hover != w) {
753                 sethover(w);
754                 return 1;
755         }
756         return 0;
757 }