Added gradient functionality to BasicRenderer2D

This commit is contained in:
OmniaX-Dev 2026-04-17 05:59:05 +02:00
parent 44354a80c2
commit d521c609b3
8 changed files with 243 additions and 15 deletions

View file

@ -117,3 +117,33 @@ window.backgroundColor = rgba(160, 160, 160, 255)
margin = Rect(0, 0, 0, 0)
}
% ===================
% ====== Label ======
(button) {
textColor = $textColor
backgroundColor = #999999FF
backgroundGradient = gradientH(#000000FF, #555555FF, #999999FF)
backgroundGradient = w_vgradientV(#000000FF, 0.2, #555555FF, 0.8, #999999FF)
borderColor = $accentColorDark
fontSize = 20
borderRadius = 0
borderWidth = 1
showBackground = true
showBorder = true
useBackgroundGradient = true
padding = Rect(15, 5, 15, 5)
margin = Rect(0, 0, 0, 0)
icon = file("testIcon.png")
}
(button:hover) {
borderColor = $accentColor
backgroundColor = #BBBBBBFF
}
(button:pressed) {
borderColor = #BBBBBBFF
textColor = #DDDDDDFF
backgroundColor = #555555FF
}
% ===================

View file

@ -24,3 +24,4 @@
#include <ogfx/gui/widgets/Label.hpp>
#include <ogfx/gui/widgets/CheckBox.hpp>
#include <ogfx/gui/widgets/Scrollbar.hpp>
#include <ogfx/gui/widgets/Button.hpp>

View file

@ -27,6 +27,60 @@ namespace ogfx
{
namespace widgets
{
Button& Button::create(const String& text)
{
setText(text);
setPadding({ 5, 5, 5, 5 });
setTypeName("ogfx::gui::widgets::Button");
disableDrawBox();
disableChildren();
enableBackground(false);
validate();
return *this;
}
void Button::applyTheme(const ostd::Stylesheet& theme)
{
setTextColor(getThemeValue<Color>(theme, "button.textColor", Colors::White));
setBackGroundColor(getThemeValue<Color>(theme, "button.backgroundColor", Colors::Transparent));
setFontSize(getThemeValue<i32>(theme, "button.fontSize", 20));
setBorderRadius(getThemeValue<i32>(theme, "button.borderRadius", 10));
setBorderWidth(getThemeValue<i32>(theme, "button.borderWidth", 2));
enableBorder(getThemeValue<bool>(theme, "button.showBorder", false));
setBorderColor(getThemeValue<Color>(theme, "button.borderColor", Colors::White));
enableBackground(getThemeValue<bool>(theme, "button.showBackground", false));
setPadding(getThemeValue<Rectangle>(theme, "button.padding", { 5, 5, 5, 5 }));
setMargin(getThemeValue<Rectangle>(theme, "button.margin", { 0, 0, 0, 0 }));
m_useBackgroundGradient = getThemeValue<bool>(theme, "button.useBackgroundGradient", false);
if (m_useBackgroundGradient && isBackgoundEnabled())
enableBackground(false);
}
void Button::onDraw(ogfx::BasicRenderer2D& gfx)
{
if (m_textChanged)
__update_size(gfx);
if (m_useBackgroundGradient)
gfx.fillGradientRect(getGlobalBounds(), m_backgroundGradient);
gfx.drawString(getText(), getGlobalContentPosition(), getTextColor(), getFontSize());
}
void Button::setText(const String& text)
{
m_text = text;
m_textChanged = true;
}
void Button::__update_size(ogfx::BasicRenderer2D& gfx)
{
auto size = gfx.getStringDimensions(getText(), getFontSize());
size.x += getPadding().left();
size.x += getPadding().right();
size.y += getPadding().top();
size.y += getPadding().bottom();
setSize({ cast<f32>(size.x), cast<f32>(size.y) });
m_textChanged = false;
}
}
}
}

View file

@ -28,6 +28,35 @@ namespace ogfx
{
namespace widgets
{
class Button : public Widget
{
public:
inline Button(WindowCore& window) : Widget({ 0, 0, 0, 0 }, window) { create(""); }
inline Button(WindowCore& window, const String& text) : Widget({ 0, 0, 0, 0 }, window) { create(text); }
Button& create(const String& text);
void applyTheme(const ostd::Stylesheet& theme) override;
void onDraw(ogfx::BasicRenderer2D& gfx) override;
void setText(const String& text);
inline String getText(void) const { return m_text; }
inline Color getTextColor(void) const { return m_textColor; }
inline void setTextColor(const Color& color) { m_textColor = color; }
inline i32 getFontSize(void) const { return m_fontSize; }
inline void setFontSize(i32 fontSize) { m_fontSize = fontSize; }
private:
void __update_size(ogfx::BasicRenderer2D& gfx);
private:
String m_text { "" };
bool m_textChanged { false };
i32 m_fontSize { 20 };
Color m_textColor { 255, 255, 255 };
bool m_useBackgroundGradient { false };
ColorGradient m_backgroundGradient {
{ Color { 160, 160, 160 }, Color { 120, 120, 120 } },
{ 1.0f }
};
};
}
}
}

View file

@ -314,6 +314,68 @@ namespace ogfx
drawImage(img, position, size, anim.getFrameRect());
}
void BasicRenderer2D::fillGradientRect(const Rectangle& rect, ColorGradient& gradient)
{
auto& colors = gradient.getColors();
auto& weights = gradient.getWeights();
f32 angle_radians = gradient.getAngleRad();
if (!m_initialized || colors.size() < 2) return;
if (weights.size() != colors.size() - 1) return; // N colors = N-1 weights
// Normalize weights
f32 total = 0;
for (auto w : weights) total += w;
for (auto& w : weights) w /= total;
// Compute cumulative positions along gradient (0 to 1)
stdvec<f32> stops(colors.size());
stops[0] = 0.0f;
for (size_t i = 0; i < weights.size(); i++)
stops[i + 1] = stops[i] + weights[i];
// Center of rect for rotation
Vec2 center = { rect.x + rect.w * 0.5f, rect.y + rect.h * 0.5f };
f32 cs = std::cos(angle_radians);
f32 sn = std::sin(angle_radians);
auto rotate = [&](Vec2 p) -> Vec2 {
p -= center;
return { center.x + p.x * cs - p.y * sn,
center.y + p.x * sn + p.y * cs };
};
// For each color band, push a quad
for (size_t i = 0; i < colors.size() - 1; i++)
{
f32 y0 = rect.y + rect.h * stops[i];
f32 y1 = rect.y + rect.h * stops[i + 1];
Vec2 tl = rotate({ rect.x, y0 });
Vec2 tr = rotate({ rect.x + rect.w, y0 });
Vec2 br = rotate({ rect.x + rect.w, y1 });
Vec2 bl = rotate({ rect.x, y1 });
if (m_vertexCount + 4 >= MaxVertices || m_indexCount + 6 >= MaxIndices)
flushBatch();
if (m_texture != nullptr)
flushBatch();
m_texture = nullptr;
SDL_FColor c0 = COLOR_CAST(colors[i]);
SDL_FColor c1 = COLOR_CAST(colors[i + 1]);
i32 base = m_vertexCount;
m_vertices[m_vertexCount++] = { { tl.x, tl.y }, c0, { 0, 0 } };
m_vertices[m_vertexCount++] = { { tr.x, tr.y }, c0, { 0, 0 } };
m_vertices[m_vertexCount++] = { { br.x, br.y }, c1, { 0, 0 } };
m_vertices[m_vertexCount++] = { { bl.x, bl.y }, c1, { 0, 0 } };
u32 inds[6] = QUAD_INDICES_ARR;
for (i32 j = 0; j < 6; j++)
m_indices[m_indexCount++] = base + inds[j];
}
}
void BasicRenderer2D::drawString(const String& str, const Vec2& position, const Color& color, i32 fontSize, f32 scale)
{
if (!isValid()) return;
@ -398,7 +460,7 @@ namespace ogfx
if (!rounded || thickness < 4)
return;
i32 segments = std::max(6, i32(thickness * 0.75f));
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);
}
@ -475,7 +537,7 @@ namespace ogfx
drawLine({ {x1, y2 - rBL}, {x1, y1 + rTL} }, color, thickness, false); // left
// Corner arcs
auto segments = [](f32 r) -> i32 { return std::max(6, i32(r * 0.75f)); };
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));
@ -487,7 +549,7 @@ namespace ogfx
{
if (!m_initialized || thickness <= 0)
return;
i32 segments = std::max(12, i32(radius * 0.75f));
i32 segments = std::max(16, i32(radius * 1.5f));
generate_ellipse_stroke(center, radius, radius, thickness, 0.0f, 2.0f * M_PI, color, segments);
}
@ -513,8 +575,7 @@ namespace ogfx
Vec2 center = { rect.x + rect.w*0.5f, rect.y + rect.h*0.5f };
f32 rx = rect.w * 0.5f;
f32 ry = rect.h * 0.5f;
i32 segments = std::max(12, i32(std::max(rx, ry) * 0.75f));
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);
}
@ -723,7 +784,7 @@ namespace ogfx
}
// Corner fills
auto segments = [](f32 r) -> i32 { return std::max(6, i32(r * 0.75f)); };
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));
@ -736,7 +797,7 @@ namespace ogfx
if (!m_initialized)
return;
i32 segments = std::max(12, i32(radius * 0.75f));
i32 segments = std::max(16, i32(radius * 1.5f));
generate_filled_ellipse(center, radius, radius, color, segments);
}
@ -767,7 +828,7 @@ namespace ogfx
f32 radiusX = rect.w * 0.5f;
f32 radiusY = rect.h * 0.5f;
i32 segments = std::max(12, i32(std::max(radiusX, radiusY) * 0.75f));
i32 segments = std::max(16, i32(std::max(radiusX, radiusY) * 1.5f));
generate_filled_ellipse(center, radiusX, radiusY, color, segments);
}

View file

@ -82,6 +82,7 @@ namespace ogfx
void drawImage(const ogfx::Image& image, const Vec2& position, const Vec2& size = { 0, 0 }, const Rectangle& srcRect = { 0, 0, 0, 0 });
void drawAnimation(const Animation& anim, const Vec2& position, const Vec2& size = { 0, 0 });
void fillGradientRect(const Rectangle& rect, ColorGradient& gradient);
void drawString(const String& str, const Vec2& position, const Color& color, i32 fontSize = 0, f32 scale = 1.0f);
void drawCenteredString(const String& str, const Vec2& center, const Color& color, i32 fontSize = 0, f32 scale = 1.0f);

View file

@ -23,6 +23,7 @@
#include <ostd/data/Types.hpp>
#include <ostd/data/BaseObject.hpp>
#include <ostd/string/String.hpp>
#include <ostd/math/Geometry.hpp>
namespace ostd
{
@ -98,6 +99,31 @@ namespace ostd
public:
};
class ColorGradient : public BaseObject
{
public:
inline ColorGradient(void) { }
inline ColorGradient(const stdvec<Color>& colors, const stdvec<f32>& weights) { m_colors = colors; m_weights = weights; }
inline void addColor(const Color& color) { m_colors.push_back(color); }
inline void addWeight(f32 w) { m_weights.push_back(w); }
inline stdvec<Color>& getColors(void) { return m_colors; }
inline stdvec<f32>& getWeights(void) { return m_weights; }
inline void setAngleDeg(f32 angle_degrees) { m_angleDeg = angle_degrees; }
inline void setAngleRad(f32 angle_radians) { m_angleDeg = RAD_TO_DEG(angle_radians); }
inline f32 getAngleDeg(void) const { return m_angleDeg; }
inline f32 getAngleRad(void) const { return DEG_TO_RAD(m_angleDeg); }
private:
stdvec<Color> m_colors;
stdvec<f32> m_weights;
float m_angleDeg { VerticalDeg };
public:
inline static constexpr f32 HorizontalDeg { 90.0f };
inline static constexpr f32 VerticalDeg { 0.0f };
inline static constexpr f32 DiagonalDeg { 45.0f };
inline static constexpr f32 Diagonal2Deg { 135.0f };
};
struct Colors
{
inline static const Color Transparent { 0, 0, 0, 0 };
@ -159,4 +185,5 @@ using Colors = ostd::Colors;
namespace ogfx
{
using Color = ostd::Color;
using ColorGradient = ostd::ColorGradient;
}

View file

@ -24,6 +24,7 @@
#include <ogfx/ogfx.hpp>
ostd::ConsoleOutputHandler out;
using namespace ogfx::gui::widgets;
class Window : public ogfx::gui::Window
{
@ -63,10 +64,15 @@ class Window : public ogfx::gui::Window
m_check1.setText("Check this out!");
m_check1.setChecked(true);
m_check1.setStateChangedCallback([&](ogfx::gui::widgets::CheckBox& sender, bool state) -> void {
m_check1.setStateChangedCallback([&](CheckBox& sender, bool state) -> void {
m_panel1.setVisible(state);
});
m_btn1.setText("BUTTON");
m_btn1.setMousePressedCallback([&](const ogfx::gui::Event& event) -> void {
});
m_label2.setText("Label2");
m_label3.setText("Label3");
@ -80,6 +86,7 @@ class Window : public ogfx::gui::Window
m_panel2.addWidget(m_label3);
m_panel2.addWidget(m_panel1, { 400, 50 });
m_panel2.addWidget(m_label1, { 0, 500 });
m_panel2.addWidget(m_btn1, { 0, 300 });
addWidget(m_check1, { 30, 30 });
addWidget(m_panel2, { 30, 100 });
@ -101,6 +108,12 @@ class Window : public ogfx::gui::Window
void onRedraw(ogfx::BasicRenderer2D& gfx) override
{
// gfx.drawAnimation(m_anim, { 200, 200 });
// static f32 angle = 0.0f;
// angle += 0.5f; // degrees per frame, adjust to taste
// if (angle >= 360.0f) angle = 0.0f;
// m_grad.setAngleDeg(angle);
// gfx.fillGradientRect({ 200, 200, 300, 300 }, m_grad);
}
void onFixedUpdate(void) override
@ -109,16 +122,28 @@ class Window : public ogfx::gui::Window
}
private:
ogfx::gui::widgets::Label m_label1 { *this };
ogfx::gui::widgets::Label m_label2 { *this };
ogfx::gui::widgets::Label m_label3 { *this };
ogfx::gui::widgets::Panel m_panel1 { *this };
ogfx::gui::widgets::Panel m_panel2 { *this };
ogfx::gui::widgets::CheckBox m_check1 { *this };
Label m_label1 { *this };
Label m_label2 { *this };
Label m_label3 { *this };
Panel m_panel1 { *this };
Panel m_panel2 { *this };
CheckBox m_check1 { *this };
Button m_btn1 { *this };
ostd::Stylesheet m_theme;
ogfx::Animation m_anim;
ogfx::Image m_img;
ogfx::ColorGradient m_grad { {
{ 255, 0, 0 }, // red
{ 255, 165, 0 }, // orange
{ 255, 255, 0 }, // yellow
{ 0, 255, 0 }, // green
{ 0, 0, 255 }, // blue
{ 148, 0, 211 }, // violet
{ 255, 0, 0 }, // red again to loop cleanly
},
{ 1, 1, 1, 1, 1, 1 }, // equal weights, gets normalized
};
};
i32 main(i32 argc, char** argv)