From 7ab45ce6bacfffb934b72a81db3729b29a043805 Mon Sep 17 00:00:00 2001 From: Mikulas Florek Date: Fri, 3 Jan 2014 01:14:27 +0100 Subject: [PATCH 1/2] GUI: multiline textbox; redirected error stream of script compiler --- src/editor_native/log_ui.cpp | 28 +++++++++++++++++++- src/editor_native/log_ui.h | 1 + src/editor_native/script_compiler.cpp | 9 ++++--- src/gui/decorators/text_decorator.cpp | 8 +++--- src/gui/irenderer.h | 4 +-- src/gui/opengl_renderer.cpp | 38 +++++++++++++++++++++++---- src/gui/opengl_renderer.h | 4 +-- 7 files changed, 74 insertions(+), 18 deletions(-) diff --git a/src/editor_native/log_ui.cpp b/src/editor_native/log_ui.cpp index d15c63e6a..98fea6b3c 100644 --- a/src/editor_native/log_ui.cpp +++ b/src/editor_native/log_ui.cpp @@ -4,12 +4,14 @@ #include "editor/server_message_types.h" #include "editor_native/main_frame.h" #include "gui/controls/scrollable.h" +#include "gui/gui.h" LogUI::LogUI(MainFrame& main_frame) : Dockable(main_frame.getGui(), NULL) , m_main_frame(main_frame) { + m_scrollable = NULL; main_frame.getDockable().dock(*this, Dockable::BOTTOM); main_frame.getEditorClient()->getEventManager().addListener(Lux::ServerMessageType::LOG_MESSAGE).bind(this); Lux::UI::Block* handle = LUX_NEW(Lux::UI::Block)(getGui(), this, "_box"); @@ -30,7 +32,31 @@ void LogUI::onLogMessage(Lux::Event& evt) cell->setArea(0, 0, 0, y, 0.3f, 0, 0, y + 20); cell = LUX_NEW(Lux::UI::Block)(getGui(), container, "_text"); + float w, h; + getGui().getRenderer().measureText(log_evt.message.c_str(), &w, &h, getGlobalWidth() * 0.7f); cell->setBlockText(log_evt.message.c_str()); - cell->setArea(0.3f, 0, 0, y, 1, 0, 0, y + 20); + cell->setArea(0.3f, 0, 0, y, 1, 0, 0, y + h + 5); layout(); } + + +void LogUI::layout() +{ + Dockable::layout(); + if(m_scrollable) + { + Lux::UI::Block* container = m_scrollable->getContainer(); + float w, h; + float y = 0; + for(int i = 1; i < container->getChildCount(); i += 2) + { + getGui().getRenderer().measureText(container->getChild(i)->getBlockText().c_str(), &w, &h, getGlobalWidth() * 0.7f); + + container->getChild(i-1)->getLocalArea().top = y; + container->getChild(i)->getLocalArea().top = y; + + y += h + 5; + } + Dockable::layout(); + } +} diff --git a/src/editor_native/log_ui.h b/src/editor_native/log_ui.h index 2d8a37a6d..bc972d423 100644 --- a/src/editor_native/log_ui.h +++ b/src/editor_native/log_ui.h @@ -27,6 +27,7 @@ class LogUI : public Lux::UI::Dockable { public: LogUI(MainFrame& main_frame); + virtual void layout() LUX_OVERRIDE; private: void onLogMessage(Lux::Event& evt); diff --git a/src/editor_native/script_compiler.cpp b/src/editor_native/script_compiler.cpp index edd4d71dc..21ba4fb93 100644 --- a/src/editor_native/script_compiler.cpp +++ b/src/editor_native/script_compiler.cpp @@ -38,8 +38,9 @@ void ScriptCompiler::compile(const char path[]) CreatePipe(&read_pipe, &write_pipe, &saAttr, 0); SetHandleInformation(read_pipe, HANDLE_FLAG_INHERIT, 0); - si.hStdInput = read_pipe; + //si.hStdInput = read_pipe; si.hStdOutput = write_pipe; + si.hStdError = write_pipe; si.dwFlags |= STARTF_USESTDHANDLES; sprintf(cmd_line, "/C scripts\\compile.bat %s", path); if ( CreateProcess("C:\\windows\\system32\\cmd.exe", // Application name @@ -53,7 +54,7 @@ void ScriptCompiler::compile(const char path[]) &si, &pi) == TRUE) { - Process* p = LUX_NEW(Process)(); + Process* p = new Process(); p->m_handle = pi.hProcess; p->m_path = path; p->m_pipe = read_pipe; @@ -78,7 +79,7 @@ void ScriptCompiler::checkFinished() Process* p = m_processes[i]; m_processes.eraseFast(i); m_delegates.invoke(p->m_path.c_str(), code); - char buf[512]; + char buf[513]; DWORD read; if(code != 0) { @@ -95,7 +96,7 @@ void ScriptCompiler::checkFinished() } CloseHandle(p->m_pipe); CloseHandle(p->m_write_pipe); - LUX_DELETE(p); + delete p; } } } diff --git a/src/gui/decorators/text_decorator.cpp b/src/gui/decorators/text_decorator.cpp index 6ca871da3..29a2e9201 100644 --- a/src/gui/decorators/text_decorator.cpp +++ b/src/gui/decorators/text_decorator.cpp @@ -18,15 +18,15 @@ namespace UI void TextDecorator::render(IRenderer& renderer, Block& block) { - float w, h; - renderer.measureText(block.getBlockText().c_str(), &w, &h); if(m_is_text_centered) { - renderer.renderText(block.getBlockText().c_str(), (block.getGlobalRight() + block.getGlobalLeft() - w) / 2, (float)block.getGlobalTop(), block.getZ()); + float w, h; + renderer.measureText(block.getBlockText().c_str(), &w, &h, block.getGlobalWidth()); + renderer.renderText(block.getBlockText().c_str(), (block.getGlobalRight() + block.getGlobalLeft() - w) / 2, (float)block.getGlobalTop(), block.getZ(), block.getGlobalWidth()); } else { - renderer.renderText(block.getBlockText().c_str(), block.getGlobalLeft(), (float)block.getGlobalTop(), block.getZ()); + renderer.renderText(block.getBlockText().c_str(), block.getGlobalLeft(), (float)block.getGlobalTop(), block.getZ(), block.getGlobalWidth()); } } diff --git a/src/gui/irenderer.h b/src/gui/irenderer.h index 43728f7d9..d97aa3ed1 100644 --- a/src/gui/irenderer.h +++ b/src/gui/irenderer.h @@ -26,8 +26,8 @@ namespace UI virtual TextureBase* loadImage(const char* name, FS::FileSystem& file_system) = 0; virtual void beginRender(float w, float h) = 0; virtual void renderImage(TextureBase* image, float* vertices, float* tex_coords, int vertex_count) = 0; - virtual void measureText(const char* text, float* w, float* h) = 0; - virtual void renderText(const char* text, float x, float y, float z) = 0; + virtual void measureText(const char* text, float* w, float* h, float max_width) = 0; + virtual void renderText(const char* text, float x, float y, float z, float max_width) = 0; virtual void pushScissorArea(float left, float top, float right, float bottom) = 0; virtual void popScissorArea() = 0; }; diff --git a/src/gui/opengl_renderer.cpp b/src/gui/opengl_renderer.cpp index 082daeb55..04c508660 100644 --- a/src/gui/opengl_renderer.cpp +++ b/src/gui/opengl_renderer.cpp @@ -221,7 +221,7 @@ namespace UI glLoadIdentity(); } - void OpenGLRenderer::measureText(const char* text, float* w, float* h) + void OpenGLRenderer::measureText(const char* text, float* w, float* h, float max_width) { if(!text) { @@ -231,7 +231,9 @@ namespace UI } float width = 0; float height = 0; + float prev_h = 0; const char* c = text; + bool is_multiline = false; while(*c) { OpenGLRendererImpl::Character character; @@ -239,11 +241,23 @@ namespace UI { width += character.x_advance; height = Math::max(height, character.pixel_h); + if(width > max_width || *c == '\n') + { + is_multiline = true; + width = 0; + prev_h += height; + } + } + else if(*c == '\n') + { + is_multiline = true; + width = 0; + prev_h += height; } ++c; } - *w = width; - *h = height; + *w = is_multiline ? max_width : width; + *h = height + prev_h; } void OpenGLRenderer::pushScissorArea(float left, float top, float right, float bottom) @@ -287,7 +301,7 @@ namespace UI } } - void OpenGLRenderer::renderText(const char* text, float x, float y, float z) + void OpenGLRenderer::renderText(const char* text, float x, float y, float z, float max_width) { if(!text) { @@ -306,13 +320,16 @@ namespace UI uvs.resize(len * 6); const char* c = text; float cur_x = x; + float line_h = 0; + float line_base = y; int i = 0; while(*c) { OpenGLRendererImpl::Character character; if(m_impl->m_characters.find(*c, character)) { - float cur_y = y + character.y_offset; + float cur_y = line_base + character.y_offset; + line_h = Math::max(line_h, character.pixel_h); verts[i*6].set(cur_x, cur_y, z); verts[i*6+1].set(cur_x, cur_y + character.pixel_h, z); verts[i*6+2].set(cur_x + character.pixel_w, cur_y + character.pixel_h, z); @@ -323,6 +340,12 @@ namespace UI cur_x += character.x_advance; + if(cur_x - x > max_width) + { + cur_x = x; + line_base += line_h; + } + uvs[i*6].set(character.left, character.top); uvs[i*6+1].set(character.left, character.bottom); uvs[i*6+2].set(character.right, character.bottom); @@ -332,6 +355,11 @@ namespace UI uvs[i*6+5].set(character.right, character.top); ++i; } + else if(*c == '\n') + { + cur_x = x; + line_base += line_h; + } ++c; } glEnable(GL_BLEND); diff --git a/src/gui/opengl_renderer.h b/src/gui/opengl_renderer.h index 07baec66a..0f500a08e 100644 --- a/src/gui/opengl_renderer.h +++ b/src/gui/opengl_renderer.h @@ -24,8 +24,8 @@ namespace UI virtual void loadFont(const char* path, FS::FileSystem& file_system) LUX_OVERRIDE; virtual void beginRender(float w, float h) LUX_OVERRIDE; virtual void renderImage(TextureBase* image, float* vertices, float* tex_coords, int vertex_count) LUX_OVERRIDE; - virtual void measureText(const char* text, float* w, float* h) LUX_OVERRIDE; - virtual void renderText(const char* text, float x, float y, float z) LUX_OVERRIDE; + virtual void measureText(const char* text, float* w, float* h, float max_width) LUX_OVERRIDE; + virtual void renderText(const char* text, float x, float y, float z, float max_width) LUX_OVERRIDE; virtual void pushScissorArea(float left, float top, float right, float bottom) LUX_OVERRIDE; virtual void popScissorArea() LUX_OVERRIDE; void setWindowHeight(int height); From b8465e682af0712cfcdfef1829c509df7f9c508c Mon Sep 17 00:00:00 2001 From: Mikulas Florek Date: Fri, 3 Jan 2014 18:52:25 +0100 Subject: [PATCH 2/2] GUI: cursor in textbox --- projects/gui/gui.vcxproj | 2 + projects/gui/gui.vcxproj.filters | 6 +++ src/core/string.h | 9 ++++ src/editor_native/main.cpp | 4 ++ src/gui/controls/text_box.cpp | 55 +++++++++++++++++-- src/gui/controls/text_box.h | 7 +++ src/gui/decorators/cursor_decorator.cpp | 54 +++++++++++++++++++ src/gui/decorators/cursor_decorator.h | 40 ++++++++++++++ src/gui/irenderer.h | 2 + src/gui/opengl_renderer.cpp | 70 ++++++++++++++++++++++++- src/gui/opengl_renderer.h | 1 + 11 files changed, 246 insertions(+), 4 deletions(-) create mode 100644 src/gui/decorators/cursor_decorator.cpp create mode 100644 src/gui/decorators/cursor_decorator.h diff --git a/projects/gui/gui.vcxproj b/projects/gui/gui.vcxproj index b25316298..5005f012c 100644 --- a/projects/gui/gui.vcxproj +++ b/projects/gui/gui.vcxproj @@ -97,6 +97,7 @@ + @@ -121,6 +122,7 @@ + diff --git a/projects/gui/gui.vcxproj.filters b/projects/gui/gui.vcxproj.filters index 13f4c1725..1801a8a63 100644 --- a/projects/gui/gui.vcxproj.filters +++ b/projects/gui/gui.vcxproj.filters @@ -50,6 +50,9 @@ decorators + + decorators + @@ -98,6 +101,9 @@ decorators + + decorators + diff --git a/src/core/string.h b/src/core/string.h index 32c408e6c..97dbf43f6 100644 --- a/src/core/string.h +++ b/src/core/string.h @@ -167,6 +167,15 @@ class base_string return ret; } + void erase(size_t pos) + { + if(pos >= 0 && pos < m_size) + { + base_string::strcpy(m_cstr + pos, m_cstr + pos + 1); + --m_size; + } + } + public: static const int npos = 0xffFFffFF; diff --git a/src/editor_native/main.cpp b/src/editor_native/main.cpp index 656d86364..fb0a44c4c 100644 --- a/src/editor_native/main.cpp +++ b/src/editor_native/main.cpp @@ -14,6 +14,7 @@ #include "graphics/renderer.h" #include "gui/block.h" #include "gui/decorators/box_decorator.h" +#include "gui/decorators/cursor_decorator.h" #include "gui/decorators/check_box_decorator.h" #include "gui/decorators/dockable_decorator.h" #include "gui/decorators/text_decorator.h" @@ -30,6 +31,7 @@ void initGui(Lux::EditorClient& client, Lux::EditorServer& server) renderer->create(); renderer->loadFont("gui/font.tga", server.getEngine().getFileSystem()); renderer->setWindowHeight(600); + Lux::UI::CursorDecorator* cursor_decorator = LUX_NEW(Lux::UI::CursorDecorator)("_cursor"); Lux::UI::CheckBoxDecorator* check_box_decorator = LUX_NEW(Lux::UI::CheckBoxDecorator)("_check_box"); Lux::UI::TextDecorator* text_decorator = LUX_NEW(Lux::UI::TextDecorator)("_text"); Lux::UI::TextDecorator* text_centered_decorator = LUX_NEW(Lux::UI::TextDecorator)("_text_centered"); @@ -39,6 +41,7 @@ void initGui(Lux::EditorClient& client, Lux::EditorServer& server) Lux::UI::ScrollbarDecorator* scrollbar_decorator = LUX_NEW(Lux::UI::ScrollbarDecorator)("_scrollbar"); server.getEngine().loadPlugin("gui.dll"); Lux::UI::Gui* gui = (Lux::UI::Gui*)server.getEngine().getPluginManager().getPlugin("gui"); + gui->addDecorator(*cursor_decorator); gui->addDecorator(*text_decorator); gui->addDecorator(*text_centered_decorator); gui->addDecorator(*box_decorator); @@ -46,6 +49,7 @@ void initGui(Lux::EditorClient& client, Lux::EditorServer& server) gui->addDecorator(*scrollbar_decorator); gui->addDecorator(*check_box_decorator); gui->setRenderer(*renderer); + cursor_decorator->create(*gui, "gui/skin.atl"); check_box_decorator->create(*gui, "gui/skin.atl"); scrollbar_decorator->create(*gui, "gui/skin.atl"); box_decorator->create(*gui, "gui/skin.atl"); diff --git a/src/gui/controls/text_box.cpp b/src/gui/controls/text_box.cpp index 9af2cde9b..6e10397a4 100644 --- a/src/gui/controls/text_box.cpp +++ b/src/gui/controls/text_box.cpp @@ -1,6 +1,7 @@ #include "gui/controls/text_box.h" #include "core/crc32.h" #include "core/iserializer.h" +#include "gui/gui.h" namespace Lux @@ -17,29 +18,77 @@ TextBox::TextBox(const char* text, Gui& gui, Block* parent) label_ui->setBlockText(text); label_ui->setArea(0, 3, 0, 0, 1, 0, 1, 0); label_ui->onEvent("key_down").bind(this); + label_ui->onEvent("focus").bind(this); + label_ui->onEvent("blur").bind(this); label_ui->setIsClipping(true); + m_cursor_pos = 0; + m_cursor = LUX_NEW(Block)(gui, label_ui, "_cursor"); + m_cursor->hide(); } +void TextBox::setCursorArea() +{ + Block::Area area = getGui().getRenderer().getCharArea(getChild(0)->getBlockText().c_str(), m_cursor_pos, getGlobalWidth()); + m_cursor->setArea(area); + layout(); +} + + +void TextBox::blurred(Block& block, void* user_data) +{ + m_cursor->hide(); +} + + +void TextBox::focused(Block& block, void* user_data) +{ + m_cursor_pos = getChild(0)->getBlockText().length(); + m_cursor->show(); + setCursorArea(); +} + +static const int32_t KEY_RIGHT = 79 + (1 << 30); +static const int32_t KEY_LEFT = 80 + (1 << 30); +static const int32_t KEY_UP = 81 + (1 << 30); +static const int32_t KEY_DOWN = 82 + (1 << 30); +static const int32_t KEY_BACKSPACE = '\b'; +static const int32_t KEY_DELETE = '\177'; + void TextBox::keyDown(Block& block, void* user_data) { Lux::string s = block.getBlockText(); char c[2]; switch((int32_t)user_data) { + case KEY_RIGHT: + m_cursor_pos = m_cursor_pos > s.length() - 1 ? s.length() : m_cursor_pos + 1; + break; + case KEY_LEFT: + m_cursor_pos = m_cursor_pos < 1 ? 0 : m_cursor_pos - 1; + break; + case KEY_UP: + case KEY_DOWN: + break; case '\r': block.emitEvent("text_accepted"); break; - case '\b': - s = s.substr(0, s.length() - 1); + case KEY_BACKSPACE: + s.erase(m_cursor_pos - 1); + if(m_cursor_pos > 0) + { + --m_cursor_pos; + } break; - default: + default: c[0] = (char)user_data; c[1] = '\0'; s += c; + ++m_cursor_pos; break; } block.setBlockText(s.c_str()); + setCursorArea(); } diff --git a/src/gui/controls/text_box.h b/src/gui/controls/text_box.h index e7d0266c5..335f79b7e 100644 --- a/src/gui/controls/text_box.h +++ b/src/gui/controls/text_box.h @@ -25,6 +25,13 @@ namespace UI private: void keyDown(Block& block, void* user_data); + void focused(Block& block, void* user_data); + void blurred(Block& block, void* user_data); + void setCursorArea(); + + private: + int m_cursor_pos; + Lux::UI::Block* m_cursor; }; diff --git a/src/gui/decorators/cursor_decorator.cpp b/src/gui/decorators/cursor_decorator.cpp new file mode 100644 index 000000000..cec98306e --- /dev/null +++ b/src/gui/decorators/cursor_decorator.cpp @@ -0,0 +1,54 @@ +#include "gui/decorators/cursor_decorator.h" +#include "core/crc32.h" +#include "gui/atlas.h" +#include "gui/block.h" +#include "gui/controls/dockable.h" +#include "gui/gui.h" +#include "gui/irenderer.h" +#include "gui/texture_base.h" + + +namespace Lux +{ +namespace UI +{ + + static const uint32_t dockable_hash = crc32("dockable"); + + void CursorDecorator::setVertices(Vec3* verts, float left, float top, float right, float bottom, float z) const + { + verts[0].set(left, top, z); + verts[1].set(left, bottom, z); + verts[2].set(right, bottom, z); + verts[3].set(left, top, z); + verts[4].set(right, bottom, z); + verts[5].set(right, top, z); + } + + + void CursorDecorator::render(IRenderer& renderer, Block& block) + { + m_part = m_part ? m_part : m_atlas->getPart("cursor"); + + if(m_part) + { + setVertices(m_vertices, block.getGlobalLeft(), block.getGlobalTop(), block.getGlobalLeft() + m_part->m_pixel_width, block.getGlobalBottom(), block.getZ()); + m_part->getUvs(&m_uvs[0]); + if(m_atlas->getTexture()) + { + renderer.renderImage(m_atlas->getTexture(), &m_vertices[0].x, m_uvs, 6); + } + } + } + + + bool CursorDecorator::create(Gui& gui, const char* atlas) + { + m_part = NULL; + m_atlas = gui.loadAtlas(atlas); + return m_atlas != NULL; + } + + +} // ~namespace UI +} // ~namespace Lux \ No newline at end of file diff --git a/src/gui/decorators/cursor_decorator.h b/src/gui/decorators/cursor_decorator.h new file mode 100644 index 000000000..e77f2406e --- /dev/null +++ b/src/gui/decorators/cursor_decorator.h @@ -0,0 +1,40 @@ +#pragma once + + +#include "core/lux.h" +#include "core/vec3.h" +#include "gui/atlas.h" +#include "gui/decorator_base.h" + + +namespace Lux +{ +namespace UI +{ + + class Dockable; + class Gui; + class IRenderer; + class TextureBase; + + + class LUX_GUI_API CursorDecorator : public DecoratorBase + { + public: + CursorDecorator(const char* name) : DecoratorBase(name) {} + + bool create(Gui& gui, const char* atlas); + virtual void render(IRenderer& renderer, Block& block) LUX_OVERRIDE; + + private: + void setVertices(Vec3* verts, float left, float top, float right, float bottom, float z) const; + + private: + Atlas* m_atlas; + const Atlas::Part* m_part; + Vec3 m_vertices[24]; + float m_uvs[48]; + }; + +} // ~namespace UI +} // ~namespace Lux \ No newline at end of file diff --git a/src/gui/irenderer.h b/src/gui/irenderer.h index d97aa3ed1..0ed69594c 100644 --- a/src/gui/irenderer.h +++ b/src/gui/irenderer.h @@ -2,6 +2,7 @@ #include "core/lux.h" +#include "gui/block.h" namespace Lux @@ -26,6 +27,7 @@ namespace UI virtual TextureBase* loadImage(const char* name, FS::FileSystem& file_system) = 0; virtual void beginRender(float w, float h) = 0; virtual void renderImage(TextureBase* image, float* vertices, float* tex_coords, int vertex_count) = 0; + virtual Block::Area getCharArea(const char* text, int pos, float max_width) = 0; virtual void measureText(const char* text, float* w, float* h, float max_width) = 0; virtual void renderText(const char* text, float x, float y, float z, float max_width) = 0; virtual void pushScissorArea(float left, float top, float right, float bottom) = 0; diff --git a/src/gui/opengl_renderer.cpp b/src/gui/opengl_renderer.cpp index 04c508660..4006da0c2 100644 --- a/src/gui/opengl_renderer.cpp +++ b/src/gui/opengl_renderer.cpp @@ -1,12 +1,13 @@ -#include "gui/opengl_renderer.h" #include #include #include +#include "gui/opengl_renderer.h" #include "core/array.h" #include "core/delegate_list.h" #include "core/file_system.h" #include "core/ifile.h" #include "core/map.h" +#include "core/math_utils.h" #include "core/pod_array.h" #include "core/string.h" #include "core/vec3.h" @@ -221,6 +222,73 @@ namespace UI glLoadIdentity(); } + Block::Area OpenGLRenderer::getCharArea(const char* text, int pos, float max_width) + { + Block::Area area; + if(text) + { + float width = 0; + float height = 0; + float prev_h = 0; + const char* c = text; + bool is_multiline = false; + OpenGLRendererImpl::Character character; + bool found = false; + bool is_some_char = false; + while(*c) + { + if(m_impl->m_characters.find(*c, character)) + { + is_some_char = true; + if(c - text == pos) + { + found = true; + area.left = width; + area.top = prev_h + character.y_offset; + area.right = width + character.x_advance; + area.bottom = prev_h + character.pixel_h + character.y_offset; + area.rel_bottom = area.rel_left = area.rel_right = area.rel_top = 0; + break; + } + width += character.x_advance; + height = Math::max(height, character.pixel_h); + if(width > max_width || *c == '\n') + { + is_multiline = true; + width = 0; + prev_h += height; + } + } + else if(*c == '\n') + { + is_multiline = true; + width = 0; + prev_h += height; + } + ++c; + } + if(!found) + { + if(is_some_char) + { + area.left = width; + area.top = prev_h + character.y_offset; + area.right = width + character.x_advance; + area.bottom = prev_h + character.pixel_h + character.y_offset; + } + else + { + area.left = 0; + area.right = 3; + area.top = 0; + area.bottom = 20; + } + area.rel_bottom = area.rel_left = area.rel_right = area.rel_top = 0; + } + } + return area; + } + void OpenGLRenderer::measureText(const char* text, float* w, float* h, float max_width) { if(!text) diff --git a/src/gui/opengl_renderer.h b/src/gui/opengl_renderer.h index 0f500a08e..b1e369ac7 100644 --- a/src/gui/opengl_renderer.h +++ b/src/gui/opengl_renderer.h @@ -24,6 +24,7 @@ namespace UI virtual void loadFont(const char* path, FS::FileSystem& file_system) LUX_OVERRIDE; virtual void beginRender(float w, float h) LUX_OVERRIDE; virtual void renderImage(TextureBase* image, float* vertices, float* tex_coords, int vertex_count) LUX_OVERRIDE; + virtual Block::Area getCharArea(const char* text, int pos, float max_width) LUX_OVERRIDE; virtual void measureText(const char* text, float* w, float* h, float max_width) LUX_OVERRIDE; virtual void renderText(const char* text, float x, float y, float z, float max_width) LUX_OVERRIDE; virtual void pushScissorArea(float left, float top, float right, float bottom) LUX_OVERRIDE;