exhibit ui improvements, and exhibit repositioning fix
[laserbrain_demo] / src / ui_exhibit.cc
index 355d078..438a52e 100644 (file)
+#include <assert.h>
+#if defined(WIN32) || defined(__WIN32__)
+#include <malloc.h>
+#else
+#include <alloca.h>
+#endif
+#include <drawtext.h>
 #include "ui_exhibit.h"
 #include "ui.h"
 #include "app.h"
-#include <drawtext.h>
+#include "snode.h"
+#include "sdr.h"
+#include "rtarg.h"
 
-static void draw_titlebar();
-static void draw_tabs();
+struct Rect {
+       float x, y, w, h;
+};
+
+static void draw_frame(const Rect &rect);
+static void draw_titlebar(const Rect &rect);
+static void draw_tabs(const Rect &rect);
+static void draw_text(const Rect &rect);
 static void layout_text(const char *text);
 
+static struct dtx_font *font;
+static int font_size;
+static unsigned int fontsdr;
+
+static float aspect;
+static int ui_width, ui_height;
+
+static Mat4 tilt_matrix;
+
+static RenderTarget *rtarg;
+static const SceneNode *parent;
 static Vec3 pos;
 static Vec2 size;
-static float text_padding;
+static int text_padding;
+static float text_scale = 0.65f;
+static float scale = 1.0f;
 static Exhibit *ex;
-static int vis_tab;
+static int vis_tab, num_tabs;
+static std::vector<std::string> tab_names;
 static float scroll;
 static std::vector<const char*> text_lines;
+static int max_line_size;
 static AudioStream *voice;
 
+enum {COL_BG, COL_FG, COL_FRM};
+static float color[][3] = {
+       {0.014, 0.016, 0.04},   // COL_BG
+       {0.31, 0.58, 0.9},      // COL_FG
+       {0.19, 0.23, 0.46}      // COL_FRM
+};
+
 
 bool exui_init()
 {
-       size.x = 150;
-       size.y = 180;
-       text_padding = size.x * 0.01;
+       if(!(font = dtx_open_font_glyphmap("data/ui_en.glyphmap"))) {
+               error_log("failed to open exhibit ui font\n");
+               return false;
+       }
+       font_size = dtx_get_glyphmap_ptsize(dtx_get_glyphmap(font, 0));
+
+       if(!(fontsdr = create_program_load("sdr/dfont.v.glsl", "sdr/dfont.p.glsl"))) {
+               error_log("failed to load font shader\n");
+               return false;
+       }
+
+       tilt_matrix = Mat4::identity;
+
+       size.x = 15;
+       size.y = 18;
+       text_padding = 6;
+
+       scale = 1.0f;
+
+       aspect = size.x / size.y;
+       ui_height = 512;
+       ui_width = ui_height * aspect;
+
+       rtarg = new RenderTarget;
+       if(!rtarg->create(ui_width, ui_height, GL_RGBA)) {
+               error_log("failed to create exui render target\n");
+               return false;
+       }
 
        return true;
 }
 
 void exui_shutdown()
 {
+       dtx_close_font(font);
+       delete rtarg;
+}
+
+void exui_setnode(const SceneNode *node)
+{
+       parent = node;
+}
+
+void exui_rotation(const Vec3 &euler)
+{
+       tilt_matrix.rotation(euler);
+}
+
+void exui_scale(float s)
+{
+       scale = s;
 }
 
 void exui_change_tab(int dir)
 {
+       vis_tab = (vis_tab + dir) % num_tabs;
 }
 
 void exui_scroll(float delta)
@@ -49,6 +129,8 @@ void exui_update(float dt)
                ex = exsel_active.ex;
                scroll = 0.0f;
                vis_tab = 0;
+               num_tabs = 0;
+               tab_names.clear();
                text_lines.clear();
                if(voice) voice->stop();
 
@@ -58,6 +140,8 @@ void exui_update(float dt)
                                if(ex->data[i].type == EXDATA_INFO) {
                                        layout_text(ex->data[i].text.c_str());
                                        voice = ex->data[i].voice;
+                                       ++num_tabs;
+                                       tab_names.push_back("info");
                                }
                        }
 
@@ -70,51 +154,296 @@ void exui_update(float dt)
        }
 }
 
+static void draw_2d_ui()
+{
+       dtx_use_font(font, font_size);
+       float rowspc = dtx_line_height() * text_scale;
+
+       glMatrixMode(GL_PROJECTION);
+       glPushMatrix();
+       glLoadIdentity();
+       glTranslatef(-1, 1, 0);
+       glScalef(2.0 / ui_width, -2.0 / ui_height, 1);
+
+       glMatrixMode(GL_MODELVIEW);
+       glPushMatrix();
+       glLoadIdentity();
+
+       glUseProgram(0);
+
+       glPushAttrib(GL_ENABLE_BIT);
+       glDisable(GL_TEXTURE_2D);
+       glDisable(GL_LIGHTING);
+       glDisable(GL_DEPTH_TEST);
+       glEnable(GL_SCISSOR_TEST);
+
+       Rect rect = {0, 0, (float)ui_width, (float)ui_height};
+       draw_frame(rect);
+       Rect tbar_rect = {rect.x, rect.y, rect.w, rowspc + text_padding};       // half the padding
+       draw_titlebar(tbar_rect);
+       Rect tabs_rect = {tbar_rect.x, tbar_rect.y + tbar_rect.h, tbar_rect.w, tbar_rect.h};
+       draw_tabs(tabs_rect);
+
+       if(num_tabs) {
+               switch(ex->data[vis_tab].type) {
+               case EXDATA_INFO:
+                       {
+                               Rect text_rect = {rect.x, tabs_rect.y + tabs_rect.h, rect.w, rect.h - tabs_rect.y - tabs_rect.h};
+                               draw_text(text_rect);
+                       }
+                       break;
+
+               default:
+                       break;
+               }
+       }
+
+       glPopAttrib();
+
+       glMatrixMode(GL_PROJECTION);
+       glPopMatrix();
+       glMatrixMode(GL_MODELVIEW);
+       glPopMatrix();
+}
+
 void exui_draw()
 {
        if(!exsel_active) return;
+       if(!font) return;
+
+       // render the 2D UI in a texture
+       push_render_target(rtarg);
+       glClearColor(0, 1, 0, 0);
+       glClear(GL_COLOR_BUFFER_BIT);
+       draw_2d_ui();
+       pop_render_target();
+
+       // place UI image into the scene
+       glMatrixMode(GL_MODELVIEW);
+       glPushMatrix();
+
+       /*
+       Mat4 mvmat;
+       glGetFloatv(GL_MODELVIEW_MATRIX, mvmat[0]);
+       if(parent) {
+               mvmat = parent->get_matrix() * mvmat;
+       }
+       mvmat.translate(pos.x, pos.y, pos.z);
+
+       mvmat[0][0] = mvmat[1][1] = mvmat[2][2] = 1.0f;
+       mvmat[0][1] = mvmat[0][2] = mvmat[1][0] = mvmat[2][0] = mvmat[1][2] = mvmat[2][1] = 0.0f;
+       glLoadMatrixf(mvmat[0]);
+       */
+       Mat4 xform;
+       if(parent) {
+               xform = parent->get_matrix();
+       }
+       xform = tilt_matrix * xform;
+       xform.pre_scale(scale, scale, scale);
+       glMultMatrixf(xform[0]);
+
+       glPushAttrib(GL_ENABLE_BIT);
+       glEnable(GL_BLEND);
+       glBlendFunc(GL_SRC_ALPHA, GL_ONE);
+       glEnable(GL_TEXTURE_2D);
+       glDisable(GL_CULL_FACE);
+       glDepthMask(0);
+
+       glUseProgram(0);
+       bind_texture(rtarg->texture());
+
+       glMatrixMode(GL_TEXTURE);
+       glLoadMatrixf(rtarg->texture_matrix()[0]);
+
+       glBegin(GL_QUADS);
+       glColor3f(1, 1, 1);
+       glTexCoord2f(0, 0); glVertex2f(-size.x / 2, -size.y / 2);
+       glTexCoord2f(1, 0); glVertex2f(size.x / 2, -size.y / 2);
+       glTexCoord2f(1, 1); glVertex2f(size.x / 2, size.y / 2);
+       glTexCoord2f(0, 1); glVertex2f(-size.x / 2, size.y / 2);
+       glEnd();
+
+       glLoadIdentity();
+
+       glDepthMask(1);
+       glPopAttrib();
+
+       glMatrixMode(GL_MODELVIEW);
+       glPopMatrix();
+}
 
-       draw_titlebar();
-       draw_tabs();
+static inline float *vrect(const Rect &rect, int i)
+{
+       static float v[2];
+       v[0] = ((i + 1) & 2) ? rect.x + rect.w : rect.x;
+       v[1] = (i & 2) ? rect.y : rect.y + rect.h;
+       return v;
 }
 
-static void draw_titlebar()
+static inline void draw_rect(const Rect &rect, int col)
 {
+       glBegin(GL_QUADS);
+       glColor3fv(color[col]);
+       for(int i=0; i<4; i++)
+               glVertex2fv(vrect(rect, i));
+       glEnd();
 }
 
-static void draw_tabs()
+static void clip_rect(const Rect &rect)
 {
+       glScissor(rect.x, ui_height - rect.y - rect.h, rect.w, rect.h);
+}
+
+static void draw_frame(const Rect &rect)
+{
+       clip_rect(rect);
+
+       draw_rect(rect, COL_BG);
+       glLineWidth(3.0);
+       glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
+       draw_rect(rect, COL_FRM);
+       glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
+}
+
+static void draw_titlebar(const Rect &rect)
+{
+       clip_rect(rect);
+
+       draw_rect(rect, COL_FRM);
+
+       const char *title = ex->get_name();
+       if(title) {
+               glUseProgram(fontsdr);
+
+               glPushMatrix();
+               glTranslatef(rect.x + text_padding, rect.y + rect.h - text_padding, 0);
+               glScalef(text_scale, -text_scale, text_scale);
+
+               glColor3fv(color[COL_BG]);
+               dtx_string(ex->get_name());
+               glPopMatrix();
+
+               glUseProgram(0);
+       }
+}
+
+static void draw_tabs(const Rect &rect)
+{
+       if(!num_tabs) return;
+
+       clip_rect(rect);
+
+       glLineWidth(1);
+       if(num_tabs == 1) {
+               glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
+               draw_rect(rect, COL_FRM);
+               glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
+       }
+
+       int max_tab_size = ui_width / 2;
+       int tab_size = std::min(max_tab_size, ui_width / num_tabs);
+
+       for(int i=0; i<num_tabs; i++) {
+               Rect tr = {rect.x + i * tab_size, rect.y, (float)tab_size, rect.h};
+
+               clip_rect(tr);
+
+               if(vis_tab == i) {
+                       draw_rect(tr, COL_FRM);
+               } else {
+                       glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
+                       draw_rect(tr, COL_FRM);
+                       glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
+               }
+
+               glPushMatrix();
+               glTranslatef(tr.x + text_padding, tr.y + tr.h - text_padding, 0);
+               glScalef(text_scale, -text_scale, text_scale);
+
+               glUseProgram(fontsdr);
+               glColor3fv(color[vis_tab == i ? COL_BG : COL_FRM]);
+               dtx_string(tab_names[i].c_str());
+               glUseProgram(0);
+
+               glPopMatrix();
+       }
+}
+
+static void draw_text(const Rect &rect)
+{
+       clip_rect(rect);
+
+       char *buf = (char*)alloca(max_line_size + 1);
+
+       float dy = dtx_line_height();
+
+       glUseProgram(fontsdr);
+
+       glMatrixMode(GL_MODELVIEW);
+       glPushMatrix();
+       glTranslatef(rect.x + text_padding, rect.y + dy + text_padding, 0);
+       glScalef(text_scale, -text_scale, text_scale);
+
+       glColor3fv(color[COL_FG]);
+
+       int nlines = text_lines.size() - 1;
+       for(int i=0; i<nlines; i++) {
+               if(i < nlines - 1) {
+                       int sz = text_lines[i + 1] - text_lines[i];
+                       assert(sz <= max_line_size);
+                       memcpy(buf, text_lines[i], sz);
+                       buf[sz] = 0;
+               } else {
+                       buf = (char*)text_lines[i];
+               }
+
+               dtx_position(0, -dy * i);
+               dtx_string(buf);
+       }
+       dtx_position(0, 0);
+
+       glPopMatrix();
+       glUseProgram(0);
 }
 
 static void layout_text(const char *text)
 {
        text_lines.clear();
        if(!text) return;
-       if(!ui_font) return;
+       if(!font) return;
+
+       dtx_use_font(font, font_size);
 
-       dtx_use_font(ui_font, ui_font_size);
+       int left_margin = text_padding;
+       int right_margin = ui_width - text_padding;
 
-       float pos = text_padding;
        text_lines.push_back(text);
        const char *last_break = 0;
+       max_line_size = 1;
 
        while(*text) {
                if(*text == '\n') {     // paragraph break
                        text_lines.push_back(text);
                        text_lines.push_back(++text);
-                       pos = text_padding;
                        last_break = 0;
                        continue;
                }
 
                int code = dtx_utf8_char_code(text);
                const char *next = dtx_utf8_next_char((char*)text);
-               pos += dtx_glyph_width(code);
 
-               if(pos >= size.x - text_padding) {
+               struct dtx_box box;
+               dtx_substring_box(text_lines.back(), 0, text - text_lines.back(), &box);
+               float pos = left_margin + (box.width + box.x) * text_scale;
+
+               if(code < 256 && isspace(code)) {
+                       last_break = text;
+               }
+
+               if(pos > right_margin) {
                        if(text == text_lines.back()) {
                                // not even a single character fits on a line... abort
-                               warning_log("text layout failed. glyph %d doesn't fit in line (%g)\n", code, size.x - 2.0 * text_padding);
+                               warning_log("text layout failed. glyph %d doesn't fit in line (%d)\n", code, right_margin - left_margin);
                                text_lines.clear();
                                return;
                        }
@@ -125,8 +454,24 @@ static void layout_text(const char *text)
                                // no good point to break, just break here
                                text_lines.push_back(text);
                        }
-                       pos = text_padding;
+
+                       int d = text_lines.back() - (text_lines[text_lines.size() - 2]);
+                       if(d > max_line_size) max_line_size = d;
                }
                text = next;
        }
+       text_lines.push_back(0);
+
+       /*
+       debug_log("text layout:\n");
+       for(size_t i=0; i<text_lines.size() - 1; i++) {
+               const char *p = text_lines[i];
+               while(*p && p != text_lines[i + 1]) {
+                       putchar(*p);
+                       ++p;
+               }
+               putchar('\n');
+       }
+       debug_log("---\n");
+       */
 }