initial commit
[ld42_outofspace] / src / uitheme.cc
1 #include <string.h>
2 #include <string>
3 #include <map>
4 #include "goatkit/goatkit.h"
5
6 #if !defined(WIN32) && !defined(__WIN32__)
7 #include <alloca.h>
8 #else
9 #include <malloc.h>
10 #endif
11
12 #include <GL/glut.h>
13
14 #define FONT    GLUT_BITMAP_HELVETICA_18
15
16 using namespace goatkit;
17
18 typedef void (*DrawFunc)(const Widget*);
19
20 static void draw_button(const Widget *w);
21 static void draw_checkbox(const Widget *w);
22 static void draw_label(const Widget *w);
23 static void draw_slider(const Widget *w);
24 static void draw_textbox(const Widget *w);
25 static float calc_text_width(const char *text);
26 static void draw_text(float x, float y, const char *text);
27 static void draw_rect(const Widget *w, float x, float y, float xsz, float ysz);
28
29 static void get_fgcolor(const Widget *w, float *col);
30 static void get_bgcolor(const Widget *w, float *col);
31
32 static struct {
33         const char *name;
34         DrawFunc func;
35 } widget_funcs[] = {
36         { "button", draw_button },
37         { "checkbox", draw_checkbox },
38         { "label", draw_label },
39         { "slider", draw_slider },
40         { "textbox", draw_textbox },
41         { 0, 0 }
42 };
43
44 static bool initialized;
45 static std::map<std::string, DrawFunc> funcmap;
46
47 /* theme colors */
48 static float fgcol[] = {0.8, 0.6, 0.4, 1.0};
49 static float fgcol_off[] = {0.65, 0.65, 0.6, 1.0};
50 static float fgcol_inact[] = {0.4, 0.4, 0.4, 1.0};
51 static float bgcol[] = {0.3, 0.3, 0.3, 1.0};
52 static float bgcol_off[] = {0.3, 0.3, 0.3, 1.0};
53 static float bgcol_inact[] = {0.3, 0.3, 0.3, 1.0};
54
55
56 extern "C" goatkit::WidgetDrawFunc get_widget_func(const char *name)
57 {
58         int i;
59
60         if(!initialized) {
61                 for(i=0; widget_funcs[i].func; i++) {
62                         funcmap[widget_funcs[i].name] = widget_funcs[i].func;
63                 }
64
65                 initialized = true;
66         }
67         return funcmap[name];
68 }
69
70 // register ourselves as a built-in theme
71 GOATKIT_BUILTIN_THEME("simple", get_widget_func);
72
73
74 #define VIS_THRES       0.0001
75
76 static void begin_drawing(const Widget *w)
77 {
78         Vec2 pos = w->get_position();
79
80         glPushAttrib(GL_ENABLE_BIT | GL_COLOR_BUFFER_BIT);
81         glDisable(GL_LIGHTING);
82         glDisable(GL_DEPTH_TEST);
83         glEnable(GL_BLEND);
84         glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
85
86         glMatrixMode(GL_MODELVIEW);
87         glPushMatrix();
88         glTranslatef(pos.x, pos.y, 0);
89 }
90
91 static void end_drawing(const Widget *w)
92 {
93         glPopMatrix();
94         glPopAttrib();
95 }
96
97
98 #define LERP(a, b, t)   ((a) + ((b) - (a)) * t)
99
100 static void draw_button(const Widget *w)
101 {
102         Vec2 sz = w->get_size();
103         float bnaspect = sz.x / sz.y;
104
105         float pressed = w->get_pressed();
106         float vis = w->get_visibility();
107
108         if(vis < VIS_THRES) {
109                 return;
110         }
111
112         float tor_rad = sz.x * 0.5 * vis;
113         float scale = LERP(1.0, 0.85, pressed);
114
115         float fg[4], bg[4];
116         get_fgcolor(w, fg);
117         get_bgcolor(w, bg);
118
119         begin_drawing(w);
120
121         glTranslatef(sz.x / 2.0, sz.y / 2.0, 0.0);
122         glScalef(1, 1, 1.0 / sz.x);
123         glRotatef(90, 1, 0, 0);
124
125         glColor4fv(fg);
126         glutSolidTorus(scale * sz.y / 2.0, scale * tor_rad / 2.0, 18, 16);
127
128         glScalef(1.0 - 0.1 / bnaspect, 1.0, 1.0 - 0.1);
129         glColor4fv(bg);
130         glutSolidTorus(scale * sz.y / 2.0, scale * tor_rad / 2.0, 18, 16);
131
132         if(vis >= 1.0) {
133                 glTranslatef(-calc_text_width(w->get_text()) / 2.0, 0, -5);
134                 glColor4fv(fg);
135                 draw_text(0, 0, w->get_text());
136         }
137
138         end_drawing(w);
139 }
140
141 static void draw_checkbox(const Widget *w)
142 {
143         Vec2 sz = w->get_size();
144         float vis = w->get_visibility();
145
146         if(vis < VIS_THRES) {
147                 return;
148         }
149
150         float fg[4];
151         get_fgcolor(w, fg);
152
153         goatkit::CheckBox *cbox = (goatkit::CheckBox*)w;
154
155         begin_drawing(w);
156
157         draw_rect(w, 0, 0, sz.y, sz.y);
158
159         float state = cbox->get_checked();
160
161         float bottom[2] = {sz.y / 2, sz.y - 1};
162         float left[2] = {-2, sz.y / 4};
163         float right[2] = {sz.y, -4};
164
165         if(state > 0.0) {
166                 // draw tickmark
167                 glBegin(GL_TRIANGLES);
168                 glColor4fv(fg);
169                 float t = state * 2.0 > 1.0 ? 1.0 : state * 2.0;
170                 glVertex2f(left[0], left[1]);
171                 glVertex2f(LERP(left[0], bottom[0], t), LERP(left[1], bottom[1], t));
172                 glVertex2f(LERP(left[0], bottom[0], t), LERP(left[1], bottom[1] - sz.y / 3, t));
173
174                 if((t = state * 2.0 - 1.0) > 0.0) {
175                         glVertex2f(bottom[0], bottom[1]);
176                         glVertex2f(LERP(bottom[0], right[0], t), LERP(bottom[1], right[1], t));
177                         glVertex2f(bottom[0], bottom[1] - sz.y / 3);
178                 }
179                 glEnd();
180         }
181
182         glTranslatef(sz.y * 1.5, sz.y / 2.0 + 5, 0);
183         glColor4fv(fg);
184         draw_text(0, 0, w->get_text());
185
186         end_drawing(w);
187 }
188
189 static void draw_label(const Widget *w)
190 {
191         Vec2 sz = w->get_size();
192         float vis = w->get_visibility();
193
194         if(vis < VIS_THRES) {
195                 return;
196         }
197
198         float fg[4];
199         get_fgcolor(w, fg);
200
201         begin_drawing(w);
202
203         glTranslatef((sz.x - calc_text_width(w->get_text())) / 2.0, sz.y / 2.0, 0);
204         glColor4fv(fg);
205         draw_text(0, 0, w->get_text());
206
207         end_drawing(w);
208 }
209
210 static void draw_slider(const Widget *w)
211 {
212         Vec2 sz = w->get_size();
213         float vis = w->get_visibility();
214
215         if(vis < VIS_THRES) {
216                 return;
217         }
218
219         float fg[4], bg[4];
220         get_fgcolor(w, fg);
221         get_bgcolor(w, bg);
222
223         Slider *slider = (Slider*)w;
224         float pad = slider->get_padding();
225         float handle_width = pad * 2.0;
226
227         float value = slider->get_value();
228         char valtext[16];
229         sprintf(valtext, "%g", value);
230
231         float trough_sz = sz.x - 2.0 * pad;
232         float x = pad + trough_sz * slider->get_value_norm();
233
234         float act_height = sz.y / 2.0;
235
236         begin_drawing(w);
237
238         float step = slider->get_step();
239         if(step > 0.0) {
240                 float beg = slider->get_range_min();
241                 float end = slider->get_range_max();
242                 int num_seg = (end - beg) / step;
243                 int num_ticks = num_seg + 1;
244                 float x = pad;
245                 float dx = trough_sz / num_seg;
246
247                 glLineWidth(1.0);
248                 glBegin(GL_LINES);
249                 glColor4fv(fg);
250                 for(int i=0; i<num_ticks; i++) {
251                         glVertex2f(x + 0.5, sz.y / 3.0 + 0.5);
252                         glVertex2f(x + 0.5, sz.y / 2.0 + 0.5);
253                         x += dx;
254                 }
255                 glEnd();
256         }
257
258         draw_rect(w, 0, sz.y - act_height + act_height / 3, sz.x, act_height / 3);
259         draw_rect(w, x - handle_width / 2.0, sz.y - act_height, handle_width, act_height);
260         draw_text(x - calc_text_width(valtext) / 2.0, act_height / 2.0, valtext);
261
262         end_drawing(w);
263 }
264
265 static void draw_textbox(const Widget *w)
266 {
267         if(!w->is_visible()) {
268                 return;
269         }
270
271         Vec2 sz = w->get_size();
272
273         float fg[4];
274         get_fgcolor(w, fg);
275
276         TextBox *tbox = (TextBox*)w;
277
278         begin_drawing(w);
279
280         draw_rect(w, 0, 0, sz.x, sz.y);
281
282         const char *str = tbox->get_text();
283         char *buf = (char*)alloca(strlen(str) + 1);
284
285         // figure out how many characters fit in the textbox
286         float tsz = 0.0;
287         const char *sptr = str;
288         char *dptr = buf;
289         while(*sptr) {
290                 float nsz = tsz + glutBitmapWidth(FONT, *sptr);
291                 if(nsz >= sz.x) {
292                         break;
293                 }
294                 *dptr++ = *sptr++;
295                 tsz = nsz;
296         }
297         *dptr = 0;
298
299         glColor4fv(fg);
300         draw_text(2, 2.0 * sz.y / 3.0, buf);
301
302         // draw the cursor
303         int cursor = tbox->get_cursor();
304         float cx = 1.0;
305
306         if(cursor > 0) {
307                 memcpy(buf, str, cursor);
308                 buf[cursor] = 0;
309                 cx += calc_text_width(buf);
310         }
311
312         glBlendFunc(GL_SRC_ALPHA, GL_ONE);
313         glColor4f(1.0 - fg[0], 1.0 - fg[1], 1.0 - fg[2], fg[3]);
314         draw_text(cx, 2.0 * sz.y / 3.0, "|");
315         glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
316
317         end_drawing(w);
318 }
319
320 static float calc_text_width(const char *text)
321 {
322         float res = 0.0f;
323
324         while(*text) {
325                 res += glutBitmapWidth(FONT, *text++);
326         }
327         return res;
328 }
329
330 static void draw_text(float x, float y, const char *text)
331 {
332         glRasterPos2f(x, y);
333
334         while(*text) {
335                 glutBitmapCharacter(FONT, *text++);
336         }
337 }
338
339 #define DISP(s, t)              LERP((s) * 2.0, 0, (t))
340 static void draw_rect(const Widget *w, float x, float y, float xsz, float ysz)
341 {
342         float fg[4], bg[4];
343         float vis = w->get_visibility();
344
345         get_fgcolor(w, fg);
346         get_bgcolor(w, bg);
347
348         glBegin(GL_QUADS);
349         glColor4fv(bg);
350         glVertex2f(x, y);
351         glVertex2f(x + xsz, y);
352         glVertex2f(x + xsz, y + ysz);
353         glVertex2f(x, y + ysz);
354         glEnd();
355
356         glLineWidth(w->get_focus() + 1.0);
357
358         glPushMatrix();
359         glTranslatef(0.5, 0.5, 0);
360
361         glBegin(GL_LINES);
362         glColor4fv(fg);
363
364         // top
365         glVertex2f(x - DISP(xsz, vis), y);
366         glVertex2f(x + xsz - DISP(xsz, vis), y);
367
368         // right
369         glVertex2f(x + xsz, y - DISP(ysz, vis));
370         glVertex2f(x + xsz, y + ysz - DISP(ysz, vis));
371
372         // bottom
373         glVertex2f(x + xsz + DISP(xsz, vis), y + ysz);
374         glVertex2f(x + DISP(xsz, vis), y + ysz);
375
376         // left
377         glVertex2f(x, y + ysz + DISP(ysz, vis));
378         glVertex2f(x, y + DISP(ysz, vis));
379
380         glEnd();
381         glPopMatrix();
382
383         glLineWidth(1);
384 }
385
386
387 static void get_fgcolor(const Widget *w, float *col)
388 {
389         float hover = w->get_under_mouse();
390         float act = w->get_active();
391         float vis = w->get_visibility();
392
393         for(int i=0; i<4; i++) {
394                 float c = LERP(fgcol_off[i], fgcol[i], hover);
395                 col[i] = LERP(fgcol_inact[i], c, act);
396         }
397         col[3] *= vis;
398 }
399
400 static void get_bgcolor(const Widget *w, float *col)
401 {
402         float hover = w->get_under_mouse();
403         float act = w->get_active();
404         float vis = w->get_visibility();
405
406         for(int i=0; i<4; i++) {
407                 float c = LERP(bgcol_off[i], bgcol[i], hover);
408                 col[i] = LERP(bgcol_inact[i], c, act);
409         }
410         col[3] *= vis;
411 }