From f6ce4fc59cf4dbce8c2cca15b5e076d60d37305d Mon Sep 17 00:00:00 2001 From: Joseph Walton-Rivers <joseph@walton-rivers.uk> Date: Fri, 22 Apr 2022 17:19:58 +0100 Subject: [PATCH] very basic font rendering for widgets --- demo/data/shader2D_frag.glsl | 7 +- demo/data/shader2D_vert.glsl | 3 + fggl/gfx/ogl/types.cpp | 3 + fggl/gfx/ogl4/canvas.cpp | 84 ++++++++++++- fggl/gui/fonts.cpp | 11 ++ fggl/gui/widgets.cpp | 27 ++++- include/fggl/data/model.hpp | 1 + include/fggl/gfx/ogl/types.hpp | 200 +++++++++++++++++++++++++++---- include/fggl/gfx/ogl4/canvas.hpp | 2 + include/fggl/gui/fonts.hpp | 17 ++- include/fggl/gui/widgets.hpp | 66 +++++++--- 11 files changed, 371 insertions(+), 50 deletions(-) diff --git a/demo/data/shader2D_frag.glsl b/demo/data/shader2D_frag.glsl index 6d4fafa..46f5a1e 100644 --- a/demo/data/shader2D_frag.glsl +++ b/demo/data/shader2D_frag.glsl @@ -1,9 +1,12 @@ #version 330 core +uniform sampler2D tex; in vec3 colour; +in vec2 texPos; + out vec4 FragColor; void main() { - FragColor = vec4(colour, 1.0f); -} + FragColor = vec4(colour.xyz, texture(tex, texPos).r); +} diff --git a/demo/data/shader2D_vert.glsl b/demo/data/shader2D_vert.glsl index 223a72b..eff72d1 100644 --- a/demo/data/shader2D_vert.glsl +++ b/demo/data/shader2D_vert.glsl @@ -1,13 +1,16 @@ #version 330 core layout (location = 0) in vec2 aPos; layout (location = 1) in vec3 aColour; +layout (location = 2) in vec2 aTexPos; uniform mat4 projection; out vec3 colour; +out vec2 texPos; void main() { gl_Position = projection * vec4(aPos, 0.0f, 1.0f); colour = aColour; + texPos = aTexPos; } diff --git a/fggl/gfx/ogl/types.cpp b/fggl/gfx/ogl/types.cpp index 7bb8293..a6aba08 100644 --- a/fggl/gfx/ogl/types.cpp +++ b/fggl/gfx/ogl/types.cpp @@ -131,5 +131,8 @@ namespace fggl::gfx::ogl { #endif } + void VertexArray::draw(Primative drawType, int first, std::size_t count) { + glDrawArrays( (GLenum)drawType, first, count); + } } // namespace fggl::gfx::ogl \ No newline at end of file diff --git a/fggl/gfx/ogl4/canvas.cpp b/fggl/gfx/ogl4/canvas.cpp index 3ea8893..729dbfe 100644 --- a/fggl/gfx/ogl4/canvas.cpp +++ b/fggl/gfx/ogl4/canvas.cpp @@ -90,7 +90,7 @@ namespace fggl::gfx::ogl4 { } } - CanvasRenderer::CanvasRenderer() { + CanvasRenderer::CanvasRenderer() : m_fontTex(ogl::TextureType::Tex2D) { m_vao.bind(); #ifdef FGGL_GL_I_BOUND @@ -103,10 +103,12 @@ namespace fggl::gfx::ogl4 { // define our attributes auto posAttr = ogl::attribute<data::Vertex2D, math::vec2>(offsetof(data::Vertex2D, position)); auto colAttr = ogl::attribute<data::Vertex2D, math::vec3>(offsetof(data::Vertex2D, colour)); + auto texAttr = ogl::attribute<data::Vertex2D, math::vec2>(offsetof(data::Vertex2D, texPos)); // bind the attributes to the vao m_vao.setAttribute(m_vertexList, 0, posAttr); m_vao.setAttribute(m_vertexList, 1, colAttr); + m_vao.setAttribute(m_vertexList, 2, texAttr); #ifdef FGGL_GL_I_BOUND // cool, rebind whatever happened before, or not @@ -142,7 +144,85 @@ namespace fggl::gfx::ogl4 { glUseProgram( 0 ); } + // slow version void CanvasRenderer::renderText(const Paint& paint, GLuint shader) { + if ( paint.textCmds().empty() ){ + return; + } + + // get the expected font + auto fontFactory = util::ServiceLocator::instance().get<gui::FontLibrary>(); + std::shared_ptr<gui::FontFace> face = fontFactory->getFont("LiberationSans-Regular.ttf"); + if ( face == nullptr ){ + // we don't know about that font... + return; + } + + // setup the shader + 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)); + + // bind the vbo we'll use for writing + m_vao.bind(); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + // for each text string, attempt to render it + for ( const auto& textCmd : paint.textCmds() ) { + const auto label = textCmd.text; + math::vec2 penPos(textCmd.pos); + + 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); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + m_fontTex.bind(1); + glUniform1i( glGetUniformLocation(shader, "tex"), 1); + + // this is why this is called the slow version, we render each quad as a single call + auto& metrics = face->metrics(letter); + float xPos = penPos.x + metrics.bearing.x; + float yPos = (penPos.y - metrics.bearing.y); + float w = metrics.size.x; + float h = metrics.size.y; + + const math::vec3 texCol{ 1.0f, 1.0f, 1.0f}; + + std::array<data::Vertex2D, 6> verts{{ + {{xPos, yPos + h}, texCol, {0.0F, 1.0F}}, + {{xPos, yPos}, texCol, {0.0F, 0.0F}}, + {{xPos + w, yPos}, texCol, {1.0F, 0.0F}}, + {{xPos, yPos + h}, texCol, {0.0F, 1.0F}}, + {{xPos + w, yPos}, texCol, {1.0F, 0.0F}}, + {{xPos + w, yPos + h}, texCol, {1.0F, 1.0F}}, + }}; + m_vertexList.replace(verts.size(), verts.data()); + + m_vao.bind(); + m_vao.draw(ogl::Primative::TRIANGLE, 0, verts.size()); + + penPos.x += (metrics.advance >> 6 ); + } + } + + glDisable(GL_BLEND); + } + + /* + void renderTextWIP(const Paint& paint, GLuint shader) { if ( paint.textCmds().empty() ) { return; @@ -190,7 +270,7 @@ namespace fggl::gfx::ogl4 { 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); diff --git a/fggl/gui/fonts.cpp b/fggl/gui/fonts.cpp index 36a72be..49bba26 100644 --- a/fggl/gui/fonts.cpp +++ b/fggl/gui/fonts.cpp @@ -59,4 +59,15 @@ namespace fggl::gui { auto it = m_metrics.emplace(letter, metrics); return it.first->second; } + + void FontFace::texture(char letter, int &width, int &height, void **buff) { + if (FT_Load_Char(m_face, letter, FT_LOAD_RENDER) ) { + // something bad happened + return; + } + + width = m_face->glyph->bitmap.width; + height = m_face->glyph->bitmap.rows; + *buff = m_face->glyph->bitmap.buffer; + } } // namespace fggl::fonts \ No newline at end of file diff --git a/fggl/gui/widgets.cpp b/fggl/gui/widgets.cpp index 77d5c45..467d2a1 100644 --- a/fggl/gui/widgets.cpp +++ b/fggl/gui/widgets.cpp @@ -9,14 +9,14 @@ namespace fggl::gui { - Button::Button( math::vec2 pos, math::vec2 size) : Widget(pos, size), m_hover(false), m_active(false) {} + Button::Button( math::vec2 pos, math::vec2 size) : Widget(pos, size), m_label(pos, size), m_hover(false), m_active(false) {} void Button::render(gfx::Paint &paint) { gfx::Path2D path{ topLeft() }; draw_button(path, topLeft(), size(), m_hover, m_active); paint.fill(path); - paint.text(m_value, topLeft()); + m_label.render(paint); } void Button::activate() { @@ -41,10 +41,29 @@ namespace fggl::gui { } void Button::label(const std::string &value) { - m_value = value; + m_label.text(value); } std::string Button::label() const { - return m_value; + return m_label.text(); + } + + void Label::layout() { + if ( m_font == nullptr ) { + return; + } + + math::vec2 size; + for (const auto& letter : m_value) { + auto metrics = m_font->metrics(letter); + size.x += (metrics.advance << 6); + size.y = std::max(size.y, metrics.size.y); + } + m_naturalSize = size; + m_needsLayout = false; + } + + Label::Label(math::vec2 pos, math::vec2 size) : Widget(pos, size) { + } } // namespace fggl::gui \ No newline at end of file diff --git a/include/fggl/data/model.hpp b/include/fggl/data/model.hpp index 6dfa2cf..37be176 100644 --- a/include/fggl/data/model.hpp +++ b/include/fggl/data/model.hpp @@ -18,6 +18,7 @@ namespace fggl::data { struct Vertex2D { fggl::math::vec2 position; fggl::math::vec3 colour; + fggl::math::vec2 texPos; }; struct Mesh2D { diff --git a/include/fggl/gfx/ogl/types.hpp b/include/fggl/gfx/ogl/types.hpp index f84e465..b121b44 100644 --- a/include/fggl/gfx/ogl/types.hpp +++ b/include/fggl/gfx/ogl/types.hpp @@ -121,6 +121,157 @@ namespace fggl::gfx::ogl { } }; + enum class Wrapping { + REPEAT = GL_REPEAT, + MIRRORED_REPEAT = GL_MIRRORED_REPEAT, + CLAMP_TO_EDGE = GL_CLAMP_TO_EDGE, + CLAMP_TO_BORDER = GL_CLAMP_TO_BORDER + }; + + enum class Filtering { + NEIGHBOUR = GL_NEAREST, + LINEAR = GL_LINEAR, + + // these are their openGL names, but they are kinda bad, can we do better/alias? + NEAREST_MIPMAP_NEAREST = GL_NEAREST_MIPMAP_NEAREST, + LINEAR_MIPMAP_NEAREST = GL_LINEAR_MIPMAP_NEAREST, + NEAREST_MIPMAP_LINEAR = GL_NEAREST_MIPMAP_LINEAR, + LINEAR_MIPMAP_LINEAR = GL_LINEAR_MIPMAP_LINEAR, + + MIPMAP_CLOSEST_NEIGHBOUR = GL_NEAREST_MIPMAP_NEAREST, + MIPMAP_CLOSEST_LINEAR = GL_LINEAR_MIPMAP_NEAREST, + TWO_MIPMAP_NEIGHBOUR = GL_NEAREST_MIPMAP_LINEAR, + TWO_MIPMAP_LINEAR = GL_LINEAR_MIPMAP_LINEAR + }; + + enum class TextureType { + Tex1D = GL_TEXTURE_1D, + Tex1DArray = GL_TEXTURE_1D_ARRAY, + + Tex2D = GL_TEXTURE_2D, + Tex2DArray = GL_TEXTURE_2D_ARRAY, + Tex2DMultisample = GL_TEXTURE_2D_MULTISAMPLE, + Tex2DMultisampleArray = GL_TEXTURE_2D_MULTISAMPLE_ARRAY, + Tex3D = GL_TEXTURE_3D, + + Rectangle = GL_TEXTURE_RECTANGLE, + CubeMap = GL_TEXTURE_CUBE_MAP, + CubeMapArray = GL_TEXTURE_CUBE_MAP_ARRAY, + Buffer = GL_TEXTURE_BUFFER, + + }; + + enum class PixelFormat { + UNSIGNED_BYTE = GL_UNSIGNED_BYTE, + BYTE, + UNSIGNED_SHORT, + SHORT, + UNSIGNED_INT, + INT, + HALF_FLOAT, + FLOAT, + + UNSIGNED_BYTE_3_3_2, + UNSIGNED_BYTE_2_3_3_REV, + UNSIGNED_SHORT_5_6_5, + UNSIGNED_SHORT_5_6_5_REV, + UNSINGED_SHORT_4_4_4_4, + UNSIGNED_SHORT_4_4_4_4_REV, + UNSIGNED_SHORT_5_5_5_1, + UNSIGNED_SHORT_1_5_5_5_REV, + UNSIGNED_INT_8_8_8_8, + UNSIGNED_INT_8_8_8_8_REV, + UNSIGNED_INT_10_10_10_10_2, + UNSIGNED_INT_10_10_10_10_2_REV, + }; + + enum class InternalImageFormat { + DepthComponent = GL_DEPTH_COMPONENT, + DepthStencil = GL_DEPTH_STENCIL, + Red = GL_RED, + RedGreen = GL_RG, + RedGreenBlue = GL_RGB, + RedGreenBlueAlpha = GL_RGBA + }; + + enum class ImageFormat { + R = GL_RED, + RG, + RGB, + RGBA, + R_INT, + RG_INT, + RGB_INT, + RGBA_INT, + BGR, + BGRA, + BGR_INT, + BGRA_INT, + STENTICL_INDEX, + DEPTH_COMPONENT, + DEPTH_STENCIL + }; + + struct Image { + PixelFormat type; + ImageFormat format; + math::vec2i size; + void* data; + }; + + class Texture { + public: + inline explicit Texture(TextureType type) : m_type(type) { + glGenTextures(1, &m_obj); + } + ~Texture() { + glDeleteTextures(1, &m_obj); + } + + void setup(InternalImageFormat iFmt, math::vec2i size) { + //bind(); + glBindTexture( (GLenum)m_type, m_obj ); + if ( iFmt == InternalImageFormat::DepthComponent ) { + glTexImage2D((GLenum) + m_type, 0, (GLint) iFmt, size.x, size.y, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, nullptr); + } else { + glTexImage2D((GLenum) + m_type, 0, (GLint) iFmt, size.x, size.y, 0, GL_RGB, GL_UNSIGNED_BYTE, nullptr); + } + } + + void setData(InternalImageFormat iFmt, Image& image) { + //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)image.type, image.data); + } + + void setDataPart(math::vec2i offset, Image& image) { + glTexImage2D( (GLenum)m_type, 0, offset.x, offset.y, image.size.x, image.size.y, (GLenum)image.format, (GLenum)image.type, image.data); + } + + void wrapMode(Wrapping wrap); + void filterModeMagnify(Filtering filter); + void filterModeMinify(Filtering filter); + + /** + * Bind this texture to a texture unit. + * + * @param textureUnit the texture unit to bind to + */ + inline void bind(unsigned int textureUnit) { + assert( 0 <= textureUnit && textureUnit < GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS ); + glActiveTexture( GL_TEXTURE0 + textureUnit ); + glBindTexture( (GLenum) m_type, m_obj ); + } + + private: + GLuint m_obj = 0; + TextureType m_type; + + void release(); + }; + enum class BuffAttrF { HALF_FLOAT = GL_HALF_FLOAT, FLOAT = GL_FLOAT, @@ -143,30 +294,30 @@ namespace fggl::gfx::ogl { }; enum class Primative { - POINT = GL_POINT, - LINE = GL_LINES, - LINE_STRIP = GL_LINE_STRIP, - LINE_LOOP = GL_LINE_LOOP, - TRIANGLE = GL_TRIANGLES, - TRIANGLE_STRIP = GL_TRIANGLE_STRIP, - TRIANGLE_FAN = GL_TRIANGLE_FAN + POINT = GL_POINT, + LINE = GL_LINES, + LINE_STRIP = GL_LINE_STRIP, + LINE_LOOP = GL_LINE_LOOP, + TRIANGLE = GL_TRIANGLES, + TRIANGLE_STRIP = GL_TRIANGLE_STRIP, + TRIANGLE_FAN = GL_TRIANGLE_FAN }; enum class BufType { - ARRAY = GL_ARRAY_BUFFER, - ELEMENT_ARRAY = GL_ELEMENT_ARRAY_BUFFER, - COPY_READ = GL_COPY_READ_BUFFER, - COPY_WRITE = GL_COPY_WRITE_BUFFER, - PIXEL_UNPACK = GL_PIXEL_UNPACK_BUFFER, - PIXEL_PACK = GL_PIXEL_PACK_BUFFER, - QUERY = GL_QUERY_BUFFER, - TEXTURE = GL_TEXTURE_BUFFER, - TRANSFORM_FEEDBACK = GL_TRANSFORM_FEEDBACK_BUFFER, - UNIFORM = GL_UNIFORM_BUFFER, - DRAW_INDRECT = GL_DRAW_INDIRECT_BUFFER, - ATOMIC_COUNTER = GL_ATOMIC_COUNTER_BUFFER, - DISPATCH_INDIRECT = GL_DISPATCH_INDIRECT_BUFFER, - SHADER_STORAGE = GL_SHADER_STORAGE_BUFFER + ARRAY = GL_ARRAY_BUFFER, + ELEMENT_ARRAY = GL_ELEMENT_ARRAY_BUFFER, + COPY_READ = GL_COPY_READ_BUFFER, + COPY_WRITE = GL_COPY_WRITE_BUFFER, + PIXEL_UNPACK = GL_PIXEL_UNPACK_BUFFER, + PIXEL_PACK = GL_PIXEL_PACK_BUFFER, + QUERY = GL_QUERY_BUFFER, + TEXTURE = GL_TEXTURE_BUFFER, + TRANSFORM_FEEDBACK = GL_TRANSFORM_FEEDBACK_BUFFER, + UNIFORM = GL_UNIFORM_BUFFER, + DRAW_INDRECT = GL_DRAW_INDIRECT_BUFFER, + ATOMIC_COUNTER = GL_ATOMIC_COUNTER_BUFFER, + DISPATCH_INDIRECT = GL_DISPATCH_INDIRECT_BUFFER, + SHADER_STORAGE = GL_SHADER_STORAGE_BUFFER }; enum class BufUsage { @@ -288,7 +439,7 @@ namespace fggl::gfx::ogl { template<typename V, typename T> AttributeF attribute(std::size_t offset) { return AttributeF{ - attr_type<T>::attr, + attr_type<T>::attr, attr_type<T>::size, sizeof(V), offset @@ -328,6 +479,7 @@ namespace fggl::gfx::ogl { void setAttributeI(const ArrayBuffer& buffer, GLuint idx, AttributeI& attr); void drawElements(const ElementBuffer& buff, Primative drawType, std::size_t size); + void draw(Primative drawType, int first, std::size_t count); }; // paranoid functions @@ -337,7 +489,7 @@ namespace fggl::gfx::ogl { template<BufType T> void bind_buffer(GLuint* marker, const Buffer<T>& buff) { #ifdef FGGL_GL_PARANOID - assert( marker != nullptr ); + assert( marker != nullptr ); glGetIntegerv( (GLenum)T, (GLint*) marker ); #endif buff.bind(); @@ -346,7 +498,7 @@ namespace fggl::gfx::ogl { template<BufType T> void unbind_buffer(GLuint* marker, const Buffer<T>& buff) { #ifdef GL_FGGL_PARANOID - assert( marker != nullptr ); + assert( marker != nullptr ); glBindVertexArray(marker); #endif } diff --git a/include/fggl/gfx/ogl4/canvas.hpp b/include/fggl/gfx/ogl4/canvas.hpp index 57cf6a5..6c90bff 100644 --- a/include/fggl/gfx/ogl4/canvas.hpp +++ b/include/fggl/gfx/ogl4/canvas.hpp @@ -21,7 +21,9 @@ namespace fggl::gfx::ogl4 { ogl::VertexArray m_vao; ogl::ArrayBuffer m_vertexList; ogl::ElementBuffer m_indexList; + gui::FontLibrary m_fonts; + ogl::Texture m_fontTex; void renderShapes(const Paint &paint, GLuint shader); void renderText(const Paint&, GLuint shader); diff --git a/include/fggl/gui/fonts.hpp b/include/fggl/gui/fonts.hpp index 735e07d..3ff8f41 100644 --- a/include/fggl/gui/fonts.hpp +++ b/include/fggl/gui/fonts.hpp @@ -46,9 +46,14 @@ namespace fggl::gui { class FontFace { public: - FontFace(FT_Face face); + explicit FontFace(FT_Face face); ~FontFace(); + FontFace(const FontFace&) = delete; + FontFace(FontFace&&) = delete; + FontFace& operator=(const FontFace&) = delete; + FontFace& operator=(FontFace&&) = delete; + inline GlyphMetrics& metrics(char letter){ auto itr = m_metrics.find(letter); if ( itr == m_metrics.end() ) { @@ -58,7 +63,7 @@ namespace fggl::gui { } math::vec2 stringSize(const std::string& text); - void texture(char letter, int& width, int& height, unsigned char* buff); + void texture(char letter, int& width, int& height, void** buff); private: FT_Face m_face; @@ -72,6 +77,12 @@ namespace fggl::gui { FontLibrary(); ~FontLibrary(); + // copy and moving not needed + FontLibrary(const FontLibrary&) = delete; + FontLibrary(FontLibrary&&) = delete; + FontLibrary& operator=(const FontLibrary&) = delete; + FontLibrary& operator=(FontLibrary&&) = delete; + inline std::shared_ptr<FontFace> getFont(const std::string& name) { auto fontItr = m_cache.find(name); if ( fontItr != m_cache.end() ) { @@ -86,7 +97,7 @@ namespace fggl::gui { if ( FT_New_Face(m_context, path.c_str(), 0, &face) ) { return nullptr; } - FT_Set_Pixel_Sizes(face, 0, 48); + FT_Set_Pixel_Sizes(face, 0, 18); // create the new font object auto ptr = std::make_shared<FontFace>(face); diff --git a/include/fggl/gui/widgets.hpp b/include/fggl/gui/widgets.hpp index 8f7ccaa..4749b20 100644 --- a/include/fggl/gui/widgets.hpp +++ b/include/fggl/gui/widgets.hpp @@ -2,12 +2,59 @@ #define FGGL_GUI_WIDGETS_H #include <functional> -#include <fggl/gui/widget.hpp> +#include <utility> + +#include "fggl/gui/widget.hpp" +#include "fggl/gui/fonts.hpp" namespace fggl::gui { using Callback = std::function<void(void)>; + class Label : public Widget { + public: + Label() = default; + Label(math::vec2 pos, math::vec2 size); + + ~Label() = default; + + void render(gfx::Paint &paint) override { + auto pos = topLeft(); + auto size2 = size(); + math::vec2 baseLine{pos.x + 10, pos.y + size2.y/2 + 5}; + paint.text(m_value,baseLine); + } + + inline void font(std::shared_ptr<FontFace> font) { + m_font = std::move(font); + m_needsLayout = true; + } + + inline void text(const std::string& value) { + m_value = value; + m_needsLayout = true; + } + + inline std::string text() const { + return m_value; + } + + math::vec2 naturalSize() { + if ( m_needsLayout ) { + layout(); + } + return m_naturalSize; + } + + void layout(); + + private: + std::shared_ptr<gui::FontFace> m_font; + std::string m_value; + bool m_needsLayout; + math::vec2 m_naturalSize; + }; + class Button : public Widget { public: Button() = default; @@ -26,28 +73,17 @@ namespace fggl::gui { void addCallback(Callback callback); private: + Label m_label; std::string m_value; std::vector<Callback> m_callbacks; bool m_hover; bool m_active; }; - class Label : public Widget { - public: - Label(); - void render(gfx::Paint &paint) override; - }; - - class TextField : public Widget { - public: - TextField(); - void render(gfx::Paint &paint) override; - }; - class Toggle : public Widget { + class Toggle : public Button { public: - Toggle(); - void render(gfx::Paint &paint) override; + Toggle() = default; }; }; //namespace fggl::gui -- GitLab