UI text
[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_show(rtk_widget *w)
130 {
131         w->any.flags |= VISIBLE;
132         rtk_invalidate(w);
133 }
134
135 void rtk_hide(rtk_widget *w)
136 {
137         w->any.flags &= ~VISIBLE;
138         invalfb(w);
139 }
140
141 int rtk_visible(const rtk_widget *w)
142 {
143         return w->any.flags & VISIBLE;
144 }
145
146 void rtk_invalidate(rtk_widget *w)
147 {
148         w->any.flags |= DIRTY;
149 }
150
151 void rtk_validate(rtk_widget *w)
152 {
153         w->any.flags &= ~DIRTY;
154 }
155
156 void rtk_win_layout(rtk_widget *w, int layout)
157 {
158         w->win.layout = layout;
159 }
160
161 void rtk_win_clear(rtk_widget *w)
162 {
163         rtk_widget *tmp;
164
165         RTK_ASSERT_TYPE(w, RTK_WIN);
166
167         while(w->win.clist) {
168                 tmp = w->win.clist;
169                 w->win.clist = w->win.clist->any.next;
170                 rtk_free_widget(tmp);
171         }
172
173         w->win.clist = w->win.ctail = 0;
174         rtk_invalidate(w);
175 }
176
177 void rtk_win_add(rtk_widget *par, rtk_widget *child)
178 {
179         RTK_ASSERT_TYPE(par, RTK_WIN);
180
181         if(rtk_win_has(par, child)) {
182                 return;
183         }
184
185         if(child->any.par) {
186                 rtk_win_rm(child->any.par, child);
187         }
188
189         if(par->win.clist) {
190                 par->win.ctail->any.next = child;
191                 par->win.ctail = child;
192         } else {
193                 par->win.clist = par->win.ctail = child;
194         }
195         child->any.next = 0;
196
197         child->any.par = par;
198         rtk_invalidate(par);
199 }
200
201 void rtk_win_rm(rtk_widget *par, rtk_widget *child)
202 {
203         rtk_widget *prev, dummy;
204
205         RTK_ASSERT_TYPE(par, RTK_WIN);
206
207         dummy.any.next = par->win.clist;
208         prev = &dummy;
209         while(prev->any.next) {
210                 if(prev->any.next == child) {
211                         if(!child->any.next) {
212                                 par->win.ctail = prev;
213                         }
214                         prev->any.next = child->any.next;
215                         break;
216                 }
217                 prev = prev->any.next;
218         }
219         par->win.clist = dummy.any.next;
220         rtk_invalidate(par);
221 }
222
223 int rtk_win_has(rtk_widget *par, rtk_widget *child)
224 {
225         rtk_widget *w;
226
227         RTK_ASSERT_TYPE(par, RTK_WIN);
228
229         w = par->win.clist;
230         while(w) {
231                 if(w == child) {
232                         return 1;
233                 }
234                 w = w->any.next;
235         }
236         return 0;
237 }
238
239 /* --- button functions --- */
240 void rtk_bn_mode(rtk_widget *w, int mode)
241 {
242         RTK_ASSERT_TYPE(w, RTK_BUTTON);
243         w->bn.mode = mode;
244         rtk_invalidate(w);
245 }
246
247 void rtk_bn_set_icon(rtk_widget *w, rtk_icon *icon)
248 {
249         rtk_rect rect;
250
251         RTK_ASSERT_TYPE(w, RTK_BUTTON);
252         w->bn.icon = icon;
253
254         calc_widget_rect(w, &rect);
255         rtk_resize(w, rect.width, rect.height);
256         rtk_invalidate(w);
257 }
258
259 rtk_icon *rtk_bn_get_icon(rtk_widget *w)
260 {
261         RTK_ASSERT_TYPE(w, RTK_BUTTON);
262         return w->bn.icon;
263 }
264
265 /* --- constructors --- */
266
267 rtk_widget *rtk_create_window(rtk_widget *par, const char *title, int x, int y,
268                 int width, int height, unsigned int flags)
269 {
270         rtk_widget *w;
271
272         if(!(w = rtk_create_widget())) {
273                 return 0;
274         }
275         w->type = RTK_WIN;
276         if(par) rtk_win_add(par, w);
277         rtk_set_text(w, title);
278         rtk_move(w, x, y);
279         rtk_resize(w, width, height);
280
281         w->any.flags |= flags << 16;
282         return w;
283 }
284
285 rtk_widget *rtk_create_button(rtk_widget *par, const char *str, rtk_callback cbfunc)
286 {
287         rtk_widget *w;
288
289         if(!(w = rtk_create_widget())) {
290                 return 0;
291         }
292         w->type = RTK_BUTTON;
293         if(par) rtk_win_add(par, w);
294         rtk_set_text(w, str);
295         rtk_set_callback(w, cbfunc, 0);
296         return w;
297 }
298
299 rtk_widget *rtk_create_iconbutton(rtk_widget *par, rtk_icon *icon, rtk_callback cbfunc)
300 {
301         rtk_widget *w;
302
303         if(!(w = rtk_create_widget())) {
304                 return 0;
305         }
306         w->type = RTK_BUTTON;
307         if(par) rtk_win_add(par, w);
308         rtk_bn_set_icon(w, icon);
309         rtk_set_callback(w, cbfunc, 0);
310         return w;
311 }
312
313 rtk_widget *rtk_create_label(rtk_widget *par, const char *text)
314 {
315         rtk_widget *w;
316
317         if(!(w = rtk_create_widget())) {
318                 return 0;
319         }
320         w->type = RTK_LABEL;
321         if(par) rtk_win_add(par, w);
322         rtk_set_text(w, text);
323         return w;
324 }
325
326 rtk_widget *rtk_create_checkbox(rtk_widget *par, const char *text, int chk, rtk_callback cbfunc)
327 {
328         rtk_widget *w;
329
330         if(!(w = rtk_create_widget())) {
331                 return 0;
332         }
333         w->type = RTK_CHECKBOX;
334         if(par) rtk_win_add(par, w);
335         rtk_set_text(w, text);
336         rtk_set_value(w, chk ? 1 : 0);
337         rtk_set_callback(w, cbfunc, 0);
338         return w;
339 }
340
341 rtk_widget *rtk_create_separator(rtk_widget *par)
342 {
343         rtk_widget *w;
344
345         if(!(w = rtk_create_widget())) {
346                 return 0;
347         }
348         w->type = RTK_SEP;
349         if(par) rtk_win_add(par, w);
350         return w;
351 }
352
353
354 /* --- icon functions --- */
355 rtk_iconsheet *rtk_load_iconsheet(const char *fname)
356 {
357          rtk_iconsheet *is;
358
359         if(!(is = malloc(sizeof *is))) {
360                 return 0;
361         }
362         is->icons = 0;
363
364         if(!(is->pixels = img_load_pixels(fname, &is->width, &is->height, IMG_FMT_RGBA32))) {
365                 free(is);
366                 return 0;
367         }
368         return is;
369 }
370
371 void rtk_free_iconsheet(rtk_iconsheet *is)
372 {
373         rtk_icon *icon;
374
375         img_free_pixels(is->pixels);
376
377         while(is->icons) {
378                 icon = is->icons;
379                 is->icons = is->icons->next;
380                 free(icon->name);
381                 free(icon);
382         }
383         free(is);
384 }
385
386 rtk_icon *rtk_define_icon(rtk_iconsheet *is, const char *name, int x, int y, int w, int h)
387 {
388         rtk_icon *icon;
389
390         if(!(icon = malloc(sizeof *icon))) {
391                 return 0;
392         }
393         if(!(icon->name = strdup(name))) {
394                 free(icon);
395                 return 0;
396         }
397         icon->width = w;
398         icon->height = h;
399         icon->scanlen = is->width;
400         icon->pixels = is->pixels + y * is->width + x;
401         return icon;
402 }
403
404 #define BEVELSZ         1
405 #define PAD                     2
406 #define OFFS            (BEVELSZ + PAD)
407 #define CHKBOXSZ        (BEVELSZ * 2 + 8)
408
409 static void calc_widget_rect(rtk_widget *w, rtk_rect *rect)
410 {
411         rtk_rect txrect = {0};
412
413         rect->x = w->any.x;
414         rect->y = w->any.y;
415
416         if(w->any.text) {
417                 gfx.textrect(w->any.text, &txrect);
418         }
419
420         switch(w->type) {
421         case RTK_WIN:
422                 rect->width = w->any.width;
423                 rect->height = w->any.height;
424                 break;
425
426         case RTK_BUTTON:
427                 if(w->bn.icon) {
428                         rect->width = w->bn.icon->width + OFFS * 2;
429                         rect->height = w->bn.icon->height + OFFS * 2;
430                 } else {
431                         rect->width = txrect.width + OFFS * 2;
432                         rect->height = txrect.height + OFFS * 2;
433                 }
434                 break;
435
436         case RTK_CHECKBOX:
437                 rect->width = txrect.width + CHKBOXSZ + OFFS * 2 + PAD;
438                 rect->height = txrect.height + OFFS * 2;
439                 break;
440
441         case RTK_LABEL:
442                 rect->width = txrect.width + PAD * 2;
443                 rect->height = txrect.height + PAD * 2;
444                 break;
445
446         case RTK_SEP:
447                 if(w->any.par->win.layout == RTK_VBOX) {
448                         rect->width = w->any.par->any.width - PAD * 2;
449                         rect->height = PAD * 4 + BEVELSZ * 2;
450                 } else if(w->any.par->win.layout == RTK_HBOX) {
451                         rect->width = PAD * 4 + BEVELSZ * 2;
452                         rect->height = w->any.par->any.height - PAD * 2;
453                 } else {
454                         rect->width = rect->height = 0;
455                 }
456                 break;
457
458         default:
459                 rect->width = rect->height = 0;
460         }
461 }
462
463 static int need_relayout(rtk_widget *w)
464 {
465         rtk_widget *c;
466
467         if(w->any.flags & GEOMCHG) {
468                 return 1;
469         }
470
471         if(w->any.type == RTK_WIN) {
472                 c = w->win.clist;
473                 while(c) {
474                         if(need_relayout(c)) {
475                                 return 1;
476                         }
477                         c = c->any.next;
478                 }
479         }
480         return 0;
481 }
482
483 static void calc_layout(rtk_widget *w)
484 {
485         int x, y;
486         rtk_widget *c;
487         rtk_rect rect;
488
489         if(w->any.type == RTK_WIN && w->win.layout != RTK_NONE) {
490                 x = y = PAD;
491
492                 c = w->win.clist;
493                 while(c) {
494                         rtk_move(c, x, y);
495                         calc_layout(c);
496
497                         if(w->win.layout == RTK_VBOX) {
498                                 y += c->any.height + PAD * 2;
499                         } else {
500                                 x += c->any.width + PAD * 2;
501                         }
502
503                         c = c->any.next;
504                 }
505         }
506
507         calc_widget_rect(w, &rect);
508         w->any.width = rect.width;
509         w->any.height = rect.height;
510
511         w->any.flags &= ~GEOMCHG;
512         rtk_invalidate(w);
513 }
514
515 void rtk_draw_widget(rtk_widget *w)
516 {
517         int dirty;
518
519         if(!(w->any.flags & VISIBLE)) {
520                 return;
521         }
522
523         if(need_relayout(w)) {
524                 calc_layout(w);
525         }
526
527         dirty = w->any.flags & DIRTY;
528         if(!dirty && w->any.type != RTK_WIN) {
529                 return;
530         }
531
532         switch(w->any.type) {
533         case RTK_WIN:
534                 draw_window(w);
535                 break;
536
537         case RTK_BUTTON:
538                 draw_button(w);
539                 break;
540
541         case RTK_CHECKBOX:
542                 draw_checkbox(w);
543                 break;
544
545         case RTK_SEP:
546                 draw_separator(w);
547                 break;
548
549         default:
550                 break;
551         }
552
553         if(dirty) {
554                 rtk_validate(w);
555                 invalfb(w);
556         }
557 }
558
559 static void widget_rect(rtk_widget *w, rtk_rect *rect)
560 {
561         rect->x = w->any.x;
562         rect->y = w->any.y;
563         rect->width = w->any.width;
564         rect->height = w->any.height;
565 }
566
567 static void abs_pos(rtk_widget *w, int *xpos, int *ypos)
568 {
569         int x, y, px, py;
570
571         x = w->any.x;
572         y = w->any.y;
573
574         if(w->any.par) {
575                 abs_pos(w->any.par, &px, &py);
576                 x += px;
577                 y += py;
578         }
579
580         *xpos = x;
581         *ypos = y;
582 }
583
584 #define COL_BG          0xff666666
585 #define COL_BGHL        0xff808080
586 #define COL_LBEV        0xffaaaaaa
587 #define COL_SBEV        0xff222222
588 #define COL_TEXT        0xff000000
589 #define COL_WINFRM      0xff2244aa
590 #define COL_WINFRM_LIT  0xff4466ff
591 #define COL_WINFRM_SHAD 0xff051166
592
593 static void hline(int x, int y, int sz, uint32_t col)
594 {
595         rtk_rect rect;
596         rect.x = x;
597         rect.y = y;
598         rect.width = sz;
599         rect.height = 1;
600         gfx.fill(&rect, col);
601 }
602
603 static void vline(int x, int y, int sz, uint32_t col)
604 {
605         rtk_rect rect;
606         rect.x = x;
607         rect.y = y;
608         rect.width = 1;
609         rect.height = sz;
610         gfx.fill(&rect, col);
611 }
612
613 enum {FRM_SOLID, FRM_OUTSET, FRM_INSET};
614
615 enum {UICOL_BG, UICOL_LBEV, UICOL_SBEV};
616 static uint32_t uicol[3];
617
618 static void uicolor(uint32_t col, uint32_t lcol, uint32_t scol)
619 {
620         uicol[UICOL_BG] = col;
621         uicol[UICOL_LBEV] = lcol;
622         uicol[UICOL_SBEV] = scol;
623 }
624
625 static void draw_frame(rtk_rect *rect, int type)
626 {
627         int tlcol, brcol;
628
629         switch(type) {
630         case FRM_SOLID:
631                 tlcol = brcol = 0xff000000;
632                 break;
633         case FRM_OUTSET:
634                 tlcol = uicol[UICOL_LBEV];
635                 brcol = uicol[UICOL_SBEV];
636                 break;
637         case FRM_INSET:
638                 tlcol = uicol[UICOL_SBEV];
639                 brcol = uicol[UICOL_LBEV];
640                 break;
641         default:
642                 break;
643         }
644
645         hline(rect->x, rect->y, rect->width, tlcol);
646         vline(rect->x, rect->y + 1, rect->height - 2, tlcol);
647         hline(rect->x, rect->y + rect->height - 1, rect->width, brcol);
648         vline(rect->x + rect->width - 1, rect->y + 1, rect->height - 2, brcol);
649 }
650
651 #define WINFRM_SZ       2
652 #define WINFRM_TBAR     16
653
654 static void draw_window(rtk_widget *w)
655 {
656         rtk_rect rect, frmrect, tbrect;
657         rtk_widget *c;
658         int win_dirty = w->any.flags & DIRTY;
659
660         if(win_dirty) {
661                 widget_rect(w, &rect);
662
663                 if(w->any.flags & FRAME) {
664                         uicolor(COL_WINFRM, COL_WINFRM_LIT, COL_WINFRM_SHAD);
665
666                         frmrect = rect;
667                         frmrect.width += WINFRM_SZ * 2;
668                         frmrect.height += WINFRM_SZ * 2 + WINFRM_TBAR;
669                         frmrect.x -= WINFRM_SZ;
670                         frmrect.y -= WINFRM_SZ + WINFRM_TBAR;
671
672                         tbrect.x = rect.x;
673                         tbrect.y = rect.y - WINFRM_TBAR;
674                         tbrect.width = rect.width;
675                         tbrect.height = WINFRM_TBAR;
676
677                         draw_frame(&frmrect, FRM_OUTSET);
678                         frmrect.x++;
679                         frmrect.y++;
680                         frmrect.width -= 2;
681                         frmrect.height -= 2;
682                         draw_frame(&frmrect, FRM_INSET);
683
684                         draw_frame(&tbrect, FRM_OUTSET);
685                         tbrect.x++;
686                         tbrect.y++;
687                         tbrect.width -= 2;
688                         tbrect.height -= 2;
689                         gfx.fill(&tbrect, COL_WINFRM);
690
691                         gfx.drawtext(tbrect.x, tbrect.y + tbrect.height - 1, w->any.text);
692                 }
693
694                 gfx.fill(&rect, COL_BG);
695         }
696
697         c = w->win.clist;
698         while(c) {
699                 if(win_dirty) {
700                         rtk_invalidate(c);
701                 }
702                 rtk_draw_widget(c);
703                 c = c->any.next;
704         }
705 }
706
707 static void draw_button(rtk_widget *w)
708 {
709         int pressed;
710         rtk_rect rect;
711
712         widget_rect(w, &rect);
713         abs_pos(w, &rect.x, &rect.y);
714
715         if(w->bn.mode == RTK_TOGGLEBN) {
716                 pressed = w->any.value;
717         } else {
718                 pressed = w->any.flags & PRESS;
719         }
720
721         uicolor(COL_BG, COL_LBEV, COL_SBEV);
722
723         if(rect.width > 2 && rect.height > 2) {
724                 draw_frame(&rect, pressed ? FRM_INSET : FRM_OUTSET);
725
726                 rect.x++;
727                 rect.y++;
728                 rect.width -= 2;
729                 rect.height -= 2;
730         }
731
732         gfx.fill(&rect, w->any.flags & HOVER ? COL_BGHL : COL_BG);
733         if(w->bn.icon) {
734                 int offs = w->any.flags & PRESS ? PAD + 1 : PAD;
735                 gfx.blit(rect.x + offs, rect.y + offs, w->bn.icon);
736         } else {
737                 gfx.fill(&rect, 0xff802020);
738         }
739 }
740
741 static void draw_checkbox(rtk_widget *w)
742 {
743 }
744
745 static void draw_separator(rtk_widget *w)
746 {
747         rtk_widget *win = w->any.par;
748         rtk_rect rect;
749
750         if(!win) return;
751
752         uicolor(COL_BG, COL_LBEV, COL_SBEV);
753
754         widget_rect(w, &rect);
755         abs_pos(w, &rect.x, &rect.y);
756
757         switch(win->win.layout) {
758         case RTK_VBOX:
759                 rect.y += PAD * 2;
760                 rect.height = 2;
761                 break;
762
763         case RTK_HBOX:
764                 rect.x += PAD * 2;
765                 rect.width = 2;
766                 break;
767
768         default:
769                 break;
770         }
771
772         draw_frame(&rect, FRM_INSET);
773 }
774
775
776 static int hittest(rtk_widget *w, int x, int y)
777 {
778         if(x < w->any.x || y < w->any.y) return 0;
779         if(x >= w->any.x + w->any.width) return 0;
780         if(y >= w->any.y + w->any.height) return 0;
781         return 1;
782 }
783
784 static void sethover(rtk_widget *w)
785 {
786         if(hover == w) return;
787
788         if(hover) {
789                 hover->any.flags &= ~HOVER;
790
791                 if(hover->type != RTK_WIN) {
792                         rtk_invalidate(hover);
793                         invalfb(hover);
794                 }
795         }
796         hover = w;
797         if(w) {
798                 w->any.flags |= HOVER;
799
800                 if(w->type != RTK_WIN) {
801                         rtk_invalidate(w);
802                         invalfb(w);
803                 }
804         }
805 }
806
807 static void setpress(rtk_widget *w)
808 {
809         if(pressed == w) return;
810
811         if(pressed) {
812                 pressed->any.flags &= ~PRESS;
813                 rtk_invalidate(pressed);
814                 invalfb(pressed);
815         }
816         pressed = w;
817         if(w) {
818                 w->any.flags |= PRESS;
819                 rtk_invalidate(w);
820                 invalfb(w);
821         }
822 }
823
824 static void click(rtk_widget *w, int x, int y)
825 {
826         switch(w->type) {
827         case RTK_BUTTON:
828                 if(w->bn.mode == RTK_TOGGLEBN) {
829         case RTK_CHECKBOX:
830                         w->any.value ^= 1;
831                 }
832                 if(w->any.cbfunc) {
833                         w->any.cbfunc(w, w->any.cbcls);
834                 }
835                 rtk_invalidate(w);
836                 invalfb(w);
837                 break;
838
839         default:
840                 break;
841         }
842 }
843
844 int rtk_input_key(rtk_widget *w, int key, int press)
845 {
846         return 0;
847 }
848
849 int rtk_input_mbutton(rtk_widget *w, int bn, int press, int x, int y)
850 {
851         if(!hittest(w, x, y)) {
852                 return 0;
853         }
854
855         if(press) {
856                 if(hover && hittest(hover, x, y)) {
857                         setpress(hover);
858                 }
859         } else {
860                 if(pressed && hittest(pressed, x, y)) {
861                         click(pressed, x, y);
862                 }
863                 setpress(0);
864         }
865
866         return 1;
867 }
868
869 int rtk_input_mmotion(rtk_widget *w, int x, int y)
870 {
871         rtk_widget *c;
872
873         if(!hittest(w, x, y)) {
874                 int res = hover ? 1 : 0;
875                 sethover(0);
876                 return res;
877         }
878
879         if(w->type == RTK_WIN) {
880                 c = w->win.clist;
881                 while(c) {
882                         if(hittest(c, x, y)) {
883                                 return rtk_input_mmotion(c, x, y);
884                         }
885                         c = c->any.next;
886                 }
887         }
888
889         if(hover != w) {
890                 sethover(w);
891                 return 1;
892         }
893         return 0;
894 }
895
896 void rtk_fix_rect(rtk_rect *rect)
897 {
898         int x, y, w, h;
899
900         x = rect->x;
901         y = rect->y;
902
903         if(rect->width < 0) {
904                 w = -rect->width;
905                 x += rect->width;
906         } else {
907                 w = rect->width;
908         }
909         if(rect->height < 0) {
910                 h = -rect->height;
911                 y += rect->height;
912         } else {
913                 h = rect->height;
914         }
915
916         rect->x = x;
917         rect->y = y;
918         rect->width = w;
919         rect->height = h;
920 }
921
922 void rtk_rect_union(rtk_rect *a, const rtk_rect *b)
923 {
924         int x0, y0, x1, y1;
925
926         x0 = a->x;
927         y0 = a->y;
928         x1 = a->x + a->width;
929         y1 = a->y + a->height;
930
931         if(b->x < x0) x0 = b->x;
932         if(b->y < y0) y0 = b->y;
933         if(b->x + b->width > x1) x1 = b->x + b->width;
934         if(b->y + b->height > y1) y1 = b->y + b->height;
935
936         a->x = x0;
937         a->y = y0;
938         a->width = x1 - x0;
939         a->height = y1 - y0;
940 }
941
942 static void invalfb(rtk_widget *w)
943 {
944         app_redisplay(w->any.x, w->any.y, w->any.width, w->any.height);
945 }