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