integrating software renderer
[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_BUTTON:
365                 if(w->bn.icon) {
366                         rect->width = w->bn.icon->width + OFFS * 2;
367                         rect->height = w->bn.icon->height + OFFS * 2;
368                 } else {
369                         rect->width = txrect.width + OFFS * 2;
370                         rect->height = txrect.height + OFFS * 2;
371                 }
372                 break;
373
374         case RTK_CHECKBOX:
375                 rect->width = txrect.width + CHKBOXSZ + OFFS * 2 + PAD;
376                 rect->height = txrect.height + OFFS * 2;
377                 break;
378
379         case RTK_LABEL:
380                 rect->width = txrect.width + PAD * 2;
381                 rect->height = txrect.height + PAD * 2;
382                 break;
383
384         case RTK_SEP:
385                 if(w->any.par->win.layout == RTK_VBOX) {
386                         rect->width = w->any.par->any.width - PAD * 2;
387                         rect->height = (PAD + BEVELSZ) * 2;
388                 } else if(w->any.par->win.layout == RTK_HBOX) {
389                         rect->width = (PAD + BEVELSZ) * 2;
390                         rect->height = w->any.par->any.height - PAD * 2;
391                 } else {
392                         rect->width = rect->height = 0;
393                 }
394                 break;
395
396         default:
397                 rect->width = rect->height = 0;
398         }
399 }
400
401 static int need_relayout(rtk_widget *w)
402 {
403         rtk_widget *c;
404
405         if(w->any.flags & GEOMCHG) {
406                 return 1;
407         }
408
409         if(w->any.type == RTK_WIN) {
410                 c = w->win.clist;
411                 while(c) {
412                         if(need_relayout(c)) {
413                                 return 1;
414                         }
415                         c = c->any.next;
416                 }
417         }
418         return 0;
419 }
420
421 static void calc_layout(rtk_widget *w)
422 {
423         int x, y;
424         rtk_widget *c;
425
426         if(w->any.type == RTK_WIN && w->win.layout != RTK_NONE) {
427                 x = y = PAD;
428
429                 c = w->win.clist;
430                 while(c) {
431                         rtk_move(c, x, y);
432                         calc_layout(c);
433
434                         if(w->win.layout == RTK_VBOX) {
435                                 y += c->any.height + PAD;
436                         } else {
437                                 x += c->any.width + PAD;
438                         }
439
440                         c = c->any.next;
441                 }
442         }
443
444         w->any.flags = (w->any.flags & ~GEOMCHG) | DIRTY;
445 }
446
447 void rtk_draw_widget(rtk_widget *w)
448 {
449         if(need_relayout(w)) {
450                 calc_layout(w);
451         }
452
453         switch(w->any.type) {
454         case RTK_WIN:
455                 draw_window(w);
456                 break;
457
458         case RTK_BUTTON:
459                 draw_button(w);
460                 break;
461
462         case RTK_CHECKBOX:
463                 draw_checkbox(w);
464                 break;
465
466         case RTK_SEP:
467                 draw_separator(w);
468                 break;
469
470         default:
471                 break;
472         }
473
474         w->any.flags &= ~DIRTY;
475 }
476
477 static void widget_rect(rtk_widget *w, rtk_rect *rect)
478 {
479         rect->x = w->any.x;
480         rect->y = w->any.y;
481         rect->width = w->any.width;
482         rect->height = w->any.height;
483 }
484
485 static void abs_pos(rtk_widget *w, int *xpos, int *ypos)
486 {
487         int x, y, px, py;
488
489         x = w->any.x;
490         y = w->any.y;
491
492         if(w->any.par) {
493                 abs_pos(w->any.par, &px, &py);
494                 x += px;
495                 y += py;
496         }
497
498         *xpos = x;
499         *ypos = y;
500 }
501
502 #define COL_BG          0xff666666
503 #define COL_LBEV        0xffaaaaaa
504 #define COL_SBEV        0xff333333
505 #define COL_TEXT        0xff000000
506
507 static void hline(int x, int y, int sz, uint32_t col)
508 {
509         rtk_rect rect;
510         rect.x = x;
511         rect.y = y;
512         rect.width = sz;
513         rect.height = 1;
514         gfx.fill(&rect, col);
515 }
516
517 static void vline(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 = 1;
523         rect.height = sz;
524         gfx.fill(&rect, col);
525 }
526
527 enum {FRM_SOLID, FRM_OUTSET, FRM_INSET};
528
529 static void draw_frame(rtk_rect *rect, int type)
530 {
531         int tlcol, brcol;
532
533         switch(type) {
534         case FRM_SOLID:
535                 tlcol = brcol = 0xff000000;
536                 break;
537         case FRM_OUTSET:
538                 tlcol = COL_LBEV;
539                 brcol = COL_SBEV;
540                 break;
541         case FRM_INSET:
542                 tlcol = COL_SBEV;
543                 brcol = COL_LBEV;
544                 break;
545         default:
546                 break;
547         }
548
549         hline(rect->x, rect->y, rect->width, tlcol);
550         vline(rect->x, rect->y + 1, rect->height - 2, tlcol);
551         hline(rect->x, rect->y + rect->height - 1, rect->width, brcol);
552         vline(rect->x + rect->width - 1, rect->y + 1, rect->height - 2, brcol);
553 }
554
555 static void draw_window(rtk_widget *w)
556 {
557         rtk_rect rect;
558         rtk_widget *c;
559
560         widget_rect(w, &rect);
561         gfx.fill(&rect, COL_BG);
562
563         c = w->win.clist;
564         while(c) {
565                 rtk_draw_widget(c);
566                 c = c->any.next;
567         }
568 }
569
570 static void draw_button(rtk_widget *w)
571 {
572         rtk_rect rect;
573
574         widget_rect(w, &rect);
575         abs_pos(w, &rect.x, &rect.y);
576
577         if(rect.width > 2 && rect.height > 2) {
578                 draw_frame(&rect, FRM_OUTSET);
579
580                 rect.x++;
581                 rect.y++;
582                 rect.width -= 2;
583                 rect.height -= 2;
584         }
585
586         gfx.fill(&rect, COL_BG);
587         if(w->bn.icon) {
588                 gfx.blit(rect.x + OFFS, rect.y + OFFS, w->bn.icon);
589         } else {
590                 gfx.fill(&rect, 0xff802020);
591         }
592 }
593
594 static void draw_checkbox(rtk_widget *w)
595 {
596 }
597
598 static void draw_separator(rtk_widget *w)
599 {
600         rtk_widget *win = w->any.par;
601         rtk_rect rect;
602
603         widget_rect(w, &rect);
604         abs_pos(w, &rect.x, &rect.y);
605
606         draw_frame(&rect, FRM_INSET);
607 }