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