exhibit ui
[laserbrain_demo] / src / ui_exhibit.cc
1 #include <assert.h>
2 #if defined(WIN32) || defined(__WIN32__)
3 #include <malloc.h>
4 #else
5 #include <alloca.h>
6 #endif
7 #include <drawtext.h>
8 #include "ui_exhibit.h"
9 #include "ui.h"
10 #include "app.h"
11 #include "snode.h"
12 #include "sdr.h"
13 #include "rtarg.h"
14
15 struct Rect {
16         float x, y, w, h;
17 };
18
19 static void draw_frame(const Rect &rect);
20 static void draw_titlebar(const Rect &rect);
21 static void draw_tabs(const Rect &rect);
22 static void draw_text(const Rect &rect);
23 static void layout_text(const char *text);
24
25 static struct dtx_font *font;
26 static int font_size;
27 static unsigned int fontsdr;
28
29 static float aspect;
30 static int ui_width, ui_height;
31
32 static RenderTarget *rtarg;
33 static const SceneNode *parent;
34 static Vec3 pos;
35 static Vec2 size;
36 static int text_padding;
37 static float text_scale = 0.65f;
38 static Exhibit *ex;
39 static int vis_tab, num_tabs;
40 static std::vector<std::string> tab_names;
41 static float scroll;
42 static std::vector<const char*> text_lines;
43 static int max_line_size;
44 static AudioStream *voice;
45
46 enum {COL_BG, COL_FG, COL_FRM};
47 static float color[][3] = {
48         {0.014, 0.016, 0.04},   // COL_BG
49         {0.31, 0.58, 0.9},      // COL_FG
50         {0.19, 0.23, 0.46}      // COL_FRM
51 };
52
53
54 bool exui_init()
55 {
56         if(!(font = dtx_open_font_glyphmap("data/ui_en.glyphmap"))) {
57                 error_log("failed to open exhibit ui font\n");
58                 return false;
59         }
60         font_size = dtx_get_glyphmap_ptsize(dtx_get_glyphmap(font, 0));
61
62         if(!(fontsdr = create_program_load("sdr/dfont.v.glsl", "sdr/dfont.p.glsl"))) {
63                 error_log("failed to load font shader\n");
64                 return false;
65         }
66
67         size.x = 15;
68         size.y = 18;
69         text_padding = 6;
70
71         aspect = size.x / size.y;
72         ui_height = 512;
73         ui_width = ui_height * aspect;
74
75         rtarg = new RenderTarget;
76         if(!rtarg->create(ui_width, ui_height, GL_RGBA)) {
77                 error_log("failed to create exui render target\n");
78                 return false;
79         }
80
81         return true;
82 }
83
84 void exui_shutdown()
85 {
86         dtx_close_font(font);
87         delete rtarg;
88 }
89
90 void exui_setnode(const SceneNode *node)
91 {
92         parent = node;
93 }
94
95 void exui_change_tab(int dir)
96 {
97         vis_tab = (vis_tab + dir) % num_tabs;
98 }
99
100 void exui_scroll(float delta)
101 {
102 }
103
104 bool exui_raycast(const Ray &ray)
105 {
106         return false;
107 }
108
109 void exui_update(float dt)
110 {
111         if(exsel_active.ex != ex) {
112                 ex = exsel_active.ex;
113                 scroll = 0.0f;
114                 vis_tab = 0;
115                 num_tabs = 0;
116                 tab_names.clear();
117                 text_lines.clear();
118                 if(voice) voice->stop();
119
120                 if(ex) {
121                         int num_data = ex->data.size();
122                         for(int i=0; i<num_data; i++) {
123                                 if(ex->data[i].type == EXDATA_INFO) {
124                                         layout_text(ex->data[i].text.c_str());
125                                         voice = ex->data[i].voice;
126                                         ++num_tabs;
127                                         tab_names.push_back("info");
128                                 }
129                         }
130
131                         if(voice) {
132                                 voice->play(AUDIO_PLAYMODE_ONCE);
133                         }
134                 } else {
135                         voice = 0;
136                 }
137         }
138 }
139
140 static void draw_2d_ui()
141 {
142         dtx_use_font(font, font_size);
143         float rowspc = dtx_line_height() * text_scale;
144
145         glMatrixMode(GL_PROJECTION);
146         glPushMatrix();
147         glLoadIdentity();
148         glTranslatef(-1, 1, 0);
149         glScalef(2.0 / ui_width, -2.0 / ui_height, 1);
150
151         glMatrixMode(GL_MODELVIEW);
152         glPushMatrix();
153         glLoadIdentity();
154
155         glUseProgram(0);
156
157         glPushAttrib(GL_ENABLE_BIT);
158         glDisable(GL_TEXTURE_2D);
159         glDisable(GL_LIGHTING);
160         glDisable(GL_DEPTH_TEST);
161         glEnable(GL_SCISSOR_TEST);
162
163         Rect rect = {0, 0, (float)ui_width, (float)ui_height};
164         draw_frame(rect);
165         Rect tbar_rect = {rect.x, rect.y, rect.w, rowspc + text_padding};       // half the padding
166         draw_titlebar(tbar_rect);
167         Rect tabs_rect = {tbar_rect.x, tbar_rect.y + tbar_rect.h, tbar_rect.w, tbar_rect.h};
168         draw_tabs(tabs_rect);
169
170         if(num_tabs) {
171                 switch(ex->data[vis_tab].type) {
172                 case EXDATA_INFO:
173                         {
174                                 Rect text_rect = {rect.x, tabs_rect.y + tabs_rect.h, rect.w, rect.h - tabs_rect.y - tabs_rect.h};
175                                 draw_text(text_rect);
176                         }
177                         break;
178
179                 default:
180                         break;
181                 }
182         }
183
184         glPopAttrib();
185
186         glMatrixMode(GL_PROJECTION);
187         glPopMatrix();
188         glMatrixMode(GL_MODELVIEW);
189         glPopMatrix();
190 }
191
192 void exui_draw()
193 {
194         if(!exsel_active) return;
195         if(!font) return;
196
197         // render the 2D UI in a texture
198         push_render_target(rtarg);
199         glClearColor(0, 1, 0, 0);
200         glClear(GL_COLOR_BUFFER_BIT);
201         draw_2d_ui();
202         pop_render_target();
203
204         // place UI image into the scene
205         glMatrixMode(GL_MODELVIEW);
206         glPushMatrix();
207
208         Mat4 mvmat;
209         glGetFloatv(GL_MODELVIEW_MATRIX, mvmat[0]);
210         if(parent) {
211                 mvmat = parent->get_matrix() * mvmat;
212         }
213         mvmat.translate(pos.x, pos.y, pos.z);
214
215         mvmat[0][0] = mvmat[1][1] = mvmat[2][2] = 1.0f;
216         mvmat[0][1] = mvmat[0][2] = mvmat[1][0] = mvmat[2][0] = mvmat[1][2] = mvmat[2][1] = 0.0f;
217         glLoadMatrixf(mvmat[0]);
218
219         glPushAttrib(GL_ENABLE_BIT);
220         glEnable(GL_BLEND);
221         glBlendFunc(GL_SRC_ALPHA, GL_ONE);
222         glEnable(GL_TEXTURE_2D);
223         glDepthMask(0);
224
225         glUseProgram(0);
226         bind_texture(rtarg->texture());
227
228         glMatrixMode(GL_TEXTURE);
229         glLoadMatrixf(rtarg->texture_matrix()[0]);
230
231         glBegin(GL_QUADS);
232         glColor3f(1, 1, 1);
233         glTexCoord2f(0, 0); glVertex2f(-size.x / 2, -size.y / 2);
234         glTexCoord2f(1, 0); glVertex2f(size.x / 2, -size.y / 2);
235         glTexCoord2f(1, 1); glVertex2f(size.x / 2, size.y / 2);
236         glTexCoord2f(0, 1); glVertex2f(-size.x / 2, size.y / 2);
237         glEnd();
238
239         glLoadIdentity();
240
241         glDepthMask(1);
242         glPopAttrib();
243
244         glMatrixMode(GL_MODELVIEW);
245         glPopMatrix();
246 }
247
248 static inline float *vrect(const Rect &rect, int i)
249 {
250         static float v[2];
251         v[0] = ((i + 1) & 2) ? rect.x + rect.w : rect.x;
252         v[1] = (i & 2) ? rect.y : rect.y + rect.h;
253         return v;
254 }
255
256 static inline void draw_rect(const Rect &rect, int col)
257 {
258         glBegin(GL_QUADS);
259         glColor3fv(color[col]);
260         for(int i=0; i<4; i++)
261                 glVertex2fv(vrect(rect, i));
262         glEnd();
263 }
264
265 static void clip_rect(const Rect &rect)
266 {
267         glScissor(rect.x, ui_height - rect.y - rect.h, rect.w, rect.h);
268 }
269
270 static void draw_frame(const Rect &rect)
271 {
272         clip_rect(rect);
273
274         draw_rect(rect, COL_BG);
275         glLineWidth(3.0);
276         glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
277         draw_rect(rect, COL_FRM);
278         glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
279 }
280
281 static void draw_titlebar(const Rect &rect)
282 {
283         clip_rect(rect);
284
285         draw_rect(rect, COL_FRM);
286
287         const char *title = ex->get_name();
288         if(title) {
289                 glUseProgram(fontsdr);
290
291                 glPushMatrix();
292                 glTranslatef(rect.x + text_padding, rect.y + rect.h - text_padding, 0);
293                 glScalef(text_scale, -text_scale, text_scale);
294
295                 glColor3fv(color[COL_BG]);
296                 dtx_string(ex->get_name());
297                 glPopMatrix();
298
299                 glUseProgram(0);
300         }
301 }
302
303 static void draw_tabs(const Rect &rect)
304 {
305         if(!num_tabs) return;
306
307         clip_rect(rect);
308
309         glLineWidth(1);
310         if(num_tabs == 1) {
311                 glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
312                 draw_rect(rect, COL_FRM);
313                 glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
314         }
315
316         int max_tab_size = ui_width / 2;
317         int tab_size = std::min(max_tab_size, ui_width / num_tabs);
318
319         for(int i=0; i<num_tabs; i++) {
320                 Rect tr = {rect.x + i * tab_size, rect.y, (float)tab_size, rect.h};
321
322                 clip_rect(tr);
323
324                 if(vis_tab == i) {
325                         draw_rect(tr, COL_FRM);
326                 } else {
327                         glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
328                         draw_rect(tr, COL_FRM);
329                         glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
330                 }
331
332                 glPushMatrix();
333                 glTranslatef(tr.x + text_padding, tr.y + tr.h - text_padding, 0);
334                 glScalef(text_scale, -text_scale, text_scale);
335
336                 glUseProgram(fontsdr);
337                 glColor3fv(color[vis_tab == i ? COL_BG : COL_FRM]);
338                 dtx_string(tab_names[i].c_str());
339                 glUseProgram(0);
340
341                 glPopMatrix();
342         }
343 }
344
345 static void draw_text(const Rect &rect)
346 {
347         clip_rect(rect);
348
349         char *buf = (char*)alloca(max_line_size + 1);
350
351         float dy = dtx_line_height();
352
353         glUseProgram(fontsdr);
354
355         glMatrixMode(GL_MODELVIEW);
356         glPushMatrix();
357         glTranslatef(rect.x + text_padding, rect.y + dy + text_padding, 0);
358         glScalef(text_scale, -text_scale, text_scale);
359
360         glColor3fv(color[COL_FG]);
361
362         int nlines = text_lines.size() - 1;
363         for(int i=0; i<nlines; i++) {
364                 if(i < nlines - 1) {
365                         int sz = text_lines[i + 1] - text_lines[i];
366                         assert(sz <= max_line_size);
367                         memcpy(buf, text_lines[i], sz);
368                         buf[sz] = 0;
369                 } else {
370                         buf = (char*)text_lines[i];
371                 }
372
373                 dtx_position(0, -dy * i);
374                 dtx_string(buf);
375         }
376         dtx_position(0, 0);
377
378         glPopMatrix();
379         glUseProgram(0);
380 }
381
382 static void layout_text(const char *text)
383 {
384         text_lines.clear();
385         if(!text) return;
386         if(!font) return;
387
388         dtx_use_font(font, font_size);
389
390         int left_margin = text_padding;
391         int right_margin = ui_width - text_padding;
392
393         text_lines.push_back(text);
394         const char *last_break = 0;
395         max_line_size = 1;
396
397         while(*text) {
398                 if(*text == '\n') {     // paragraph break
399                         text_lines.push_back(text);
400                         text_lines.push_back(++text);
401                         last_break = 0;
402                         continue;
403                 }
404
405                 int code = dtx_utf8_char_code(text);
406                 const char *next = dtx_utf8_next_char((char*)text);
407
408                 struct dtx_box box;
409                 dtx_substring_box(text_lines.back(), 0, text - text_lines.back(), &box);
410                 float pos = left_margin + (box.width + box.x) * text_scale;
411
412                 if(code < 256 && isspace(code)) {
413                         last_break = text;
414                 }
415
416                 if(pos > right_margin) {
417                         if(text == text_lines.back()) {
418                                 // not even a single character fits on a line... abort
419                                 warning_log("text layout failed. glyph %d doesn't fit in line (%d)\n", code, right_margin - left_margin);
420                                 text_lines.clear();
421                                 return;
422                         }
423                         if(last_break) {
424                                 text_lines.push_back(last_break + 1);
425                                 last_break = 0;
426                         } else {
427                                 // no good point to break, just break here
428                                 text_lines.push_back(text);
429                         }
430
431                         int d = text_lines.back() - (text_lines[text_lines.size() - 2]);
432                         if(d > max_line_size) max_line_size = d;
433                 }
434                 text = next;
435         }
436         text_lines.push_back(0);
437
438         /*
439         debug_log("text layout:\n");
440         for(size_t i=0; i<text_lines.size() - 1; i++) {
441                 const char *p = text_lines[i];
442                 while(*p && p != text_lines[i + 1]) {
443                         putchar(*p);
444                         ++p;
445                 }
446                 putchar('\n');
447         }
448         debug_log("---\n");
449         */
450 }