diff --git a/extra/FreeSans.ttf b/extra/FreeSans.ttf
new file mode 100644
index 0000000..6de62eb
Binary files /dev/null and b/extra/FreeSans.ttf differ
diff --git a/extra/testTheme.txt b/extra/testTheme.txt
index 28d7ae9..57dc24b 100644
--- a/extra/testTheme.txt
+++ b/extra/testTheme.txt
@@ -5,7 +5,7 @@ $accentColor = Color(rgba(255, 0, 255, 255))
const $color2 = Color(#0000FFFF)
% -------------------
-window.backgroundColor = Color(rgba(0, 0, 0, 255))
+window.backgroundColor = Color(rgba(160, 160, 160, 255))
$accentColor = Color(rgb(255, 0, 0))
@@ -13,7 +13,7 @@ $accentColor = Color(rgb(255, 0, 0))
textColor = $accentColor
backgroundColor = Color(#000000FF)
borderColor = $accentColor
- fontSize = 50
+ fontSize = 20
borderRadius = 0
borderWidth = 2
showBackground = true
@@ -65,7 +65,7 @@ $accentColor = Color(rgb(255, 0, 0))
(@testLabel label) {
textColor = $color2
backgroundColor = Color(#000000FF)
- fontSize = 20
+ fontSize = 50
}
(@testLabel2 label) {
@@ -108,7 +108,6 @@ $accentColor = Color(rgb(255, 0, 0))
(@label3 label) {
textColor = $color2
backgroundColor = Color(#000000FF)
- fontSize = 20
borderColor = Color(#FF0000FF)
borderWidth = 4
borderRadius = 15
diff --git a/other/TODO.txt b/other/TODO.txt
index 91db5f1..4fbae84 100644
--- a/other/TODO.txt
+++ b/other/TODO.txt
@@ -14,6 +14,11 @@ Add theme caching
Implement callback for state changes in checkbox
FIX: WindowOutputHandler text rendering
find a way to add fixed update to gui::Window
+Implement global scale (probably query desktop scale and adjust accordingly)
+Fix: Text rendering bug in labels
+Implement show/hide functionality for widgets
+Implement wrappers in Renderer2D to take a center and size for rects
+
Implement following widgets:
diff --git a/other/build.nr b/other/build.nr
index 1113ea2..8c6e25c 100644
--- a/other/build.nr
+++ b/other/build.nr
@@ -1 +1 @@
-2060
+2061
diff --git a/src/ogfx/gui/widgets/CheckBox.cpp b/src/ogfx/gui/widgets/CheckBox.cpp
index d5aedfa..5c52c3b 100644
--- a/src/ogfx/gui/widgets/CheckBox.cpp
+++ b/src/ogfx/gui/widgets/CheckBox.cpp
@@ -73,6 +73,8 @@ namespace ogfx
void CheckBox::onMouseReleased(const Event& event)
{
+ if (!isMouseInside())
+ return;
m_checked = !m_checked;
setThemeQualifier("active", m_checked);
}
diff --git a/src/ogfx/render/BasicRenderer.cpp b/src/ogfx/render/BasicRenderer.cpp
index c6925ec..7ab85f3 100644
--- a/src/ogfx/render/BasicRenderer.cpp
+++ b/src/ogfx/render/BasicRenderer.cpp
@@ -1,21 +1,21 @@
/*
- OmniaFramework - A collection of useful functionality
- Copyright (C) 2025 OmniaX-Dev
+ OmniaFramework - A collection of useful functionality
+ Copyright (C) 2025 OmniaX-Dev
- This file is part of OmniaFramework.
+ This file is part of OmniaFramework.
- OmniaFramework is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
+ OmniaFramework is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
- OmniaFramework 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 General Public License for more details.
+ OmniaFramework 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 General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with OmniaFramework. If not, see .
+ You should have received a copy of the GNU General Public License
+ along with OmniaFramework. If not, see .
*/
#include "BasicRenderer.hpp"
@@ -77,7 +77,7 @@ namespace ogfx
void BasicRenderer2D::flushBatch(void)
{
if (!m_initialized || m_vertexCount == 0)
- return;
+ return;
SDL_RenderGeometry(m_window->getSDLRenderer(), m_texture, m_vertices.data(), m_vertexCount, m_indices.data(), m_indexCount);
m_drawCallCount++;
m_texture = nullptr;
@@ -95,22 +95,22 @@ namespace ogfx
{
if (!m_initialized) return;
- ostd::Rectangle finalRect = rect;
+ ostd::Rectangle finalRect = rect;
- if (additive && !m_clipStack.empty())
- finalRect = m_clipStack.back().getIntersection(rect, false);
+ if (additive && !m_clipStack.empty())
+ finalRect = m_clipStack.back().getIntersection(rect, false);
if (!m_clipStack.empty() && m_clipStack.back() == finalRect)
return;
flushBatch();
- m_clipStack.push_back(finalRect);
+ m_clipStack.push_back(finalRect);
- SDL_Rect r;
- r.x = (int)std::round(finalRect.x);
- r.y = (int)std::round(finalRect.y);
- r.w = (int)std::round(finalRect.w);
- r.h = (int)std::round(finalRect.h);
+ SDL_Rect r;
+ r.x = (int)std::round(finalRect.x);
+ r.y = (int)std::round(finalRect.y);
+ r.w = (int)std::round(finalRect.w);
+ r.h = (int)std::round(finalRect.h);
SDL_SetRenderClipRect(m_window->getSDLRenderer(), &r);
}
@@ -118,26 +118,26 @@ namespace ogfx
void BasicRenderer2D::popClippingRect(void)
{
if (!m_initialized) return;
- if (m_clipStack.empty()) return;
+ if (m_clipStack.empty()) return;
- m_clipStack.pop_back();
+ m_clipStack.pop_back();
flushBatch();
- if (m_clipStack.empty())
- {
- SDL_SetRenderClipRect(m_window->getSDLRenderer(), nullptr);
- return;
- }
+ if (m_clipStack.empty())
+ {
+ SDL_SetRenderClipRect(m_window->getSDLRenderer(), nullptr);
+ return;
+ }
- const auto& rect = m_clipStack.back();
+ const auto& rect = m_clipStack.back();
- SDL_Rect r;
- r.x = (int)std::round(rect.x);
- r.y = (int)std::round(rect.y);
- r.w = (int)std::round(rect.w);
- r.h = (int)std::round(rect.h);
+ SDL_Rect r;
+ r.x = (int)std::round(rect.x);
+ r.y = (int)std::round(rect.y);
+ r.w = (int)std::round(rect.w);
+ r.h = (int)std::round(rect.h);
- SDL_SetRenderClipRect(m_window->getSDLRenderer(), &r);
+ SDL_SetRenderClipRect(m_window->getSDLRenderer(), &r);
}
int32_t BasicRenderer2D::loadDefaultFont(int32_t fontSize)
@@ -211,44 +211,30 @@ namespace ogfx
return set_error_state(tErrors::NoError);
}
- ostd::IPoint BasicRenderer2D::getStringDimensions(const ostd::String& message, int32_t fontSize)
+ ostd::Vec2 BasicRenderer2D::getStringDimensions(const ostd::String& message, int32_t fontSize, TTF_Font* font)
{
- if (!m_initialized)
- {
- set_error_state(tErrors::Uninitialized);
- return { 0, 0 };
- }
- if (!m_fontOpen)
- {
- set_error_state(tErrors::NoFont);
- return { 0, 0 };
- }
- if (m_font == nullptr)
- {
- set_error_state(tErrors::NullFont);
- return { 0, 0 };
- }
- setFontSize(fontSize);
- SDL_Surface* surf = TTF_RenderText_Blended(m_font, message.c_str(), message.len(), { 0, 0, 0, 255 });
- if (surf == nullptr)
- {
- set_error_state(tErrors::TTFRenderTextBlendedFail);
- print_ttf_error("TTF_RenderText_Blended");
- return { 0, 0 };
- }
- SDL_Texture* texture = SDL_CreateTextureFromSurface(m_window->getSDLRenderer(), surf);
- if (texture == nullptr)
- {
- set_error_state(tErrors::TTFCreateTextureFromSurfaceFail);
- print_ttf_error("SDL_CreateTextureFromSurface");
- return { 0, 0 };
- }
- SDL_DestroySurface(surf);
+ if (!isValid()) return { 0, 0 };
+ if (fontSize <= 0) fontSize = m_fontSize;
+ if (!font) font = m_font;
+ int32_t oldFontSize = getFontSize();
+ setFontSize(fontSize);
- float w = 0, h = 0;
- SDL_GetTextureSize(texture, &w, &h);
- SDL_DestroyTexture(texture);
- return { static_cast(w), static_cast(h) };
+ auto glyphs = m_fontGlyphAtlas.processString(message, font, fontSize);
+ if (glyphs.empty()) return { 0, 0 };
+
+ float totalWidth = 0;
+ for (size_t i = 0; i < glyphs.size(); i++)
+ {
+ totalWidth += glyphs[i]->advance;
+ if (i > 0)
+ {
+ int kern = 0;
+ TTF_GetGlyphKerning(font, glyphs[i - 1]->codepoint, glyphs[i]->codepoint, &kern);
+ totalWidth += kern;
+ }
+ }
+ setFontSize(oldFontSize);
+ return { totalWidth, glyphs[0]->size.y };
}
// ===================================================== UTILS =====================================================
@@ -257,83 +243,122 @@ namespace ogfx
// ===================================================== SPECIALIZED =====================================================
- void BasicRenderer2D::drawImage(const ogfx::Image& image, const ostd::Vec2& position, const ostd::Vec2& size, const ostd::Rectangle& srcRect)
- {
- if (!m_initialized || !image.isLoaded())
- return;
-
- SDL_Texture* tex = image.getSDLTexture();
- if (!tex)
- return;
-
- ostd::Vec2 texSize = image.getSize();
-
- // 1. Resolve source rectangle
- float sx, sy, sw, sh;
- if (srcRect.w == 0 && srcRect.h == 0)
- {
- // Use entire texture
- sx = 0.0f;
- sy = 0.0f;
- sw = texSize.x;
- sh = texSize.y;
- }
- else
- {
- sx = srcRect.x;
- sy = srcRect.y;
- sw = srcRect.w;
- sh = srcRect.h;
- }
-
- // 2. Resolve destination rectangle
- float dx = position.x;
- float dy = position.y;
- float dw = (size.x == 0) ? sw : size.x;
- float dh = (size.y == 0) ? sh : size.y;
- float x1 = dx;
- float y1 = dy;
- float x2 = dx + dw;
- float y2 = dy + dh;
-
- // 3. Build quad vertices
- ostd::Vec2 verts[4] = {
- { x1, y1 },
- { x2, y1 },
- { x2, y2 },
- { x1, y2 }
- };
-
- // 4. Build UVs (normalized)
- ostd::Vec2 uvs[4] = {
- { sx / texSize.x, sy / texSize.y },
- { (sx + sw) / texSize.x, sy / texSize.y },
- { (sx + sw) / texSize.x, (sy + sh) / texSize.y },
- { sx / texSize.x, (sy + sh) / texSize.y }
- };
-
- // 5. Push quad
- uint32_t inds[6] = QUAD_INDICES_ARR;
- push_polygon(verts, uvs, 4, inds, 6, ostd::Colors::White, tex);
- }
-
- void BasicRenderer2D::drawAnimation(const Animation& anim, const ostd::Vec2& position, const ostd::Vec2& size)
- {
- if (!m_initialized) return;
- if (!anim.hasImage()) return;
- const Image& img = anim.getSpriteSheet();
- if (!img.isLoaded() || !img.isValid()) return;
- drawImage(img, position, size, anim.getFrameRect());
- }
-
- void BasicRenderer2D::drawString(const ostd::String& str, const ostd::Vec2& position, const ostd::Color& color, int32_t fontSize)
+ void BasicRenderer2D::drawImage(const ogfx::Image& image, const ostd::Vec2& position, const ostd::Vec2& size, const ostd::Rectangle& srcRect)
{
- if (!m_initialized) return;
+ if (!m_initialized || !image.isLoaded())
+ return;
+
+ SDL_Texture* tex = image.getSDLTexture();
+ if (!tex)
+ return;
+
+ ostd::Vec2 texSize = image.getSize();
+
+ // 1. Resolve source rectangle
+ float sx, sy, sw, sh;
+ if (srcRect.w == 0 && srcRect.h == 0)
+ {
+ // Use entire texture
+ sx = 0.0f;
+ sy = 0.0f;
+ sw = texSize.x;
+ sh = texSize.y;
+ }
+ else
+ {
+ sx = srcRect.x;
+ sy = srcRect.y;
+ sw = srcRect.w;
+ sh = srcRect.h;
+ }
+
+ // 2. Resolve destination rectangle
+ float dx = position.x;
+ float dy = position.y;
+ float dw = (size.x == 0) ? sw : size.x;
+ float dh = (size.y == 0) ? sh : size.y;
+ float x1 = dx;
+ float y1 = dy;
+ float x2 = dx + dw;
+ float y2 = dy + dh;
+
+ // 3. Build quad vertices
+ ostd::Vec2 verts[4] = {
+ { x1, y1 },
+ { x2, y1 },
+ { x2, y2 },
+ { x1, y2 }
+ };
+
+ // 4. Build UVs (normalized)
+ ostd::Vec2 uvs[4] = {
+ { sx / texSize.x, sy / texSize.y },
+ { (sx + sw) / texSize.x, sy / texSize.y },
+ { (sx + sw) / texSize.x, (sy + sh) / texSize.y },
+ { sx / texSize.x, (sy + sh) / texSize.y }
+ };
+
+ // 5. Push quad
+ uint32_t inds[6] = QUAD_INDICES_ARR;
+ push_polygon(verts, uvs, 4, inds, 6, ostd::Colors::White, tex);
}
- void BasicRenderer2D::drawCenteredString(const ostd::String& str, const ostd::Vec2& center, const ostd::Color& color, int32_t fontSize)
+ void BasicRenderer2D::drawAnimation(const Animation& anim, const ostd::Vec2& position, const ostd::Vec2& size)
{
if (!m_initialized) return;
+ if (!anim.hasImage()) return;
+ const Image& img = anim.getSpriteSheet();
+ if (!img.isLoaded() || !img.isValid()) return;
+ drawImage(img, position, size, anim.getFrameRect());
+ }
+
+ void BasicRenderer2D::drawString(const ostd::String& str, const ostd::Vec2& position, const ostd::Color& color, int32_t fontSize, float scale)
+ {
+ if (!isValid()) return;
+ if (fontSize <= 0)
+ fontSize = m_fontSize;
+ setFontSize(fontSize);
+
+ auto glyphs = m_fontGlyphAtlas.processString(str, m_font, fontSize);
+ if (glyphs.empty()) return;
+
+ float x = position.x;
+ float y = position.y;
+
+
+ for (size_t i = 0; i < glyphs.size(); i++)
+ {
+ auto& g = glyphs[i];
+
+ if (i > 0)
+ {
+ int kern = 0;
+ TTF_GetGlyphKerning(m_font, glyphs[i - 1]->codepoint, g->codepoint, &kern);
+ x += (kern * scale);
+ }
+
+ ostd::Vec2 verts[4] = {
+ { x, y },
+ { x + g->size.x * scale, y },
+ { x + g->size.x * scale, y + g->size.y * scale },
+ { x, y + g->size.y * scale }
+ };
+ uint32_t inds[6] = QUAD_INDICES_ARR;
+ push_polygon(verts, g->uvs, 4, inds, 6, color, g->atlas);
+
+ x += (g->advance * scale);
+ }
+ }
+
+ void BasicRenderer2D::drawCenteredString(const ostd::String& str, const ostd::Vec2& center, const ostd::Color& color, int32_t fontSize, float scale)
+ {
+ auto dims = getStringDimensions(str, fontSize);
+ drawString(str, { center.x - (dims.x * scale) * 0.5f, center.y - (dims.y * scale) * 0.5f }, color, fontSize, scale);
+ }
+
+ void BasicRenderer2D::drawCenteredString(const ostd::String& str, const ostd::Rectangle& bounds, const ostd::Color& color, int32_t fontSize, float scale)
+ {
+ drawCenteredString(str, ostd::Vec2 { bounds.x + bounds.w * 0.5f, bounds.y + bounds.h * 0.5f }, color, fontSize, scale);
}
// ===================================================== SPECIALIZED =====================================================
@@ -357,7 +382,7 @@ namespace ogfx
Vec2 off = perp * half;
std::array verts = {{
- p1 - off,
+ p1 - off,
p1 + off,
p2 + off,
p2 - off
@@ -366,248 +391,248 @@ namespace ogfx
push_polygon(verts.data(), nullptr, 4, inds.data(), 6, color, nullptr);
- if (!rounded || thickness < 4)
- return;
+ if (!rounded || thickness < 4)
+ return;
int segments = std::max(6, int(thickness * 0.75f));
- generate_half_circle(p1, -dir, half, segments, color);
- generate_half_circle(p2, dir, half, segments, color);
+ generate_half_circle(p1, -dir, half, segments, color);
+ generate_half_circle(p2, dir, half, segments, color);
}
void BasicRenderer2D::drawRect(const ostd::Rectangle& rect, const ostd::Color& color, int32_t thickness)
{
if (!m_initialized || thickness <= 0)
- return;
+ return;
- float half = thickness * 0.5f;
+ float half = thickness * 0.5f;
- // Inset rectangle so the outline stays inside the original bounds
- float x1 = rect.x + half;
- float y1 = rect.y + half;
- float x2 = rect.x + rect.w - half;
- float y2 = rect.y + rect.h - half;
+ // Inset rectangle so the outline stays inside the original bounds
+ float x1 = rect.x + half;
+ float y1 = rect.y + half;
+ float x2 = rect.x + rect.w - half;
+ float y2 = rect.y + rect.h - half;
- // Top
- drawLine({ {x1, y1}, {x2, y1} }, color, thickness, false);
+ // Top
+ drawLine({ {x1, y1}, {x2, y1} }, color, thickness, false);
- // Right
- drawLine({ {x2, y1 - half}, {x2, y2 + half} }, color, thickness, false);
+ // Right
+ drawLine({ {x2, y1 - half}, {x2, y2 + half} }, color, thickness, false);
- // Bottom
- drawLine({ {x2, y2}, {x1, y2} }, color, thickness, false);
+ // Bottom
+ drawLine({ {x2, y2}, {x1, y2} }, color, thickness, false);
- // Left
- drawLine({ {x1, y2 + half}, {x1, y1 - half} }, color, thickness, false);
+ // Left
+ drawLine({ {x1, y2 + half}, {x1, y1 - half} }, color, thickness, false);
}
void BasicRenderer2D::drawRoundRect(const ostd::Rectangle& rect, const ostd::Color& color, float radius, int32_t thickness)
{
if (!m_initialized || thickness <= 0)
- return;
+ return;
- // Clamp radius so it never exceeds half the smallest dimension
- radius = std::max(0.0f, std::min(radius, std::min(rect.w, rect.h) * 0.5f));
+ // Clamp radius so it never exceeds half the smallest dimension
+ radius = std::max(0.0f, std::min(radius, std::min(rect.w, rect.h) * 0.5f));
- float half = thickness * 0.5f;
+ float half = thickness * 0.5f;
- // Inset rectangle so the border stays INSIDE the given bounds
- float x1 = rect.x + half;
- float y1 = rect.y + half;
- float x2 = rect.x + rect.w - half;
- float y2 = rect.y + rect.h - half;
+ // Inset rectangle so the border stays INSIDE the given bounds
+ float x1 = rect.x + half;
+ float y1 = rect.y + half;
+ float x2 = rect.x + rect.w - half;
+ float y2 = rect.y + rect.h - half;
- // Corner centers
- ostd::Vec2 TL { x1 + radius, y1 + radius };
- ostd::Vec2 TR { x2 - radius, y1 + radius };
- ostd::Vec2 BR { x2 - radius, y2 - radius };
- ostd::Vec2 BL { x1 + radius, y2 - radius };
+ // Corner centers
+ ostd::Vec2 TL { x1 + radius, y1 + radius };
+ ostd::Vec2 TR { x2 - radius, y1 + radius };
+ ostd::Vec2 BR { x2 - radius, y2 - radius };
+ ostd::Vec2 BL { x1 + radius, y2 - radius };
- // Straight edges (shortened to meet the arcs cleanly)
- drawLine({ {x1 + radius, y1}, {x2 - radius, y1} }, color, thickness, false); // top
- drawLine({ {x2, y1 + radius}, {x2, y2 - radius} }, color, thickness, false); // right
- drawLine({ {x2 - radius, y2}, {x1 + radius, y2} }, color, thickness, false); // bottom
- drawLine({ {x1, y2 - radius}, {x1, y1 + radius} }, color, thickness, false); // left
+ // Straight edges (shortened to meet the arcs cleanly)
+ drawLine({ {x1 + radius, y1}, {x2 - radius, y1} }, color, thickness, false); // top
+ drawLine({ {x2, y1 + radius}, {x2, y2 - radius} }, color, thickness, false); // right
+ drawLine({ {x2 - radius, y2}, {x1 + radius, y2} }, color, thickness, false); // bottom
+ drawLine({ {x1, y2 - radius}, {x1, y1 + radius} }, color, thickness, false); // left
- // Number of segments for smooth arcs
- int segments = std::max(6, int(radius * 0.75f));
+ // Number of segments for smooth arcs
+ int segments = std::max(6, int(radius * 0.75f));
- // Quarter‑ellipse strokes (each is 90°)
- generate_ellipse_stroke(TL, radius, radius, thickness, M_PI, M_PI + M_PI * 0.5f, color, segments); // TL
- generate_ellipse_stroke(TR, radius, radius, thickness, 1.5f * M_PI, 2.0f * M_PI, color, segments); // TR
- generate_ellipse_stroke(BR, radius, radius, thickness, 0.0f, M_PI * 0.5f, color, segments); // BR
- generate_ellipse_stroke(BL, radius, radius, thickness, M_PI * 0.5f, M_PI, color, segments); // BL
+ // Quarter‑ellipse strokes (each is 90°)
+ generate_ellipse_stroke(TL, radius, radius, thickness, M_PI, M_PI + M_PI * 0.5f, color, segments); // TL
+ generate_ellipse_stroke(TR, radius, radius, thickness, 1.5f * M_PI, 2.0f * M_PI, color, segments); // TR
+ generate_ellipse_stroke(BR, radius, radius, thickness, 0.0f, M_PI * 0.5f, color, segments); // BR
+ generate_ellipse_stroke(BL, radius, radius, thickness, M_PI * 0.5f, M_PI, color, segments); // BL
}
void BasicRenderer2D::drawCircle(const ostd::Vec2& center, float radius, const ostd::Color& color, int32_t thickness)
{
if (!m_initialized || thickness <= 0)
- return;
- int segments = std::max(12, int(radius * 0.75f));
- generate_ellipse_stroke(center, radius, radius, thickness, 0.0f, 2.0f * M_PI, color, segments);
+ return;
+ int segments = std::max(12, int(radius * 0.75f));
+ generate_ellipse_stroke(center, radius, radius, thickness, 0.0f, 2.0f * M_PI, color, segments);
}
void BasicRenderer2D::drawCircle(const ostd::Rectangle& rect, const ostd::Color& color, int32_t thickness)
{
- if (!m_initialized || thickness <= 0)
- return;
+ if (!m_initialized || thickness <= 0)
+ return;
- ostd::Vec2 center {
- rect.x + rect.w * 0.5f,
- rect.y + rect.h * 0.5f
- };
+ ostd::Vec2 center {
+ rect.x + rect.w * 0.5f,
+ rect.y + rect.h * 0.5f
+ };
- float radius = std::min(rect.w, rect.h) * 0.5f;
- drawCircle(center, radius, color, thickness);
+ float radius = std::min(rect.w, rect.h) * 0.5f;
+ drawCircle(center, radius, color, thickness);
}
void BasicRenderer2D::drawEllipse(const ostd::Rectangle& rect, const ostd::Color& color, int32_t thickness)
{
if (!m_initialized || thickness <= 0)
- return;
+ return;
ostd::Vec2 center = { rect.x + rect.w*0.5f, rect.y + rect.h*0.5f };
- float rx = rect.w * 0.5f;
- float ry = rect.h * 0.5f;
+ float rx = rect.w * 0.5f;
+ float ry = rect.h * 0.5f;
- int segments = std::max(12, int(std::max(rx, ry) * 0.75f));
- generate_ellipse_stroke(center, rx, ry, thickness, 0.0f, 2.0f * M_PI, color, segments);
+ int segments = std::max(12, int(std::max(rx, ry) * 0.75f));
+ generate_ellipse_stroke(center, rx, ry, thickness, 0.0f, 2.0f * M_PI, color, segments);
}
void BasicRenderer2D::fillRect(const ostd::Rectangle& rect, const ostd::Color& color)
{
if (!m_initialized)
- return;
+ return;
- float x1 = rect.x;
- float y1 = rect.y;
- float x2 = rect.x + rect.w;
- float y2 = rect.y + rect.h;
+ float x1 = rect.x;
+ float y1 = rect.y;
+ float x2 = rect.x + rect.w;
+ float y2 = rect.y + rect.h;
- ostd::Vec2 verts[4] = {
- { x1, y1 },
- { x2, y1 },
- { x2, y2 },
- { x1, y2 }
- };
- uint32_t inds[6] = QUAD_INDICES_ARR;
- push_polygon(verts, nullptr, 4, inds, 6, color, nullptr);
+ ostd::Vec2 verts[4] = {
+ { x1, y1 },
+ { x2, y1 },
+ { x2, y2 },
+ { x1, y2 }
+ };
+ uint32_t inds[6] = QUAD_INDICES_ARR;
+ push_polygon(verts, nullptr, 4, inds, 6, color, nullptr);
}
void BasicRenderer2D::fillRoundRect(const ostd::Rectangle& rect, const ostd::Color& color, float radius)
{
if (!m_initialized)
- return;
+ return;
- // Clamp radius
- radius = std::max(0.0f, std::min(radius, std::min(rect.w, rect.h) * 0.5f));
+ // Clamp radius
+ radius = std::max(0.0f, std::min(radius, std::min(rect.w, rect.h) * 0.5f));
- float x1 = rect.x;
- float y1 = rect.y;
- float x2 = rect.x + rect.w;
- float y2 = rect.y + rect.h;
+ float x1 = rect.x;
+ float y1 = rect.y;
+ float x2 = rect.x + rect.w;
+ float y2 = rect.y + rect.h;
- float cx1 = x1 + radius;
- float cy1 = y1 + radius;
- float cx2 = x2 - radius;
- float cy2 = y2 - radius;
+ float cx1 = x1 + radius;
+ float cy1 = y1 + radius;
+ float cx2 = x2 - radius;
+ float cy2 = y2 - radius;
- // Number of segments for smooth corners
- int segments = std::max(6, int(radius * 0.75f));
+ // Number of segments for smooth corners
+ int segments = std::max(6, int(radius * 0.75f));
- //
- // 1. Fill the center rectangle
- //
- {
- ostd::Vec2 verts[4] = {
- { cx1, cy1 },
- { cx2, cy1 },
- { cx2, cy2 },
- { cx1, cy2 }
- };
- uint32_t inds[6] = { 0,1,2, 2,3,0 };
- push_polygon(verts, nullptr, 4, inds, 6, color, nullptr);
- }
+ //
+ // 1. Fill the center rectangle
+ //
+ {
+ ostd::Vec2 verts[4] = {
+ { cx1, cy1 },
+ { cx2, cy1 },
+ { cx2, cy2 },
+ { cx1, cy2 }
+ };
+ uint32_t inds[6] = { 0,1,2, 2,3,0 };
+ push_polygon(verts, nullptr, 4, inds, 6, color, nullptr);
+ }
- //
- // 2. Fill the four side rectangles
- //
+ //
+ // 2. Fill the four side rectangles
+ //
- // Top
- if (radius > 0)
- {
- ostd::Vec2 verts[4] = {
- { x1 + radius, y1 },
- { x2 - radius, y1 },
- { x2 - radius, y1 + radius },
- { x1 + radius, y1 + radius }
- };
- uint32_t inds[6] = { 0,1,2, 2,3,0 };
- push_polygon(verts, nullptr, 4, inds, 6, color, nullptr);
- }
+ // Top
+ if (radius > 0)
+ {
+ ostd::Vec2 verts[4] = {
+ { x1 + radius, y1 },
+ { x2 - radius, y1 },
+ { x2 - radius, y1 + radius },
+ { x1 + radius, y1 + radius }
+ };
+ uint32_t inds[6] = { 0,1,2, 2,3,0 };
+ push_polygon(verts, nullptr, 4, inds, 6, color, nullptr);
+ }
- // Bottom
- {
- ostd::Vec2 verts[4] = {
- { x1 + radius, y2 - radius },
- { x2 - radius, y2 - radius },
- { x2 - radius, y2 },
- { x1 + radius, y2 }
- };
- uint32_t inds[6] = { 0,1,2, 2,3,0 };
- push_polygon(verts, nullptr, 4, inds, 6, color, nullptr);
- }
+ // Bottom
+ {
+ ostd::Vec2 verts[4] = {
+ { x1 + radius, y2 - radius },
+ { x2 - radius, y2 - radius },
+ { x2 - radius, y2 },
+ { x1 + radius, y2 }
+ };
+ uint32_t inds[6] = { 0,1,2, 2,3,0 };
+ push_polygon(verts, nullptr, 4, inds, 6, color, nullptr);
+ }
- // Left
- {
- ostd::Vec2 verts[4] = {
- { x1, y1 + radius },
- { x1 + radius, y1 + radius },
- { x1 + radius, y2 - radius },
- { x1, y2 - radius }
- };
- uint32_t inds[6] = { 0,1,2, 2,3,0 };
- push_polygon(verts, nullptr, 4, inds, 6, color, nullptr);
- }
+ // Left
+ {
+ ostd::Vec2 verts[4] = {
+ { x1, y1 + radius },
+ { x1 + radius, y1 + radius },
+ { x1 + radius, y2 - radius },
+ { x1, y2 - radius }
+ };
+ uint32_t inds[6] = { 0,1,2, 2,3,0 };
+ push_polygon(verts, nullptr, 4, inds, 6, color, nullptr);
+ }
- // Right
- {
- ostd::Vec2 verts[4] = {
- { x2 - radius, y1 + radius },
- { x2, y1 + radius },
- { x2, y2 - radius },
- { x2 - radius, y2 - radius }
- };
- uint32_t inds[6] = { 0,1,2, 2,3,0 };
- push_polygon(verts, nullptr, 4, inds, 6, color, nullptr);
- }
+ // Right
+ {
+ ostd::Vec2 verts[4] = {
+ { x2 - radius, y1 + radius },
+ { x2, y1 + radius },
+ { x2, y2 - radius },
+ { x2 - radius, y2 - radius }
+ };
+ uint32_t inds[6] = { 0,1,2, 2,3,0 };
+ push_polygon(verts, nullptr, 4, inds, 6, color, nullptr);
+ }
- //
- // 3. Fill the four rounded corners (quarter ellipses)
- //
+ //
+ // 3. Fill the four rounded corners (quarter ellipses)
+ //
- generate_filled_ellipse_stroke({cx1, cy1}, radius, radius, M_PI, color, segments); // TL
- generate_filled_ellipse_stroke({cx2, cy1}, radius, radius, 1.5f * M_PI, color, segments); // TR
- generate_filled_ellipse_stroke({cx2, cy2}, radius, radius, 0.0f, color, segments); // BR
- generate_filled_ellipse_stroke({cx1, cy2}, radius, radius, 0.5f * M_PI, color, segments); // BL
+ generate_filled_ellipse_stroke({cx1, cy1}, radius, radius, M_PI, color, segments); // TL
+ generate_filled_ellipse_stroke({cx2, cy1}, radius, radius, 1.5f * M_PI, color, segments); // TR
+ generate_filled_ellipse_stroke({cx2, cy2}, radius, radius, 0.0f, color, segments); // BR
+ generate_filled_ellipse_stroke({cx1, cy2}, radius, radius, 0.5f * M_PI, color, segments); // BL
}
void BasicRenderer2D::fillCircle(const ostd::Vec2& center, float radius, const ostd::Color& color)
{
if (!m_initialized)
- return;
+ return;
- int segments = std::max(12, int(radius * 0.75f));
- generate_filled_ellipse(center, radius, radius, color, segments);
+ int segments = std::max(12, int(radius * 0.75f));
+ generate_filled_ellipse(center, radius, radius, color, segments);
}
void BasicRenderer2D::fillCircle(const ostd::Rectangle& rect, const ostd::Color& color)
{
if (!m_initialized)
- return;
+ return;
ostd::Vec2 center {
- rect.x + rect.w * 0.5f,
- rect.y + rect.h * 0.5f
+ rect.x + rect.w * 0.5f,
+ rect.y + rect.h * 0.5f
};
float radius = std::min(rect.w, rect.h) * 0.5f;
@@ -617,18 +642,18 @@ namespace ogfx
void BasicRenderer2D::fillEllipse(const ostd::Rectangle& rect, const ostd::Color& color)
{
if (!m_initialized)
- return;
+ return;
- ostd::Vec2 center {
- rect.x + rect.w * 0.5f,
- rect.y + rect.h * 0.5f
- };
+ ostd::Vec2 center {
+ rect.x + rect.w * 0.5f,
+ rect.y + rect.h * 0.5f
+ };
- float radiusX = rect.w * 0.5f;
- float radiusY = rect.h * 0.5f;
+ float radiusX = rect.w * 0.5f;
+ float radiusY = rect.h * 0.5f;
- int segments = std::max(12, int(std::max(radiusX, radiusY) * 0.75f));
- generate_filled_ellipse(center, radiusX, radiusY, color, segments);
+ int segments = std::max(12, int(std::max(radiusX, radiusY) * 0.75f));
+ generate_filled_ellipse(center, radiusX, radiusY, color, segments);
}
void BasicRenderer2D::outlinedRect(const ostd::Rectangle& rect, const ostd::Color& fillColor, const ostd::Color& outlineColor, int32_t outlineThickness)
@@ -657,11 +682,11 @@ namespace ogfx
void BasicRenderer2D::outlinedCircle(const ostd::Rectangle& rect, const ostd::Color& fillColor, const ostd::Color& outlineColor, int32_t outlineThickness)
{
if (!m_initialized)
- return;
+ return;
ostd::Vec2 center {
- rect.x + rect.w * 0.5f,
- rect.y + rect.h * 0.5f
+ rect.x + rect.w * 0.5f,
+ rect.y + rect.h * 0.5f
};
float radius = std::min(rect.w, rect.h) * 0.5f;
@@ -696,52 +721,52 @@ namespace ogfx
void BasicRenderer2D::generate_half_circle(const ostd::Vec2& center, const ostd::Vec2& dir, float radius, int segments, const ostd::Color& color)
{
- // Ensure we have room
- if (m_vertexCount + segments + 2 >= MaxVertices || m_indexCount + segments * 3 >= MaxIndices)
- flushBatch();
+ // Ensure we have room
+ if (m_vertexCount + segments + 2 >= MaxVertices || m_indexCount + segments * 3 >= MaxIndices)
+ flushBatch();
- SDL_FColor col = COLOR_CAST(color);
+ SDL_FColor col = COLOR_CAST(color);
- // Index of the center vertex
- int centerIndex = m_vertexCount;
+ // Index of the center vertex
+ int centerIndex = m_vertexCount;
- // Push center vertex
- m_vertices[m_vertexCount++] = {
- { center.x, center.y },
- col,
- { 0.0f, 0.0f },
- };
+ // Push center vertex
+ m_vertices[m_vertexCount++] = {
+ { center.x, center.y },
+ col,
+ { 0.0f, 0.0f },
+ };
- // Compute the base angle from the direction vector
- float baseAngle = std::atan2(dir.y, dir.x);
+ // Compute the base angle from the direction vector
+ float baseAngle = std::atan2(dir.y, dir.x);
- // Half circle spans 180 degrees
- float startAngle = baseAngle - M_PI * 0.5f;
- float endAngle = baseAngle + M_PI * 0.5f;
+ // Half circle spans 180 degrees
+ float startAngle = baseAngle - M_PI * 0.5f;
+ float endAngle = baseAngle + M_PI * 0.5f;
- // Generate arc vertices
- for (int i = 0; i <= segments; i++)
- {
- float t = float(i) / float(segments);
- float angle = startAngle + t * (endAngle - startAngle);
+ // Generate arc vertices
+ for (int i = 0; i <= segments; i++)
+ {
+ float t = float(i) / float(segments);
+ float angle = startAngle + t * (endAngle - startAngle);
- float x = center.x + std::cos(angle) * radius;
- float y = center.y + std::sin(angle) * radius;
+ float x = center.x + std::cos(angle) * radius;
+ float y = center.y + std::sin(angle) * radius;
- m_vertices[m_vertexCount++] = {
- { x, y },
- col,
- { 0.0f, 0.0f }
- };
- }
+ m_vertices[m_vertexCount++] = {
+ { x, y },
+ col,
+ { 0.0f, 0.0f }
+ };
+ }
- // Generate triangle fan indices
- for (int i = 0; i < segments; i++)
- {
- m_indices[m_indexCount++] = centerIndex;
- m_indices[m_indexCount++] = centerIndex + 1 + i;
- m_indices[m_indexCount++] = centerIndex + 2 + i;
- }
+ // Generate triangle fan indices
+ for (int i = 0; i < segments; i++)
+ {
+ m_indices[m_indexCount++] = centerIndex;
+ m_indices[m_indexCount++] = centerIndex + 1 + i;
+ m_indices[m_indexCount++] = centerIndex + 2 + i;
+ }
}
void BasicRenderer2D::generate_quarter_circle(const ostd::Vec2& center, float radius, float thickness, float startAngle, const ostd::Color& color, int segments)
@@ -752,11 +777,11 @@ namespace ogfx
float innerR = radius - half;
if (innerR < 0.0f)
- innerR = 0.0f;
+ innerR = 0.0f;
// Ensure capacity
if (m_vertexCount + (segments + 1) * 2 >= MaxVertices || m_indexCount + segments * 6 >= MaxIndices)
- flushBatch();
+ flushBatch();
SDL_FColor col = COLOR_CAST(color);
@@ -767,263 +792,263 @@ namespace ogfx
// Generate vertices: outer arc + inner arc
for (int i = 0; i <= segments; i++)
{
- float t = float(i) / float(segments);
- float angle = startAngle + t * (endAngle - startAngle);
+ float t = float(i) / float(segments);
+ float angle = startAngle + t * (endAngle - startAngle);
- float cs = std::cos(angle);
- float sn = std::sin(angle);
+ float cs = std::cos(angle);
+ float sn = std::sin(angle);
- // Outer arc
- m_vertices[m_vertexCount++] = {
- { center.x + cs * outerR, center.y + sn * outerR },
- col,
- { 0, 0 }
- };
+ // Outer arc
+ m_vertices[m_vertexCount++] = {
+ { center.x + cs * outerR, center.y + sn * outerR },
+ col,
+ { 0, 0 }
+ };
- // Inner arc
- m_vertices[m_vertexCount++] = {
- { center.x + cs * innerR, center.y + sn * innerR },
- col,
- { 0, 0 }
- };
+ // Inner arc
+ m_vertices[m_vertexCount++] = {
+ { center.x + cs * innerR, center.y + sn * innerR },
+ col,
+ { 0, 0 }
+ };
}
// Generate indices (triangle strip converted to triangles)
for (int i = 0; i < segments; i++)
{
- int o0 = base + i * 2;
- int i0 = o0 + 1;
- int o1 = o0 + 2;
- int i1 = o0 + 3;
+ int o0 = base + i * 2;
+ int i0 = o0 + 1;
+ int o1 = o0 + 2;
+ int i1 = o0 + 3;
- // Triangle 1
- m_indices[m_indexCount++] = o0;
- m_indices[m_indexCount++] = o1;
- m_indices[m_indexCount++] = i0;
+ // Triangle 1
+ m_indices[m_indexCount++] = o0;
+ m_indices[m_indexCount++] = o1;
+ m_indices[m_indexCount++] = i0;
- // Triangle 2
- m_indices[m_indexCount++] = o1;
- m_indices[m_indexCount++] = i1;
- m_indices[m_indexCount++] = i0;
+ // Triangle 2
+ m_indices[m_indexCount++] = o1;
+ m_indices[m_indexCount++] = i1;
+ m_indices[m_indexCount++] = i0;
}
}
void BasicRenderer2D::generate_circle_stroke(const ostd::Vec2& center, float radius, float thickness, const ostd::Color& color, int segments)
{
- float half = thickness * 0.5f;
+ float half = thickness * 0.5f;
- float outerR = radius + half;
- float innerR = radius - half;
- if (innerR < 0.0f)
- innerR = 0.0f;
+ float outerR = radius + half;
+ float innerR = radius - half;
+ if (innerR < 0.0f)
+ innerR = 0.0f;
- // Ensure capacity
- if (m_vertexCount + (segments + 1) * 2 >= MaxVertices || m_indexCount + segments * 6 >= MaxIndices)
- flushBatch();
+ // Ensure capacity
+ if (m_vertexCount + (segments + 1) * 2 >= MaxVertices || m_indexCount + segments * 6 >= MaxIndices)
+ flushBatch();
- SDL_FColor col = COLOR_CAST(color);
+ SDL_FColor col = COLOR_CAST(color);
- int base = m_vertexCount;
+ int base = m_vertexCount;
- // Generate vertices: outer arc + inner arc
- for (int i = 0; i <= segments; i++)
- {
- float t = float(i) / float(segments);
- float angle = t * (2.0f * M_PI);
+ // Generate vertices: outer arc + inner arc
+ for (int i = 0; i <= segments; i++)
+ {
+ float t = float(i) / float(segments);
+ float angle = t * (2.0f * M_PI);
- float cs = std::cos(angle);
- float sn = std::sin(angle);
+ float cs = std::cos(angle);
+ float sn = std::sin(angle);
- // Outer arc
- m_vertices[m_vertexCount++] = {
- { center.x + cs * outerR, center.y + sn * outerR },
- col,
- { 0, 0 }
- };
+ // Outer arc
+ m_vertices[m_vertexCount++] = {
+ { center.x + cs * outerR, center.y + sn * outerR },
+ col,
+ { 0, 0 }
+ };
- // Inner arc
- m_vertices[m_vertexCount++] = {
- { center.x + cs * innerR, center.y + sn * innerR },
- col,
- { 0, 0 }
- };
- }
+ // Inner arc
+ m_vertices[m_vertexCount++] = {
+ { center.x + cs * innerR, center.y + sn * innerR },
+ col,
+ { 0, 0 }
+ };
+ }
- // Generate indices (triangle strip → triangles)
- for (int i = 0; i < segments; i++)
- {
- int o0 = base + i * 2;
- int i0 = o0 + 1;
- int o1 = o0 + 2;
- int i1 = o0 + 3;
+ // Generate indices (triangle strip → triangles)
+ for (int i = 0; i < segments; i++)
+ {
+ int o0 = base + i * 2;
+ int i0 = o0 + 1;
+ int o1 = o0 + 2;
+ int i1 = o0 + 3;
- // Triangle 1
- m_indices[m_indexCount++] = o0;
- m_indices[m_indexCount++] = o1;
- m_indices[m_indexCount++] = i0;
+ // Triangle 1
+ m_indices[m_indexCount++] = o0;
+ m_indices[m_indexCount++] = o1;
+ m_indices[m_indexCount++] = i0;
- // Triangle 2
- m_indices[m_indexCount++] = o1;
- m_indices[m_indexCount++] = i1;
- m_indices[m_indexCount++] = i0;
- }
+ // Triangle 2
+ m_indices[m_indexCount++] = o1;
+ m_indices[m_indexCount++] = i1;
+ m_indices[m_indexCount++] = i0;
+ }
}
void BasicRenderer2D::generate_ellipse_stroke(const ostd::Vec2& center, float radiusX, float radiusY, float thickness, float startAngle, float endAngle, const ostd::Color& color, int segments)
{
- float half = thickness * 0.5f;
+ float half = thickness * 0.5f;
- float outerRX = radiusX + half;
- float outerRY = radiusY + half;
- float innerRX = std::max(0.0f, radiusX - half);
- float innerRY = std::max(0.0f, radiusY - half);
+ float outerRX = radiusX + half;
+ float outerRY = radiusY + half;
+ float innerRX = std::max(0.0f, radiusX - half);
+ float innerRY = std::max(0.0f, radiusY - half);
- // Ensure capacity
- if (m_vertexCount + (segments + 1) * 2 >= MaxVertices ||
- m_indexCount + segments * 6 >= MaxIndices)
- {
- flushBatch();
- }
+ // Ensure capacity
+ if (m_vertexCount + (segments + 1) * 2 >= MaxVertices ||
+ m_indexCount + segments * 6 >= MaxIndices)
+ {
+ flushBatch();
+ }
- SDL_FColor col = COLOR_CAST(color);
+ SDL_FColor col = COLOR_CAST(color);
- int base = m_vertexCount;
+ int base = m_vertexCount;
- float angleRange = endAngle - startAngle;
+ float angleRange = endAngle - startAngle;
- // Generate vertices: outer arc + inner arc
- for (int i = 0; i <= segments; i++)
- {
- float t = float(i) / float(segments);
- float angle = startAngle + t * angleRange;
+ // Generate vertices: outer arc + inner arc
+ for (int i = 0; i <= segments; i++)
+ {
+ float t = float(i) / float(segments);
+ float angle = startAngle + t * angleRange;
- float cs = std::cos(angle);
- float sn = std::sin(angle);
+ float cs = std::cos(angle);
+ float sn = std::sin(angle);
- // Outer arc
- m_vertices[m_vertexCount++] = {
- { center.x + cs * outerRX, center.y + sn * outerRY },
- col,
- { 0, 0 }
- };
+ // Outer arc
+ m_vertices[m_vertexCount++] = {
+ { center.x + cs * outerRX, center.y + sn * outerRY },
+ col,
+ { 0, 0 }
+ };
- // Inner arc
- m_vertices[m_vertexCount++] = {
- { center.x + cs * innerRX, center.y + sn * innerRY },
- col,
- { 0, 0 }
- };
- }
+ // Inner arc
+ m_vertices[m_vertexCount++] = {
+ { center.x + cs * innerRX, center.y + sn * innerRY },
+ col,
+ { 0, 0 }
+ };
+ }
- // Generate indices (triangle strip → triangles)
- for (int i = 0; i < segments; i++)
- {
- int o0 = base + i * 2;
- int i0 = o0 + 1;
- int o1 = o0 + 2;
- int i1 = o0 + 3;
+ // Generate indices (triangle strip → triangles)
+ for (int i = 0; i < segments; i++)
+ {
+ int o0 = base + i * 2;
+ int i0 = o0 + 1;
+ int o1 = o0 + 2;
+ int i1 = o0 + 3;
- // Triangle 1
- m_indices[m_indexCount++] = o0;
- m_indices[m_indexCount++] = o1;
- m_indices[m_indexCount++] = i0;
+ // Triangle 1
+ m_indices[m_indexCount++] = o0;
+ m_indices[m_indexCount++] = o1;
+ m_indices[m_indexCount++] = i0;
- // Triangle 2
- m_indices[m_indexCount++] = o1;
- m_indices[m_indexCount++] = i1;
- m_indices[m_indexCount++] = i0;
- }
+ // Triangle 2
+ m_indices[m_indexCount++] = o1;
+ m_indices[m_indexCount++] = i1;
+ m_indices[m_indexCount++] = i0;
+ }
}
void BasicRenderer2D::generate_filled_ellipse_stroke(const ostd::Vec2& center, float radiusX, float radiusY, float startAngle, const ostd::Color& color, int segments)
{
- // Ensure capacity
- if (m_vertexCount + segments + 2 >= MaxVertices || m_indexCount + segments * 3 >= MaxIndices)
- flushBatch();
+ // Ensure capacity
+ if (m_vertexCount + segments + 2 >= MaxVertices || m_indexCount + segments * 3 >= MaxIndices)
+ flushBatch();
- SDL_FColor col = COLOR_CAST(color);
+ SDL_FColor col = COLOR_CAST(color);
- int base = m_vertexCount;
+ int base = m_vertexCount;
- // Center vertex
- m_vertices[m_vertexCount++] = {
- { center.x, center.y },
- col,
- { 0, 0 }
- };
+ // Center vertex
+ m_vertices[m_vertexCount++] = {
+ { center.x, center.y },
+ col,
+ { 0, 0 }
+ };
- float endAngle = startAngle + M_PI * 0.5f;
+ float endAngle = startAngle + M_PI * 0.5f;
- for (int i = 0; i <= segments; i++)
- {
- float t = float(i) / float(segments);
- float angle = startAngle + t * (endAngle - startAngle);
+ for (int i = 0; i <= segments; i++)
+ {
+ float t = float(i) / float(segments);
+ float angle = startAngle + t * (endAngle - startAngle);
- float x = center.x + std::cos(angle) * radiusX;
- float y = center.y + std::sin(angle) * radiusY;
+ float x = center.x + std::cos(angle) * radiusX;
+ float y = center.y + std::sin(angle) * radiusY;
- m_vertices[m_vertexCount++] = {
- { x, y },
- col,
- { 0, 0 }
- };
- }
+ m_vertices[m_vertexCount++] = {
+ { x, y },
+ col,
+ { 0, 0 }
+ };
+ }
- // Triangle fan indices
- for (int i = 0; i < segments; i++)
- {
- m_indices[m_indexCount++] = base;
- m_indices[m_indexCount++] = base + 1 + i;
- m_indices[m_indexCount++] = base + 2 + i;
- }
+ // Triangle fan indices
+ for (int i = 0; i < segments; i++)
+ {
+ m_indices[m_indexCount++] = base;
+ m_indices[m_indexCount++] = base + 1 + i;
+ m_indices[m_indexCount++] = base + 2 + i;
+ }
}
void BasicRenderer2D::generate_filled_ellipse(const ostd::Vec2& center, float radiusX, float radiusY, const ostd::Color& color, int segments)
{
- // Ensure capacity
- if (m_vertexCount + segments + 2 >= MaxVertices || m_indexCount + segments * 3 >= MaxIndices)
- flushBatch();
+ // Ensure capacity
+ if (m_vertexCount + segments + 2 >= MaxVertices || m_indexCount + segments * 3 >= MaxIndices)
+ flushBatch();
- SDL_FColor col = COLOR_CAST(color);
+ SDL_FColor col = COLOR_CAST(color);
- int base = m_vertexCount;
+ int base = m_vertexCount;
- // Center vertex
- m_vertices[m_vertexCount++] = {
- { center.x, center.y },
- col,
- { 0, 0 }
- };
+ // Center vertex
+ m_vertices[m_vertexCount++] = {
+ { center.x, center.y },
+ col,
+ { 0, 0 }
+ };
- // Outer ring vertices
- for (int i = 0; i <= segments; i++)
- {
- float t = float(i) / float(segments);
- float angle = t * (2.0f * M_PI);
+ // Outer ring vertices
+ for (int i = 0; i <= segments; i++)
+ {
+ float t = float(i) / float(segments);
+ float angle = t * (2.0f * M_PI);
- float x = center.x + std::cos(angle) * radiusX;
- float y = center.y + std::sin(angle) * radiusY;
+ float x = center.x + std::cos(angle) * radiusX;
+ float y = center.y + std::sin(angle) * radiusY;
- m_vertices[m_vertexCount++] = {
- { x, y },
- col,
- { 0, 0 }
- };
- }
+ m_vertices[m_vertexCount++] = {
+ { x, y },
+ col,
+ { 0, 0 }
+ };
+ }
- // Triangle fan indices
- for (int i = 0; i < segments; i++)
- {
- m_indices[m_indexCount++] = base;
- m_indices[m_indexCount++] = base + 1 + i;
- m_indices[m_indexCount++] = base + 2 + i;
- }
+ // Triangle fan indices
+ for (int i = 0; i < segments; i++)
+ {
+ m_indices[m_indexCount++] = base;
+ m_indices[m_indexCount++] = base + 1 + i;
+ m_indices[m_indexCount++] = base + 2 + i;
+ }
}
void BasicRenderer2D::push_polygon(const ostd::Vec2* verts, const ostd::Vec2* texCoords, uint32_t vertCount, const uint32_t* inds, uint32_t indexCount, const ostd::Color& color, SDL_Texture* texture)
{
- if (!m_initialized || vertCount <= 0 || indexCount <= 0)
- return;
+ if (!m_initialized || vertCount <= 0 || indexCount <= 0)
+ return;
if (vertCount > MaxVertices || indexCount > MaxIndices)
{
@@ -1031,127 +1056,33 @@ namespace ogfx
return;
}
if (texture != m_texture)
- flushBatch();
- m_texture = texture;
- if (m_vertexCount + vertCount >= MaxVertices || m_indexCount + indexCount >= MaxIndices)
- flushBatch();
+ flushBatch();
+ m_texture = texture;
+ if (m_vertexCount + vertCount >= MaxVertices || m_indexCount + indexCount >= MaxIndices)
+ flushBatch();
- SDL_FColor col = COLOR_CAST(color);
+ SDL_FColor col = COLOR_CAST(color);
- int base = m_vertexCount;
+ int base = m_vertexCount;
bool hasTexCoords = texCoords != nullptr;
- for (int i = 0; i < vertCount; i++)
- {
+ for (int i = 0; i < vertCount; i++)
+ {
ostd::Vec2 tc { 0.0f, 0.0f };
if (hasTexCoords)
tc = texCoords[i];
- m_vertices[m_vertexCount++] = {
- { verts[i].x, verts[i].y },
- col,
- { tc.x, tc.y },
- };
- }
- for (int i = 0; i < indexCount; i++)
- m_indices[m_indexCount++] = base + inds[i];
+ m_vertices[m_vertexCount++] = {
+ { verts[i].x, verts[i].y },
+ col,
+ { tc.x, tc.y },
+ };
+ }
+ for (int i = 0; i < indexCount; i++)
+ m_indices[m_indexCount++] = base + inds[i];
}
void BasicRenderer2D::print_ttf_error(const ostd::String& funcName)
{
m_out.fg(ostd::ConsoleColors::Red).p(funcName).p("(...) failed: ").p(ostd::String(SDL_GetError())).reset().nl();
}
-
- void BasicRenderer2D::renderText(const ostd::String& message, int32_t x, int32_t y, const ostd::Color& color, int32_t fontSize)
- {
- if (!m_initialized)
- {
- set_error_state(tErrors::Uninitialized);
- return;
- }
- if (!m_fontOpen)
- {
- set_error_state(tErrors::NoFont);
- return;
- }
- if (m_font == nullptr)
- {
- set_error_state(tErrors::NullFont);
- return;
- }
- setFontSize(fontSize);
- SDL_Surface* surf = TTF_RenderText_Blended(m_font, message.c_str(), message.len(), { color.r, color.g, color.b, color.a });
- if (surf == nullptr)
- {
- set_error_state(tErrors::TTFRenderTextBlendedFail);
- print_ttf_error("TTF_RenderText_Blended");
- return;
- }
- auto renderer = m_window->getSDLRenderer();
- SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surf);
- if (texture == nullptr)
- {
- set_error_state(tErrors::TTFCreateTextureFromSurfaceFail);
- print_ttf_error("SDL_CreateTextureFromSurface");
- return;
- }
- SDL_DestroySurface(surf);
-
- float w = 0, h = 0;
- SDL_GetTextureSize(texture, &w, &h);
-
- SDL_FRect dst;
- dst.x = x;
- dst.y = y;
- dst.w = w;
- dst.h = h;
- SDL_RenderTexture(renderer, texture, NULL, &dst);
- SDL_DestroyTexture(texture);
- }
-
- void BasicRenderer2D::renderCenteredText(const ostd::String& message, int32_t center_x, int32_t center_y, const ostd::Color& color, int32_t fontSize)
- {
- if (!m_initialized)
- {
- set_error_state(tErrors::Uninitialized);
- return;
- }
- if (!m_fontOpen)
- {
- set_error_state(tErrors::NoFont);
- return;
- }
- if (m_font == nullptr)
- {
- set_error_state(tErrors::NullFont);
- return;
- }
- setFontSize(fontSize);
- auto renderer = m_window->getSDLRenderer();
- SDL_Surface* surf = TTF_RenderText_Blended(m_font, message.c_str(), message.len(), { color.r, color.g, color.b, color.a });
- if (surf == nullptr)
- {
- set_error_state(tErrors::TTFRenderTextBlendedFail);
- print_ttf_error("TTF_RenderText_Blended");
- return;
- }
- SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surf);
- if (texture == nullptr)
- {
- set_error_state(tErrors::TTFCreateTextureFromSurfaceFail);
- print_ttf_error("SDL_CreateTextureFromSurface");
- return;
- }
- SDL_DestroySurface(surf);
-
- float w = 0, h = 0;
- SDL_GetTextureSize(texture, &w, &h);
-
- SDL_FRect dst;
- dst.x = center_x - (w / 2);
- dst.y = center_y - (h / 2);
- dst.w = w;
- dst.h = h;
- SDL_RenderTexture(renderer, texture, NULL, &dst);
- SDL_DestroyTexture(texture);
- }
// ===================================================== PRIVATE HELPERS =====================================================
}
diff --git a/src/ogfx/render/BasicRenderer.hpp b/src/ogfx/render/BasicRenderer.hpp
index 0f49c1b..dfd5b0a 100644
--- a/src/ogfx/render/BasicRenderer.hpp
+++ b/src/ogfx/render/BasicRenderer.hpp
@@ -1,21 +1,21 @@
/*
- OmniaFramework - A collection of useful functionality
- Copyright (C) 2025 OmniaX-Dev
+ OmniaFramework - A collection of useful functionality
+ Copyright (C) 2025 OmniaX-Dev
- This file is part of OmniaFramework.
+ This file is part of OmniaFramework.
- OmniaFramework is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
+ OmniaFramework is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
- OmniaFramework 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 General Public License for more details.
+ OmniaFramework 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 General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with OmniaFramework. If not, see .
+ You should have received a copy of the GNU General Public License
+ along with OmniaFramework. If not, see .
*/
#pragma once
@@ -68,7 +68,7 @@ namespace ogfx
int32_t openFont(const ostd::String& fontPath, int32_t fontSize = 0);
int32_t openFont(const ostd::UByte resource_data[], uint32_t data_size, int32_t fontSize = 0);
int32_t setFontSize(int32_t fontSize);
- ostd::IPoint getStringDimensions(const ostd::String& message, int32_t fontSize = 0);
+ ostd::Vec2 getStringDimensions(const ostd::String& message, int32_t fontSize = 0, TTF_Font* font = nullptr);
inline uint32_t getDrawCallCount(void) { return m_drawCallCount; }
inline bool hasOpenFont(void) { return m_fontOpen; }
@@ -80,11 +80,13 @@ namespace ogfx
inline bool isInitialized(void) { return m_initialized; }
inline FontGlyphAtlas& getFontGlyphAtlas(void) { return m_fontGlyphAtlas; }
- void drawImage(const ogfx::Image& image, const ostd::Vec2& position, const ostd::Vec2& size = { 0, 0 }, const ostd::Rectangle& srcRect = { 0, 0, 0, 0 });
- void drawAnimation(const Animation& anim, const ostd::Vec2& position, const ostd::Vec2& size = { 0, 0 });
+ void drawImage(const ogfx::Image& image, const ostd::Vec2& position, const ostd::Vec2& size = { 0, 0 }, const ostd::Rectangle& srcRect = { 0, 0, 0, 0 });
+ void drawAnimation(const Animation& anim, const ostd::Vec2& position, const ostd::Vec2& size = { 0, 0 });
+
+ void drawString(const ostd::String& str, const ostd::Vec2& position, const ostd::Color& color, int32_t fontSize = 0, float scale = 1.0f);
+ void drawCenteredString(const ostd::String& str, const ostd::Vec2& center, const ostd::Color& color, int32_t fontSize = 0, float scale = 1.0f);
+ void drawCenteredString(const ostd::String& str, const ostd::Rectangle& bounds, const ostd::Color& color, int32_t fontSize = 0, float scale = 1.0f);
- void drawString(const ostd::String& str, const ostd::Vec2& position, const ostd::Color& color, int32_t fontSize = 0);
- void drawCenteredString(const ostd::String& str, const ostd::Vec2& center, const ostd::Color& color, int32_t fontSize = 0);
void drawLine(const ostd::FLine& line, const ostd::Color& color, int32_t thickness = 1, bool rounded = true);
@@ -106,7 +108,7 @@ namespace ogfx
void outlinedCircle(const ostd::Rectangle& rect, const ostd::Color& fillColor, const ostd::Color& outlineColor, int32_t outlineThickness = 1);
void outlinedEllipse(const ostd::Rectangle& rect, const ostd::Color& fillColor, const ostd::Color& outlineColor, int32_t outlineThickness = 1);
- public:
+ private:
void init_arrays(void);
void generate_half_circle(const ostd::Vec2& center, const ostd::Vec2& dir, float radius, int segments, const ostd::Color& color);
void generate_quarter_circle(const ostd::Vec2& center, float radius, float thickness, float startAngle, const ostd::Color& color, int segments);
@@ -117,8 +119,6 @@ namespace ogfx
void push_polygon(const ostd::Vec2* verts, const ostd::Vec2* texCoords, uint32_t vertCount, const uint32_t* inds, uint32_t indexCount, const ostd::Color& color, SDL_Texture* texture);
void print_ttf_error(const ostd::String& funcName);
inline int32_t set_error_state(int32_t err) { m_errorState = err; return m_errorState; }
- void renderText(const ostd::String& message, int32_t x, int32_t y, const ostd::Color& color, int32_t fontSize = 0);
- void renderCenteredText(const ostd::String& message, int32_t center_x, int32_t center_y, const ostd::Color& color, int32_t fontSize = 0);
public:
inline static constexpr int32_t MaxVertices { 8192 };
diff --git a/src/ogfx/render/FontGlyphAtlas.cpp b/src/ogfx/render/FontGlyphAtlas.cpp
index d11446b..529486d 100644
--- a/src/ogfx/render/FontGlyphAtlas.cpp
+++ b/src/ogfx/render/FontGlyphAtlas.cpp
@@ -23,6 +23,7 @@
#include
#include
#include
+#include
namespace ogfx
{
@@ -37,45 +38,67 @@ namespace ogfx
return *this;
}
- bool FontGlyphAtlas::rasterize_glyph(const ostd::String& glyphStr, TTF_Font* font, uint32_t fontSize)
+ const std::vector FontGlyphAtlas::processString(const ostd::String& str, TTF_Font* font, uint32_t fontSize)
+ {
+ if (m_currentAtlasCount <= 0)
+ return {};
+
+ // Pass 1: ensure all glyphs are rasterized (map may rehash here)
+ for (auto& c : str)
+ {
+ const GlyphInfo* dummy;
+ if (!rasterize_glyph(ostd::String("").addChar(c), font, fontSize, &dummy))
+ return {};
+ }
+
+ // Pass 2: collect stable pointers (no more insertions, no rehash risk)
+ std::vector glyphs;
+ glyphs.reserve(str.len());
+ for (auto& c : str)
+ {
+ auto cps = ostd::String("").addChar(c).getUTF8Codepoints();
+ if (cps.size() != 1) return {};
+ GlyphKey key { cps[0], uint64_t(font), fontSize };
+ glyphs.push_back(&m_uvs[key]);
+ }
+ return glyphs;
+ }
+
+ bool FontGlyphAtlas::rasterize_glyph(const ostd::String& glyphStr, TTF_Font* font, uint32_t fontSize, const GlyphInfo** outGlyph)
{
if (!font || m_currentAtlasCount <= 0)
return false;
- // 1. Extract codepoint from the ostd::String
auto cps = glyphStr.getUTF8Codepoints();
if (cps.size() != 1)
- return false; // must be exactly one character
+ return false;
uint32_t codepoint = cps[0];
GlyphKey key { codepoint, uint64_t(font), fontSize };
- if (auto it = m_uvs.find(key) != m_uvs.end())
+ auto it = m_uvs.find(key);
+ if (it != m_uvs.end())
+ {
+ *outGlyph = &(it->second);
return true;
+ }
- // 2. Convert the ostd::String to UTF-8 for SDL_ttf
- std::string utf8 = glyphStr.cpp_str();
-
- // 3. Rasterize glyph using SDL_ttf
- SDL_Surface* surf = TTF_RenderText_Blended(font, utf8.c_str(), utf8.length(), SDL_Color{255, 255, 255, 255});
-
+ SDL_Surface* surf = TTF_RenderText_Blended(font, glyphStr.c_str(), glyphStr.len(), SDL_Color{255, 255, 255, 255});
if (!surf)
return false;
- // SDL_Surface* converted = SDL_ConvertSurface(surf, SDL_PIXELFORMAT_RGBA32);
- // SDL_DestroySurface(surf);
- // surf = converted;
-
int gw = surf->w;
int gh = surf->h;
- // 4. Get current atlas
+ int minx, maxx, miny, maxy, advance;
+ TTF_GetGlyphMetrics(font, codepoint, &minx, &maxx, &miny, &maxy, &advance);
+
+ GlyphInfo glyph;
+ glyph.advance = advance;
+ glyph.codepoint = codepoint;
+
SDL_Texture* atlas = m_atlases[m_currentAtlasCount - 1];
- // SDL_Log("atlas format = %s", SDL_GetPixelFormatName(atlas->format));
- // SDL_Log("surf format = %s", SDL_GetPixelFormatName(surf->format));
-
- // 5. Shelf packing: move to next row if needed
if (m_penX + gw > int(AtlasTextureDimension))
{
m_penX = 0;
@@ -83,13 +106,12 @@ namespace ogfx
m_rowHeight = 0;
}
- // 6. If still doesn't fit → create new atlas
if (m_penY + gh > int(AtlasTextureDimension))
{
SDL_DestroySurface(surf);
if (!create_new_atlas())
- return false; // no more atlases available
+ return false;
atlas = m_atlases[m_currentAtlasCount - 1];
m_penX = 0;
@@ -97,74 +119,28 @@ namespace ogfx
m_rowHeight = 0;
}
- uint8_t* pixels = (uint8_t*)surf->pixels;
- int pitch = surf->pitch;
-
- // for (int y = 0; y < surf->h; y++)
- // {
- // uint32_t* row = (uint32_t*)(pixels + y * pitch);
-
- // for (int x = 0; x < surf->w; x++)
- // {
- // uint32_t px = row[x];
-
- // uint8_t a = (px >> 24) & 0xFF; // ABGR → A is highest byte
- // uint8_t r = (px >> 16) & 0xFF;
- // uint8_t g = (px >> 8) & 0xFF;
- // uint8_t b = (px) & 0xFF;
-
- // std::cout << (int)r << "," << (int)g << "," << (int)b << "," << (int)a << " ";
-
- // row[x] = (a << 24) | (r << 16) | (g << 8) | b;
- // }
- // }
-
- // 7. Upload glyph bitmap into atlas
SDL_Rect dstRect { m_penX, m_penY, gw, gh };
-
SDL_LockSurface(surf);
SDL_UpdateTexture(atlas, &dstRect, surf->pixels, surf->pitch);
SDL_UnlockSurface(surf);
SDL_DestroySurface(surf);
- // void* lockedPixels = nullptr;
- // int lockedPitch = 0;
- // if (SDL_LockTexture(atlas, &dstRect, &lockedPixels, &lockedPitch))
- // {
- // SDL_LockSurface(surf);
- // uint8_t* src = (uint8_t*)surf->pixels;
- // uint8_t* dst = (uint8_t*)lockedPixels;
- // for (int y = 0; y < gh; y++)
- // {
- // memcpy(dst, src, gw * 4); // 4 bytes per pixel (ARGB8888)
- // src += surf->pitch;
- // dst += lockedPitch;
- // }
- // SDL_UnlockSurface(surf);
- // SDL_UnlockTexture(atlas);
- // }
- // // SDL_UpdateTexture(atlas, &dstRect, surf->pixels, surf->pitch);
- // SDL_DestroySurface(surf);
-
- // 8. Compute UVs
float u0 = float(m_penX) / float(AtlasTextureDimension);
float v0 = float(m_penY) / float(AtlasTextureDimension);
float u1 = float(m_penX + gw) / float(AtlasTextureDimension);
float v1 = float(m_penY + gh) / float(AtlasTextureDimension);
- GlyphUV uv;
- uv.atlas = atlas;
- uv.uvs[0] = { u0, v0 };
- uv.uvs[1] = { u1, v0 };
- uv.uvs[2] = { u1, v1 };
- uv.uvs[3] = { u0, v1 };
- uv.size = { static_cast(gw), static_cast(gh) };
+ glyph.atlas = atlas;
+ glyph.uvs[0] = { u0, v0 };
+ glyph.uvs[1] = { u1, v0 };
+ glyph.uvs[2] = { u1, v1 };
+ glyph.uvs[3] = { u0, v1 };
+ glyph.size = { static_cast(gw), static_cast(gh) };
- // 9. Store in hashmap
- m_uvs[key] = uv;
+ m_uvs[key] = glyph;
+ *outGlyph = &(m_uvs[key]);
- // 10. Advance shelf packing cursor
m_penX += gw;
m_rowHeight = std::max(m_rowHeight, gh);
return true;
@@ -172,7 +148,6 @@ namespace ogfx
bool FontGlyphAtlas::create_new_atlas(void)
{
- // 1. Check if we can create another atlas
if (m_currentAtlasCount >= int(MaxAtlasCount))
return false;
@@ -183,18 +158,15 @@ namespace ogfx
if (!sdlRenderer)
return false;
- // 2. Create the texture
- SDL_Texture* tex = SDL_CreateTexture(sdlRenderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_STREAMING, AtlasTextureDimension, AtlasTextureDimension);
-
+ SDL_Texture* tex = SDL_CreateTexture(sdlRenderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, AtlasTextureDimension, AtlasTextureDimension);
if (!tex)
return false;
- // 3. Set blend mode (important for alpha glyphs)
SDL_SetTextureBlendMode(tex, SDL_BLENDMODE_BLEND);
SDL_SetTextureScaleMode(tex, SDL_SCALEMODE_LINEAR);
- // 4. Clear the texture to transparent
- {
+
+ { // Clear the texture to transparent
void* pixels = nullptr;
int pitch = 0;
if (SDL_LockTexture(tex, nullptr, &pixels, &pitch) == 0)
@@ -204,52 +176,13 @@ namespace ogfx
}
}
- // 5. Store the atlas
m_atlases[m_currentAtlasCount] = tex;
m_currentAtlasCount++;
- // 6. Reset packing cursor for this new atlas
m_penX = 0;
m_penY = 0;
m_rowHeight = 0;
return true;
}
-
- void FontGlyphAtlas::save_atlas_to_png(SDL_Renderer* renderer, SDL_Texture* atlas, const char* filename)
- {
- // 1. Get texture dimensions and format
- float w, h;
- SDL_GetTextureSize(atlas, &w, &h);
- int iw = (int)w, ih = (int)h;
-
- // 2. Create a target surface
- SDL_Surface* surf = SDL_CreateSurface(iw, ih, SDL_PIXELFORMAT_ARGB8888);
- if (!surf) return;
-
- // 3. Read pixels back from the texture
- SDL_Rect rect { 0, 0, iw, ih };
-
- // For STREAMING textures, lock/read directly:
- void* pixels = nullptr;
- int pitch = 0;
- if (SDL_LockTexture(atlas, nullptr, &pixels, &pitch))
- {
- uint8_t* src = (uint8_t*)pixels;
- uint8_t* dst = (uint8_t*)surf->pixels;
- for (int y = 0; y < ih; y++)
- {
- memcpy(dst, src, iw * 4);
- src += pitch;
- dst += surf->pitch;
- }
- SDL_UnlockTexture(atlas);
- }
-
- // 4. Save
- IMG_SavePNG(surf, filename); // needs SDL3_image
- // or: SDL_SaveBMP(surf, "atlas_debug.bmp");
-
- SDL_DestroySurface(surf);
- }
}
diff --git a/src/ogfx/render/FontGlyphAtlas.hpp b/src/ogfx/render/FontGlyphAtlas.hpp
index 748cdfa..a090e8d 100644
--- a/src/ogfx/render/FontGlyphAtlas.hpp
+++ b/src/ogfx/render/FontGlyphAtlas.hpp
@@ -29,11 +29,13 @@ namespace ogfx
class BasicRenderer2D;
class FontGlyphAtlas
{
- public: struct GlyphUV
+ public: struct GlyphInfo
{
ostd::Vec2 uvs[4];
SDL_Texture* atlas { nullptr };
ostd::Vec2 size;
+ uint32_t codepoint { 0 };
+ int32_t advance { 0 };
};
struct GlyphKey
{
@@ -62,18 +64,18 @@ namespace ogfx
inline FontGlyphAtlas(void) { }
inline FontGlyphAtlas(BasicRenderer2D& renderer) { init(renderer); }
FontGlyphAtlas init(BasicRenderer2D& renderer);
+ const std::vector processString(const ostd::String& str, TTF_Font* font, uint32_t fontSize);
- public:
- bool rasterize_glyph(const ostd::String& glyphStr, TTF_Font* font, uint32_t fontSize);
+ private:
+ bool rasterize_glyph(const ostd::String& glyphStr, TTF_Font* font, uint32_t fontSize, const GlyphInfo** outGlyph);
bool create_new_atlas(void);
- void save_atlas_to_png(SDL_Renderer* renderer, SDL_Texture* atlas, const char* filename);
public:
inline static constexpr uint32_t AtlasTextureDimension { 8192 };
inline static constexpr uint32_t MaxAtlasCount { 16 };
public:
- std::unordered_map m_uvs;
+ std::unordered_map m_uvs;
SDL_Texture* m_atlases[FontGlyphAtlas::MaxAtlasCount];
int32_t m_currentAtlasCount { 0 };
BasicRenderer2D* m_renderer { nullptr };
diff --git a/src/test/GuiTest.cpp b/src/test/GuiTest.cpp
index b736ea5..0787c06 100644
--- a/src/test/GuiTest.cpp
+++ b/src/test/GuiTest.cpp
@@ -1,21 +1,21 @@
/*
- OmniaFramework - A collection of useful functionality
- Copyright (C) 2025 OmniaX-Dev
+ OmniaFramework - A collection of useful functionality
+ Copyright (C) 2025 OmniaX-Dev
- This file is part of OmniaFramework.
+ This file is part of OmniaFramework.
- OmniaFramework is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
+ OmniaFramework is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
- OmniaFramework 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 General Public License for more details.
+ OmniaFramework 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 General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with OmniaFramework. If not, see .
+ You should have received a copy of the GNU General Public License
+ along with OmniaFramework. If not, see .
*/
#include "gui/Events.hpp"
@@ -25,42 +25,39 @@
ostd::ConsoleOutputHandler out;
-void drawTexturedQuad(SDL_Renderer* renderer,
- SDL_Texture* tex,
- const ostd::Rectangle& rect,
- const ostd::Vec2 uvs[4])
+void drawTexturedQuad(SDL_Renderer* renderer, SDL_Texture* tex, const ostd::Rectangle& rect, const ostd::Vec2 uvs[4])
{
- SDL_Vertex verts[4];
+ SDL_Vertex verts[4];
- // Expand rectangle into quad corners
- const float x = rect.x;
- const float y = rect.y;
- const float w = rect.w;
- const float h = rect.h;
+ // Expand rectangle into quad corners
+ const float x = rect.x;
+ const float y = rect.y;
+ const float w = rect.w;
+ const float h = rect.h;
- const ostd::Vec2 quad[4] = {
- { x, y },
- { x + w, y },
- { x + w, y + h },
- { x, y + h }
- };
+ const ostd::Vec2 quad[4] = {
+ { x, y },
+ { x + w, y },
+ { x + w, y + h },
+ { x, y + h }
+ };
- for (int i = 0; i < 4; i++)
- {
- verts[i].position.x = quad[i].x;
- verts[i].position.y = quad[i].y;
+ for (int i = 0; i < 4; i++)
+ {
+ verts[i].position.x = quad[i].x;
+ verts[i].position.y = quad[i].y;
- verts[i].tex_coord.x = uvs[i].x;
- verts[i].tex_coord.y = uvs[i].y;
+ verts[i].tex_coord.x = uvs[i].x;
+ verts[i].tex_coord.y = uvs[i].y;
- auto crim = ostd::Colors::Crimson.getNormalizedColor();
- verts[i].color = SDL_FColor{crim.r, crim.g, crim.b, crim.a};
- }
+ auto crim = ostd::Colors::Crimson.getNormalizedColor();
+ verts[i].color = SDL_FColor{crim.r, crim.g, crim.b, crim.a};
+ }
- // Two triangles: (0,1,2) and (2,3,0)
- const int indices[6] = { 0, 1, 2, 2, 3, 0 };
+ // Two triangles: (0,1,2) and (2,3,0)
+ const int indices[6] = { 0, 1, 2, 2, 3, 0 };
- SDL_RenderGeometry(renderer, tex, verts, 4, indices, 6);
+ SDL_RenderGeometry(renderer, tex, verts, 4, indices, 6);
}
@@ -105,14 +102,14 @@ class Window : public ogfx::gui::Window
m_label1.setMouseScrolledCallback([&](const ogfx::gui::Event& event) -> void {
std::cout << "SCROLL!\n";
});
- // addWidget(m_label1);
+ addWidget(m_label1);
m_panel2.addChild(m_panel1);
- // addWidget(m_panel2);
+ addWidget(m_panel2);
m_check1.setPosition(30, 30);
m_check1.setText("Check this out!");
- // addWidget(m_check1);
+ addWidget(m_check1);
m_label2.setPosition(0, 0);
m_label2.setText("Ciccia Bella!");
@@ -156,63 +153,7 @@ class Window : public ogfx::gui::Window
m_panel1.addChild(m_label3);
setTheme(m_theme);
-
- m_gfx.setFontSize(fs);
- m_gfx.getFontGlyphAtlas().rasterize_glyph(ostd::String("").addChar('H'), m_gfx.getSDLFont(), fs);
- m_gfx.getFontGlyphAtlas().rasterize_glyph(ostd::String("").addChar('e'), m_gfx.getSDLFont(), fs);
- m_gfx.getFontGlyphAtlas().rasterize_glyph(ostd::String("").addChar('l'), m_gfx.getSDLFont(), fs);
- m_gfx.getFontGlyphAtlas().rasterize_glyph(ostd::String("").addChar('o'), m_gfx.getSDLFont(), fs);
-
- // int32_t count = 0;
- // for (char c = 'A'; c <= 'Z'; c++)
- // {
- // for (int32_t i = 0; i < 200; i++)
- // {
- // count++;
- // uint32_t _fs = ostd::Random::getui32(8, 200);
- // m_gfx.setFontSize(_fs);
- // m_gfx.getFontGlyphAtlas().rasterize_glyph(ostd::String("").addChar(c), m_gfx.getSDLFont(), _fs);
- // }
- // }
- // for (char c = '0'; c <= '9'; c++)
- // {
- // for (int32_t i = 0; i < 200; i++)
- // {
- // count++;
- // uint32_t _fs = ostd::Random::getui32(8, 200);
- // m_gfx.setFontSize(_fs);
- // m_gfx.getFontGlyphAtlas().rasterize_glyph(ostd::String("").addChar(c), m_gfx.getSDLFont(), _fs);
- // }
- // }
- // for (char c = 'a'; c <= 'z'; c++)
- // {
- // count++;
- // for (int32_t i = 0; i < 200; i++)
- // {
- // uint32_t _fs = ostd::Random::getui32(8, 200);
- // m_gfx.setFontSize(_fs);
- // m_gfx.getFontGlyphAtlas().rasterize_glyph(ostd::String("").addChar(c), m_gfx.getSDLFont(), _fs);
- // }
- // }
- // for (char c = '0'; c <= '9'; c++)
- // {
- // for (int32_t i = 0; i < 200; i++)
- // {
- // count++;
- // uint32_t _fs = ostd::Random::getui32(8, 200);
- // m_gfx.setFontSize(_fs);
- // m_gfx.getFontGlyphAtlas().rasterize_glyph(ostd::String("").addChar(c), m_gfx.getSDLFont(), _fs);
- // }
- // }
-
- // for (int32_t i = 0; i < m_gfx.getFontGlyphAtlas().m_currentAtlasCount; i++)
- // {
- // ostd::String file("./atlas");
- // file.add(i).add(".png");
- // m_gfx.getFontGlyphAtlas().save_atlas_to_png(m_gfx.getSDLRenderer(), m_gfx.getFontGlyphAtlas().m_atlases[i], file);
- // }
- // std::cout << "Atlas cound: " << (int)count << "\n";
- }
+ }
inline void onSignal(ostd::Signal& signal) override
{
@@ -226,39 +167,6 @@ class Window : public ogfx::gui::Window
void onRedraw(ogfx::BasicRenderer2D& gfx) override
{
- // auto l_rndPoint = [&](void) -> ostd::FPoint {
- // using rnd = ostd::Random;
- // return rnd::getVec2({ 0, (float)getWindowWidth() / 2 }, { 0, (float)getWindowHeight() / 2 });
- // };
- // for (int32_t i = 0; i < 1000; i++)
- // gfx.drawLine({ l_rndPoint(), l_rndPoint() }, ostd::Colors::Crimson, 10);
- // gfx.drawLine({ { 10, 10 }, { 200, 100 } }, ostd::Colors::Crimson, 3);
- // gfx.drawRect({ 50, 50, 200, 120 }, ostd::Colors::Aquamarine, 1);
-
- // std::cout << (int)(gfx.getDrawCallCount() + 1) << "\n";
-
- gfx.endFrame();
-
- auto l_renderGlyph = [&](char c, float x, float y, float scale = 1) -> ostd::Vec2 {
- auto& tex = m_gfx.getFontGlyphAtlas().m_atlases[0];
- auto glyph = m_gfx.getFontGlyphAtlas().m_uvs[ { static_cast(c), (uint64_t)gfx.getSDLFont(), fs }];
- drawTexturedQuad(getSDLRenderer(), tex, { x, y, glyph.size.x * scale, glyph.size.y * scale }, glyph.uvs);
- return glyph.size;
- };
- auto l_renderString = [&](const ostd::String& str, const ostd::Vec2& pos, float scale = 1.0f) -> void {
- float x = pos.x;
- float y = pos.y;
- for (auto& c : str)
- {
- auto size = l_renderGlyph(c, x, y, scale);
- x += (size.x * scale);
- }
- };
-
- l_renderString("Hello", { 10, 140 });
-
- gfx.renderText("Hello", 10, 100, ostd::Colors::Crimson, fs);
-
}
private:
@@ -270,7 +178,6 @@ class Window : public ogfx::gui::Window
ogfx::gui::widgets::CheckBox m_check1 { *this };
ostd::Stylesheet m_theme;
ostd::Vec2 pos { 0, 0 };
- uint32_t fs = 30;
};
int main(int argc, char** argv)