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 Implement cursors in Stylesheet
FIX: Window getting exponentially bigger when Desktop scale changes FIX: Window getting exponentially bigger when Desktop scale changes
FIX: Refreshing scroll when content extent changes FIX: Refreshing scroll when content extent changes
Add Dark Mode Default theme

View file

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

View file

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

View file

@ -169,9 +169,17 @@ namespace ogfx
{ // Clear the texture to transparent { // Clear the texture to transparent
void* pixels = nullptr; void* pixels = nullptr;
i32 pitch = 0; i32 pitch = 0;
if (SDL_LockTexture(tex, nullptr, &pixels, &pitch) == 0) if (SDL_LockTexture(tex, nullptr, &pixels, &pitch))
{ {
memset(pixels, 0x00, pitch * AtlasTextureDimension); 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); SDL_UnlockTexture(tex);
} }
} }
@ -179,7 +187,7 @@ namespace ogfx
m_atlases[m_currentAtlasCount] = tex; m_atlases[m_currentAtlasCount] = tex;
m_currentAtlasCount++; m_currentAtlasCount++;
m_penX = 0; m_penX = 4;
m_penY = 0; m_penY = 0;
m_rowHeight = 0; m_rowHeight = 0;

View file

@ -65,6 +65,8 @@ namespace ogfx
inline FontGlyphAtlas(BasicRenderer2D& renderer) { init(renderer); } inline FontGlyphAtlas(BasicRenderer2D& renderer) { init(renderer); }
FontGlyphAtlas init(BasicRenderer2D& renderer); FontGlyphAtlas init(BasicRenderer2D& renderer);
const stdvec<const GlyphInfo*> processString(const String& str, TTF_Font* font, u32 fontSize); 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: private:
bool rasterize_glyph(const String& glyphStr, TTF_Font* font, u32 fontSize, const GlyphInfo** outGlyph); bool rasterize_glyph(const String& glyphStr, TTF_Font* font, u32 fontSize, const GlyphInfo** outGlyph);
@ -74,7 +76,7 @@ namespace ogfx
inline static constexpr u32 AtlasTextureDimension { 8192 }; inline static constexpr u32 AtlasTextureDimension { 8192 };
inline static constexpr u32 MaxAtlasCount { 16 }; inline static constexpr u32 MaxAtlasCount { 16 };
public: private:
std::unordered_map<GlyphKey, GlyphInfo, GlyphKeyHasher> m_uvs; std::unordered_map<GlyphKey, GlyphInfo, GlyphKeyHasher> m_uvs;
SDL_Texture* m_atlases[FontGlyphAtlas::MaxAtlasCount]; SDL_Texture* m_atlases[FontGlyphAtlas::MaxAtlasCount];
i32 m_currentAtlasCount { 0 }; i32 m_currentAtlasCount { 0 };
@ -82,5 +84,6 @@ namespace ogfx
i32 m_penX { 0 }; i32 m_penX { 0 };
i32 m_penY { 0 }; i32 m_penY { 0 };
i32 m_rowHeight { 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_label4.setText("Label4");
m_label5.setText("Label5"); m_label5.setText("Label5");
m_panel1.setSize(300, 300); m_panel1.setSize(700, 300);
m_panel1.allowVScroll(false); m_panel1.allowVScroll(false);
m_panel1.allowHScroll(false); m_panel1.allowHScroll(false);
m_panel1.setTitle("Panel 1"); m_panel1.setTitle("Panel 1");
@ -99,7 +99,7 @@ class Window : public ogfx::gui::Window
m_panel2.addWidget(m_btn1, { 0, 300 }); m_panel2.addWidget(m_btn1, { 0, 300 });
addWidget(m_check1, { 30, 30 }); addWidget(m_check1, { 30, 30 });
addWidget(m_panel2, { 30, 100 }); addWidget(m_panel2, { 500, 100 });
m_theme.loadFromFile("./DefaultTheme.oss", true, getDefaultStylesheetVariableList()); m_theme.loadFromFile("./DefaultTheme.oss", true, getDefaultStylesheetVariableList());
setTheme(m_theme); setTheme(m_theme);
@ -118,6 +118,7 @@ class Window : public ogfx::gui::Window
void onRedraw(ogfx::BasicRenderer2D& gfx) override void onRedraw(ogfx::BasicRenderer2D& gfx) override
{ {
gfx.drawAnimation(m_anim, { 200, 200 }); gfx.drawAnimation(m_anim, { 200, 200 });
// std::cout << (i32)gfx.getDrawCallCount() << "\n";
// gfx.fillRect(m_panel2.getGlobalPureContentBounds(), { 0, 255, 0, 120 }); // gfx.fillRect(m_panel2.getGlobalPureContentBounds(), { 0, 255, 0, 120 });
} }