Added a null texture slot to FontGlyphAtlas
This commit is contained in:
parent
a20e5849f5
commit
0cf7478c24
8 changed files with 268 additions and 227 deletions
|
|
@ -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
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 },
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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) };
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 });
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue