Added a null texture slot to FontGlyphAtlas

This commit is contained in:
OmniaX-Dev 2026-04-19 22:15:51 +02:00
parent a20e5849f5
commit 0cf7478c24
8 changed files with 268 additions and 227 deletions

View file

@ -24,6 +24,7 @@ Add theme caching
Implement cursors in Stylesheet
FIX: Window getting exponentially bigger when Desktop scale changes
FIX: Refreshing scroll when content extent changes
Add Dark Mode Default theme

View file

@ -133,6 +133,111 @@ namespace ogfx
default: break;
}
}
TabPanel& TabPanel::create(void)
{
setPadding({ 0, 0, 0, 0 });
setTypeName("ogfx::gui::widgets::TabPanel");
disableDrawBox();
disableFocus();
enableStopEvents();
validate();
return *this;
}
void TabPanel::applyTheme(const ostd::Stylesheet& theme)
{
}
void TabPanel::onMouseReleased(const Event& event)
{
}
void TabPanel::onDraw(ogfx::BasicRenderer2D& gfx)
{
}
Panel& TabPanel::addTab(const String& title)
{
m_tabs.push_back(std::make_unique<Panel>(getWindow()));
auto& tab = *m_tabs.back();
// Initialization code here
return tab;
}
bool TabPanel::removeTab(Panel& tab)
{
auto it = std::find_if(m_tabs.begin(), m_tabs.end(), [&](const std::unique_ptr<Panel>& p) { return p.get() == &tab; });
if (it == m_tabs.end())
return false;
if (m_currentTab == it->get())
prepare_for_current_tab_removal();
m_tabs.erase(it);
return true;
}
bool TabPanel::removeTab(i32 index)
{
if (index < 0 || index >= (i32)m_tabs.size())
return false;
auto it = std::find_if(m_tabs.begin(), m_tabs.end(), [&](const std::unique_ptr<Panel>& p) { return p.get() == m_currentTab; });
if (std::distance(m_tabs.begin(), it) == index)
prepare_for_current_tab_removal();
m_tabs.erase(m_tabs.begin() + index);
return true;
}
bool TabPanel::removeTab(const String& title)
{
auto it = std::find_if(m_tabs.begin(), m_tabs.end(), [&](const std::unique_ptr<Panel>& p) { return p->getTitle() == title; });
if (it == m_tabs.end())
return false;
m_tabs.erase(it);
return true;
}
bool TabPanel::setCurrentTab(Panel& tab)
{
auto it = std::find_if(m_tabs.begin(), m_tabs.end(), [&](const std::unique_ptr<Panel>& p) { return p.get() == &tab; });
if (it == m_tabs.end())
return false;
m_currentTab = it->get();
return true;
}
bool TabPanel::setCurrentTab(i32 index)
{
if (index < 0 || index >= (i32)m_tabs.size())
return false;
m_currentTab = m_tabs[index].get();
return true;
}
void TabPanel::prepare_for_current_tab_removal(void)
{
if (m_currentTab == nullptr)
return;
if (m_tabs.size() < 2)
{
m_currentTab = nullptr;
return;
}
auto it = std::find_if(m_tabs.begin(), m_tabs.end(), [&](const std::unique_ptr<Panel>& p) { return p.get() == m_currentTab; });
if (it == m_tabs.end())
return; // shouldn't happen but defensive
if (it != m_tabs.begin())
m_currentTab = (it - 1)->get(); // tab to the left
else
m_currentTab = (it + 1)->get(); // first tab being removed, go right
}
}
}
}

View file

@ -70,6 +70,30 @@ namespace ogfx
Color m_titlebarBorderColor { Colors::Black };
i32 m_titleTextAlign { 0 };
};
class TabPanel : public Widget
{
public:
inline TabPanel(WindowCore& window) : Widget({ 0, 0, 0, 0 }, window) { create(); }
TabPanel& create(void);
void applyTheme(const ostd::Stylesheet& theme) override;
void onMouseReleased(const Event& event) override;
void onDraw(ogfx::BasicRenderer2D& gfx) override;
Panel& addTab(const String& title);
bool removeTab(Panel& tab);
bool removeTab(i32 index);
bool removeTab(const String& title);
bool setCurrentTab(Panel& tab);
bool setCurrentTab(i32 index);
private:
void prepare_for_current_tab_removal(void);
private:
stdvec<std::unique_ptr<Panel>> m_tabs;
Panel* m_currentTab { nullptr };
f32 m_tabBarHeight { 30 };
};
}
}
}

View file

@ -66,6 +66,9 @@ namespace ogfx
loadDefaultFont(20);
m_initialized = false;
m_fontGlyphAtlas.init(*this);
auto atlas = m_fontGlyphAtlas.getAtlas(0);
if (atlas) //TODO: Error
m_lastUsedGlyphAtlasTex = atlas;
m_initialized = true;
return set_error_state(tErrors::NoError);
}
@ -89,6 +92,7 @@ namespace ogfx
void BasicRenderer2D::endFrame(void)
{
flushBatch();
m_lastFrameDrawCallCount = m_drawCallCount;
m_drawCallCount = 0;
}
@ -309,7 +313,7 @@ namespace ogfx
// 5. Push quad
u32 inds[6] = QUAD_INDICES_ARR;
push_polygon(verts, uvs, 4, inds, 6, Colors::White, tex);
push_polygon(verts, uvs, 4, inds, 6, Colors::White, tex, false);
}
void BasicRenderer2D::drawAnimation(const Animation& anim, const Vec2& position, const Vec2& size)
@ -416,8 +420,8 @@ namespace ogfx
{ x, y + g->size.y * scale }
};
u32 inds[6] = QUAD_INDICES_ARR;
push_polygon(verts, g->uvs, 4, inds, 6, color, g->atlas);
push_polygon(verts, g->uvs, 4, inds, 6, color, g->atlas, false);
m_lastUsedGlyphAtlasTex = g->atlas;
x += (g->advance * scale);
}
setFontSize(oldFontSize);
@ -462,14 +466,14 @@ namespace ogfx
}};
std::array<u32, 6> inds = QUAD_INDICES_ARR;
push_polygon(verts.data(), nullptr, 4, inds.data(), 6, color, nullptr);
push_polygon(verts.data(), nullptr, 4, inds.data(), 6, color, nullptr, true);
if (!rounded || thickness < 4)
return;
i32 segments = std::max(16, i32(thickness * 1.5f));
generate_half_circle(p1, -dir, half, segments, color);
generate_half_circle(p2, dir, half, segments, color);
generate_half_circle(p1, -dir, half, segments, color, true);
generate_half_circle(p2, dir, half, segments, color, true);
}
void BasicRenderer2D::drawRect(const Rectangle& rect, const Color& color, i32 thickness)
@ -546,10 +550,10 @@ namespace ogfx
// Corner arcs
auto segments = [](f32 r) -> i32 { return std::max(16, i32(r * 1.5f)); };
if (rTL > 0) generate_ellipse_stroke({ x1 + rTL, y1 + rTL }, rTL, rTL, thickness, M_PI, M_PI * 1.5f, color, segments(rTL));
if (rTR > 0) generate_ellipse_stroke({ x2 - rTR, y1 + rTR }, rTR, rTR, thickness, M_PI * 1.5f, M_PI * 2.0f, color, segments(rTR));
if (rBR > 0) generate_ellipse_stroke({ x2 - rBR, y2 - rBR }, rBR, rBR, thickness, 0.0f, M_PI * 0.5f, color, segments(rBR));
if (rBL > 0) generate_ellipse_stroke({ x1 + rBL, y2 - rBL }, rBL, rBL, thickness, M_PI * 0.5f, M_PI, color, segments(rBL));
if (rTL > 0) generate_ellipse_stroke({ x1 + rTL, y1 + rTL }, rTL, rTL, thickness, M_PI, M_PI * 1.5f, color, segments(rTL), true);
if (rTR > 0) generate_ellipse_stroke({ x2 - rTR, y1 + rTR }, rTR, rTR, thickness, M_PI * 1.5f, M_PI * 2.0f, color, segments(rTR), true);
if (rBR > 0) generate_ellipse_stroke({ x2 - rBR, y2 - rBR }, rBR, rBR, thickness, 0.0f, M_PI * 0.5f, color, segments(rBR), true);
if (rBL > 0) generate_ellipse_stroke({ x1 + rBL, y2 - rBL }, rBL, rBL, thickness, M_PI * 0.5f, M_PI, color, segments(rBL), true);
}
void BasicRenderer2D::drawCircle(const Vec2& center, f32 radius, const Color& color, i32 thickness)
@ -557,7 +561,7 @@ namespace ogfx
if (!m_initialized || thickness <= 0)
return;
i32 segments = std::max(16, i32(radius * 1.5f));
generate_ellipse_stroke(center, radius, radius, thickness, 0.0f, 2.0f * M_PI, color, segments);
generate_ellipse_stroke(center, radius, radius, thickness, 0.0f, 2.0f * M_PI, color, segments, true);
}
void BasicRenderer2D::drawCircle(const Rectangle& rect, const Color& color, i32 thickness)
@ -583,7 +587,7 @@ namespace ogfx
f32 rx = rect.w * 0.5f;
f32 ry = rect.h * 0.5f;
i32 segments = std::max(16, i32(std::max(rx, ry) * 1.5f));
generate_ellipse_stroke(center, rx, ry, thickness, 0.0f, 2.0f * M_PI, color, segments);
generate_ellipse_stroke(center, rx, ry, thickness, 0.0f, 2.0f * M_PI, color, segments, true);
}
void BasicRenderer2D::drawTriangle(const Triangle& tri, const Color& color, i32 thickness)
@ -622,7 +626,7 @@ namespace ogfx
{ x1, y2 }
};
u32 inds[6] = QUAD_INDICES_ARR;
push_polygon(verts, nullptr, 4, inds, 6, color, nullptr);
push_polygon(verts, nullptr, 4, inds, 6, color, nullptr, true);
}
void BasicRenderer2D::fillRect(const Vec2& center, const Vec2& size, const Color& color)
@ -676,7 +680,7 @@ namespace ogfx
{ x1 + leftInset, y2 - bottomInset }
};
u32 inds[6] = QUAD_INDICES_ARR;
push_polygon(verts, nullptr, 4, inds, 6, color, nullptr);
push_polygon(verts, nullptr, 4, inds, 6, color, nullptr, true);
}
// Top strip
@ -688,7 +692,7 @@ namespace ogfx
{ x1 + rTL, y1 + topInset }
};
u32 inds[6] = QUAD_INDICES_ARR;
push_polygon(verts, nullptr, 4, inds, 6, color, nullptr);
push_polygon(verts, nullptr, 4, inds, 6, color, nullptr, true);
}
// Bottom strip
@ -700,7 +704,7 @@ namespace ogfx
{ x1 + rBL, y2 }
};
u32 inds[6] = QUAD_INDICES_ARR;
push_polygon(verts, nullptr, 4, inds, 6, color, nullptr);
push_polygon(verts, nullptr, 4, inds, 6, color, nullptr, true);
}
// Left strip
@ -712,7 +716,7 @@ namespace ogfx
{ x1, y2 - rBL }
};
u32 inds[6] = QUAD_INDICES_ARR;
push_polygon(verts, nullptr, 4, inds, 6, color, nullptr);
push_polygon(verts, nullptr, 4, inds, 6, color, nullptr, true);
}
// Right strip
@ -724,7 +728,7 @@ namespace ogfx
{ x2 - rightInset, y2 - rBR }
};
u32 inds[6] = QUAD_INDICES_ARR;
push_polygon(verts, nullptr, 4, inds, 6, color, nullptr);
push_polygon(verts, nullptr, 4, inds, 6, color, nullptr, true);
}
// Fill gaps between corners and strips when radii differ on the same side
@ -738,7 +742,7 @@ namespace ogfx
{ x1, y1 + topInset }
};
u32 inds[6] = QUAD_INDICES_ARR;
push_polygon(verts, nullptr, 4, inds, 6, color, nullptr);
push_polygon(verts, nullptr, 4, inds, 6, color, nullptr, true);
}
// Top-right gap
@ -751,7 +755,7 @@ namespace ogfx
{ x2 - rightInset, y1 + topInset }
};
u32 inds[6] = QUAD_INDICES_ARR;
push_polygon(verts, nullptr, 4, inds, 6, color, nullptr);
push_polygon(verts, nullptr, 4, inds, 6, color, nullptr, true);
}
// Bottom-left gap
@ -764,7 +768,7 @@ namespace ogfx
{ x1, y2 - rBL }
};
u32 inds[6] = QUAD_INDICES_ARR;
push_polygon(verts, nullptr, 4, inds, 6, color, nullptr);
push_polygon(verts, nullptr, 4, inds, 6, color, nullptr, true);
}
// Bottom-right gap
@ -777,7 +781,7 @@ namespace ogfx
{ x2 - rightInset, y2 - rBR }
};
u32 inds[6] = QUAD_INDICES_ARR;
push_polygon(verts, nullptr, 4, inds, 6, color, nullptr);
push_polygon(verts, nullptr, 4, inds, 6, color, nullptr, true);
}
// Left-side gap (if rTL != rBL, leftInset is the max, smaller one needs a gap)
@ -793,10 +797,10 @@ namespace ogfx
// Corner fills
auto segments = [](f32 r) -> i32 { return std::max(16, i32(r * 1.5f)); };
if (rTL > 0) generate_filled_ellipse_stroke({ x1 + rTL, y1 + rTL }, rTL, rTL, M_PI, color, segments(rTL));
if (rTR > 0) generate_filled_ellipse_stroke({ x2 - rTR, y1 + rTR }, rTR, rTR, M_PI * 1.5f, color, segments(rTR));
if (rBR > 0) generate_filled_ellipse_stroke({ x2 - rBR, y2 - rBR }, rBR, rBR, 0.0f, color, segments(rBR));
if (rBL > 0) generate_filled_ellipse_stroke({ x1 + rBL, y2 - rBL }, rBL, rBL, M_PI * 0.5f, color, segments(rBL));
if (rTL > 0) generate_filled_ellipse_stroke({ x1 + rTL, y1 + rTL }, rTL, rTL, M_PI, color, segments(rTL), true);
if (rTR > 0) generate_filled_ellipse_stroke({ x2 - rTR, y1 + rTR }, rTR, rTR, M_PI * 1.5f, color, segments(rTR), true);
if (rBR > 0) generate_filled_ellipse_stroke({ x2 - rBR, y2 - rBR }, rBR, rBR, 0.0f, color, segments(rBR), true);
if (rBL > 0) generate_filled_ellipse_stroke({ x1 + rBL, y2 - rBL }, rBL, rBL, M_PI * 0.5f, color, segments(rBL), true);
}
void BasicRenderer2D::fillCircle(const Vec2& center, f32 radius, const Color& color)
@ -805,7 +809,7 @@ namespace ogfx
return;
i32 segments = std::max(16, i32(radius * 1.5f));
generate_filled_ellipse(center, radius, radius, color, segments);
generate_filled_ellipse(center, radius, radius, color, segments, true);
}
void BasicRenderer2D::fillCircle(const Rectangle& rect, const Color& color)
@ -836,7 +840,7 @@ namespace ogfx
f32 radiusY = rect.h * 0.5f;
i32 segments = std::max(16, i32(std::max(radiusX, radiusY) * 1.5f));
generate_filled_ellipse(center, radiusX, radiusY, color, segments);
generate_filled_ellipse(center, radiusX, radiusY, color, segments, true);
}
void BasicRenderer2D::fillTriangle(const Triangle& tri, const Color& color)
@ -968,7 +972,7 @@ namespace ogfx
i = 0;
}
void BasicRenderer2D::generate_half_circle(const Vec2& center, const Vec2& dir, f32 radius, i32 segments, const Color& color)
void BasicRenderer2D::generate_half_circle(const Vec2& center, const Vec2& dir, f32 radius, i32 segments, const Color& color, bool use_null_tex)
{
// Ensure we have room
if (m_vertexCount + segments + 2 >= MaxVertices || m_indexCount + segments * 3 >= MaxIndices)
@ -993,6 +997,10 @@ namespace ogfx
f32 startAngle = baseAngle - M_PI * 0.5f;
f32 endAngle = baseAngle + M_PI * 0.5f;
Vec2 uvs = { 0.0f, 0.0f };
if (use_null_tex)
uvs = m_fontGlyphAtlas.getNullTextureSlotUVs();
// Generate arc vertices
for (i32 i = 0; i <= segments; i++)
{
@ -1005,7 +1013,7 @@ namespace ogfx
m_vertices[m_vertexCount++] = {
{ x, y },
col,
{ 0.0f, 0.0f }
{ uvs.x, uvs.y }
};
}
@ -1018,132 +1026,7 @@ namespace ogfx
}
}
void BasicRenderer2D::generate_quarter_circle(const Vec2& center, f32 radius, f32 thickness, f32 startAngle, const Color& color, i32 segments)
{
f32 half = thickness * 0.5f;
f32 outerR = radius + half;
f32 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();
SDL_FColor col = COLOR_CAST(color);
i32 base = m_vertexCount;
f32 endAngle = startAngle + M_PI * 0.5f;
// Generate vertices: outer arc + inner arc
for (i32 i = 0; i <= segments; i++)
{
f32 t = f32(i) / f32(segments);
f32 angle = startAngle + t * (endAngle - startAngle);
f32 cs = std::cos(angle);
f32 sn = std::sin(angle);
// 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 }
};
}
// Generate indices (triangle strip converted to triangles)
for (i32 i = 0; i < segments; i++)
{
i32 o0 = base + i * 2;
i32 i0 = o0 + 1;
i32 o1 = o0 + 2;
i32 i1 = o0 + 3;
// 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;
}
}
void BasicRenderer2D::generate_circle_stroke(const Vec2& center, f32 radius, f32 thickness, const Color& color, i32 segments)
{
f32 half = thickness * 0.5f;
f32 outerR = radius + half;
f32 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();
SDL_FColor col = COLOR_CAST(color);
i32 base = m_vertexCount;
// Generate vertices: outer arc + inner arc
for (i32 i = 0; i <= segments; i++)
{
f32 t = f32(i) / f32(segments);
f32 angle = t * (2.0f * M_PI);
f32 cs = std::cos(angle);
f32 sn = std::sin(angle);
// 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 }
};
}
// Generate indices (triangle strip → triangles)
for (i32 i = 0; i < segments; i++)
{
i32 o0 = base + i * 2;
i32 i0 = o0 + 1;
i32 o1 = o0 + 2;
i32 i1 = o0 + 3;
// 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;
}
}
void BasicRenderer2D::generate_ellipse_stroke(const Vec2& center, f32 radiusX, f32 radiusY, f32 thickness, f32 startAngle, f32 endAngle, const Color& color, i32 segments)
void BasicRenderer2D::generate_ellipse_stroke(const Vec2& center, f32 radiusX, f32 radiusY, f32 thickness, f32 startAngle, f32 endAngle, const Color& color, i32 segments, bool use_null_tex)
{
f32 half = thickness * 0.5f;
@ -1165,6 +1048,10 @@ namespace ogfx
f32 angleRange = endAngle - startAngle;
Vec2 uvs = { 0.0f, 0.0f };
if (use_null_tex)
uvs = m_fontGlyphAtlas.getNullTextureSlotUVs();
// Generate vertices: outer arc + inner arc
for (i32 i = 0; i <= segments; i++)
{
@ -1178,14 +1065,14 @@ namespace ogfx
m_vertices[m_vertexCount++] = {
{ center.x + cs * outerRX, center.y + sn * outerRY },
col,
{ 0, 0 }
{ uvs.x, uvs.y }
};
// Inner arc
m_vertices[m_vertexCount++] = {
{ center.x + cs * innerRX, center.y + sn * innerRY },
col,
{ 0, 0 }
{ uvs.x, uvs.y }
};
}
@ -1209,7 +1096,7 @@ namespace ogfx
}
}
void BasicRenderer2D::generate_filled_ellipse_stroke(const Vec2& center, f32 radiusX, f32 radiusY, f32 startAngle, const Color& color, i32 segments)
void BasicRenderer2D::generate_filled_ellipse_stroke(const Vec2& center, f32 radiusX, f32 radiusY, f32 startAngle, const Color& color, i32 segments, bool use_null_tex)
{
// Ensure capacity
if (m_vertexCount + segments + 2 >= MaxVertices || m_indexCount + segments * 3 >= MaxIndices)
@ -1228,6 +1115,10 @@ namespace ogfx
f32 endAngle = startAngle + M_PI * 0.5f;
Vec2 uvs = { 0.0f, 0.0f };
if (use_null_tex)
uvs = m_fontGlyphAtlas.getNullTextureSlotUVs();
for (i32 i = 0; i <= segments; i++)
{
f32 t = f32(i) / f32(segments);
@ -1239,7 +1130,7 @@ namespace ogfx
m_vertices[m_vertexCount++] = {
{ x, y },
col,
{ 0, 0 }
{ uvs.x, uvs.y }
};
}
@ -1252,7 +1143,7 @@ namespace ogfx
}
}
void BasicRenderer2D::generate_filled_ellipse(const Vec2& center, f32 radiusX, f32 radiusY, const Color& color, i32 segments)
void BasicRenderer2D::generate_filled_ellipse(const Vec2& center, f32 radiusX, f32 radiusY, const Color& color, i32 segments, bool use_null_tex)
{
// Ensure capacity
if (m_vertexCount + segments + 2 >= MaxVertices || m_indexCount + segments * 3 >= MaxIndices)
@ -1269,6 +1160,10 @@ namespace ogfx
{ 0, 0 }
};
Vec2 uvs = { 0.0f, 0.0f };
if (use_null_tex)
uvs = m_fontGlyphAtlas.getNullTextureSlotUVs();
// Outer ring vertices
for (i32 i = 0; i <= segments; i++)
{
@ -1281,7 +1176,7 @@ namespace ogfx
m_vertices[m_vertexCount++] = {
{ x, y },
col,
{ 0, 0 }
{ uvs.x, uvs.y }
};
}
@ -1294,7 +1189,7 @@ namespace ogfx
}
}
void BasicRenderer2D::push_polygon(const Vec2* verts, const Vec2* texCoords, u32 vertCount, const u32* inds, u32 indexCount, const Color& color, SDL_Texture* texture)
void BasicRenderer2D::push_polygon(const Vec2* verts, const Vec2* texCoords, u32 vertCount, const u32* inds, u32 indexCount, const Color& color, SDL_Texture* texture, bool use_null_tex)
{
if (!m_initialized || vertCount <= 0 || indexCount <= 0)
return;
@ -1304,20 +1199,24 @@ namespace ogfx
OX_ERROR("Maximum number of vertices exceeded in single draw call.");
return;
}
if (texture != m_texture)
use_null_tex = use_null_tex || !texCoords;
auto tmpTex = (use_null_tex ? m_lastUsedGlyphAtlasTex : texture);
if (tmpTex != m_texture)
flushBatch();
m_texture = texture;
m_texture = tmpTex;
if (m_vertexCount + vertCount >= MaxVertices || m_indexCount + indexCount >= MaxIndices)
flushBatch();
SDL_FColor col = COLOR_CAST(color);
i32 base = m_vertexCount;
bool hasTexCoords = texCoords != nullptr;
Vec2 whiteUV = m_fontGlyphAtlas.getNullTextureSlotUVs();
for (i32 i = 0; i < vertCount; i++)
{
Vec2 tc { 0.0f, 0.0f };
if (hasTexCoords)
if (use_null_tex)
tc = whiteUV;
else
tc = texCoords[i];
m_vertices[m_vertexCount++] = {
{ verts[i].x, verts[i].y },

View file

@ -70,7 +70,7 @@ namespace ogfx
i32 setFontSize(i32 fontSize);
Vec2 getStringDimensions(const String& message, i32 fontSize = 0, TTF_Font* font = nullptr);
inline u32 getDrawCallCount(void) { return m_drawCallCount; }
inline u32 getDrawCallCount(void) { return m_lastFrameDrawCallCount; }
inline bool hasOpenFont(void) { return m_fontOpen; }
inline TTF_Font* getSDLFont(void) { return m_font; }
inline bool isValid(void) const { return m_initialized && m_fontOpen && (m_font != nullptr || m_fontFromMemory); }
@ -126,13 +126,11 @@ namespace ogfx
private:
void init_arrays(void);
void generate_half_circle(const Vec2& center, const Vec2& dir, f32 radius, i32 segments, const Color& color);
void generate_quarter_circle(const Vec2& center, f32 radius, f32 thickness, f32 startAngle, const Color& color, i32 segments);
void generate_circle_stroke(const Vec2& center, f32 radius, f32 thickness, const Color& color, i32 segments);
void generate_ellipse_stroke(const Vec2& center, f32 radiusX, f32 radiusY, f32 thickness, f32 startAngle, f32 endAngle, const Color& color, i32 segments);
void generate_filled_ellipse_stroke(const Vec2& center, f32 radiusX, f32 radiusY, f32 startAngle, const Color& color, i32 segments);
void generate_filled_ellipse(const Vec2& center, f32 radiusX, f32 radiusY, const Color& color, i32 segments);
void push_polygon(const Vec2* verts, const Vec2* texCoords, u32 vertCount, const u32* inds, u32 indexCount, const Color& color, SDL_Texture* texture);
void generate_half_circle(const Vec2& center, const Vec2& dir, f32 radius, i32 segments, const Color& color, bool use_null_tex = false);
void generate_ellipse_stroke(const Vec2& center, f32 radiusX, f32 radiusY, f32 thickness, f32 startAngle, f32 endAngle, const Color& color, i32 segments, bool use_null_tex = false);
void generate_filled_ellipse_stroke(const Vec2& center, f32 radiusX, f32 radiusY, f32 startAngle, const Color& color, i32 segments, bool use_null_tex = false);
void generate_filled_ellipse(const Vec2& center, f32 radiusX, f32 radiusY, const Color& color, i32 segments, bool use_null_tex = false);
void push_polygon(const Vec2* verts, const Vec2* texCoords, u32 vertCount, const u32* inds, u32 indexCount, const Color& color, SDL_Texture* texture, bool use_null_tex = false);
void print_ttf_error(const String& funcName);
inline i32 set_error_state(i32 err) { m_errorState = err; return m_errorState; }
@ -152,6 +150,7 @@ namespace ogfx
SDL_Texture* m_texture { nullptr };
u32 m_drawCallCount { 0 };
u32 m_lastFrameDrawCallCount { 0 };
bool m_fontOpen { false };
TTF_Font* m_font { nullptr };
@ -159,6 +158,7 @@ namespace ogfx
i32 m_fontSize { DefaultFontSize };
bool m_fontFromMemory { false };
FontGlyphAtlas m_fontGlyphAtlas;
SDL_Texture* m_lastUsedGlyphAtlasTex { nullptr };
SignalHandler m_sigHandler { *this };
inline static constexpr i32 DefaultFontSize { 16 };

View file

@ -41,27 +41,27 @@ namespace ogfx
const stdvec<const FontGlyphAtlas::GlyphInfo*> FontGlyphAtlas::processString(const String& str, TTF_Font* font, u32 fontSize)
{
if (m_currentAtlasCount <= 0)
return {};
return {};
// Pass 1: ensure all glyphs are rasterized (map may rehash here)
for (auto& c : str)
{
const GlyphInfo* dummy;
if (!rasterize_glyph(String("").addChar(c), font, fontSize, &dummy))
return {};
}
// Pass 1: ensure all glyphs are rasterized (map may rehash here)
for (auto& c : str)
{
const GlyphInfo* dummy;
if (!rasterize_glyph(String("").addChar(c), font, fontSize, &dummy))
return {};
}
// Pass 2: collect stable pointers (no more insertions, no rehash risk)
stdvec<const GlyphInfo*> glyphs;
glyphs.reserve(str.len());
for (auto& c : str)
{
auto cps = String("").addChar(c).getUTF8Codepoints();
if (cps.size() != 1) return {};
GlyphKey key { cps[0], u64(font), fontSize };
glyphs.push_back(&m_uvs[key]);
}
return glyphs;
// Pass 2: collect stable pointers (no more insertions, no rehash risk)
stdvec<const GlyphInfo*> glyphs;
glyphs.reserve(str.len());
for (auto& c : str)
{
auto cps = String("").addChar(c).getUTF8Codepoints();
if (cps.size() != 1) return {};
GlyphKey key { cps[0], u64(font), fontSize };
glyphs.push_back(&m_uvs[key]);
}
return glyphs;
}
bool FontGlyphAtlas::rasterize_glyph(const String& glyphStr, TTF_Font* font, u32 fontSize, const GlyphInfo** outGlyph)
@ -80,7 +80,7 @@ namespace ogfx
if (it != m_uvs.end())
{
*outGlyph = &(it->second);
return true;
return true;
}
SDL_Surface* surf = TTF_RenderText_Blended(font, glyphStr.c_str(), glyphStr.len(), SDL_Color{255, 255, 255, 255});
@ -169,9 +169,17 @@ namespace ogfx
{ // Clear the texture to transparent
void* pixels = nullptr;
i32 pitch = 0;
if (SDL_LockTexture(tex, nullptr, &pixels, &pitch) == 0)
if (SDL_LockTexture(tex, nullptr, &pixels, &pitch))
{
memset(pixels, 0x00, pitch * AtlasTextureDimension);
auto u8_pixels = cast<u8*>(pixels);
for (i32 y = 0; y < 4; y++)
{
for (i32 x = 0; x < 4 * 4; x++)
{
u8_pixels[x + (y * pitch)] = 0xFF;
}
}
SDL_UnlockTexture(tex);
}
}
@ -179,7 +187,7 @@ namespace ogfx
m_atlases[m_currentAtlasCount] = tex;
m_currentAtlasCount++;
m_penX = 0;
m_penX = 4;
m_penY = 0;
m_rowHeight = 0;

View file

@ -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 <https://www.gnu.org/licenses/>.
You should have received a copy of the GNU General Public License
along with OmniaFramework. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
@ -39,25 +39,25 @@ namespace ogfx
};
struct GlyphKey
{
u32 codepoint;
u64 fontID;
u32 pixelSize;
u32 codepoint;
u64 fontID;
u32 pixelSize;
bool operator==(const GlyphKey& other) const noexcept
bool operator==(const GlyphKey& other) const noexcept
{
return codepoint == other.codepoint &&
fontID == other.fontID &&
pixelSize == other.pixelSize;
}
return codepoint == other.codepoint &&
fontID == other.fontID &&
pixelSize == other.pixelSize;
}
};
struct GlyphKeyHasher
{
size_t operator()(const GlyphKey& k) const noexcept
{
size_t h = std::hash<u32>()(k.codepoint);
h ^= std::hash<u64>()(k.fontID) + 0x9e3779b9 + (h << 6) + (h >> 2);
h ^= std::hash<u32>()(k.pixelSize) + 0x9e3779b9 + (h << 6) + (h >> 2);
return h;
size_t h = std::hash<u32>()(k.codepoint);
h ^= std::hash<u64>()(k.fontID) + 0x9e3779b9 + (h << 6) + (h >> 2);
h ^= std::hash<u32>()(k.pixelSize) + 0x9e3779b9 + (h << 6) + (h >> 2);
return h;
}
};
public:
@ -65,6 +65,8 @@ namespace ogfx
inline FontGlyphAtlas(BasicRenderer2D& renderer) { init(renderer); }
FontGlyphAtlas init(BasicRenderer2D& renderer);
const stdvec<const GlyphInfo*> processString(const String& str, TTF_Font* font, u32 fontSize);
inline Vec2 getNullTextureSlotUVs(void) const { return m_nullTexUVs; }
inline SDL_Texture* getAtlas(u32 index) const { return (index >= m_currentAtlasCount ? nullptr : m_atlases[index]); }
private:
bool rasterize_glyph(const String& glyphStr, TTF_Font* font, u32 fontSize, const GlyphInfo** outGlyph);
@ -74,13 +76,14 @@ namespace ogfx
inline static constexpr u32 AtlasTextureDimension { 8192 };
inline static constexpr u32 MaxAtlasCount { 16 };
public:
private:
std::unordered_map<GlyphKey, GlyphInfo, GlyphKeyHasher> m_uvs;
SDL_Texture* m_atlases[FontGlyphAtlas::MaxAtlasCount];
i32 m_currentAtlasCount { 0 };
BasicRenderer2D* m_renderer { nullptr };
i32 m_penX { 0 };
i32 m_penY { 0 };
i32 m_rowHeight { 0 };
i32 m_penY { 0 };
i32 m_rowHeight { 0 };
Vec2 m_nullTexUVs { 2.0f / cast<f32>(AtlasTextureDimension), 2.0f / cast<f32>(AtlasTextureDimension) };
};
}

View file

@ -74,7 +74,7 @@ class Window : public ogfx::gui::Window
m_label4.setText("Label4");
m_label5.setText("Label5");
m_panel1.setSize(300, 300);
m_panel1.setSize(700, 300);
m_panel1.allowVScroll(false);
m_panel1.allowHScroll(false);
m_panel1.setTitle("Panel 1");
@ -99,7 +99,7 @@ class Window : public ogfx::gui::Window
m_panel2.addWidget(m_btn1, { 0, 300 });
addWidget(m_check1, { 30, 30 });
addWidget(m_panel2, { 30, 100 });
addWidget(m_panel2, { 500, 100 });
m_theme.loadFromFile("./DefaultTheme.oss", true, getDefaultStylesheetVariableList());
setTheme(m_theme);
@ -118,6 +118,7 @@ class Window : public ogfx::gui::Window
void onRedraw(ogfx::BasicRenderer2D& gfx) override
{
gfx.drawAnimation(m_anim, { 200, 200 });
// std::cout << (i32)gfx.getDrawCallCount() << "\n";
// gfx.fillRect(m_panel2.getGlobalPureContentBounds(), { 0, 255, 0, 120 });
}