diff --git a/demo/demo/grid.cpp b/demo/demo/grid.cpp index b3c2760db6ca05d813ae4b330f028e29912a84bf..2897bb8be194afe2d20a70f1d52a0f92557f7b81 100644 --- a/demo/demo/grid.cpp +++ b/demo/demo/grid.cpp @@ -20,6 +20,8 @@ #include "fggl/assets/loader.hpp" #include "fggl/entity/gridworld/zone.hpp" + +#include "fggl/gui/model/parser.hpp" #include "fggl/gui/renderer/renderer.hpp" using namespace fggl::gfx::colours; @@ -196,7 +198,7 @@ namespace demo { constexpr float DRAW_HALF = DRAW_SIZE / 2.0F; constexpr float WALL_HALF = 2.5F; - static void render_grid(fggl::gfx::Paint& paint, fggl::entity::grid::Area2D<255, 255>& grid) { + static void render_grid(fggl::gfx::Paint& paint, fggl::entity::grid::Area2D<255, 255>& grid, fggl::App& app) { const fggl::math::vec2 wallOffsetNorth {0, -DRAW_HALF}; const fggl::math::vec2 wallOffsetWest {-DRAW_HALF, 0}; @@ -226,28 +228,33 @@ namespace demo { } // UI test - fggl::gui::model::Widget widget; - widget.set("position", fggl::math::vec2{200.0F, 100.F}); - widget.set("size", fggl::math::vec2{500.0F, 300.F}); - fggl::gui::model::attr_box_set(widget, "padding", 5.0F); - widget.set("colour", fggl::gfx::colours::BLANCHED_ALMOND); - - fggl::gui::model::Widget handle; - handle.set("border::bottom",5.0F); - handle.set("position", fggl::math::vec2{0.0F, 0.0F}); - handle.set("size", fggl::math::vec2{INFINITY, 50.0F}); - handle.set("text", "hello, world!"); - fggl::gui::model::attr_box_set(handle, "padding", 5.0F); - handle.set("colour", fggl::gfx::colours::ORANGE); - widget.addChild(handle); - - fggl::gui::model::Widget content; - content.set("position", fggl::math::vec2{0.0F, 50.0F}); - content.set("size", fggl::math::vec2{INFINITY, INFINITY}); - content.set("colour", fggl::gfx::colours::BURLYWOOD); - widget.addChild(content); - - fggl::gui::renderer::visit(widget, paint); + auto widgetFactory = app.service<fggl::gui::model::WidgetFactory>(); + + auto widget = widgetFactory->buildEmpty(); + widget->set("position", fggl::math::vec2{200.0F, 100.F}); + widget->set("size", fggl::math::vec2{500.0F, 300.F}); + widget->set("colour", fggl::gfx::colours::BLANCHED_ALMOND); + fggl::gui::model::attr_box_set(*widget, "padding", 5.0F); + + auto handle = widgetFactory->buildEmpty(); + handle->set("border::bottom",5.0F); + handle->set("position", fggl::math::vec2{0.0F, 0.0F}); + //handle->set("size", fggl::math::vec2{INFINITY, 50.0F}); + handle->set("text", "hello, world!"); + handle->set("colour", fggl::gfx::colours::ORANGE); + fggl::gui::model::attr_box_set(*handle, "padding", 5.0F); + widget->addChild(*handle); + delete handle; + + auto content = widgetFactory->buildEmpty(); + content->set("position", fggl::math::vec2{0.0F, 50.0F}); + //content->set("size", fggl::gui::model::UNDEFINED_SIZE); + content->set("colour", fggl::gfx::colours::BURLYWOOD); + widget->addChild(*content); + delete content; + + fggl::gui::renderer::layout(*widget); + fggl::gui::renderer::visit(*widget, paint); } @@ -350,7 +357,7 @@ namespace demo { void GridScene::render(fggl::gfx::Graphics &gfx) { fggl::gfx::Paint paint; - render_grid(paint, *m_grid); + render_grid(paint, *m_grid, owner()); render_objects(paint, *m_grid); m_canvas.render(paint); diff --git a/fggl/data/assimp/module.cpp b/fggl/data/assimp/module.cpp index 0b070168e1eb2567c1ffe82bd3873dd21f8fde22..d7852e26a475695a7493ea3354f6ce8d46627a6a 100644 --- a/fggl/data/assimp/module.cpp +++ b/fggl/data/assimp/module.cpp @@ -232,7 +232,7 @@ namespace fggl::data::models { return nullptr; } else { debug::info("image reports it loaded correctly, adding {} to database", guid); - manager->set(guid, image); + manager->set(guid, image ); } return nullptr; diff --git a/fggl/gfx/ogl/renderer.cpp b/fggl/gfx/ogl/renderer.cpp index f35642c72de9d9c511769c05b7e8803927e3a2b7..ea6e7a2e8e387ccbda76a24cd3a16e47b347f096 100644 --- a/fggl/gfx/ogl/renderer.cpp +++ b/fggl/gfx/ogl/renderer.cpp @@ -141,43 +141,42 @@ namespace fggl::gfx { cache->load(ShaderConfig::named("debug")); } - static void splat_checkerboard(GLuint* memory) { - for (int i = 0; i < 128 * 128; ++i) { - if( i / 128 & 16 ^ i % 128 & 16 ) { - memory[i] = ogl4::TEX_WHITE; - } else { - memory[i] = ogl4::TEX_CHECKER; - } + static void splat_checkerboard(GLuint* memory, int width = 128, int height = 128) { + int counter = 0; + auto colour = ogl4::TEX_CHECKER; + + for ( auto i = 0u; i < width * height; ++i) { + memory[i] = ogl4::TEX_CHECKER; + counter++; + if (counter == 5) { + counter = 0; + colour = colour == ogl4::TEX_CHECKER ? ogl4::TEX_WHITE : ogl4::TEX_CHECKER; + } } } static auto make_solid(uint8_t width, uint8_t height, GLuint colour) -> ogl::Image { - - auto *texData = new GLuint[width * height]; - for (int i = 0; i < width * height; ++i) { - texData[i] = colour; + ogl::PixelDataArray data(ogl::PixelFormat::UNSIGNED_INT, width * height); + for ( auto i = 0u; i < width * height; ++i) { + data.ui[i] = colour; } - return { - .type = ogl::PixelFormat::UNSIGNED_BYTE, - .format = ogl::ImageFormat::RGBA, - .size = {width, height}, - .data = texData - }; + return ogl::Image( + ogl::ImageFormat::RGBA, + {width, height}, + std::move(data) + ); } static void setup_fallback_texture(assets::AssetManager* assets) { { + // generate the image + ogl::Image image(ogl::ImageFormat::RGBA, ogl::PixelFormat::UNSIGNED_INT, {128,128}); + splat_checkerboard( image.data.ui ); + + // setup the texture auto *fallback2D = new ogl::Texture(ogl::TextureType::Tex2D); - GLuint myData[128 * 128]; - splat_checkerboard(myData); - ogl::Image image{ - .type = ogl::PixelFormat::UNSIGNED_BYTE, - .format = ogl::ImageFormat::RGBA, - .size = {128, 128}, - .data = myData - }; - fallback2D->setData(ogl::InternalImageFormat::RedGreenBlueAlpha, image); + fallback2D->setData(ogl::InternalImageFormat::RedGreenBlueAlpha, image, ogl::PixelFormat::UNSIGNED_BYTE); assets->set(ogl4::FALLBACK_TEX, fallback2D); } diff --git a/fggl/gfx/ogl4/canvas.cpp b/fggl/gfx/ogl4/canvas.cpp index 00da747d4f1dd1c9e0304d97e6fb1193ecd80fdc..21b733f1a5b0c3f8fc587617101311c8a268e34a 100644 --- a/fggl/gfx/ogl4/canvas.cpp +++ b/fggl/gfx/ogl4/canvas.cpp @@ -153,7 +153,7 @@ namespace fggl::gfx::ogl4 { // get the expected font - std::shared_ptr<gui::FontFace> face = m_fonts->getFont("LiberationSans-Regular.ttf"); + std::shared_ptr<gui::FontFace> face = m_fonts->getDefaultFont(); if (face == nullptr) { // we don't know about that font... return; @@ -179,14 +179,15 @@ namespace fggl::gfx::ogl4 { const auto label = textCmd.text; math::vec2 penPos(textCmd.pos); + // setup a non-owning holder for the characters + data::Texture2D tex; + tex.channels = 1; + for (auto letter : label) { - ogl::Image img{}; - img.format = ogl::ImageFormat::R; - img.type = ogl::PixelFormat::UNSIGNED_BYTE; glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - face->texture(letter, img.size.x, img.size.y, &img.data); - m_fontTex.setData(ogl::InternalImageFormat::Red, img); + face->texture(letter, tex.size.x, tex.size.y, &tex.data); + m_fontTex.setData(ogl::InternalImageFormat::Red, &tex); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); @@ -218,6 +219,9 @@ namespace fggl::gfx::ogl4 { penPos.x += (metrics.advance >> 6); } + + // textures assume they own their contained data, we need to make sure we clear it + tex.data = nullptr; } glDisable(GL_BLEND); diff --git a/fggl/gfx/ogl4/meshes.cpp b/fggl/gfx/ogl4/meshes.cpp index 7ccae929f1fae6c83576c749e485c26aa9f8cc00..d4f6469c52eb6a5029f970bb49f1cee587a51c7c 100644 --- a/fggl/gfx/ogl4/meshes.cpp +++ b/fggl/gfx/ogl4/meshes.cpp @@ -184,16 +184,12 @@ namespace fggl::gfx::ogl4 { return texture; } + // get the texture data we plan to load auto *textureData = manager->get<data::Texture2D>(name); - ogl::Image image{ - .type = ogl::PixelFormat::UNSIGNED_BYTE, - .format = ogl::ImageFormat::RGB, - .size = textureData->size, - .data = textureData->data, - }; + // create a texture texture = new ogl::Texture(ogl::TextureType::Tex2D); - texture->setData( ogl::InternalImageFormat::RedGreenBlue, image); + texture->setData( ogl::InternalImageFormat::RedGreenBlue, textureData); return manager->set(uploadedTex, texture); } diff --git a/fggl/gfx/window.cpp b/fggl/gfx/window.cpp index cf1da9c482749b34b34aa9c7409d668a2b37950c..cac8e496a30f5af1f9471ef5e9fcb2545935c05e 100644 --- a/fggl/gfx/window.cpp +++ b/fggl/gfx/window.cpp @@ -190,6 +190,7 @@ namespace fggl::display::glfw { Window::~Window() { if ( m_graphics != nullptr ) { + delete m_graphics; m_graphics = nullptr; } diff --git a/fggl/gui/containers.cpp b/fggl/gui/containers.cpp index 15da80673aa12839530bd05872f897d60a4ece22..4f2a4b0c2c041f0d7c3cced6c22febbdb74162f6 100644 --- a/fggl/gui/containers.cpp +++ b/fggl/gui/containers.cpp @@ -113,6 +113,9 @@ namespace fggl::gui { } } + // cleanup variables + delete[] widths; + delete[] heights; } } diff --git a/fggl/gui/fonts.cpp b/fggl/gui/fonts.cpp index 172cc47108df4d6b47ac5004dbefd417f7492102..634543f346edb0e7908f80bd98a2ece0ddd79f45 100644 --- a/fggl/gui/fonts.cpp +++ b/fggl/gui/fonts.cpp @@ -25,13 +25,13 @@ namespace fggl::gui { FontLibrary::FontLibrary(data::Storage *storage) : m_context(nullptr), m_storage(storage) { FT_Init_FreeType(&m_context); + m_defaultFont = getFont(DEFAULT_FONT_NAME); } FontLibrary::~FontLibrary() { // free all fonts - for (auto &face : m_cache) { - face.second = nullptr; - } + m_defaultFont = nullptr; + m_cache.clear(); // shut the library down FT_Done_FreeType(m_context); @@ -54,7 +54,7 @@ namespace fggl::gui { return it.first->second; } - void FontFace::texture(char letter, int &width, int &height, void **buff) { + void FontFace::texture(char letter, int &width, int &height, unsigned char **buff) { if (FT_Load_Char(m_face, letter, FT_LOAD_RENDER)) { // something bad happened return; diff --git a/fggl/gui/model/parser.cpp b/fggl/gui/model/parser.cpp index ab42e05c7a4ffc7833d1dbfc422bca3e616998ba..92c2493772f7501f8e770190f3332db2c64d4ca4 100644 --- a/fggl/gui/model/parser.cpp +++ b/fggl/gui/model/parser.cpp @@ -42,7 +42,8 @@ namespace fggl::gui::model { // are we a template definition? if ( config["define"] ) { - factory.push( config["define"].as<std::string>(), *root ); + factory.push( config["define"].as<std::string>(), std::move(*root) ); + return factory.getTemplate( config["define"].as<std::string>() ); } return root; diff --git a/fggl/gui/model/structure.cpp b/fggl/gui/model/structure.cpp index baf5d17f5ce5936b5b91c3b1dd2cefe66833afce..a6c48a96ed869f0b3e65af06f5b40e13bfb06bca 100644 --- a/fggl/gui/model/structure.cpp +++ b/fggl/gui/model/structure.cpp @@ -17,7 +17,54 @@ // #include "fggl/gui/model/structure.hpp" +#include "fggl/gui/fonts.hpp" + +#include <algorithm> +#include <utility> +#include <string> namespace fggl::gui::model { + math::vec2 calcTextBounds(const std::string& value, std::shared_ptr<FontFace> face) { + if ( face == nullptr ){ + debug::warning("No preferred font sent, cowardly refusing to process text"); + return {}; + } + + math::vec2 max{0, 0}; + for (auto letter : value) { + auto metrics = face->metrics(letter); + + max.x += metrics.size.x + (metrics.advance >> 6); + max.y = std::max(max.y, metrics.size.y); + } + return max; + } + + inline math::vec2 calcBoxContrib(const Widget& widget, const std::string& name) { + return math::vec2{ + widget.get_or_default<float>( name + "::left") + widget.get_or_default<float>(name + "::right"), + widget.get_or_default<float>( name + "::top" ) + widget.get_or_default<float>( name + "::bottom" ) + }; + } + + void Widget::calcPrefSize(std::shared_ptr<FontFace> face) { + if ( !m_dirty ){ + return; + } + + auto padding = calcBoxContrib( *this, "padding"); + auto border = calcBoxContrib( *this, "border"); + auto content = math::vec2{0,0}; + + if (hasAttr("text")) { + content += calcTextBounds( get<std::string>("text"), std::move(face) ); + } + + m_cachedSize = padding + content + content; + debug::info("my preferred size is: ({}, {})", m_cachedSize.x, m_cachedSize.y); + + m_dirty = false; + } + } \ No newline at end of file diff --git a/fggl/gui/renderer/renderer.cpp b/fggl/gui/renderer/renderer.cpp index 8c6d98848e65da825db3aceeea1e192d2cd1b813..8e336736ab3e21ca78ec5e2fb484a275af8898f7 100644 --- a/fggl/gui/renderer/renderer.cpp +++ b/fggl/gui/renderer/renderer.cpp @@ -66,6 +66,40 @@ namespace fggl::gui::renderer { paint.fill(path); } + void layout(model::Widget& current) { + if ( current.isLeaf() ) { + // if the widget has a defined size, use that + if ( current.hasAttr("size") && !current.hasAttr("text") ) { + return; + } + + // else, use the model's preferred size + auto preferred = current.preferredSize(); + if ( preferred.has_value() ) { + current.set<math::vec2>("size", preferred.value() ); + } + } else { + auto topPad = current.get_or_default<float>("border:top") + current.get_or_default<float>("padding::top"); + auto leftPad = current.get_or_default<float>("border::left") + current.get_or_default<float>("padding::left"); + + math::vec2 size = {topPad, leftPad}; + + // layout all children + for ( auto& child : current ) { + layout(child); + + auto childSize = child.get_or_default<math::vec2>("size"); + size.x = std::max( childSize.x, size.x ); + size.y += childSize.y; + + child.set<math::vec2>("position", {leftPad, size.y}); + } + + // set our size based on that + current.set<math::vec2>("size", size ); + } + } + void visit(const model::Widget& root, gfx::Paint& paint, Box offset) { // get border size auto border = get_box(root, "border"); @@ -79,12 +113,12 @@ namespace fggl::gui::renderer { bounds.left += offset.left; // deal with right hand size bounds - bounds.right = std::min( size.x, offset.width() ); - bounds.right += offset.left; + //bounds.right = std::min( size.x, offset.width() ); + //bounds.right += offset.left; // deal with bottom bounds - bounds.bottom = std::min( size.y, offset.height() ); - bounds.bottom += offset.top; + //bounds.bottom = std::min( size.y, offset.height() ); + //bounds.bottom += offset.top; auto background = bounds.trim(border); diff --git a/include/fggl/assets/manager.hpp b/include/fggl/assets/manager.hpp index de8f3713f4a3921e05e03a80f67445469bea73a0..ef1ffee15a0b737154f9fac4e6f829261e45b4e1 100644 --- a/include/fggl/assets/manager.hpp +++ b/include/fggl/assets/manager.hpp @@ -41,6 +41,24 @@ namespace fggl::assets { struct AssetBoxT : public AssetBox { T* asset = nullptr; + explicit inline AssetBoxT(T* aasset) : asset(aasset) {} + + // no copy: we own our resource! + AssetBoxT(const AssetBoxT&) = delete; + AssetBoxT& operator=(const AssetBoxT&) = delete; + + // move OK - we can steal the asset + AssetBoxT(AssetBoxT&& other) : asset(other.asset) { + other.asset = nullptr; + } + + inline ~AssetBoxT() override { + if ( asset != nullptr ) { + delete asset; + asset = nullptr; + } + } + inline void release() override { asset = nullptr; } @@ -74,10 +92,21 @@ namespace fggl::assets { } } + /** + * Pass ownership of the asset to the asset system. + * + * Once this method is called, the asset system owns the asset, and will free + * it when it is no longer required. The asset system assumes it is fully aware + * of all usages of the assets it manages via its graph. + * + * @tparam T the asset type to be managed + * @param guid the asset name + * @param assetRef the asset itself + * @return the owned asset pointer + */ template<typename T> T* set(const AssetID &guid, T* assetRef) { - auto ptr = std::make_shared<AssetBoxT<T>>(); - ptr->asset = assetRef; + auto ptr = std::make_shared<AssetBoxT<T>>(assetRef); m_registry[guid] = ptr; return (*ptr).asset; diff --git a/include/fggl/audio/audio.hpp b/include/fggl/audio/audio.hpp index 67c8889b8fd80d889500e1b351da07d46f595d21..a31eb6d57fb8ba149e29f129afceae81211e1983 100644 --- a/include/fggl/audio/audio.hpp +++ b/include/fggl/audio/audio.hpp @@ -37,6 +37,14 @@ namespace fggl::audio { int sampleCount = -1; T *data = nullptr; + AudioClip() = default; + AudioClip(const AudioClip&) = delete; + + inline ~AudioClip() { + std::free(data); + data = nullptr; + } + [[nodiscard]] inline int size() const { return sampleCount * sizeof(T); diff --git a/include/fggl/data/texture.hpp b/include/fggl/data/texture.hpp index 243e331d785761af7c544b4c8946353f92cf4394..b1cf34c04c25f42855f7e8c15dc226a0042fff63 100644 --- a/include/fggl/data/texture.hpp +++ b/include/fggl/data/texture.hpp @@ -28,7 +28,14 @@ namespace fggl::data { struct Texture2D { math::vec2i size; int channels; - void* data; + unsigned char* data; + + Texture2D() = default; + Texture2D(const Texture2D& other) = delete; + + inline ~Texture2D() { + delete data; + } }; } diff --git a/include/fggl/gfx/ogl/types.hpp b/include/fggl/gfx/ogl/types.hpp index 4478b17e9c60f7996c86d5b3862c2880a5b92cb7..af5e6a36993d25be79fe1ab2ede03d2f120599ad 100644 --- a/include/fggl/gfx/ogl/types.hpp +++ b/include/fggl/gfx/ogl/types.hpp @@ -27,6 +27,7 @@ #include "fggl/gfx/ogl/common.hpp" #include "fggl/math/types.hpp" +#include "fggl/data/texture.hpp" namespace fggl::gfx::ogl { @@ -177,7 +178,7 @@ namespace fggl::gfx::ogl { SHORT = GL_SHORT, UNSIGNED_INT = GL_UNSIGNED_INT, INT = GL_INT, - HALF_FLOAT = GL_HALF_FLOAT, + //HALF_FLOAT = GL_HALF_FLOAT, FLOAT = GL_FLOAT, /* UNSIGNED_BYTE_3_3_2, @@ -221,11 +222,165 @@ namespace fggl::gfx::ogl { DEPTH_STENCIL = GL_DEPTH_STENCIL }; - struct Image { + struct PixelDataArray { PixelFormat type; + union { + unsigned char *uc; + char *c; + std::uint16_t *us; + std::int16_t *s; + float *f; + std::int32_t *i; + std::uint32_t *ui; + }; + bool owning; + + inline PixelDataArray(PixelFormat fmt, std::size_t size) : type(fmt), owning(true) { + switch (type) { + case PixelFormat::UNSIGNED_BYTE: + uc = new unsigned char[size]; + break; + case PixelFormat::BYTE: + c = new char[size]; + break; + case PixelFormat::UNSIGNED_SHORT: + us = new std::uint16_t[size]; + break; + case PixelFormat::SHORT: + s = new std::int16_t[size]; + break; + case PixelFormat::FLOAT: + f = new float[size]; + break; + case PixelFormat::INT: + i = new std::int32_t[size]; + break; + case PixelFormat::UNSIGNED_INT: + ui = new std::uint32_t[size]; + break; + } + } + + inline explicit PixelDataArray(unsigned char* data) : type(PixelFormat::UNSIGNED_BYTE), uc(data), owning(false) {} + inline explicit PixelDataArray(char* data) : type(PixelFormat::BYTE), c(data), owning(false) {} + + // no copy + PixelDataArray(const PixelDataArray&) = delete; + PixelDataArray& operator=(const PixelDataArray&) = delete; + + // move ok + PixelDataArray(PixelDataArray&& other) : type(other.type), owning(other.owning) { + switch (type) { + case PixelFormat::UNSIGNED_BYTE: + uc = other.uc; + other.uc = nullptr; + break; + case PixelFormat::BYTE: + c = other.c; + other.c = nullptr; + break; + case PixelFormat::UNSIGNED_SHORT: + us = other.us; + other.us = nullptr; + break; + case PixelFormat::SHORT: + s = other.s; + other.s = nullptr; + break; + case PixelFormat::FLOAT: + f = other.f; + other.f = nullptr; + break; + case PixelFormat::INT: + i = other.i; + other.i = nullptr; + break; + case PixelFormat::UNSIGNED_INT: + ui = other.ui; + other.ui = nullptr; + break; + } + } + + inline ~PixelDataArray() { + if (owning) { + switch (type) { + case PixelFormat::UNSIGNED_BYTE: delete[] uc; + uc = nullptr; + break; + case PixelFormat::BYTE: delete[] c; + c = nullptr; + break; + case PixelFormat::UNSIGNED_SHORT: delete[] us; + us = nullptr; + break; + case PixelFormat::SHORT: delete[] s; + s = nullptr; + break; + case PixelFormat::FLOAT: delete[] f; + f = nullptr; + break; + case PixelFormat::INT: delete[] i; + i = nullptr; + break; + case PixelFormat::UNSIGNED_INT: + delete[] ui; + ui = nullptr; + break; + } + } + } + + void* data() { + switch (type) { + case PixelFormat::UNSIGNED_BYTE: + return uc; + case PixelFormat::BYTE: + return c; + case PixelFormat::UNSIGNED_SHORT: + return us; + case PixelFormat::SHORT: + return s; + case PixelFormat::FLOAT: + return f; + case PixelFormat::INT: + return i; + case PixelFormat::UNSIGNED_INT: + return ui; + } + + // unknown type? + return nullptr; + } + + }; + + struct Image { ImageFormat format; math::vec2i size; - void *data; + PixelDataArray data; + + //Image() = default; + + inline Image(ImageFormat fmt, PixelFormat pxFmt, math::vec2i asize) : + format(fmt), + size(asize), + data(pxFmt, asize.x * asize.y){} + + inline Image(ImageFormat fmt, math::vec2i asize, PixelDataArray&& adata) : + format(fmt), + size(asize), + data(std::move(adata)) {} + + Image(const Image&) = delete; + + inline PixelFormat type() const { + return data.type; + } + + void* dataPtr() { + return data.data(); + } }; class Texture { @@ -257,6 +412,25 @@ namespace fggl::gfx::ogl { } } + void setData(InternalImageFormat iFmt, Image &image, PixelFormat extFormat) { + //bind(); + glBindTexture((GLenum) m_type, m_obj); + glTexImage2D((GLenum) m_type, + 0, + (GLint) iFmt, + image.size.x, + image.size.y, + 0, + (GLenum) image.format, + (GLenum) extFormat, + image.dataPtr()); + + if ( m_type == TextureType::Tex2D ) { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + } + } + void setData(InternalImageFormat iFmt, Image &image) { //bind(); glBindTexture((GLenum) m_type, m_obj); @@ -267,8 +441,42 @@ namespace fggl::gfx::ogl { image.size.y, 0, (GLenum) image.format, - (GLenum) image.type, - image.data); + (GLenum) image.type(), + image.dataPtr()); + + if ( m_type == TextureType::Tex2D ) { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + } + } + + void setData(InternalImageFormat iFmt, const data::Texture2D *image) { + + ImageFormat imageFormat; + if (image->channels == 1) { + imageFormat = ImageFormat::R; + } else if ( image->channels == 2) { + imageFormat = ImageFormat::RG; + } else if ( image->channels == 3) { + imageFormat = ImageFormat::RGB; + } else if ( image->channels == 4) { + imageFormat = ImageFormat::RGBA; + } else { + // unknown image format -> channels mapping, having a bad day! + return; + } + + //bind(); + glBindTexture((GLenum) m_type, m_obj); + glTexImage2D((GLenum) m_type, + 0, + (GLint) iFmt, + image->size.x, + image->size.y, + 0, + (GLenum) imageFormat, + GL_UNSIGNED_BYTE, + image->data); if ( m_type == TextureType::Tex2D ) { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); @@ -284,8 +492,8 @@ namespace fggl::gfx::ogl { image.size.x, image.size.y, (GLenum) image.format, - (GLenum) image.type, - image.data); + (GLenum) image.type(), + image.dataPtr()); } void wrapMode(Wrapping wrap); diff --git a/include/fggl/gfx/ogl4/fallback.hpp b/include/fggl/gfx/ogl4/fallback.hpp index 5c466d46db5bb4a08e35ec31e0d85775a2632d23..297e794c2fa3cb99082698e6e4f55a4d8a1a0703 100644 --- a/include/fggl/gfx/ogl4/fallback.hpp +++ b/include/fggl/gfx/ogl4/fallback.hpp @@ -60,8 +60,8 @@ namespace fggl::gfx::ogl4 { fragColour = vec4(colour.xyz, texture(tex, texPos).r); })glsl"; - constexpr const GLuint TEX_WHITE = 0xFFFFFFFF; - constexpr const GLuint TEX_CHECKER = 0x00FF00FF; //FIXME pixel order is reversed?! + constexpr const GLuint TEX_CHECKER = 0x11FF11FF; //FIXME pixel order is reversed?! + constexpr const GLuint TEX_WHITE = 0xFF0000FF; constexpr const assets::AssetID FALLBACK_TEX = assets::make_asset_id("fallback", "FALLBACK_TEX"); constexpr const assets::AssetID FALLBACK_MAT = assets::make_asset_id("fallback", "FALLBACK_MAT"); diff --git a/include/fggl/gui/fonts.hpp b/include/fggl/gui/fonts.hpp index 2aa44a41e83e0141fa3b7c01abe6b61b0dabbaf5..902f85ebaf8610db6462b0a2d6b9e71bd873db26 100644 --- a/include/fggl/gui/fonts.hpp +++ b/include/fggl/gui/fonts.hpp @@ -30,7 +30,10 @@ #include <ft2build.h> #include FT_FREETYPE_H + namespace fggl::gui { + constexpr const char* DEFAULT_FONT_NAME = "LiberationSans-Regular.ttf"; + struct GlyphMetrics { math::vec2 size; @@ -57,7 +60,7 @@ namespace fggl::gui { } math::vec2 stringSize(const std::string &text); - void texture(char letter, int &width, int &height, void **buff); + void texture(char letter, int &width, int &height, unsigned char **buff); private: FT_Face m_face; @@ -70,7 +73,7 @@ namespace fggl::gui { public: constexpr static const modules::ModuleService service = modules::make_service("fggl::gui::font"); - FontLibrary(data::Storage *storage); + explicit FontLibrary(data::Storage *storage); ~FontLibrary(); // copy and moving not needed @@ -100,10 +103,18 @@ namespace fggl::gui { return ptr; } + inline void setDefaultFont(const std::string& name) { + m_defaultFont = getFont(name); + } + + inline std::shared_ptr<FontFace> getDefaultFont() const { + return m_defaultFont; + } private: FT_Library m_context; data::Storage *m_storage; std::map<const std::string, std::shared_ptr<FontFace>> m_cache; + std::shared_ptr<FontFace> m_defaultFont; }; } // nmespace fggl::gui diff --git a/include/fggl/gui/model/parser.hpp b/include/fggl/gui/model/parser.hpp index 608aaa70ff4f82d16a25bf4c0c261d13e7860b3a..c87ede4166418d5aca1ec3e79c64d309828741cc 100644 --- a/include/fggl/gui/model/parser.hpp +++ b/include/fggl/gui/model/parser.hpp @@ -20,25 +20,41 @@ #define FGGL_GUI_MODEL_PARSER_HPP #include <fggl/gui/model/structure.hpp> +#include <fggl/gui/fonts.hpp> +#include <fggl/modules/module.hpp> namespace fggl::gui::model { + constexpr auto WIDGET_FACTORY_SERVICE = modules::make_service("gui::WidgetService"); + class WidgetFactory { public: + constexpr static auto service = WIDGET_FACTORY_SERVICE; + inline WidgetFactory(FontLibrary* lib) : m_fontLibrary(lib) {} + inline Widget* build(std::string templateName) { - return new Widget(m_templates.at(templateName)); + return new Widget(m_templates.at(templateName).get()); } inline Widget* buildEmpty() { - return new Widget(); + return new Widget(m_fontLibrary); + } + + inline void push(std::string name, const Widget&& definition) { + m_templates.emplace(name, std::make_unique<Widget>(definition)); } - inline void push(std::string name, const Widget& definition) { - m_templates[name] = definition; + Widget* getTemplate(const std::string& name) { + auto itr = m_templates.find(name); + if ( itr == m_templates.end() ){ + return nullptr; + } + return itr->second.get(); } private: - std::map<std::string, Widget> m_templates; + std::map<std::string, std::unique_ptr<Widget>> m_templates; + FontLibrary* m_fontLibrary; }; Widget* parseFile(WidgetFactory& factory, const std::string& path); diff --git a/include/fggl/gui/model/structure.hpp b/include/fggl/gui/model/structure.hpp index a8cd119bbf348585e47089da3adb65c47c0ce825..7f2063b66d123a6c68a67aa4e44508fbe40635c4 100644 --- a/include/fggl/gui/model/structure.hpp +++ b/include/fggl/gui/model/structure.hpp @@ -23,19 +23,42 @@ #include <variant> #include <vector> #include <string> +#include <optional> #include <fggl/math/types.hpp> +#include <fggl/gui/fonts.hpp> namespace fggl::gui::model { using AttrKey = std::string; using AttrValue = std::variant<bool, int, float, std::string, math::vec2, math::vec3>; + constexpr float UNDEFINED = INFINITY; + constexpr math::vec2 UNDEFINED_SIZE{INFINITY, INFINITY}; + class Widget { public: using ChildItr = std::vector<Widget>::iterator; using ChildItrConst = std::vector<Widget>::const_iterator; + inline Widget(FontLibrary* library) : + m_parent(nullptr), + m_children(), + m_fontLibrary(library), + m_attrs(), + m_cachedSize(), + m_dirty(true) { + } + + explicit inline Widget(Widget* parent) : + m_parent(parent), + m_children(), + m_fontLibrary(parent->m_fontLibrary), + m_attrs(), + m_cachedSize(), + m_dirty(true) { + } + inline bool isRoot() const { return m_parent == nullptr; } @@ -101,10 +124,20 @@ namespace fggl::gui::model { return m_children.cend(); } + inline std::optional<math::vec2> preferredSize() { + calcPrefSize(m_fontLibrary->getDefaultFont()); + return m_cachedSize; + } + private: Widget *m_parent; std::vector<Widget> m_children; + FontLibrary* m_fontLibrary; std::map< AttrKey, AttrValue > m_attrs; + math::vec2 m_cachedSize; + bool m_dirty; + + void calcPrefSize(std::shared_ptr<FontFace> face); }; void attr_box_set(Widget& widget, const AttrKey& key, auto top, auto right, auto bottom, auto left) { diff --git a/include/fggl/gui/module.hpp b/include/fggl/gui/module.hpp index 3e54d06f6210f001cb8b361257904f93f1f9d6c6..cfe103430160db19c8e09dba13ec4bd95def553d 100644 --- a/include/fggl/gui/module.hpp +++ b/include/fggl/gui/module.hpp @@ -20,6 +20,8 @@ #define FGGL_GUI_MODULE_HPP #include "fggl/gui/fonts.hpp" +#include "fggl/gui/model/parser.hpp" + #include "fggl/data/module.hpp" #include "fggl/assets/packed/module.hpp" @@ -37,8 +39,9 @@ namespace fggl::gui { struct FreeType { constexpr static const char *name = "fggl::gui::FreeType"; - constexpr static const std::array<modules::ModuleService, 1> provides = { - FontLibrary::service + constexpr static const std::array<modules::ModuleService, 2> provides = { + FontLibrary::service, + model::WidgetFactory::service }; constexpr static const std::array<modules::ModuleService, 2> depends = { data::Storage::service, @@ -57,6 +60,13 @@ namespace fggl::gui { return true; } + + if ( service == model::WidgetFactory::service ) { + auto fonts = services.get<FontLibrary>(); + services.create<model::WidgetFactory>(fonts); + return true; + } + return false; } } diff --git a/include/fggl/gui/renderer/renderer.hpp b/include/fggl/gui/renderer/renderer.hpp index c90d8034c9ac6af88c4f1fab4ebe71a098adb4d8..393ecd11214eaca48d0b4232c1f0230417a68eeb 100644 --- a/include/fggl/gui/renderer/renderer.hpp +++ b/include/fggl/gui/renderer/renderer.hpp @@ -97,6 +97,8 @@ namespace fggl::gui::renderer { void draw_background_solid(gfx::Paint& paint, Box& bounds, math::vec3 colour); + void layout(model::Widget& root); + void visit(const model::Widget& root, gfx::Paint& paint, Box offset = {0.0F, 1024.0F, 768.0F, 0.0F}); } // namespace fggl::gui::renderer