Working FontManager and Label implementation (WIP)
This commit is contained in:
parent
3b2be83590
commit
af6967151e
15 changed files with 608 additions and 61 deletions
9
.ccls
Normal file
9
.ccls
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
clang++
|
||||
-std=c++17
|
||||
-DDEBUG
|
||||
-Iinclude
|
||||
-I/usr/include/freetype2
|
||||
-I/usr/include/libpng16
|
||||
-I/usr/include/harfbuzz
|
||||
-I/usr/include/glib-2.0
|
||||
-I/usr/lib/glib-2.0/include
|
||||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
#define GLM_FORCE_RADIANS
|
||||
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
|
||||
#include <glm/vec4.hpp>
|
||||
#include <glm/mat4x4.hpp>
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
// clang-format off
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
#include <vector>
|
||||
|
||||
#include "graphic_context.hpp"
|
||||
#include "uui/opengl/font_manager.hpp"
|
||||
|
||||
namespace uui
|
||||
{
|
||||
|
|
@ -33,11 +34,13 @@ private:
|
|||
static GlApplication* application_pointer;
|
||||
};
|
||||
|
||||
class GlLabel;
|
||||
class GlComponent;
|
||||
class GlWindow
|
||||
{
|
||||
friend class GlApplication;
|
||||
friend class GlComponent;
|
||||
friend class GlLabel;
|
||||
|
||||
public:
|
||||
std::string title;
|
||||
|
|
@ -50,14 +53,21 @@ public:
|
|||
std::shared_ptr<GlComponent> create_component(
|
||||
const std::variant<int, float> x, const std::variant<int, float> y, const std::variant<int, float> width,
|
||||
const std::variant<int, float> height);
|
||||
std::shared_ptr<GlLabel> create_label(
|
||||
const std::variant<int, float> x, const std::variant<int, float> y, const std::string& text,
|
||||
const std::tuple<float, float, float, float>& text_color);
|
||||
|
||||
private:
|
||||
protected:
|
||||
GlApplication* app;
|
||||
|
||||
GlFontManager font_manager;
|
||||
|
||||
int width;
|
||||
int height;
|
||||
bool need_resize_refresh;
|
||||
|
||||
bool iconified;
|
||||
|
||||
size_t index;
|
||||
bool initialized;
|
||||
|
||||
|
|
@ -72,6 +82,8 @@ private:
|
|||
void draw();
|
||||
|
||||
static void glfw_resize_callback(GLFWwindow* window, int width, int height);
|
||||
static void glfw_iconify_callback(GLFWwindow* window, int iconified);
|
||||
static void glfw_key_callback(GLFWwindow* window, int key, int scancode, int action, int mods);
|
||||
};
|
||||
|
||||
} // namespace uui
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <array>
|
||||
// #include <memory>
|
||||
#include <memory>
|
||||
#include <variant>
|
||||
|
||||
#include "graphic_context.hpp"
|
||||
|
|
@ -20,7 +20,7 @@ public:
|
|||
|
||||
void set_background_color(float r, float g, float b, float a = 0.0f);
|
||||
|
||||
private:
|
||||
protected:
|
||||
std::shared_ptr<GlWindow> window;
|
||||
|
||||
std::variant<int, float> position_x;
|
||||
|
|
@ -41,11 +41,11 @@ private:
|
|||
std::shared_ptr<GlWindow> window, const std::variant<int, float> x, const std::variant<int, float> y,
|
||||
const std::variant<int, float> width, const std::variant<int, float> height);
|
||||
|
||||
void init();
|
||||
void set_vbo_data();
|
||||
void deinit();
|
||||
void render();
|
||||
void on_resize();
|
||||
virtual void init();
|
||||
virtual void set_vbo_data();
|
||||
virtual void deinit();
|
||||
virtual void render();
|
||||
virtual void on_resize();
|
||||
};
|
||||
|
||||
} // namespace uui
|
||||
|
|
|
|||
68
include/uui/opengl/font_manager.hpp
Normal file
68
include/uui/opengl/font_manager.hpp
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
#include <ft2build.h>
|
||||
#include FT_FREETYPE_H
|
||||
#include "graphic_context.hpp"
|
||||
|
||||
namespace uui
|
||||
{
|
||||
class GlWindow;
|
||||
class GlFontManager
|
||||
{
|
||||
friend class GlWindow;
|
||||
|
||||
public:
|
||||
struct character_info
|
||||
{
|
||||
float advance_x; // advance.x
|
||||
float advance_y; // advance.y
|
||||
|
||||
float bitmap_width; // bitmap.width;
|
||||
float bitmap_height; // bitmap.rows;
|
||||
|
||||
float bitmap_left; // bitmap_left;
|
||||
float bitmap_top; // bitmap_top;
|
||||
|
||||
float texture_x; // x offset of glyph in texture coordinates
|
||||
float texture_y; // y offset of glyph in texture coordinates
|
||||
};
|
||||
|
||||
struct Font
|
||||
{
|
||||
character_info char_infos[256];
|
||||
GLuint tex;
|
||||
unsigned int atlas_width;
|
||||
unsigned int atlas_height;
|
||||
};
|
||||
|
||||
std::vector<Font> fonts;
|
||||
|
||||
GlFontManager();
|
||||
~GlFontManager();
|
||||
|
||||
void render_text(
|
||||
const std::string& text, Font& font, float x, float y, float scale_x, float scale_y,
|
||||
const std::tuple<float, float, float, float>& text_color);
|
||||
std::tuple<int, int> get_text_size(const std::string& text, Font& font, float scale_x, float scale_y);
|
||||
|
||||
private:
|
||||
static constexpr int MAX_WIDTH = 1024;
|
||||
|
||||
bool initialized;
|
||||
|
||||
FT_Library ft;
|
||||
|
||||
GLuint gl_program;
|
||||
GLuint gl_vao;
|
||||
GLuint gl_vbo;
|
||||
|
||||
void init();
|
||||
void deinit();
|
||||
const Font& init_font(const std::string& font_path, size_t font_size);
|
||||
};
|
||||
} // namespace uui
|
||||
33
include/uui/opengl/label.hpp
Normal file
33
include/uui/opengl/label.hpp
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
|
||||
#include "uui/opengl/component.hpp"
|
||||
|
||||
namespace uui
|
||||
{
|
||||
class GlLabel: public GlComponent
|
||||
{
|
||||
friend class GlWindow;
|
||||
|
||||
size_t font_index;
|
||||
|
||||
float get_width() const;
|
||||
float get_height() const;
|
||||
|
||||
protected:
|
||||
std::string text;
|
||||
const std::tuple<float, float, float, float>& text_color;
|
||||
|
||||
int text_width;
|
||||
int text_height;
|
||||
|
||||
GlLabel(
|
||||
std::shared_ptr<GlWindow> window, const std::variant<int, float> x, const std::variant<int, float> y, const std::string& text,
|
||||
const std::tuple<float, float, float, float>& text_color);
|
||||
|
||||
void render();
|
||||
};
|
||||
|
||||
} // namespace uui
|
||||
4
make.py
4
make.py
|
|
@ -21,8 +21,8 @@ class Config:
|
|||
COMMON_FLAGS = '-std=c++17 -fno-semantic-interposition'
|
||||
COMMON_DEBUG_FLAGS = '-g -DDEBUG'
|
||||
COMMON_RELEASE_FLAGS = '-O2' # -flto'
|
||||
COMPILE_FLAGS = f'-Wall -I{INCLUDE_DIR} `pkg-config --cflags glfw3 vulkan gl x11`'
|
||||
LINK_FLAGS = '-lpthread -ldl `pkg-config --libs glfw3 vulkan gl x11`'
|
||||
COMPILE_FLAGS = f'-Wall -I{INCLUDE_DIR} `pkg-config --cflags glfw3 vulkan gl x11 freetype2`'
|
||||
LINK_FLAGS = '-lpthread -ldl `pkg-config --libs glfw3 vulkan gl x11 freetype2`'
|
||||
|
||||
PRE_COMPILE_FUNCTION = None
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include "uui/opengl/application.hpp"
|
||||
#include "uui/opengl/component.hpp"
|
||||
#include "uui/opengl/label.hpp"
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
|
@ -16,21 +17,23 @@ int main()
|
|||
auto window = app.create_window(800, 600, "OpenGl!");
|
||||
auto comp = window->create_component(0, 100, 0.5f, 50);
|
||||
comp->set_background_color(0.8f, 0.1f, 0.5f);
|
||||
auto label = window->create_label(0.5f, 0.5f, "Hello World!", {1.0f, 1.0f, 1.0f, 0.5f});
|
||||
label->set_background_color(0.1f, 0.5f, 0.1f);
|
||||
app.run();
|
||||
}
|
||||
cout << "\nStarting example 2" << endl;
|
||||
{
|
||||
uui::GlApplication app;
|
||||
auto window_1 = app.create_window(800, 600, "OpenGl!");
|
||||
auto comp = window_1->create_component(0.25f, 0.25f, 0.5f, 0.5f);
|
||||
comp->set_background_color(0.8f, 0.1f, 0.5f);
|
||||
comp = window_1->create_component(0.75f, 0.75f, 0.25f, 0.25f);
|
||||
comp->set_background_color(0.2f, 0.1f, 0.5f);
|
||||
auto window_2 = app.create_window(600, 600, "OpenGL 2!");
|
||||
comp = window_2->create_component(0, 0.5f, 100, 0.5f);
|
||||
comp->set_background_color(0.2f, 0.5f, 0.3f);
|
||||
app.run();
|
||||
}
|
||||
// cout << "\nStarting example 2" << endl;
|
||||
// {
|
||||
// uui::GlApplication app;
|
||||
// auto window_1 = app.create_window(800, 600, "OpenGl!");
|
||||
// auto comp = window_1->create_component(0.25f, 0.25f, 0.5f, 0.5f);
|
||||
// comp->set_background_color(0.8f, 0.1f, 0.5f);
|
||||
// comp = window_1->create_component(0.75f, 0.75f, 0.25f, 0.25f);
|
||||
// comp->set_background_color(0.2f, 0.1f, 0.5f);
|
||||
// auto window_2 = app.create_window(600, 600, "OpenGL 2!");
|
||||
// comp = window_2->create_component(0, 0.5f, 100, 0.5f);
|
||||
// comp->set_background_color(0.2f, 0.5f, 0.3f);
|
||||
// app.run();
|
||||
// }
|
||||
}
|
||||
catch(const std::exception& error)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ uui::GlApplication::GlApplication()
|
|||
|
||||
uui::GlApplication::~GlApplication()
|
||||
{
|
||||
if constexpr(debug_mode)
|
||||
std::cout << "Destroying the GlApplication" << std::endl;
|
||||
windows.clear();
|
||||
glfwTerminate();
|
||||
|
|
@ -36,14 +37,18 @@ uui::GlApplication::~GlApplication()
|
|||
|
||||
void uui::GlApplication::run()
|
||||
{
|
||||
if constexpr(debug_mode)
|
||||
std::cout << "Starting the GlApplication" << std::endl;
|
||||
|
||||
auto start_time = std::chrono::high_resolution_clock::now();
|
||||
auto end_time = start_time;
|
||||
float render_duration;
|
||||
float frame_count = 0.0f;
|
||||
if constexpr(debug_mode)
|
||||
std::cout << "\n";
|
||||
while(!windows.empty())
|
||||
{
|
||||
if constexpr(debug_mode)
|
||||
{
|
||||
end_time = std::chrono::high_resolution_clock::now();
|
||||
render_duration = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time).count();
|
||||
|
|
@ -54,6 +59,7 @@ void uui::GlApplication::run()
|
|||
frame_count = 0.0f;
|
||||
start_time = end_time;
|
||||
}
|
||||
}
|
||||
glfwPollEvents();
|
||||
|
||||
// Destroying windows
|
||||
|
|
@ -74,8 +80,10 @@ void uui::GlApplication::run()
|
|||
else
|
||||
window->draw();
|
||||
}
|
||||
if constexpr(debug_mode)
|
||||
frame_count += 1;
|
||||
}
|
||||
if constexpr(debug_mode)
|
||||
std::cout << "Ending the GlApplication" << std::endl;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ uui::GlComponent::GlComponent(
|
|||
window(window),
|
||||
position_x(x), position_y(y), size_width(width), size_height(height), initialized(false)
|
||||
{
|
||||
if constexpr(uui::GlApplication::debug_mode)
|
||||
std::cout << "Creating a GlBox" << std::endl;
|
||||
|
||||
coord_all_relative = true;
|
||||
|
|
@ -33,6 +34,7 @@ uui::GlComponent::GlComponent(
|
|||
|
||||
uui::GlComponent::~GlComponent()
|
||||
{
|
||||
if constexpr(uui::GlApplication::debug_mode)
|
||||
std::cout << "Closing a GlBox" << std::endl;
|
||||
if(initialized)
|
||||
deinit();
|
||||
|
|
|
|||
308
src/uui/opengl/font_manager.cpp
Normal file
308
src/uui/opengl/font_manager.cpp
Normal file
|
|
@ -0,0 +1,308 @@
|
|||
#include "uui/opengl/font_manager.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
|
||||
#include "uui/opengl/application.hpp"
|
||||
#include "uui/opengl/gl_utils.hpp"
|
||||
#include "uui/shader.hpp"
|
||||
|
||||
uui::GlFontManager::GlFontManager(): initialized(false) {}
|
||||
|
||||
uui::GlFontManager::~GlFontManager()
|
||||
{
|
||||
if(initialized)
|
||||
deinit();
|
||||
}
|
||||
|
||||
void uui::GlFontManager::init()
|
||||
{
|
||||
if(initialized)
|
||||
throw std::runtime_error("GlFontManager error : calling init while initialized");
|
||||
|
||||
if constexpr(uui::GlApplication::debug_mode)
|
||||
std::cout << "Init glFontManager" << std::endl;
|
||||
|
||||
if(FT_Init_FreeType(&ft))
|
||||
throw std::runtime_error("ERROR::FREETYPE: Could not init FreeType Library");
|
||||
|
||||
gl_program = glCreateProgram();
|
||||
// Vertex shader compilation
|
||||
GLuint vertex_shader = glCreateShader(GL_VERTEX_SHADER);
|
||||
{
|
||||
auto vertex_src = readFile("bin/uui/opengl/shaders/text.vert");
|
||||
auto vertex_str = std::string(vertex_src.begin(), vertex_src.end());
|
||||
auto vertex_code = vertex_str.c_str();
|
||||
glShaderSource(vertex_shader, 1, &vertex_code, NULL);
|
||||
glCompileShader(vertex_shader);
|
||||
if constexpr(uui::GlApplication::debug_mode)
|
||||
{
|
||||
int success;
|
||||
char info[512];
|
||||
glGetShaderiv(vertex_shader, GL_COMPILE_STATUS, &success);
|
||||
if(!success)
|
||||
{
|
||||
glGetShaderInfoLog(vertex_shader, 512, NULL, info);
|
||||
std::cout << "GlBox vertex shader creation error : " << info << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fragment shader compilation
|
||||
GLuint fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
|
||||
{
|
||||
auto fragment_src = readFile("bin/uui/opengl/shaders/text.frag");
|
||||
auto fragment_str = std::string(fragment_src.begin(), fragment_src.end());
|
||||
auto fragment_code = fragment_str.c_str();
|
||||
glShaderSource(fragment_shader, 1, &fragment_code, NULL);
|
||||
glCompileShader(fragment_shader);
|
||||
if constexpr(uui::GlApplication::debug_mode)
|
||||
{
|
||||
int success;
|
||||
char info[512];
|
||||
glGetShaderiv(fragment_shader, GL_COMPILE_STATUS, &success);
|
||||
if(!success)
|
||||
{
|
||||
glGetShaderInfoLog(fragment_shader, 512, NULL, info);
|
||||
std::cout << "GlBox fragment shader creation error : " << info << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Linking shader program
|
||||
glAttachShader(gl_program, vertex_shader);
|
||||
if constexpr(uui::GlApplication::debug_mode)
|
||||
_glCheckError();
|
||||
glAttachShader(gl_program, fragment_shader);
|
||||
if constexpr(uui::GlApplication::debug_mode)
|
||||
_glCheckError();
|
||||
glLinkProgram(gl_program);
|
||||
if constexpr(uui::GlApplication::debug_mode)
|
||||
{
|
||||
int success;
|
||||
char info[512];
|
||||
glGetProgramiv(gl_program, GL_LINK_STATUS, &success);
|
||||
if(!success)
|
||||
{
|
||||
glGetProgramInfoLog(gl_program, 512, NULL, info);
|
||||
std::cout << "GlBox error : " << info << std::endl;
|
||||
}
|
||||
}
|
||||
glDeleteShader(vertex_shader);
|
||||
glDeleteShader(fragment_shader);
|
||||
|
||||
// init_font("/usr/share/fonts/noto/NotoSans-Regular.ttf", 32);
|
||||
init_font("FreeSans.ttf", 32);
|
||||
|
||||
// Initialize VAO/VBO for rendering
|
||||
glGenVertexArrays(1, &gl_vao);
|
||||
glGenBuffers(1, &gl_vbo);
|
||||
glBindVertexArray(gl_vao);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, gl_vbo);
|
||||
glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 6 * 4, NULL, GL_DYNAMIC_DRAW);
|
||||
glEnableVertexAttribArray(0);
|
||||
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(float), 0);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
glBindVertexArray(0);
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
void uui::GlFontManager::deinit()
|
||||
{
|
||||
if(!initialized)
|
||||
throw std::runtime_error("GlFontManager error : calling deinit while not initialized");
|
||||
|
||||
for(const auto& font: fonts) glDeleteTextures(1, &font.tex);
|
||||
glDeleteBuffers(1, &gl_vbo);
|
||||
glDeleteBuffers(1, &gl_vao);
|
||||
glDeleteProgram(gl_program);
|
||||
FT_Done_FreeType(ft);
|
||||
initialized = false;
|
||||
}
|
||||
|
||||
const uui::GlFontManager::Font& uui::GlFontManager::init_font(const std::string& font_path, size_t font_size)
|
||||
{
|
||||
fonts.emplace_back();
|
||||
auto& font = fonts.back();
|
||||
FT_Face face;
|
||||
if(FT_New_Face(ft, font_path.c_str(), 0, &face))
|
||||
throw std::runtime_error("ERROR::FREETYPE: Failed to load font");
|
||||
FT_Set_Pixel_Sizes(face, 0, font_size);
|
||||
|
||||
FT_GlyphSlot glyph = face->glyph;
|
||||
|
||||
unsigned int row_width = 0;
|
||||
unsigned int row_height = 0;
|
||||
|
||||
memset(font.char_infos, 0, sizeof(font.char_infos));
|
||||
|
||||
// Find minimum size for a texture holding all visible ASCII characters
|
||||
for(int i = 32; i < 128; i++)
|
||||
{
|
||||
if(FT_Load_Char(face, i, FT_LOAD_RENDER))
|
||||
{
|
||||
std::cout << "GlFontManager warning : loading character " << i << " failed!" << std::endl;
|
||||
continue;
|
||||
}
|
||||
if(row_width + glyph->bitmap.width + 1 >= MAX_WIDTH)
|
||||
{
|
||||
font.atlas_width = std::max(font.atlas_width, row_width);
|
||||
font.atlas_height += row_height;
|
||||
row_width = 0;
|
||||
row_height = 0;
|
||||
}
|
||||
row_width += glyph->bitmap.width + 1;
|
||||
row_height = std::max(row_height, glyph->bitmap.rows);
|
||||
}
|
||||
|
||||
font.atlas_width = std::max(font.atlas_width, row_width);
|
||||
font.atlas_height += row_height;
|
||||
|
||||
glLinkProgram(gl_program);
|
||||
|
||||
// Create a texture that will be used to hold all ASCII glyphs
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glGenTextures(1, &font.tex);
|
||||
glBindTexture(GL_TEXTURE_2D, font.tex);
|
||||
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, font.atlas_width, font.atlas_height, 0, GL_RED, GL_UNSIGNED_BYTE, 0);
|
||||
|
||||
// We require 1 byte alignment when uploading texture data
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||
|
||||
// Clamping to edges is important to prevent artifacts when scaling
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
|
||||
// Linear filtering usually looks best for text
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
|
||||
// Paste all glyph bitmaps into the texture, remembering the offset
|
||||
int offset_x = 0;
|
||||
int offset_y = 0;
|
||||
|
||||
row_height = 0;
|
||||
|
||||
for(int i = 32; i < 128; i++)
|
||||
{
|
||||
if(FT_Load_Char(face, i, FT_LOAD_RENDER))
|
||||
{
|
||||
std::cout << "GlFontManager warning : loading character " << i << " failed!" << std::endl;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(offset_x + glyph->bitmap.width + 1 >= MAX_WIDTH)
|
||||
{
|
||||
offset_y += row_height;
|
||||
row_height = 0;
|
||||
offset_x = 0;
|
||||
}
|
||||
|
||||
glTexSubImage2D(
|
||||
GL_TEXTURE_2D, 0, offset_x, offset_y, glyph->bitmap.width, glyph->bitmap.rows, GL_RED, GL_UNSIGNED_BYTE, glyph->bitmap.buffer);
|
||||
font.char_infos[i].advance_x = glyph->advance.x >> 6;
|
||||
font.char_infos[i].advance_y = glyph->advance.y >> 6;
|
||||
|
||||
font.char_infos[i].bitmap_width = glyph->bitmap.width;
|
||||
font.char_infos[i].bitmap_height = glyph->bitmap.rows;
|
||||
|
||||
font.char_infos[i].bitmap_left = glyph->bitmap_left;
|
||||
font.char_infos[i].bitmap_top = glyph->bitmap_top;
|
||||
|
||||
font.char_infos[i].texture_x = offset_x / (float)font.atlas_width;
|
||||
font.char_infos[i].texture_y = offset_y / (float)font.atlas_height;
|
||||
|
||||
row_height = std::max(row_height, glyph->bitmap.rows);
|
||||
offset_x += glyph->bitmap.width + 1;
|
||||
}
|
||||
FT_Done_Face(face);
|
||||
|
||||
return font;
|
||||
}
|
||||
|
||||
std::tuple<int, int> uui::GlFontManager::get_text_size(
|
||||
const std::string& text, uui::GlFontManager::Font& font, float scale_x, float scale_y)
|
||||
{
|
||||
float width = 0.0f;
|
||||
float height = 0.0f;
|
||||
for(auto current_char: text)
|
||||
{
|
||||
const auto& char_info = font.char_infos[static_cast<size_t>(current_char)];
|
||||
width += char_info.advance_x * scale_x;
|
||||
// height = std::max(height, char_info.bitmap_top + char_info.bitmap_height * scale_y);
|
||||
height = std::max(height, roundf(char_info.bitmap_height * scale_y));
|
||||
}
|
||||
return {static_cast<int>(width), static_cast<int>(height)};
|
||||
}
|
||||
|
||||
void uui::GlFontManager::render_text(
|
||||
const std::string& text, uui::GlFontManager::Font& font, float x, float y, float scale_x, float scale_y,
|
||||
const std::tuple<float, float, float, float>& text_color)
|
||||
{
|
||||
GLuint gl_prev_program;
|
||||
glGetIntegerv(GL_CURRENT_PROGRAM, (GLint*)(&gl_prev_program));
|
||||
glUseProgram(gl_program);
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
auto [color_r, color_g, color_b, color_a] = text_color;
|
||||
glUniform4f(glGetUniformLocation(gl_program, "textColor"), color_r, color_g, color_b, color_a);
|
||||
glBindVertexArray(gl_vao);
|
||||
|
||||
// Use the texture containing the atlas
|
||||
glBindTexture(GL_TEXTURE_2D, font.tex);
|
||||
|
||||
std::vector<float> vertices;
|
||||
vertices.resize(24 * text.size());
|
||||
// vertices.resize(text.size());
|
||||
int char_count = 0;
|
||||
|
||||
// Loop through all characters
|
||||
float out_base_x = x;
|
||||
float out_base_y = y;
|
||||
for(auto current_char: text)
|
||||
{
|
||||
const auto& char_info = font.char_infos[static_cast<size_t>(current_char)];
|
||||
// Calculate the vertex and texture coordinates
|
||||
float char_x = out_base_x + char_info.bitmap_left * scale_x;
|
||||
float char_y = -out_base_y - char_info.bitmap_top * scale_y;
|
||||
float w = char_info.bitmap_width * scale_x;
|
||||
float h = char_info.bitmap_height * scale_y;
|
||||
|
||||
// Advance the cursor to the start of the next character
|
||||
out_base_x += char_info.advance_x * scale_x;
|
||||
out_base_y += char_info.advance_y * scale_y;
|
||||
|
||||
// Skip glyphs that have no pixels
|
||||
if(!w || !h)
|
||||
continue;
|
||||
|
||||
// clang-format off
|
||||
float current_vertex[24] = {
|
||||
char_x, -char_y, char_info.texture_x, char_info.texture_y, // top_left
|
||||
char_x + w, -char_y, char_info.texture_x + char_info.bitmap_width / font.atlas_width, char_info.texture_y, // top-right
|
||||
char_x, -char_y - h, char_info.texture_x, char_info.texture_y + char_info.bitmap_height / font.atlas_height, // bottom-right
|
||||
char_x + w, -char_y, char_info.texture_x + char_info.bitmap_width / font.atlas_width, char_info.texture_y, // top-right
|
||||
char_x, -char_y - h, char_info.texture_x, char_info.texture_y + char_info.bitmap_height / font.atlas_height, // bottom-left
|
||||
char_x + w, -char_y - h, char_info.texture_x + char_info.bitmap_width / font.atlas_width,
|
||||
char_info.texture_y + char_info.bitmap_height / font.atlas_height // bottom-right
|
||||
};
|
||||
std::memcpy(vertices.data() + (char_count * 24), current_vertex, 24 * sizeof(float));
|
||||
// clang-format on
|
||||
char_count += 1;
|
||||
}
|
||||
|
||||
// Draw all the character on the screen in one go
|
||||
glBindBuffer(GL_ARRAY_BUFFER, gl_vbo);
|
||||
glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(float), vertices.data(), GL_DYNAMIC_DRAW);
|
||||
glDrawArrays(GL_TRIANGLES, 0, vertices.size());
|
||||
|
||||
// glDisableVertexAttribArray(0);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
glBindVertexArray(0);
|
||||
glUseProgram(gl_prev_program);
|
||||
}
|
||||
35
src/uui/opengl/label.cpp
Normal file
35
src/uui/opengl/label.cpp
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
#include "uui/opengl/label.hpp"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "uui/opengl/gl_utils.hpp"
|
||||
|
||||
uui::GlLabel::GlLabel(
|
||||
std::shared_ptr<uui::GlWindow> window, const std::variant<int, float> x, const std::variant<int, float> y, const std::string& text,
|
||||
const std::tuple<float, float, float, float>& text_color):
|
||||
GlComponent(window, x, y, 0.0f, 0.0f),
|
||||
text(text), text_color(text_color)
|
||||
{
|
||||
if constexpr(uui::GlApplication::debug_mode)
|
||||
std::cout << "Creating a GlLabel" << std::endl;
|
||||
|
||||
font_index = 0;
|
||||
std::tie(text_width, text_height) = window->font_manager.get_text_size(text, window->font_manager.fonts[font_index], 1.0f, 1.0f);
|
||||
std::cout << "Text size: " << this->text_width << "x" << text_height << std::endl;
|
||||
size_width = text_width;
|
||||
size_height = text_height;
|
||||
}
|
||||
|
||||
void uui::GlLabel::render()
|
||||
{
|
||||
float text_x =
|
||||
position_x.index() == 1 ? std::get<1>(position_x) * static_cast<float>(window->width) : static_cast<float>(std::get<0>(position_x));
|
||||
float text_y =
|
||||
position_y.index() == 1 ? std::get<1>(position_y) * static_cast<float>(window->height) : static_cast<float>(std::get<0>(position_y));
|
||||
text_y -= static_cast<float>(text_height) - 1;
|
||||
|
||||
glBindVertexArray(gl_vao);
|
||||
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, (void*)0);
|
||||
window->font_manager.render_text(text.c_str(), window->font_manager.fonts.front(), text_x, text_y, 1.0f, 1.0f, text_color);
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
12
src/uui/opengl/shaders/text.frag
Normal file
12
src/uui/opengl/shaders/text.frag
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
#version 460 core
|
||||
|
||||
in vec2 textCoords;
|
||||
|
||||
out vec4 color;
|
||||
|
||||
uniform sampler2D text;
|
||||
uniform vec4 textColor;
|
||||
|
||||
void main(void) {
|
||||
color = vec4(1.0, 1.0, 1.0, texture2D(text, textCoords).r) * textColor;
|
||||
}
|
||||
12
src/uui/opengl/shaders/text.vert
Normal file
12
src/uui/opengl/shaders/text.vert
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
#version 460 core
|
||||
|
||||
layout (location = 0) in vec4 coord;
|
||||
|
||||
out vec2 textCoords;
|
||||
|
||||
uniform mat4 projection;
|
||||
|
||||
void main(void) {
|
||||
gl_Position = projection * vec4(coord.xy, 0.0, 1.0);
|
||||
textCoords = coord.zw;
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
#include "uui/opengl/component.hpp"
|
||||
#include "uui/opengl/gl_utils.hpp"
|
||||
#include "uui/opengl/label.hpp"
|
||||
#include "uui/shader.hpp"
|
||||
|
||||
static void APIENTRY
|
||||
|
|
@ -59,26 +60,38 @@ void uui::GlWindow::glfw_resize_callback(GLFWwindow* window, int width, int heig
|
|||
gl_window->width = width;
|
||||
gl_window->height = height;
|
||||
gl_window->need_resize_refresh = true;
|
||||
|
||||
glm::mat4 projection = glm::ortho(0.0f, static_cast<float>(width), 0.0f, static_cast<float>(height));
|
||||
glUniformMatrix4fv(glGetUniformLocation(gl_window->font_manager.gl_program, "projection"), 1, GL_FALSE, glm::value_ptr(projection));
|
||||
}
|
||||
|
||||
static void glfw_key_callback(GLFWwindow* window, int key, int scancode, int action, int mods)
|
||||
void uui::GlWindow::glfw_iconify_callback(GLFWwindow* window, int iconified)
|
||||
{
|
||||
auto gl_window = reinterpret_cast<uui::GlWindow*>(glfwGetWindowUserPointer(window));
|
||||
gl_window->iconified = iconified;
|
||||
}
|
||||
|
||||
void uui::GlWindow::glfw_key_callback(GLFWwindow* window, int key, int scancode, int action, int mods)
|
||||
{
|
||||
if(key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
|
||||
glfwSetWindowShouldClose(window, true);
|
||||
}
|
||||
|
||||
uui::GlWindow::GlWindow(uui::GlApplication* app, size_t index, int width, int height, const std::string& title):
|
||||
app(app), width(width), height(height), need_resize_refresh(false), index(index), initialized(false)
|
||||
app(app), width(width), height(height), need_resize_refresh(false), iconified(false), index(index), initialized(false)
|
||||
{
|
||||
if constexpr(uui::GlApplication::debug_mode)
|
||||
std::cout << "Creating a GlWindow" << std::endl;
|
||||
this->title = title;
|
||||
}
|
||||
|
||||
uui::GlWindow::~GlWindow()
|
||||
{
|
||||
if constexpr(uui::GlApplication::debug_mode)
|
||||
std::cout << "Closing a GlWindow" << std::endl;
|
||||
if(initialized)
|
||||
{
|
||||
if constexpr(uui::GlApplication::debug_mode)
|
||||
std::cout << "Destructor-deinit a GlWindow" << std::endl;
|
||||
deinit();
|
||||
}
|
||||
|
|
@ -97,10 +110,15 @@ void uui::GlWindow::init()
|
|||
glfwSetWindowUserPointer(glfw_window, this);
|
||||
glfwSetFramebufferSizeCallback(glfw_window, glfw_resize_callback);
|
||||
glfwSetKeyCallback(glfw_window, glfw_key_callback);
|
||||
glfwSetWindowIconifyCallback(glfw_window, glfw_iconify_callback);
|
||||
|
||||
if(!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
|
||||
throw std::runtime_error("GLWindow : Failed to initialize OpenGL context");
|
||||
|
||||
// glEnable(GL_CULL_FACE);
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
if constexpr(uui::GlApplication::debug_mode)
|
||||
{
|
||||
GLint flags;
|
||||
|
|
@ -178,6 +196,13 @@ void uui::GlWindow::init()
|
|||
}
|
||||
glDeleteShader(vertex_shader);
|
||||
glDeleteShader(fragment_shader);
|
||||
|
||||
font_manager.init();
|
||||
glUseProgram(font_manager.gl_program);
|
||||
glm::mat4 projection = glm::ortho(0.0f, static_cast<float>(width), 0.0f, static_cast<float>(height));
|
||||
glUniformMatrix4fv(glGetUniformLocation(font_manager.gl_program, "projection"), 1, GL_FALSE, glm::value_ptr(projection));
|
||||
glUseProgram(gl_program);
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
|
|
@ -186,12 +211,14 @@ void uui::GlWindow::deinit()
|
|||
if(!initialized)
|
||||
throw std::runtime_error("GlWindow error : calling deinit while not initialized");
|
||||
|
||||
if constexpr(uui::GlApplication::debug_mode)
|
||||
std::cout << "Deinit a GlWindow" << std::endl;
|
||||
|
||||
glfwMakeContextCurrent(glfw_window);
|
||||
glDeleteProgram(gl_program);
|
||||
for(auto& component: components) component->deinit();
|
||||
components.clear();
|
||||
font_manager.deinit();
|
||||
glfwDestroyWindow(glfw_window);
|
||||
|
||||
initialized = false;
|
||||
|
|
@ -199,6 +226,8 @@ void uui::GlWindow::deinit()
|
|||
|
||||
void uui::GlWindow::draw()
|
||||
{
|
||||
if(!iconified)
|
||||
{
|
||||
glfwMakeContextCurrent(glfw_window);
|
||||
glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
|
@ -210,6 +239,7 @@ void uui::GlWindow::draw()
|
|||
need_resize_refresh = false;
|
||||
}
|
||||
for(auto& component: components) component->render();
|
||||
}
|
||||
|
||||
glfwSwapBuffers(glfw_window);
|
||||
}
|
||||
|
|
@ -227,3 +257,17 @@ std::shared_ptr<uui::GlComponent> uui::GlWindow::create_component(
|
|||
components.push_back(component);
|
||||
return component;
|
||||
}
|
||||
|
||||
std::shared_ptr<uui::GlLabel> uui::GlWindow::create_label(
|
||||
const std::variant<int, float> x, const std::variant<int, float> y, const std::string& text,
|
||||
const std::tuple<float, float, float, float>& text_color)
|
||||
{
|
||||
if(!initialized)
|
||||
throw std::runtime_error("GlWindow error : cannot create component while not initialized");
|
||||
|
||||
glfwMakeContextCurrent(glfw_window);
|
||||
std::shared_ptr<uui::GlLabel> label(new uui::GlLabel(app->get_window(index), x, y, text, text_color));
|
||||
label->init();
|
||||
components.push_back(label);
|
||||
return label;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue