diff --git a/CMakeLists.txt b/CMakeLists.txt index 6912209..c0d11fc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,10 +10,18 @@ endif() set(CMAKE_CXX_STANDARD 11) #set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/cmake") #if(CMAKE_COMPILER_IS_GNUCXX) -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -s USE_SDL=2 --preload-file ../resources/client") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -s USE_SDL_IMAGE=2 -s SDL2_IMAGE_FORMATS='[\"bmp\"]'") +set(CMAKE_CXX_FLAGS + "${CMAKE_CXX_FLAGS} -std=c++11 -s USE_SDL=2") +set(CMAKE_CXX_FLAGS + "${CMAKE_CXX_FLAGS} --preload-file ../resources/client") +set(CMAKE_CXX_FLAGS + "${CMAKE_CXX_FLAGS} --shell-file ../src/client/shell.html") +set(CMAKE_CXX_FLAGS + "${CMAKE_CXX_FLAGS} -s USE_SDL_IMAGE=2 -s SDL2_IMAGE_FORMATS='[\"bmp\"]'") #endif() +set(CMAKE_EXECUTABLE_SUFFIX ".html") + ## CLIENT BUILD ## #find_package(OpenGL REQUIRED) diff --git a/resources/client/fonts/scape.bmp b/resources/client/fonts/scape.bmp index 50b8e29..5a20acc 100644 Binary files a/resources/client/fonts/scape.bmp and b/resources/client/fonts/scape.bmp differ diff --git a/resources/client/shaders/font/font.frag b/resources/client/shaders/font/font.frag index 8995e4a..fa77ba0 100644 --- a/resources/client/shaders/font/font.frag +++ b/resources/client/shaders/font/font.frag @@ -1,14 +1,14 @@ -#version 330 core -in vec2 texCoords; -out vec4 fragColor; +precision mediump float; + +varying vec2 texCoords; uniform vec4 fontColor; uniform sampler2D fontBitmap; void main() { - vec4 outColor = texture(fontBitmap, texCoords); + vec4 outColor = texture2D(fontBitmap, texCoords); if(outColor.xyz == vec3(0.0, 0.0, 0.0)) discard; - fragColor = fontColor * outColor; + gl_FragColor = fontColor * outColor; } \ No newline at end of file diff --git a/resources/client/shaders/font/font.vert b/resources/client/shaders/font/font.vert index ed26e42..e8ca390 100644 --- a/resources/client/shaders/font/font.vert +++ b/resources/client/shaders/font/font.vert @@ -1,8 +1,7 @@ -#version 330 core -layout (location = 0) in vec2 aScreenCoords; -layout (location = 1) in vec2 aTexCoords; +attribute vec2 aScreenCoords; +attribute vec2 aTexCoords; -out vec2 texCoords; +varying vec2 texCoords; uniform mat4 transMatrix; uniform mat4 orthoMatrix; diff --git a/resources/client/shaders/test.frag b/resources/client/shaders/test.frag index 1eff33f..2042ad4 100644 --- a/resources/client/shaders/test.frag +++ b/resources/client/shaders/test.frag @@ -1,9 +1,5 @@ -#version 330 core -in vec2 texCoords; -out vec4 fragColor; - -uniform sampler2D fontBitmap; +precision mediump float; void main() { - fragColor = texture(fontBitmap, texCoords); + gl_FragColor = vec4(0.5, 0, 0, 1); } \ No newline at end of file diff --git a/resources/client/shaders/test.vert b/resources/client/shaders/test.vert index 3ad07a7..08171f8 100644 --- a/resources/client/shaders/test.vert +++ b/resources/client/shaders/test.vert @@ -1,12 +1,5 @@ -#version 330 core -layout (location = 0) in vec4 aScreenCoords; -layout (location = 1) in vec2 aTexCoords; - -out vec2 texCoords; - -uniform mat4 orthoMatrix; +attribute vec2 aPosition; void main() { - gl_Position = orthoMatrix * aScreenCoords; - texCoords = aTexCoords; + gl_Position = vec4(aPosition, 0, 1); } \ No newline at end of file diff --git a/src/client/common.hpp b/src/client/common.hpp new file mode 100644 index 0000000..0defa4f --- /dev/null +++ b/src/client/common.hpp @@ -0,0 +1,14 @@ +#ifndef SOSC_CLIENT_COMMON_H +#define SOSC_CLIENT_COMMON_H + +#include + +#ifdef SOSC_DEBUG +#define SOSC_RESOURCE_PATH (std::string("../resources/client/")) +#else +#define SOSC_RESOURCE_PATH (std::string("resources/")) +#endif + +#define SOSC_RESC(X) (SOSC_RESOURCE_PATH + std::string(X)) + +#endif diff --git a/src/client/main.cpp b/src/client/main.cpp index 9ad29f5..1ec0ca3 100644 --- a/src/client/main.cpp +++ b/src/client/main.cpp @@ -2,15 +2,33 @@ #include #include #include +#include +#include "ui/font.hpp" static struct { SDL_Window* window; SDL_GLContext gl_ctx; + + sosc::ui::Font* font; + sosc::ui::Text* text; } _ctx; void draw(); int main(int argc, char** argv) { + using namespace sosc; + + /*if(argc != 3) + return -1;*/ + + int client_width = std::stoi(argv[1]), + client_height = std::stoi(argv[2]); + + std::cout << "VERSION 9: " << argc << std::endl + << argv[0] << std::endl + << argv[1] << std::endl + << argv[2] << std::endl; + if(SDL_Init(SDL_INIT_VIDEO) < 0) { std::cout << SDL_GetError() << std::endl; return -1; @@ -21,7 +39,7 @@ int main(int argc, char** argv) { _ctx.window = SDL_CreateWindow( "SockScape Client", 0, 0, - 640, 480, + client_width, client_height, SDL_WINDOW_OPENGL ); @@ -32,12 +50,28 @@ int main(int argc, char** argv) { if(glewInit() != GLEW_OK) return -1; + ui::font_init_subsystem(_ctx.window); + _ctx.font = new ui::Font( + SOSC_RESC("fonts/scape.bmp"), + SOSC_RESC("fonts/scape.dat") + ); + ui::font_set_default(_ctx.font); + + _ctx.text = new ui::Text( + 75, glm::vec4(1, 0, 0, 1), "test text", + 100, 100, 400 + ); + emscripten_set_main_loop(draw, 0, 1); + + std::cout << "end test" << std::endl; } void draw() { glClear(GL_COLOR_BUFFER_BIT); glClearColor(.25, .25, .25, 1); + _ctx.text->Render(); + SDL_GL_SwapWindow(_ctx.window); } \ No newline at end of file diff --git a/src/client/shaders/_shader.cpp b/src/client/shaders/_shader.cpp new file mode 100644 index 0000000..194ac54 --- /dev/null +++ b/src/client/shaders/_shader.cpp @@ -0,0 +1,109 @@ +#include "_shader.hpp" + +sosc::shdr::Shader::Shader() : loaded(false) {} + +bool sosc::shdr::Shader::Load() { + if(this->loaded) + return true; + + this->program = glCreateProgram(); + this->PrepareLoad(); + + this->loaded = true; + return true; +} + +void sosc::shdr::Shader::Start() const { + if(!this->loaded) + return; + + glUseProgram(this->program); +} + +void sosc::shdr::Shader::Stop() const { + if(!this->loaded) + return; + + glUseProgram(0); +} + +void sosc::shdr::Shader::Unload() { + if(!this->loaded) + return; + + PrepareUnload(); + glDeleteProgram(this->program); + this->loaded = false; +} + +bool sosc::shdr::Shader::AttachSource + (const std::string& fileName, GLuint shaderType) +{ + if(this->loaded) + return false; + + GLuint shader = glCreateShader(shaderType); + + std::ifstream file(fileName); + std::stringstream ss; + ss << file.rdbuf(); + std::string source = ss.str(); + + const char* src = source.c_str(); + glShaderSource(shader, 1, &src, nullptr); + glCompileShader(shader); + + GLint err_chk; + glGetShaderiv(shader, GL_COMPILE_STATUS, &err_chk); + if(err_chk == GL_FALSE) { + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &err_chk); + auto msg = new char[err_chk]; + + glGetShaderInfoLog(shader, err_chk, nullptr, msg); + std::cerr << "Error in '"<< fileName << "':" << msg << std::endl; + delete[] msg; + + return false; + } + + glAttachShader(this->program, shader); + this->shaders.push_back(shader); + return true; +} + +bool sosc::shdr::Shader::LinkProgram() { + GLint err_chk; + + glLinkProgram(this->program); + glGetProgramiv(this->program, GL_LINK_STATUS, &err_chk); + if(err_chk == GL_FALSE) { + glGetProgramiv(this->program, GL_INFO_LOG_LENGTH, &err_chk); + auto msg = new char[err_chk]; + + glGetProgramInfoLog(this->program, err_chk, nullptr, msg); + std::cerr << "Error linking shader: " << msg << std::endl; + delete[] msg; + + return false; + } + + for(const auto& shader : this->shaders) + glDeleteShader(shader); + return true; +} + +bool sosc::shdr::Shader::LoadUniforms(std::vector names) { + if(this->loaded) + return false; + + GLint id; + this->locations.clear(); + for(const auto& name : names) { + if((id = glGetUniformLocation(this->program, name.c_str())) == -1) + 0; //return false; + + this->locations.push_back(id); + } + + return true; +} \ No newline at end of file diff --git a/src/client/shaders/_shader.hpp b/src/client/shaders/_shader.hpp new file mode 100644 index 0000000..641dd82 --- /dev/null +++ b/src/client/shaders/_shader.hpp @@ -0,0 +1,52 @@ +#ifndef SOSC_SHADER_CORE_H +#define SOSC_SHADER_CORE_H + +#include "common.hpp" +#include +#include +#include +#include +#include +#include + +namespace sosc { +namespace shdr { +class Shader { +public: + Shader(); + + bool Load(); + void Start() const; + + inline GLint operator[] (std::vector::size_type index) { + if(index >= locations.size()) + return -1; + + return locations[index]; + } + + inline GLint Uniform(std::vector::size_type index) { + if(index >= locations.size()) + return -1; + + return locations[index]; + } + + void Stop() const; + void Unload(); +protected: + virtual void PrepareLoad() = 0; + virtual void PrepareUnload() {}; + + bool AttachSource(const std::string& fileName, GLuint shaderType); + bool LinkProgram(); + bool LoadUniforms(std::vector names); +private: + std::vector locations; + std::vector shaders; + GLuint program; + bool loaded; +}; +}} + +#endif diff --git a/src/client/shaders/test.hpp b/src/client/shaders/test.hpp new file mode 100644 index 0000000..df3eabf --- /dev/null +++ b/src/client/shaders/test.hpp @@ -0,0 +1,20 @@ +#ifndef SOSC_SHADER_TEST_H +#define SOSC_SHADER_TEST_H + +#include +#include "common.hpp" +#include "_shader.hpp" + +namespace sosc { +namespace shdr { +class TestShader : public Shader { +protected: + void PrepareLoad() override { + this->AttachSource(SOSC_RESC("shaders/test.vert"), GL_VERTEX_SHADER); + this->AttachSource(SOSC_RESC("shaders/test.frag"), GL_FRAGMENT_SHADER); + this->LinkProgram(); + } +}; +}} + +#endif diff --git a/src/client/shell.html b/src/client/shell.html new file mode 100644 index 0000000..8793667 --- /dev/null +++ b/src/client/shell.html @@ -0,0 +1,54 @@ + + + + + SockScape Client + + + +
+ +
+ + {{{ SCRIPT }}} + + \ No newline at end of file diff --git a/src/client/ui/font.cpp b/src/client/ui/font.cpp new file mode 100644 index 0000000..6b8bb7d --- /dev/null +++ b/src/client/ui/font.cpp @@ -0,0 +1,412 @@ +#include "font.hpp" + +#define LILEND_UNPACK(X,N) \ + (((uint32_t)(X)[N]) | \ + ((((uint32_t)(X)[(N)+1])) << 8u) | \ + ((((uint32_t)(X)[(N)+2])) << 16u) | \ + ((((uint32_t)(X)[(N)+3])) << 24u)) + +namespace sosc { +namespace ui { +class FontShader; +}} + +// STATE STRUCT // + +static struct { + sosc::ui::FontShader* shader; + sosc::ui::Font* default_font; +} _font_ctx; + +// FONT SHADER CLASS // + +namespace sosc { +namespace ui { +class FontShader : public sosc::shdr::Shader { +public: + enum Uniforms { + ORTHO_MATRIX = 0, + TRANSLATION_MATRIX, + FONT_BITMAP, + FONT_COLOR + }; + + void UpdateWindow(SDL_Window *window) { + this->Start(); + + int width, height; + SDL_GetWindowSize(window, &width, &height); + glm::mat4 orthoMatrix = + glm::ortho(0.f, (float)width, (float)height, 0.f); + glUniformMatrix4fv( + (*this)[ORTHO_MATRIX], 1, GL_FALSE, + glm::value_ptr(orthoMatrix) + ); + + this->Stop(); + } + +protected: + void PrepareLoad() override { + this->AttachSource( + SOSC_RESC("shaders/font/font.vert"), GL_VERTEX_SHADER + ); + this->AttachSource( + SOSC_RESC("shaders/font/font.frag"), GL_FRAGMENT_SHADER + ); + + this->LinkProgram(); + this->LoadUniforms({ + "orthoMatrix", + "transMatrix", + "fontBitmap", + "fontColor" + }); + } +}; +}} + +// SUBSYSTEM FUNCS // + +void sosc::ui::font_init_subsystem(SDL_Window* window) { + _font_ctx.shader = new FontShader(); + _font_ctx.shader->Load(); + _font_ctx.shader->UpdateWindow(window); + _font_ctx.default_font = nullptr; +} + +void sosc::ui::font_set_default(Font *font) { + _font_ctx.default_font = font; +} + +void sosc::ui::font_window_changed(SDL_Window* window) { + _font_ctx.shader->UpdateWindow(window); +} + +void sosc::ui::font_deinit_subsystem() { + _font_ctx.shader->Unload(); + delete _font_ctx.shader; +} + +// FONT CLASS // + +sosc::ui::Font::Font + (const std::string& bitmapPath, const std::string& dataPath, + bool useNearest) +{ + this->loaded = false; + this->Load(bitmapPath, dataPath); +} + +bool sosc::ui::Font::Load + (const std::string& bitmapPath, const std::string& dataPath, + bool useNearest) +{ + if(this->loaded) + this->Unload(); + + SDL_RWops* rwop = SDL_RWFromFile(bitmapPath.c_str(), "rb"); + SDL_Surface* image = SDL_LoadBMP_RW(rwop, 1); + if(!image) + return false; + + char buffer[0x111]; + std::ifstream dataFile(dataPath, std::ios::in | std::ios::binary); + if(!dataFile.is_open()) + return false; + dataFile.read(buffer, 0x111); + dataFile.close(); + std::string data(buffer, 0x111); + if(buffer[0x10] != 0) + return false; + + this->width = (uint32_t)image->w; + this->height = (uint32_t)image->h; + + this->cell_width = LILEND_UNPACK(buffer, 0x08); + this->cell_height = LILEND_UNPACK(buffer, 0x0C); + + for(int i = 0; i < 256; ++i) { + auto glyph = &this->glyphs[i]; + auto width = (uint8_t)buffer[0x11 + i]; + + glyph->width = (double)width / (double)this->cell_width; + + int x = (this->cell_width * i) % this->width; + int y = ((this->cell_width * i) / this->width) * this->cell_height; + + glyph->top_left = glm::vec2( + (double)x / (double)this->width, + 1 - (double)y / (double)this->height + ); + glyph->top_right = glm::vec2( + (double)(x + width) / (double)this->width, + 1 - (double)y / (double)this->height + ); + glyph->bottom_left = glm::vec2( + (double)x / (double)this->width, + 1 - (double)(y + this->cell_height) / (double)this->height + ); + glyph->bottom_right = glm::vec2( + (double)(x + width) / (double)this->width, + 1 - (double)(y + this->cell_height) / (double)this->height + ); + } + + glGenTextures(1, &this->texture); + glBindTexture(GL_TEXTURE_2D, this->texture); + glTexImage2D( + GL_TEXTURE_2D, 0, GL_RGB, + this->width, this->height, 0, + GL_RGB, + GL_UNSIGNED_BYTE, image->pixels + ); + + glTexParameteri( + GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, + useNearest ? GL_NEAREST : GL_LINEAR + ); + glTexParameteri( + GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, + useNearest ? GL_NEAREST : GL_LINEAR + ); + glBindTexture(GL_TEXTURE_2D, 0); + + SDL_FreeSurface(image); + this->loaded = true; + return true; +} + +void sosc::ui::Font::BindBitmap() const { + if(!this->loaded) + return; + + glBindTexture(GL_TEXTURE_2D, this->texture); +} + +void sosc::ui::Font::UnbindBitmap() const { + if(!this->loaded) + return; + + glBindTexture(GL_TEXTURE_2D, 0); +} + +void sosc::ui::Font::Unload() { + glDeleteTextures(1, &this->texture); + this->loaded = false; +} + +// TEXT CLASS // + +sosc::ui::Text::Text() { + this->font = _font_ctx.default_font; + this->font_size = 0; + this->vertices = nullptr; + this->tex_coords = nullptr; + + glGenVertexArrays(1, &this->vao); + glGenBuffers(2, this->vbos); +} + +sosc::ui::Text::Text + (sosc::ui::Font *font, uint32_t size, const glm::vec4& color) : Text() +{ + this->font = font; + this->font_size = size; + this->font_color = color; +} + +sosc::ui::Text::Text(uint32_t size, const glm::vec4& color, + const std::string &text, uint32_t x, uint32_t y, uint32_t w) : Text() +{ + this->Set(size, color, text, x, y, w); +} + +sosc::ui::Text::Text + (sosc::ui::Font *font, uint32_t size, const glm::vec4& color, + const std::string &text, uint32_t x, uint32_t y, uint32_t w) : Text() +{ + this->font = font; + this->Set(size, color, text, x, y, w); +} + +void sosc::ui::Text::Set(uint32_t size, const glm::vec4& color, + const std::string &text, uint32_t x, uint32_t y, uint32_t w) +{ + this->font_size = size; + this->font_color = color; + this->text = text; + if(w != 0) + this->wrap_width = w; + this->trans_matrix = glm::translate(glm::mat4(1.f), glm::vec3(x, y, 0.f)); + this->Redraw(); +} + +void sosc::ui::Text::SetFont(Font *font, uint32_t size) { + this->font = font; + if(size != 0) + this->font_size = size; + this->Redraw(); +} + +void sosc::ui::Text::SetFontSize(uint32_t size) { + this->font_size = size; + this->Redraw(); +} + +void sosc::ui::Text::SetFontColor(const glm::vec4 &color) { + this->font_color = color; +} + +void sosc::ui::Text::SetText(const std::string &text) { + this->text = text; + this->Redraw(); +} + +void sosc::ui::Text::SetPosition(uint32_t x, uint32_t y) { + this->trans_matrix = glm::translate(glm::mat4(1.f), glm::vec3(x, y, 0.f)); +} + +void sosc::ui::Text::SetWrapWidth(uint32_t w) { + this->wrap_width = w; + this->Redraw(); +} + +uint32_t sosc::ui::Text::GetHeight() const { + return this->GetLineCount() * this->font_size; +} + +uint32_t sosc::ui::Text::GetLineCount() const { + if(this->wrap_width == 0) + return 1; + else { + uint32_t count = 1, topleft_x = 0; + for(const auto c : this->text) { + auto width = (uint32_t)((*this->font)[c].width * this->font_size); + + if(topleft_x + width > this->wrap_width) { + ++count; + topleft_x = 0; + } + + topleft_x += width; + } + return count; + } +} + +void sosc::ui::Text::Render() { + auto shdr = _font_ctx.shader; + + shdr->Start(); + glUniformMatrix4fv( + (*shdr)[shdr->TRANSLATION_MATRIX], + 1, GL_FALSE, + glm::value_ptr(this->trans_matrix) + ); + glUniform4f( + (*shdr)[shdr->FONT_COLOR], + this->font_color.r, this->font_color.g, + this->font_color.b, this->font_color.a + ); + + glActiveTexture(GL_TEXTURE0); + this->font->BindBitmap(); + + glBindVertexArray(this->vao); + glDrawArrays(GL_TRIANGLES, 0, this->vertex_count); + glBindVertexArray(0); + + this->font->UnbindBitmap(); + shdr->Stop(); +} + +void sosc::ui::Text::Destroy() { + glDeleteBuffers(2, this->vbos); + glDeleteVertexArrays(1, &this->vao); + + delete[] this->vertices; + delete[] this->tex_coords; +} + +void sosc::ui::Text::Redraw() { + this->vertex_count = (GLuint)(6 * this->text.length()); + + delete[] this->vertices; + this->vertices = new float[this->vertex_count * 2]; + + delete[]this->tex_coords; + this->tex_coords = new float[this->vertex_count * 2]; + + uint32_t top_x = 0, top_y = 0; + for(int i = 0; i < this->text.length(); ++i) { + auto glyph = (*this->font)[this->text[i]]; + uint32_t width = (uint32_t)(this->font_size * glyph.width), + height = this->font_size; + + if(top_x + width > this->wrap_width && this->wrap_width != 0) { + top_x = 0; + top_y += height; + } + + /// TRIANGLE 1 /// + // TOP LEFT + this->vertices[i*12] = top_x; + this->vertices[i*12 + 1] = top_y; + this->tex_coords[i*12] = glyph.top_left.x; + this->tex_coords[i*12 + 1] = glyph.top_left.y; + // TOP RIGHT + this->vertices[i*12 + 2] = top_x + width; + this->vertices[i*12 + 3] = top_y; + this->tex_coords[i*12 + 2] = glyph.top_right.x; + this->tex_coords[i*12 + 3] = glyph.top_right.y; + // BOTTOM LEFT + this->vertices[i*12 + 4] = top_x; + this->vertices[i*12 + 5] = top_y + height; + this->tex_coords[i*12 + 4] = glyph.bottom_left.x; + this->tex_coords[i*12 + 5] = glyph.bottom_left.y; + + /// TRIANGLE 2 /// + // BOTTOM LEFT + this->vertices[i*12 + 6] = top_x; + this->vertices[i*12 + 7] = top_y + height; + this->tex_coords[i*12 + 6] = glyph.bottom_left.x; + this->tex_coords[i*12 + 7] = glyph.bottom_left.y; + // TOP RIGHT + this->vertices[i*12 + 8] = top_x + width; + this->vertices[i*12 + 9] = top_y; + this->tex_coords[i*12 + 8] = glyph.top_right.x; + this->tex_coords[i*12 + 9] = glyph.top_right.y; + // BOTTOM RIGHT + this->vertices[i*12 + 10] = top_x + width; + this->vertices[i*12 + 11] = top_y + height; + this->tex_coords[i*12 + 10] = glyph.bottom_right.x; + this->tex_coords[i*12 + 11] = glyph.bottom_right.y; + + top_x += width; + } + + glBindVertexArray(this->vao); + + glEnableVertexAttribArray(0); + glBindBuffer(GL_ARRAY_BUFFER, this->vbos[0]); + glBufferData( + GL_ARRAY_BUFFER, + this->vertex_count * 2 * sizeof(float), + this->vertices, + GL_STATIC_DRAW + ); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, (void*)0); + + glEnableVertexAttribArray(1); + glBindBuffer(GL_ARRAY_BUFFER, this->vbos[1]); + glBufferData( + GL_ARRAY_BUFFER, + this->vertex_count * 2 * sizeof(float), + this->tex_coords, + GL_STATIC_DRAW + ); + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, (void*)0); + + glBindVertexArray(0); +} \ No newline at end of file diff --git a/src/client/ui/font.hpp b/src/client/ui/font.hpp new file mode 100644 index 0000000..74bb1d5 --- /dev/null +++ b/src/client/ui/font.hpp @@ -0,0 +1,100 @@ +#ifndef SOSC_UI_FONT_H +#define SOSC_UI_FONT_H + +#include +#include +#include +#include +#include +#include + +#include +#include +#include "shaders/_shader.hpp" + +namespace sosc { +namespace ui { +class Font; +void font_init_subsystem(SDL_Window* window); +void font_set_default(sosc::ui::Font* font); +void font_window_changed(SDL_Window* window); +void font_deinit_subsystem(); + +class Font { +public: + struct glyph_t { + double width; + glm::vec2 + top_left, + top_right, + bottom_left, + bottom_right; + }; + + Font() : loaded(false) {} + Font(const std::string& bitmapPath, + const std::string& dataPath, bool useNearest = true); + bool Load(const std::string& bitmapPath, + const std::string& dataPath, bool useNearest = true); + + inline const glyph_t& operator[] (char c) const { + return this->glyphs[(uint8_t)c]; + } + + void Unload(); +private: + void BindBitmap() const; + void UnbindBitmap() const; + + bool loaded; + GLuint texture; + uint32_t + width, height, + cell_width, cell_height; + glyph_t glyphs[256]; + + friend class Text; +}; + +class Text { +public: + Text(); + Text(Font* font, uint32_t size, const glm::vec4& color); + Text(uint32_t size, const glm::vec4& color, const std::string& text, + uint32_t x, uint32_t y, uint32_t w = 0); + Text(Font* font, uint32_t size, const glm::vec4& color, + const std::string& text, uint32_t x, uint32_t y, uint32_t w = 0); + + void Set(uint32_t size, const glm::vec4& color, const std::string& text, + uint32_t x, uint32_t y, uint32_t w = 0); + void SetFont(Font* font, uint32_t size = 0); + void SetFontSize(uint32_t size); + void SetFontColor(const glm::vec4& color); + void SetText(const std::string& text); + void SetPosition(uint32_t x, uint32_t y); + void SetWrapWidth(uint32_t w); + + uint32_t GetHeight() const; + uint32_t GetLineCount() const; + + void Render(); + + void Destroy(); +private: + void Redraw(); + + Font* font; + glm::vec4 font_color; + uint32_t font_size, + wrap_width; + std::string text; + glm::mat4 trans_matrix; + + GLsizei vertex_count; + float* vertices; + float* tex_coords; + GLuint vao, vbos[2]; +}; +}} + +#endif