43de80db560eebb84e4a4b420e8c6a88f6fcd111
[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
10 void rtk_setup(rtk_draw_ops *drawop)
11 {
12         gfx = *drawop;
13 }
14
15 rtk_widget *rtk_create_widget(void)
16 {
17         rtk_widget *w;
18
19         if(!(w = calloc(1, sizeof *w))) {
20                 return 0;
21         }
22         w->any.flags = VISIBLE | ENABLED | GEOMCHG | DIRTY;
23         return w;
24 }
25
26 void rtk_free_widget(rtk_widget *w)
27 {
28         if(!w) return;
29
30         if(w->type == RTK_WIN) {
31                 while(w->win.clist) {
32                         rtk_widget *c = w->win.clist;
33                         w->win.clist = w->win.clist->any.next;
34                         rtk_free_widget(c);
35                 }
36         }
37
38         free(w->any.text);
39         free(w);
40 }
41
42 int rtk_type(rtk_widget *w)
43 {
44         return w->type;
45 }
46
47 void rtk_move(rtk_widget *w, int x, int y)
48 {
49         w->any.x = x;
50         w->any.y = y;
51         w->any.flags |= GEOMCHG;
52 }
53
54 void rtk_pos(rtk_widget *w, int *xptr, int *yptr)
55 {
56         *xptr = w->any.x;
57         *yptr = w->any.y;
58 }
59
60 void rtk_resize(rtk_widget *w, int xsz, int ysz)
61 {
62         w->any.width = xsz;
63         w->any.height = ysz;
64         w->any.flags |= GEOMCHG;
65 }
66
67 void rtk_size(rtk_widget *w, int *xptr, int *yptr)
68 {
69         *xptr = w->any.width;
70         *yptr = w->any.height;
71 }
72
73 int rtk_set_text(rtk_widget *w, const char *str)
74 {
75         rtk_rect rect;
76         char *s = strdup(str);
77         if(!s) return -1;
78
79         free(w->any.text);
80         w->any.text = s;
81
82         calc_widget_rect(w, &rect);
83         rtk_resize(w, rect.width, rect.height);
84         return 0;
85 }
86
87 const char *rtk_get_text(rtk_widget *w)
88 {
89         return w->any.text;
90 }
91
92 void rtk_set_value(rtk_widget *w, int val)
93 {
94         w->any.value = val;
95 }
96
97 int rtk_get_value(rtk_widget *w)
98 {
99         return w->any.value;
100 }
101
102 void rtk_set_callback(rtk_widget *w, rtk_callback cbfunc, void *cls)
103 {
104         w->any.cbfunc = cbfunc;
105         w->any.cbcls = cls;
106 }
107
108 void rtk_win_layout(rtk_widget *w, int layout)
109 {
110         w->win.layout = layout;
111 }
112
113 void rtk_win_clear(rtk_widget *w)
114 {
115         rtk_widget *tmp;
116
117         RTK_ASSERT_TYPE(w, RTK_WIN);
118
119         while(w->win.clist) {
120                 tmp = w->win.clist;
121                 w->win.clist = w->win.clist->any.next;
122                 rtk_free_widget(tmp);
123         }
124
125         w->win.clist = w->win.ctail = 0;
126 }
127
128 void rtk_win_add(rtk_widget *par, rtk_widget *child)
129 {
130         RTK_ASSERT_TYPE(par, RTK_WIN);
131
132         if(rtk_win_has(par, child)) {
133                 return;
134         }
135
136         if(child->any.par) {
137                 rtk_win_rm(child->any.par, child);
138         }
139
140         if(par->win.clist) {
141                 par->win.ctail->any.next = child;
142                 par->win.ctail = child;
143         } else {
144                 par->win.clist = par->win.ctail = child;
145         }
146         child->any.next = 0;
147
148         child->any.par = par;
149 }
150
151 void rtk_win_rm(rtk_widget *par, rtk_widget *child)
152 {
153         rtk_widget *prev, dummy;
154
155         RTK_ASSERT_TYPE(par, RTK_WIN);
156
157         dummy.any.next = par->win.clist;
158         prev = &dummy;
159         while(prev->any.next) {
160                 if(prev->any.next == child) {
161                         if(!child->any.next) {
162                                 par->win.ctail = prev;
163                         }
164                         prev->any.next = child->any.next;
165                         break;
166                 }
167                 prev = prev->any.next;
168         }
169         par->win.clist = dummy.any.next;
170 }
171
172 int rtk_win_has(rtk_widget *par, rtk_widget *child)
173 {
174         rtk_widget *w;
175
176         RTK_ASSERT_TYPE(par, RTK_WIN);
177
178         w = par->win.clist;
179         while(w) {
180                 if(w == child) {
181                         return 1;
182                 }
183                 w = w->any.next;
184         }
185         return 0;
186 }
187
188 /* --- button functions --- */
189
190 void rtk_bn_set_icon(rtk_widget *w, rtk_icon *icon)
191 {
192         RTK_ASSERT_TYPE(w, RTK_BUTTON);
193         w->bn.icon = icon;
194 }
195
196 rtk_icon *rtk_bn_get_icon(rtk_widget *w)
197 {
198         RTK_ASSERT_TYPE(w, RTK_BUTTON);
199         return w->bn.icon;
200 }
201
202 /* --- constructors --- */
203
204 rtk_widget *rtk_create_window(rtk_widget *par, const char *title, int x, int y, int width, int height)
205 {
206         rtk_widget *w;
207
208         if(!(w = rtk_create_widget())) {
209                 return 0;
210         }
211         if(par) rtk_win_add(par, w);
212         rtk_set_text(w, title);
213         rtk_move(w, x, y);
214         rtk_resize(w, width, height);
215         return w;
216 }
217
218 rtk_widget *rtk_create_button(rtk_widget *par, const char *str, rtk_callback cbfunc)
219 {
220         rtk_widget *w;
221
222         if(!(w = rtk_create_widget())) {
223                 return 0;
224         }
225         if(par) rtk_win_add(par, w);
226         rtk_set_text(w, str);
227         rtk_set_callback(w, cbfunc, 0);
228         return w;
229 }
230
231 rtk_widget *rtk_create_iconbutton(rtk_widget *par, rtk_icon *icon, rtk_callback cbfunc)
232 {
233         rtk_widget *w;
234
235         if(!(w = rtk_create_widget())) {
236                 return 0;
237         }
238         if(par) rtk_win_add(par, w);
239         rtk_bn_set_icon(w, icon);
240         rtk_set_callback(w, cbfunc, 0);
241         return w;
242 }
243
244 rtk_widget *rtk_create_label(rtk_widget *par, const char *text)
245 {
246         rtk_widget *w;
247
248         if(!(w = rtk_create_widget())) {
249                 return 0;
250         }
251         if(par) rtk_win_add(par, w);
252         rtk_set_text(w, text);
253         return w;
254 }
255
256 rtk_widget *rtk_create_checkbox(rtk_widget *par, const char *text, int chk, rtk_callback cbfunc)
257 {
258         rtk_widget *w;
259
260         if(!(w = rtk_create_widget())) {
261                 return 0;
262         }
263         if(par) rtk_win_add(par, w);
264         rtk_set_text(w, text);
265         rtk_set_value(w, chk ? 1 : 0);
266         rtk_set_callback(w, cbfunc, 0);
267         return w;
268 }
269
270 /* --- icon functions --- */
271 rtk_iconsheet *rtk_load_iconsheet(const char *fname)
272 {
273          rtk_iconsheet *is;
274
275         if(!(is = malloc(sizeof *is))) {
276                 return 0;
277         }
278         is->icons = 0;
279
280         if(!(is->pixels = img_load_pixels(fname, &is->width, &is->height, IMG_FMT_RGBA32))) {
281                 free(is);
282                 return 0;
283         }
284         return is;
285 }
286
287 void rtk_free_iconsheet(rtk_iconsheet *is)
288 {
289         rtk_icon *icon;
290
291         img_free_pixels(is->pixels);
292
293         while(is->icons) {
294                 icon = is->icons;
295                 is->icons = is->icons->next;
296                 free(icon->name);
297                 free(icon);
298         }
299         free(is);
300 }
301
302 rtk_icon *rtk_define_icon(rtk_iconsheet *is, const char *name, int x, int y, int w, int h)
303 {
304         rtk_icon *icon;
305
306         if(!(icon = malloc(sizeof *icon))) {
307                 return 0;
308         }
309         if(!(icon->name = strdup(name))) {
310                 free(icon);
311                 return 0;
312         }
313         icon->width = w;
314         icon->height = h;
315         icon->scanlen = is->width;
316         icon->pixels = is->pixels + y * is->width + x;
317         return icon;
318 }
319
320 #define BEVELSZ         1
321 #define PAD                     2
322 #define OFFS            (BEVELSZ + PAD)
323 #define CHKBOXSZ        (BEVELSZ * 2 + 8)
324
325 static void calc_widget_rect(rtk_widget *w, rtk_rect *rect)
326 {
327         rtk_rect txrect = {0};
328
329         rect->x = w->any.x;
330         rect->y = w->any.y;
331
332         if(w->any.text) {
333                 gfx.textrect(w->any.text, &txrect);
334         }
335
336         switch(w->type) {
337         case RTK_BUTTON:
338                 if(w->bn.icon) {
339                         rect->width = w->bn.icon->width + OFFS * 2;
340                         rect->height = w->bn.icon->height + OFFS * 2;
341                 } else {
342                         rect->width = txrect.width + OFFS * 2;
343                         rect->height = txrect.height + OFFS * 2;
344                 }
345                 break;
346
347         case RTK_CHECKBOX:
348                 rect->width = txrect.width + CHKBOXSZ + OFFS * 2 + PAD;
349                 rect->height = txrect.height + OFFS * 2;
350                 break;
351
352         case RTK_LABEL:
353                 rect->width = txrect.width + PAD * 2;
354                 rect->height = txrect.height + PAD * 2;
355                 break;
356
357         default:
358                 rect->width = rect->height = 0;
359         }
360 }
361
362 static int need_relayout(rtk_widget *w)
363 {
364         rtk_widget *c;
365
366         if(w->any.flags & GEOMCHG) {
367                 return 1;
368         }
369
370         if(w->any.type == RTK_WIN) {
371                 c = w->win.clist;
372                 while(c) {
373                         if(need_relayout(c)) {
374                                 return 1;
375                         }
376                         c = c->any.next;
377                 }
378         }
379         return 0;
380 }
381
382 static void calc_layout(rtk_widget *w)
383 {
384         int x, y;
385         rtk_widget *c;
386
387         if(w->any.type == RTK_WIN && w->win.layout != RTK_NONE) {
388                 x = y = PAD;
389
390                 c = w->win.clist;
391                 while(c) {
392                         rtk_move(c, x, y);
393                         calc_layout(c);
394
395                         if(w->win.layout == RTK_VBOX) {
396                                 y += c->any.height + PAD;
397                         } else {
398                                 x += c->any.width + PAD;
399                         }
400
401                         c = c->any.next;
402                 }
403         }
404
405         w->any.flags = (w->any.flags & ~GEOMCHG) | DIRTY;
406 }
407
408 static void draw_window(rtk_widget *w);
409 static void draw_button(rtk_widget *w);
410 static void draw_checkbox(rtk_widget *w);
411
412 void rtk_draw_widget(rtk_widget *w)
413 {
414         if(need_relayout(w)) {
415                 calc_layout(w);
416         }
417
418         switch(w->any.type) {
419         case RTK_WIN:
420                 draw_window(w);
421                 break;
422
423         case RTK_BUTTON:
424                 draw_button(w);
425                 break;
426
427         case RTK_CHECKBOX:
428                 draw_checkbox(w);
429                 break;
430
431         default:
432                 break;
433         }
434
435         w->any.flags &= ~DIRTY;
436 }
437
438 static void widget_rect(rtk_widget *w, rtk_rect *rect)
439 {
440         rect->x = w->any.x;
441         rect->y = w->any.y;
442         rect->width = w->any.width;
443         rect->height = w->any.height;
444 }
445
446 static void abs_pos(rtk_widget *w, int *xpos, int *ypos)
447 {
448         int x, y, px, py;
449
450         x = w->any.x;
451         y = w->any.y;
452
453         if(w->any.par) {
454                 abs_pos(w->any.par, &px, &py);
455                 x += px;
456                 y += py;
457         }
458
459         *xpos = x;
460         *ypos = y;
461 }
462
463 #define COL_BG          0xa0a0a0
464 #define COL_LBEV        0xcccccc
465 #define COL_SBEV        0x202020
466 #define COL_TEXT        0
467
468 static void draw_window(rtk_widget *w)
469 {
470         rtk_rect rect;
471
472         widget_rect(w, &rect);
473         gfx.fill(&rect, COL_BG);
474 }
475
476 static void draw_button(rtk_widget *w)
477 {
478         rtk_rect rect;
479
480         widget_rect(w, &rect);
481         abs_pos(w, &rect.x, &rect.y);
482
483         gfx.fill(&rect, COL_BG);
484         if(w->bn.icon) {
485                 gfx.blit(rect.x + OFFS, rect.y + OFFS, w->bn.icon);
486         } else {
487                 gfx.fill(&rect, 0x802020);
488         }
489 }
490
491 static void draw_checkbox(rtk_widget *w)
492 {
493 }