e583e5d5dd31d568e5e66ae6617c338567c62d76
[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 "shader.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 float aspect;
26 static int ui_width, ui_height;
27
28 static RenderTarget *rtarg;
29 static const SceneNode *parent;
30 static Vec3 pos;
31 static Vec2 size;
32 static int text_padding;
33 static float text_scale = 1.0f;//0.05f;
34 static Exhibit *ex;
35 static int vis_tab;
36 static float scroll;
37 static std::vector<const char*> text_lines;
38 static int max_line_size;
39 static AudioStream *voice;
40
41 enum {COL_BG, COL_FG, COL_FRM};
42 static float color[][3] = {
43         {0.014, 0.016, 0.04},   // COL_BG
44         {0.31, 0.58, 0.9},      // COL_FG
45         {0.19, 0.23, 0.46}      // COL_FRM
46 };
47
48
49 bool exui_init()
50 {
51         size.x = 15;
52         size.y = 18;
53         text_padding = 6;
54
55         aspect = size.x / size.y;
56         ui_height = 512;
57         ui_width = ui_height * aspect;
58
59         rtarg = new RenderTarget;
60         if(!rtarg->create(ui_width, ui_height, GL_SRGB_ALPHA)) {
61                 error_log("failed to create exui render target\n");
62                 return false;
63         }
64
65         return true;
66 }
67
68 void exui_shutdown()
69 {
70         delete rtarg;
71 }
72
73 void exui_setnode(const SceneNode *node)
74 {
75         parent = node;
76 }
77
78 void exui_change_tab(int dir)
79 {
80 }
81
82 void exui_scroll(float delta)
83 {
84 }
85
86 bool exui_raycast(const Ray &ray)
87 {
88         return false;
89 }
90
91 void exui_update(float dt)
92 {
93         if(exsel_active.ex != ex) {
94                 ex = exsel_active.ex;
95                 scroll = 0.0f;
96                 vis_tab = 0;
97                 text_lines.clear();
98                 if(voice) voice->stop();
99
100                 if(ex) {
101                         int num_data = ex->data.size();
102                         for(int i=0; i<num_data; i++) {
103                                 if(ex->data[i].type == EXDATA_INFO) {
104                                         layout_text(ex->data[i].text.c_str());
105                                         voice = ex->data[i].voice;
106                                 }
107                         }
108
109                         if(voice) {
110                                 voice->play(AUDIO_PLAYMODE_ONCE);
111                         }
112                 } else {
113                         voice = 0;
114                 }
115         }
116 }
117
118 static void draw_2d_ui()
119 {
120         dtx_use_font(ui_font, ui_font_size);
121         float rowspc = dtx_line_height() * text_scale;
122
123         glMatrixMode(GL_PROJECTION);
124         glPushMatrix();
125         glLoadIdentity();
126         glTranslatef(-1, 1, 0);
127         glScalef(2.0 / ui_width, -2.0 / ui_height, 1);
128
129         glMatrixMode(GL_MODELVIEW);
130         glPushMatrix();
131         glLoadIdentity();
132
133         bind_shader(0);
134
135         glPushAttrib(GL_ENABLE_BIT);
136         glDisable(GL_TEXTURE_2D);
137         glDisable(GL_LIGHTING);
138         glDisable(GL_DEPTH_TEST);
139
140         Rect rect = {0, 0, (float)ui_width, (float)ui_height};
141         draw_frame(rect);
142         Rect tbar_rect = {rect.x, rect.y, rect.w, rowspc + text_padding};       // half the padding
143         draw_titlebar(tbar_rect);
144         Rect tabs_rect = {tbar_rect.x, tbar_rect.y + tbar_rect.h, tbar_rect.w, tbar_rect.h};
145         draw_tabs(tabs_rect);
146         Rect text_rect = {rect.x, tabs_rect.y + tabs_rect.h, rect.w, rect.h - tabs_rect.y - tabs_rect.h};
147         draw_text(text_rect);
148
149         glPopAttrib();
150
151         glMatrixMode(GL_PROJECTION);
152         glPopMatrix();
153         glMatrixMode(GL_MODELVIEW);
154         glPopMatrix();
155 }
156
157 void exui_draw()
158 {
159         if(!exsel_active) return;
160         if(!ui_font) return;
161
162         // render the 2D UI in a texture
163         push_render_target(rtarg);
164         glClearColor(0, 1, 0, 0);
165         glClear(GL_COLOR_BUFFER_BIT);
166         draw_2d_ui();
167         pop_render_target();
168
169         // place UI image into the scene
170         glMatrixMode(GL_MODELVIEW);
171         glPushMatrix();
172
173         Mat4 mvmat;
174         glGetFloatv(GL_MODELVIEW_MATRIX, mvmat[0]);
175
176         /*
177         if(parent) {
178                 glMultMatrixf(parent->get_matrix()[0]);
179         }
180         glTranslatef(pos.x, pos.y, pos.z);
181         */
182         if(parent) {
183                 mvmat = parent->get_matrix() * mvmat;
184         }
185         mvmat.translate(pos.x, pos.y, pos.z);
186
187         mvmat[0][0] = mvmat[1][1] = mvmat[2][2] = 1.0f;
188         mvmat[0][1] = mvmat[0][2] = mvmat[1][0] = mvmat[2][0] = mvmat[1][2] = mvmat[2][1] = 0.0f;
189
190         glLoadMatrixf(mvmat[0]);
191
192         glPushAttrib(GL_ENABLE_BIT);
193         glEnable(GL_BLEND);
194         glBlendFunc(GL_SRC_ALPHA, GL_ONE);
195         glEnable(GL_TEXTURE_2D);
196         glDepthMask(0);
197
198         bind_shader(0);
199         bind_texture(rtarg->texture());
200
201         glMatrixMode(GL_TEXTURE);
202         glLoadMatrixf(rtarg->texture_matrix()[0]);
203
204         glBegin(GL_QUADS);
205         glColor3f(1, 1, 1);
206         glTexCoord2f(0, 0); glVertex2f(-size.x / 2, -size.y / 2);
207         glTexCoord2f(1, 0); glVertex2f(size.x / 2, -size.y / 2);
208         glTexCoord2f(1, 1); glVertex2f(size.x / 2, size.y / 2);
209         glTexCoord2f(0, 1); glVertex2f(-size.x / 2, size.y / 2);
210         glEnd();
211
212         glLoadIdentity();
213
214         glDepthMask(1);
215         glPopAttrib();
216
217         glMatrixMode(GL_MODELVIEW);
218         glPopMatrix();
219 }
220
221 static inline float *vrect(const Rect &rect, int i)
222 {
223         static float v[2];
224         v[0] = ((i + 1) & 2) ? rect.x + rect.w : rect.x;
225         v[1] = (i & 2) ? rect.y : rect.y + rect.h;
226         return v;
227 }
228
229 static inline void draw_rect(const Rect &rect, int col)
230 {
231         glBegin(GL_QUADS);
232         glColor3fv(color[col]);
233         for(int i=0; i<4; i++)
234                 glVertex2fv(vrect(rect, i));
235         glEnd();
236 }
237
238 static void draw_frame(const Rect &rect)
239 {
240         draw_rect(rect, COL_BG);
241         glLineWidth(3.0);
242         glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
243         draw_rect(rect, COL_FRM);
244         glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
245 }
246
247 static void draw_titlebar(const Rect &rect)
248 {
249         draw_rect(rect, COL_FRM);
250
251         const char *title = ex->get_name();
252         if(title) {
253                 glPushMatrix();
254                 glTranslatef(rect.x + text_padding, rect.y + rect.h - text_padding, 0);
255                 glScalef(text_scale, -text_scale, text_scale);
256
257                 glColor3fv(color[COL_BG]);
258                 dtx_string(ex->get_name());
259                 glPopMatrix();
260         }
261 }
262
263 static void draw_tabs(const Rect &rect)
264 {
265 }
266
267 static void draw_text(const Rect &rect)
268 {
269         char *buf = (char*)alloca(max_line_size + 1);
270
271         float dy = dtx_line_height() * text_scale;
272
273         glMatrixMode(GL_MODELVIEW);
274         glPushMatrix();
275         glTranslatef(rect.x + text_padding, rect.y + dy + text_padding, 0);
276         glScalef(text_scale, -text_scale, text_scale);
277
278         glColor3fv(color[COL_FG]);
279
280         int nlines = text_lines.size() - 1;
281         for(int i=0; i<nlines; i++) {
282                 if(i < nlines - 1) {
283                         int sz = text_lines[i + 1] - text_lines[i];
284                         assert(sz <= max_line_size);
285                         memcpy(buf, text_lines[i], sz);
286                         buf[sz] = 0;
287                 } else {
288                         buf = (char*)text_lines[i];
289                 }
290
291                 dtx_string(buf);
292                 glTranslatef(0, -dy, 0);
293         }
294
295         glPopMatrix();
296 }
297
298 static void layout_text(const char *text)
299 {
300         text_lines.clear();
301         if(!text) return;
302         if(!ui_font) return;
303
304         dtx_use_font(ui_font, ui_font_size);
305
306         int left_margin = text_padding;
307         int right_margin = ui_width - text_padding;
308
309         text_lines.push_back(text);
310         const char *last_break = 0;
311         max_line_size = 1;
312
313         while(*text) {
314                 if(*text == '\n') {     // paragraph break
315                         text_lines.push_back(text);
316                         text_lines.push_back(++text);
317                         last_break = 0;
318                         continue;
319                 }
320
321                 int code = dtx_utf8_char_code(text);
322                 const char *next = dtx_utf8_next_char((char*)text);
323
324                 struct dtx_box box;
325                 dtx_substring_box(text_lines.back(), 0, text - text_lines.back(), &box);
326                 float pos = left_margin + (box.width + box.x) * text_scale;
327
328                 if(code < 256 && isspace(code)) {
329                         last_break = text;
330                 }
331
332                 if(pos > right_margin) {
333                         if(text == text_lines.back()) {
334                                 // not even a single character fits on a line... abort
335                                 warning_log("text layout failed. glyph %d doesn't fit in line (%d)\n", code, right_margin - left_margin);
336                                 text_lines.clear();
337                                 return;
338                         }
339                         if(last_break) {
340                                 text_lines.push_back(last_break + 1);
341                                 last_break = 0;
342                         } else {
343                                 // no good point to break, just break here
344                                 text_lines.push_back(text);
345                         }
346
347                         int d = text_lines.back() - (text_lines[text_lines.size() - 2]);
348                         if(d > max_line_size) max_line_size = d;
349                 }
350                 text = next;
351         }
352         text_lines.push_back(0);
353
354         /*
355         debug_log("text layout:\n");
356         for(size_t i=0; i<text_lines.size() - 1; i++) {
357                 const char *p = text_lines[i];
358                 while(*p && p != text_lines[i + 1]) {
359                         putchar(*p);
360                         ++p;
361                 }
362                 putchar('\n');
363         }
364         debug_log("---\n");
365         */
366 }