diff --git a/CMakeLists.txt b/CMakeLists.txt index 7a6431a10faa4c74e2eda5d2101dbc57d2a8524d..18f5917437ddfec06e7054e91747fd66e8ef8757 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,6 +26,7 @@ if (CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) glad/0.1.35 glm/0.9.9.8 spdlog/1.10.0 + freetype/2.11.1 GENERATORS cmake_find_package cmake_find_package_multi diff --git a/demo/data/LiberationSans-Regular.ttf b/demo/data/LiberationSans-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..22bae91bf0dcb729520a0da12d834db056e5044a Binary files /dev/null and b/demo/data/LiberationSans-Regular.ttf differ diff --git a/demo/main.cpp b/demo/main.cpp index 73ff92b915fb8ebb51a12aa17d33156b43ee7eca..f17d9fe2fffa7cfd8d9c1afb6b21312349590275 100644 --- a/demo/main.cpp +++ b/demo/main.cpp @@ -337,6 +337,8 @@ int main(int argc, const char* argv[]) { auto& locator = fggl::util::ServiceLocator::instance(); auto inputs = locator.supply<fggl::input::Input>(std::make_shared<fggl::input::Input>()); auto storage = locator.supply<fggl::data::Storage>(std::make_shared<fggl::data::Storage>()); + + locator.supply<fggl::gui::FontLibrary>(std::make_shared<fggl::gui::FontLibrary>()); locator.supply<fggl::ecs3::TypeRegistry>(std::make_shared<fggl::ecs3::TypeRegistry>()); // Would be nice to not take args like this, it messes with lifetimes diff --git a/fggl/CMakeLists.txt b/fggl/CMakeLists.txt index f2db608663e1ecf4f1e4d64894cd07ebd110c750..fbcb32f26ccc9af780c6fd433bd2da038ac5517e 100644 --- a/fggl/CMakeLists.txt +++ b/fggl/CMakeLists.txt @@ -50,6 +50,7 @@ target_sources(${PROJECT_NAME} gui/widget.cpp gui/widgets.cpp gui/containers.cpp + gui/fonts.cpp math/triangulation.cpp math/shapes.cpp ) @@ -58,6 +59,9 @@ target_sources(${PROJECT_NAME} find_package( spdlog ) target_link_libraries(${PROJECT_NAME} PRIVATE spdlog::spdlog) +find_package( freetype ) +target_link_libraries(${PROJECT_NAME} PUBLIC freetype ) + # Graphics backend add_subdirectory(gfx) diff --git a/fggl/gfx/ogl/shader.cpp b/fggl/gfx/ogl/shader.cpp index 63e69face5b108c876cac08643dc276414aa748c..e512f56bedba8435315de59e64bc1d71f5520a59 100644 --- a/fggl/gfx/ogl/shader.cpp +++ b/fggl/gfx/ogl/shader.cpp @@ -42,7 +42,7 @@ bool ShaderCache::compileShader(const std::string& fname, GLuint sid) { ShaderCache::ShaderCache(std::shared_ptr<fggl::data::Storage> storage) : m_storage(storage), m_shaders(), m_binary(true) { - if (GLAD_GL_ARB_get_program_binary) { + if ( !GLAD_GL_ARB_get_program_binary ) { spdlog::warn("the graphics card doesn support shader caching, disabling"); m_binary = false; } diff --git a/fggl/gfx/ogl4/canvas.cpp b/fggl/gfx/ogl4/canvas.cpp index 548dc152acf8f239f8d3c410d3ac858acf383b6a..3ea8893691144096fbe5447fa4b6a268472a7402 100644 --- a/fggl/gfx/ogl4/canvas.cpp +++ b/fggl/gfx/ogl4/canvas.cpp @@ -26,11 +26,22 @@ #include <glm/gtc/type_ptr.hpp> #include "fggl/math/triangulation.hpp" +#include "fggl/util/service.h" + +#include "fggl/gui/fonts.hpp" #define FGGL_OPENGL_CORRECTNESS namespace fggl::gfx::ogl4 { + static void make_box( gfx::Path2D& path, glm::vec2 topLeft, glm::vec2 bottomRight ) { + path.moveTo( { topLeft.x, topLeft.y } ); + path.pathTo( { bottomRight.x, topLeft.y } ); + path.pathTo( { bottomRight.x, bottomRight.y } ); + path.pathTo( { topLeft.x, bottomRight.y } ); + path.pathTo( { topLeft.x, topLeft.y } ); + } + inline static void add_mesh_triangle(data::Mesh2D& mesh, const std::vector<data::Vertex2D>& verts) { assert( verts.size() == 3); for( const auto& vert : verts ) { @@ -73,7 +84,7 @@ namespace fggl::gfx::ogl4 { } } - if (!verts.empty()) { + if (!verts.empty() && verts.size() > 3) { math::fan_triangulation(verts, mesh); } } @@ -105,7 +116,7 @@ namespace fggl::gfx::ogl4 { glBindVertexArray(0); } - void CanvasRenderer::render(GLuint shader, const gfx::Paint &paint) { + void CanvasRenderer::renderShapes (const gfx::Paint& paint, GLuint shader) { data::Mesh2D mesh; convert_to_mesh(paint, mesh); @@ -131,4 +142,59 @@ namespace fggl::gfx::ogl4 { glUseProgram( 0 ); } + void CanvasRenderer::renderText(const Paint& paint, GLuint shader) { + + if ( paint.textCmds().empty() ) { + return; + } + + auto fontFactory = util::ServiceLocator::instance().get<gui::FontLibrary>(); + std::shared_ptr<gui::FontFace> face = fontFactory->getFont("LiberationSans-Regular.ttf"); + if ( face == nullptr ){ + return; + } + + Paint textPaint; + for ( const auto& textCmd : paint.textCmds() ) { + auto label = textCmd.text; + + gfx::Path2D textPath(textCmd.pos); + math::vec2 penPos(textCmd.pos); + + // create a quad for this character + for (auto letter : label) { + auto& metrics = face->metrics(letter); + + std::cerr << metrics.size.x << std::endl; + + make_box(textPath, penPos, penPos + metrics.size); + penPos.x += (metrics.advance >> 6 ); + } + + textPaint.fill(textPath); + } + + data::Mesh2D mesh; + convert_to_mesh(textPaint, mesh); + + // render the text mesh + m_vao.bind(); + m_vertexList.replace(mesh.vertexList.size(), mesh.vertexList.data()); + m_indexList.replace(mesh.indexList.size(), mesh.indexList.data()); + + // draw + glUseProgram( shader ); + auto projMat = glm::ortho(0.0f, 1920.0f, 1080.0f, 0.f); + glUniformMatrix4fv(glGetUniformLocation( shader, "projection"), 1, GL_FALSE, + glm::value_ptr(projMat)); + + m_vao.drawElements(m_indexList, ogl::Primative::TRIANGLE, mesh.indexList.size()); + glUseProgram( 0 ); + } + + void CanvasRenderer::render(GLuint shader, const gfx::Paint &paint) { + renderShapes(paint, shader); + renderText(paint, shader); + } + } diff --git a/fggl/gui/fonts.cpp b/fggl/gui/fonts.cpp new file mode 100644 index 0000000000000000000000000000000000000000..36a72bea3d6f488af4ded3c25913b8f673580fde --- /dev/null +++ b/fggl/gui/fonts.cpp @@ -0,0 +1,62 @@ +/* + * ${license.title} + * Copyright (C) 2022 ${license.owner} + * ${license.mailto} + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "fggl/gui/fonts.hpp" + +namespace fggl::gui { + + FontFace::FontFace(FT_Face face) : m_face(face) { + } + + FontFace::~FontFace() { + FT_Done_Face(m_face); + } + + FontLibrary::FontLibrary() { + FT_Init_FreeType(&m_context); + } + + FontLibrary::~FontLibrary() { + // free all fonts + for (auto& face : m_cache ){ + face.second = nullptr; + } + + // shut the library down + FT_Done_FreeType(m_context); + } + + GlyphMetrics &FontFace::populateMetrics(char letter) { + if (FT_Load_Char(m_face, letter, FT_LOAD_RENDER) ) { + // something bad happened + return m_metrics['?']; + } + + GlyphMetrics metrics { + { m_face->glyph->bitmap.width, m_face->glyph->bitmap.rows}, + { m_face->glyph->bitmap_left, m_face->glyph->bitmap_top }, + m_face->glyph->advance.x + }; + + // return the new metrics data + auto it = m_metrics.emplace(letter, metrics); + return it.first->second; + } +} // namespace fggl::fonts \ No newline at end of file diff --git a/fggl/gui/widgets.cpp b/fggl/gui/widgets.cpp index c10d07d75d51b9c8ae25fe02bdaece0b4d547908..77d5c459dff780042d9494d7089e0d9c54b1b3ab 100644 --- a/fggl/gui/widgets.cpp +++ b/fggl/gui/widgets.cpp @@ -15,6 +15,8 @@ namespace fggl::gui { gfx::Path2D path{ topLeft() }; draw_button(path, topLeft(), size(), m_hover, m_active); paint.fill(path); + + paint.text(m_value, topLeft()); } void Button::activate() { @@ -37,4 +39,12 @@ namespace fggl::gui { void Button::addCallback(Callback cb) { m_callbacks.push_back( cb ); } + + void Button::label(const std::string &value) { + m_value = value; + } + + std::string Button::label() const { + return m_value; + } } // namespace fggl::gui \ No newline at end of file diff --git a/fggl/math/triangulation.cpp b/fggl/math/triangulation.cpp index 88d3d19c0c8d373dbc76a19fd8565d25003b0eb1..d235b8ef7130b0e9fd9ebe5530235a459d25fdd9 100644 --- a/fggl/math/triangulation.cpp +++ b/fggl/math/triangulation.cpp @@ -19,6 +19,7 @@ */ #include "fggl/math/triangulation.hpp" +#include <iostream> namespace fggl::math { diff --git a/fggl/scenes/menu.cpp b/fggl/scenes/menu.cpp index 26b800aff6309723e12afa993ce86223f7cd945a..c1629e2932e0f0fdf84c867aa228aa53830c23fa 100644 --- a/fggl/scenes/menu.cpp +++ b/fggl/scenes/menu.cpp @@ -82,6 +82,8 @@ namespace fggl::scenes { // build the button auto btn = std::make_unique<gui::Button>(pos, btnSize); + btn->label(name); + btn->addCallback(cb); m_canvas.add(std::move(btn)); } diff --git a/include/fggl/gfx/ogl4/canvas.hpp b/include/fggl/gfx/ogl4/canvas.hpp index 6908867173e49ef6f1f36029309b880781b1d98d..57cf6a5e805f92c243073fa1e1da6ea59da625b4 100644 --- a/include/fggl/gfx/ogl4/canvas.hpp +++ b/include/fggl/gfx/ogl4/canvas.hpp @@ -8,6 +8,8 @@ #include "fggl/gfx/paint.hpp" #include "fggl/gfx/ogl/types.hpp" +#include "fggl/gui/fonts.hpp" + namespace fggl::gfx::ogl4 { class CanvasRenderer { @@ -19,6 +21,10 @@ namespace fggl::gfx::ogl4 { ogl::VertexArray m_vao; ogl::ArrayBuffer m_vertexList; ogl::ElementBuffer m_indexList; + gui::FontLibrary m_fonts; + + void renderShapes(const Paint &paint, GLuint shader); + void renderText(const Paint&, GLuint shader); }; } // namespace fggl::gfx::ogl4 diff --git a/include/fggl/gfx/paint.hpp b/include/fggl/gfx/paint.hpp index 01a63cf2daa3adea35189cab7b169e465863f57e..5afd9ec02970532333adb42bf6e77c4f1d43fac9 100644 --- a/include/fggl/gfx/paint.hpp +++ b/include/fggl/gfx/paint.hpp @@ -89,6 +89,10 @@ namespace fggl::gfx { return m_cmds; } + const std::vector<TextCmd>& textCmds() const { + return m_text; + } + private: std::vector<PaintCmd> m_cmds; std::vector<TextCmd> m_text; diff --git a/include/fggl/gui/fonts.hpp b/include/fggl/gui/fonts.hpp new file mode 100644 index 0000000000000000000000000000000000000000..735e07d68e0e3b43ba5d6abdd3208055a89a9474 --- /dev/null +++ b/include/fggl/gui/fonts.hpp @@ -0,0 +1,106 @@ +/* + * ${license.title} + * Copyright (C) 2022 ${license.owner} + * ${license.mailto} + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +// +// Created by webpigeon on 21/04/22. +// + +#ifndef FGGL_GUI_FONTS_HPP +#define FGGL_GUI_FONTS_HPP + +#include <memory> +#include <map> +#include <string> + +#include "fggl/math/types.hpp" +#include "fggl/data/storage.hpp" +#include "fggl/util/service.h" + +#include <ft2build.h> +#include FT_FREETYPE_H + +namespace fggl::gui { + + struct GlyphMetrics { + math::vec2 size; + math::vec2 bearing; + long advance; + }; + + class FontFace { + public: + FontFace(FT_Face face); + ~FontFace(); + + inline GlyphMetrics& metrics(char letter){ + auto itr = m_metrics.find(letter); + if ( itr == m_metrics.end() ) { + return populateMetrics(letter); + } + return itr->second; + } + + math::vec2 stringSize(const std::string& text); + void texture(char letter, int& width, int& height, unsigned char* buff); + + private: + FT_Face m_face; + std::map<char, GlyphMetrics> m_metrics; + + GlyphMetrics& populateMetrics(char letter); + }; + + class FontLibrary { + public: + FontLibrary(); + ~FontLibrary(); + + inline std::shared_ptr<FontFace> getFont(const std::string& name) { + auto fontItr = m_cache.find(name); + if ( fontItr != m_cache.end() ) { + return fontItr->second; + } + + // need to load the font... + auto storage = util::ServiceLocator::instance().get<data::Storage>(); + auto path = storage->resolvePath(data::StorageType::Data, name); + + FT_Face face; + if ( FT_New_Face(m_context, path.c_str(), 0, &face) ) { + return nullptr; + } + FT_Set_Pixel_Sizes(face, 0, 48); + + // create the new font object + auto ptr = std::make_shared<FontFace>(face); + m_cache[name] = ptr; + return ptr; + } + + private: + FT_Library m_context; + std::map<const std::string, std::shared_ptr<FontFace>> m_cache; + }; + + + +} // nmespace fggl::gui + +#endif //FGGL_GUI_FONTS_HPP diff --git a/include/fggl/gui/widgets.hpp b/include/fggl/gui/widgets.hpp index 27a620723d7753ad6f78e6acb288eb193e668987..8f7ccaa59d314341315666be3044f3858f3e5b9f 100644 --- a/include/fggl/gui/widgets.hpp +++ b/include/fggl/gui/widgets.hpp @@ -19,9 +19,14 @@ namespace fggl::gui { void onEnter() override; void onExit() override; - void addCallback(Callback cb); + void label(const std::string& value); + + [[nodiscard]] + std::string label() const; + + void addCallback(Callback callback); private: - const std::string m_value; + std::string m_value; std::vector<Callback> m_callbacks; bool m_hover; bool m_active; diff --git a/include/fggl/scenes/menu.hpp b/include/fggl/scenes/menu.hpp index 8bb8c2be3a4d7c36e2cfa10ef26f368f8df4b202..b7ce39d7bea349ad187af294cc6378eab9b7959f 100644 --- a/include/fggl/scenes/menu.hpp +++ b/include/fggl/scenes/menu.hpp @@ -16,7 +16,7 @@ namespace fggl::scenes { class BasicMenu : public AppState { public: - BasicMenu(App &owner); + explicit BasicMenu(App &owner); void update() override; void render(gfx::Paint &paint) override;