From 84c4c2eb88fc2d7b214a93cec900c4d6d9a12c6f Mon Sep 17 00:00:00 2001 From: OmniaX-Dev Date: Wed, 15 Apr 2026 21:00:39 +0200 Subject: [PATCH] Added Panel title --- extra/testTheme.txt | 50 ++--- other/TODO.txt | 6 +- src/ogfx/gui/Window.cpp | 4 + src/ogfx/gui/Window.hpp | 5 + src/ogfx/gui/widgets/Containers.cpp | 79 +++++++- src/ogfx/gui/widgets/Containers.hpp | 28 ++- src/ogfx/render/BasicRenderer.cpp | 273 ++++++++++++++++++---------- src/ogfx/render/BasicRenderer.hpp | 6 + src/ostd/io/Stylesheet.cpp | 30 ++- src/test/GuiTest.cpp | 3 +- 10 files changed, 347 insertions(+), 137 deletions(-) diff --git a/extra/testTheme.txt b/extra/testTheme.txt index 7fef94b..5bea0d7 100644 --- a/extra/testTheme.txt +++ b/extra/testTheme.txt @@ -1,17 +1,17 @@ % ================== testTheme.txt ================== % ---- Variables ---- -$accentColor = Color(rgba(255, 0, 255, 255)) -const $color2 = Color(#0000FFFF) +$accentColor = rgba(255, 0, 255, 255) +const $color2 = #0000FFFF % ------------------- -window.backgroundColor = Color(rgba(160, 160, 160, 255)) +window.backgroundColor = rgba(160, 160, 160, 255) -$accentColor = Color(rgb(255, 0, 0)) +$accentColor = rgb(255, 0, 0) (label) { textColor = $accentColor - backgroundColor = Color(#000000FF) + backgroundColor = #000000FF borderColor = $accentColor fontSize = 20 borderRadius = 0 @@ -46,26 +46,30 @@ $accentColor = Color(rgb(255, 0, 0)) } (checkbox:active) { - textColor = Color(#000050FF) + textColor = #000050FF } (panel) { - backgroundColor = Color(#AAAAAAFF) - borderColor = Color(#000000FF) - borderRadius = 8 - borderWidth = 4 + backgroundColor = #AAAAAAFF + borderColor = color_black + borderRadius = 10 + borderWidth = 2 showBorder = true showBackground = true padding = Rect(15, 15, 15, 15) - showTitle = true - titleColor = Color(#000000FF) - titlebarType = "full" - titleHeight = 30 + titleColor = #444444FF + titlebarColor = #999999FF + titlebarBorderColor = #909090FF + titlebarBorderWidth = 2 + titlebarType = full + titlebarHeight = 18 + titlebarFontSize = 18 + titlebarTextAlign = text_left } (@testLabel label) { textColor = $color2 - backgroundColor = Color(#000000FF) + backgroundColor = #000000FF fontSize = 50 } @@ -75,7 +79,7 @@ $accentColor = Color(rgb(255, 0, 0)) } (@testLabel2 label:hover) { - borderColor = Color(#208080FF) + borderColor = #208080FF } (@testLabel2 label:pressed) { @@ -84,7 +88,7 @@ $accentColor = Color(rgb(255, 0, 0)) (label:hover) { textColor = color_green - borderColor = Color(rgb(0, 255, 0)) + borderColor = rgb(0, 255, 0) } (label:pressed) { @@ -97,19 +101,19 @@ $accentColor = Color(rgb(255, 0, 0)) (@testLabel label:pressed) { backgroundColor = color_firebrick - textColor = Color(#505050FF) + textColor = #505050FF } (label:disabled) { - backgroundColor = Color(#EEEEEEFF) - %textColor = Color(#505050FF) - %borderColor = Color(#000000FF) + backgroundColor = #EEEEEEFF + %textColor = #505050FF + %borderColor = #000000FF } (@label3 label) { textColor = $color2 - backgroundColor = Color(#000000FF) - borderColor = Color(#FF0000FF) + backgroundColor = #000000FF + borderColor = #FF0000FF borderWidth = 4 borderRadius = 15 } diff --git a/other/TODO.txt b/other/TODO.txt index 040506c..d3bc900 100644 --- a/other/TODO.txt +++ b/other/TODO.txt @@ -16,10 +16,10 @@ ***Implement wrappers in Renderer2D to take a center and size for rects ***find a way to add fixed update to gui::Window ***Add triangle drawing functions to BasicRenderer2D +***Implement Panel title Add theme caching Implement global scale (probably query desktop scale and adjust accordingly) Implement show/hide functionality for widgets -Implement Panel title Create reliable default stylesheet Implement cursors in Stylesheet @@ -28,10 +28,10 @@ Implement cursors in Stylesheet Implement following widgets: ***Label ***Checkbox + ***Panel / Container + ***Title Label Button Image/Icon - Panel / Container - Title Label Radio Button Grouping Text Input diff --git a/src/ogfx/gui/Window.cpp b/src/ogfx/gui/Window.cpp index a8d8cab..d6d1c30 100644 --- a/src/ogfx/gui/Window.cpp +++ b/src/ogfx/gui/Window.cpp @@ -494,6 +494,10 @@ namespace ogfx m_defaultStylesheetVariables["color_darkcyan"] = { "Color(" + Colors::DarkCyan.hexString(true, "#") + ")", true }; m_defaultStylesheetVariables["color_darkmagenta"] = { "Color(" + Colors::DarkMagenta.hexString(true, "#") + ")", true }; m_defaultStylesheetVariables["color_darkyellow"] = { "Color(" + Colors::DarkYellow.hexString(true, "#") + ")", true }; + + //Text Align + m_defaultStylesheetVariables["text_center"] = { String("").add(cast(eTextAlign::Center)), true }; + m_defaultStylesheetVariables["text_left"] = { String("").add(cast(eTextAlign::Left)), true }; } diff --git a/src/ogfx/gui/Window.hpp b/src/ogfx/gui/Window.hpp index 2d8cdd3..c7ddd3c 100644 --- a/src/ogfx/gui/Window.hpp +++ b/src/ogfx/gui/Window.hpp @@ -59,6 +59,11 @@ namespace ogfx Count }; + public: enum class eTextAlign : u8 { + Default = 0, + Center, + Left + }; public: inline WindowCore(void) { } virtual ~WindowCore(void); diff --git a/src/ogfx/gui/widgets/Containers.cpp b/src/ogfx/gui/widgets/Containers.cpp index a6fab02..3b9f31d 100644 --- a/src/ogfx/gui/widgets/Containers.cpp +++ b/src/ogfx/gui/widgets/Containers.cpp @@ -20,6 +20,7 @@ #include "Containers.hpp" #include "../../render/BasicRenderer.hpp" +#include "../Window.hpp" namespace ogfx { @@ -46,15 +47,85 @@ namespace ogfx enableBorder(getThemeValue(theme, "panel.showBorder", true)); enableBackground(getThemeValue(theme, "panel.showBackground", true)); setBorderColor(getThemeValue(theme, "panel.borderColor", Colors::Black)); - m_titleColor = getThemeValue(theme, "panel.titleColor", Colors::Black); - m_showTitle = getThemeValue(theme, "panel.showTitle", false); - m_titleHeight = getThemeValue(theme, "panel.titleHeight", 30); - m_titleType = getThemeValue(theme, "panel.titlebarType", TitleBarTypes::None); setPadding(getThemeValue(theme, "panel.padding", { 15, 15, 15, 15 })); + m_basePadding = getPadding(); + m_titleColor = getThemeValue(theme, "panel.titleColor", Colors::Black); + m_titlebarColor = getThemeValue(theme, "panel.titlebarColor", Colors::Transparent); + m_titlebarBorderColor = getThemeValue(theme, "panel.titlebarBorderColor", Colors::Black); + m_titlebarHeight = getThemeValue(theme, "panel.titlebarHeight", 30); + m_titlebarBorderWidth = getThemeValue(theme, "panel.titlebarBorderWidth", 1); + m_titlebarFontSize = getThemeValue(theme, "panel.titlebarFontSize", 26); + m_titleTextAlign = getThemeValue(theme, "panel.titlebarTextAlign", cast(WindowCore::eTextAlign::Left)); + setTitlebarType(getThemeValue(theme, "panel.titlebarType", TitleBarTypes::None)); } void Panel::onDraw(ogfx::BasicRenderer2D& gfx) { + draw_titlebar(gfx); + } + + void Panel::setTitlebarType(const String& type) + { + String t = type.new_toLower().trim(); + if (t == TitleBarTypes::None) + { + setPadding(m_basePadding); + m_titlebarType = TitleBarTypes::NoneValue; + } + else if (t == TitleBarTypes::Minimal) + { + setPadding(m_basePadding + Rectangle { 0, m_titlebarHeight, 0, 0 }); + m_titlebarType = TitleBarTypes::MinimalValue; + } + else if (t == TitleBarTypes::Full) + { + setPadding(m_basePadding + Rectangle { 0, m_titlebarHeight, 0, 0 }); + m_titlebarType = TitleBarTypes::FullValue; + } + } + + String Panel::getTitlebarType(void) const + { + switch (m_titlebarType) + { + case TitleBarTypes::FullValue: return TitleBarTypes::Full; + case TitleBarTypes::MinimalValue: return TitleBarTypes::Minimal; + case TitleBarTypes::NoneValue: return TitleBarTypes::None; + default: return TitleBarTypes::None; + } + } + + void Panel::draw_titlebar(BasicRenderer2D& gfx) + { + f32 br = cast(getBorderWidth()); + br /= 2; + Rectangle titleBarBounds { + getGlobalPosition() + Vec2 { br, br }, + Vec2 { getw(), m_titlebarHeight } - Vec2 { br * 2, br * 2 } + }; + switch (m_titlebarType) + { + case TitleBarTypes::FullValue: + { + gfx.outlinedRoundRect(titleBarBounds, m_titlebarColor, m_titlebarColor, { cast(getBorderRadius()), cast(getBorderRadius()), 0, 0 }, getBorderWidth()); + gfx.drawLine({ getGlobalPosition() + Vec2 { 0, m_titlebarHeight - 2 }, getGlobalPosition() + Vec2 { getGlobalBounds().w, m_titlebarHeight - 2 } }, m_titlebarBorderColor, m_titlebarBorderWidth); + if (m_titleTextAlign == cast(WindowCore::eTextAlign::Center)) + gfx.drawCenteredString(m_title, titleBarBounds, m_titleColor, m_titlebarFontSize); + else + gfx.drawString(m_title, titleBarBounds.getPosition() + Vec2 { 10, 5 }, m_titleColor, m_titlebarFontSize); + break; + } + case TitleBarTypes::MinimalValue: + { + if (m_titleTextAlign == cast(WindowCore::eTextAlign::Center)) + gfx.drawCenteredString(m_title, titleBarBounds, m_titleColor, m_titlebarFontSize); + else + gfx.drawString(m_title, titleBarBounds.getPosition() + Vec2 { 10, 5 }, m_titleColor, m_titlebarFontSize); + break; + } + case TitleBarTypes::NoneValue: + default: break; + } } } } diff --git a/src/ogfx/gui/widgets/Containers.hpp b/src/ogfx/gui/widgets/Containers.hpp index 51d12d1..8198fb0 100644 --- a/src/ogfx/gui/widgets/Containers.hpp +++ b/src/ogfx/gui/widgets/Containers.hpp @@ -34,7 +34,11 @@ namespace ogfx { inline static const String None { "none" }; inline static const String Full { "full" }; - inline static const String Minimal { "monimal" }; + inline static const String Minimal { "minimal" }; + + inline static constexpr i32 NoneValue = 0; + inline static constexpr i32 FullValue = 1; + inline static constexpr i32 MinimalValue = 2; }; public: inline Panel(WindowCore& window) : Widget({ 0, 0, 0, 0 }, window) { create(); } @@ -42,15 +46,27 @@ namespace ogfx void applyTheme(const ostd::Stylesheet& theme) override; void onDraw(ogfx::BasicRenderer2D& gfx) override; void setTitlebarType(const String& type); + String getTitlebarType(void) const; inline void setBackGroundColor(const Color& color) { m_backgroundColor = color; } inline Color getBackgroundColor(void) { return m_backgroundColor; } - inline String getTitlebarType(void) const { return m_titleType; } + inline String getTitle(void) const { return m_title; } + inline void setTitle(const String& title) { m_title = title; } private: - Color m_titleColor { 0, 0, 0 }; - bool m_showTitle { false }; - String m_titleType = TitleBarTypes::None; - f32 m_titleHeight { 30 }; + void draw_titlebar(BasicRenderer2D& gfx); + + private: + String m_title { "Panel" }; + + Color m_titleColor { Colors::Black }; + i32 m_titlebarType = TitleBarTypes::NoneValue; + f32 m_titlebarHeight { 30 }; + i32 m_titlebarBorderWidth { 1 }; + i32 m_titlebarFontSize { 26 }; + Color m_titlebarColor { Colors::Transparent }; + Color m_titlebarBorderColor { Colors::Black }; + Rectangle m_basePadding { 0, 0, 0, 0 }; + i32 m_titleTextAlign { 0 }; }; } } diff --git a/src/ogfx/render/BasicRenderer.cpp b/src/ogfx/render/BasicRenderer.cpp index 7019cd0..5263782 100644 --- a/src/ogfx/render/BasicRenderer.cpp +++ b/src/ogfx/render/BasicRenderer.cpp @@ -60,6 +60,7 @@ namespace ogfx { m_window = &window; if (m_initialized) return set_error_state(tErrors::NoError); + SDL_SetRenderDrawBlendMode(m_window->getSDLRenderer(), SDL_BLENDMODE_BLEND); init_arrays(); m_initialized = true; loadDefaultFont(20); @@ -434,46 +435,52 @@ namespace ogfx } void BasicRenderer2D::drawRoundRect(const Rectangle& rect, const Color& color, f32 radius, i32 thickness) + { + drawRoundRect(rect, color, { radius, radius, radius, radius }, thickness); + } + + void BasicRenderer2D::drawRoundRect(const Vec2& center, const Vec2& size, const Color& color, f32 radius, i32 thickness) + { + drawRoundRect({ center.x - size.x * 0.5f, center.y - size.y * 0.5f, size.x, size.y }, color, { radius, radius, radius, radius }, thickness); + } + + void BasicRenderer2D::drawRoundRect(const Vec2& center, const Vec2& size, const Color& color, const Rectangle& radii, i32 thickness) + { + drawRoundRect({ center.x - size.x * 0.5f, center.y - size.y * 0.5f, size.x, size.y }, color, radii, thickness); + } + + void BasicRenderer2D::drawRoundRect(const Rectangle& rect, const Color& color, const Rectangle& radii, i32 thickness) { if (!m_initialized || thickness <= 0) return; - // Clamp radius so it never exceeds half the smallest dimension - radius = std::max(0.0f, std::min(radius, std::min(rect.w, rect.h) * 0.5f)); + // radii: x=TL, y=TR, w=BR, h=BL + f32 maxR = std::min(rect.w, rect.h) * 0.5f; + f32 rTL = std::max(0.0f, std::min(radii.x, maxR)); + f32 rTR = std::max(0.0f, std::min(radii.y, maxR)); + f32 rBR = std::max(0.0f, std::min(radii.w, maxR)); + f32 rBL = std::max(0.0f, std::min(radii.h, maxR)); f32 half = thickness * 0.5f; - // Inset rectangle so the border stays INSIDE the given bounds f32 x1 = rect.x + half; f32 y1 = rect.y + half; f32 x2 = rect.x + rect.w - half; f32 y2 = rect.y + rect.h - half; - // Corner centers - Vec2 TL { x1 + radius, y1 + radius }; - Vec2 TR { x2 - radius, y1 + radius }; - Vec2 BR { x2 - radius, y2 - radius }; - Vec2 BL { x1 + radius, y2 - radius }; + // Straight edges + drawLine({ {x1 + rTL, y1}, {x2 - rTR, y1} }, color, thickness, false); // top + drawLine({ {x2, y1 + rTR}, {x2, y2 - rBR} }, color, thickness, false); // right + drawLine({ {x2 - rBR, y2}, {x1 + rBL, y2} }, color, thickness, false); // bottom + drawLine({ {x1, y2 - rBL}, {x1, y1 + rTL} }, color, thickness, false); // left - // Straight edges (shortened to meet the arcs cleanly) - drawLine({ {x1 + radius, y1}, {x2 - radius, y1} }, color, thickness, false); // top - drawLine({ {x2, y1 + radius}, {x2, y2 - radius} }, color, thickness, false); // right - drawLine({ {x2 - radius, y2}, {x1 + radius, y2} }, color, thickness, false); // bottom - drawLine({ {x1, y2 - radius}, {x1, y1 + radius} }, color, thickness, false); // left + // Corner arcs + auto segments = [](f32 r) -> i32 { return std::max(6, i32(r * 0.75f)); }; - // Number of segments for smooth arcs - i32 segments = std::max(6, i32(radius * 0.75f)); - - // Quarter‑ellipse strokes (each is 90°) - generate_ellipse_stroke(TL, radius, radius, thickness, M_PI, M_PI + M_PI * 0.5f, color, segments); // TL - generate_ellipse_stroke(TR, radius, radius, thickness, 1.5f * M_PI, 2.0f * M_PI, color, segments); // TR - generate_ellipse_stroke(BR, radius, radius, thickness, 0.0f, M_PI * 0.5f, color, segments); // BR - generate_ellipse_stroke(BL, radius, radius, thickness, M_PI * 0.5f, M_PI, color, segments); // BL - } - - void BasicRenderer2D::drawRoundRect(const Vec2& center, const Vec2& size, const Color& color, f32 radius, i32 thickness) - { - drawRoundRect({ center.x - size.x * 0.5f, center.y - size.y * 0.5f, size.x, size.y }, color, radius, thickness); + 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)); } void BasicRenderer2D::drawCircle(const Vec2& center, f32 radius, const Color& color, i32 thickness) @@ -500,7 +507,7 @@ namespace ogfx void BasicRenderer2D::drawEllipse(const Rectangle& rect, const Color& color, i32 thickness) { - if (!m_initialized || thickness <= 0) + if (!m_initialized || thickness <= 0) return; Vec2 center = { rect.x + rect.w*0.5f, rect.y + rect.h*0.5f }; @@ -526,9 +533,9 @@ namespace ogfx drawLine({ B, C }, color, thickness, false); drawLine({ C, A }, color, thickness, false); } - - - + + + void BasicRenderer2D::fillRect(const Rectangle& rect, const Color& color) { @@ -556,106 +563,172 @@ namespace ogfx } void BasicRenderer2D::fillRoundRect(const Rectangle& rect, const Color& color, f32 radius) + { + fillRoundRect(rect, color, { radius, radius, radius, radius }); + } + + void BasicRenderer2D::fillRoundRect(const Vec2& center, const Vec2& size, const Color& color, f32 radius) + { + fillRoundRect({ center.x - size.x * 0.5f, center.y - size.y * 0.5f, size.x, size.y }, color, { radius, radius, radius, radius }); + } + + void BasicRenderer2D::fillRoundRect(const Vec2& center, const Vec2& size, const Color& color, const Rectangle& radii) + { + fillRoundRect({ center.x - size.x * 0.5f, center.y - size.y * 0.5f, size.x, size.y }, color, radii); + } + + void BasicRenderer2D::fillRoundRect(const Rectangle& rect, const Color& color, const Rectangle& radii) { if (!m_initialized) return; - // Clamp radius - radius = std::max(0.0f, std::min(radius, std::min(rect.w, rect.h) * 0.5f)); + f32 maxR = std::min(rect.w, rect.h) * 0.5f; + f32 rTL = std::max(0.0f, std::min(radii.x, maxR)); + f32 rTR = std::max(0.0f, std::min(radii.y, maxR)); + f32 rBR = std::max(0.0f, std::min(radii.w, maxR)); + f32 rBL = std::max(0.0f, std::min(radii.h, maxR)); f32 x1 = rect.x; f32 y1 = rect.y; f32 x2 = rect.x + rect.w; f32 y2 = rect.y + rect.h; - f32 cx1 = x1 + radius; - f32 cy1 = y1 + radius; - f32 cx2 = x2 - radius; - f32 cy2 = y2 - radius; + // The largest corner radius on each side determines the inset for that side + f32 topInset = std::max(rTL, rTR); + f32 bottomInset = std::max(rBL, rBR); + f32 leftInset = std::max(rTL, rBL); + f32 rightInset = std::max(rTR, rBR); - // Number of segments for smooth corners - i32 segments = std::max(6, i32(radius * 0.75f)); - - // - // 1. Fill the center rectangle - // + // Center rectangle { Vec2 verts[4] = { - { cx1, cy1 }, - { cx2, cy1 }, - { cx2, cy2 }, - { cx1, cy2 } + { x1 + leftInset, y1 + topInset }, + { x2 - rightInset, y1 + topInset }, + { x2 - rightInset, y2 - bottomInset }, + { x1 + leftInset, y2 - bottomInset } }; - u32 inds[6] = { 0,1,2, 2,3,0 }; + u32 inds[6] = QUAD_INDICES_ARR; push_polygon(verts, nullptr, 4, inds, 6, color, nullptr); } - // - // 2. Fill the four side rectangles - // - - // Top - if (radius > 0) + // Top strip { Vec2 verts[4] = { - { x1 + radius, y1 }, - { x2 - radius, y1 }, - { x2 - radius, y1 + radius }, - { x1 + radius, y1 + radius } + { x1 + rTL, y1 }, + { x2 - rTR, y1 }, + { x2 - rTR, y1 + topInset }, + { x1 + rTL, y1 + topInset } }; - u32 inds[6] = { 0,1,2, 2,3,0 }; + u32 inds[6] = QUAD_INDICES_ARR; push_polygon(verts, nullptr, 4, inds, 6, color, nullptr); } - // Bottom + // Bottom strip { Vec2 verts[4] = { - { x1 + radius, y2 - radius }, - { x2 - radius, y2 - radius }, - { x2 - radius, y2 }, - { x1 + radius, y2 } + { x1 + rBL, y2 - bottomInset }, + { x2 - rBR, y2 - bottomInset }, + { x2 - rBR, y2 }, + { x1 + rBL, y2 } }; - u32 inds[6] = { 0,1,2, 2,3,0 }; + u32 inds[6] = QUAD_INDICES_ARR; push_polygon(verts, nullptr, 4, inds, 6, color, nullptr); } - // Left + // Left strip { Vec2 verts[4] = { - { x1, y1 + radius }, - { x1 + radius, y1 + radius }, - { x1 + radius, y2 - radius }, - { x1, y2 - radius } + { x1, y1 + rTL }, + { x1 + leftInset, y1 + rTL }, + { x1 + leftInset, y2 - rBL }, + { x1, y2 - rBL } }; - u32 inds[6] = { 0,1,2, 2,3,0 }; + u32 inds[6] = QUAD_INDICES_ARR; push_polygon(verts, nullptr, 4, inds, 6, color, nullptr); } - // Right + // Right strip { Vec2 verts[4] = { - { x2 - radius, y1 + radius }, - { x2, y1 + radius }, - { x2, y2 - radius }, - { x2 - radius, y2 - radius } + { x2 - rightInset, y1 + rTR }, + { x2, y1 + rTR }, + { x2, y2 - rBR }, + { x2 - rightInset, y2 - rBR } }; - u32 inds[6] = { 0,1,2, 2,3,0 }; + u32 inds[6] = QUAD_INDICES_ARR; push_polygon(verts, nullptr, 4, inds, 6, color, nullptr); } - // - // 3. Fill the four rounded corners (quarter ellipses) - // + // Fill gaps between corners and strips when radii differ on the same side + // Top-left gap (if rTL < topInset, there's a rectangular gap below the TL corner) + if (rTL < topInset) + { + Vec2 verts[4] = { + { x1, y1 + rTL }, + { x1 + leftInset, y1 + rTL }, + { x1 + leftInset, y1 + topInset }, + { x1, y1 + topInset } + }; + u32 inds[6] = QUAD_INDICES_ARR; + push_polygon(verts, nullptr, 4, inds, 6, color, nullptr); + } - generate_filled_ellipse_stroke({cx1, cy1}, radius, radius, M_PI, color, segments); // TL - generate_filled_ellipse_stroke({cx2, cy1}, radius, radius, 1.5f * M_PI, color, segments); // TR - generate_filled_ellipse_stroke({cx2, cy2}, radius, radius, 0.0f, color, segments); // BR - generate_filled_ellipse_stroke({cx1, cy2}, radius, radius, 0.5f * M_PI, color, segments); // BL - } + // Top-right gap + if (rTR < topInset) + { + Vec2 verts[4] = { + { x2 - rightInset, y1 + rTR }, + { x2, y1 + rTR }, + { x2, y1 + topInset }, + { x2 - rightInset, y1 + topInset } + }; + u32 inds[6] = QUAD_INDICES_ARR; + push_polygon(verts, nullptr, 4, inds, 6, color, nullptr); + } - void BasicRenderer2D::fillRoundRect(const Vec2& center, const Vec2& size, const Color& color, f32 radius) - { - fillRoundRect({ center.x - size.x * 0.5f, center.y - size.y * 0.5f, size.x, size.y }, color, radius); + // Bottom-left gap + if (rBL < bottomInset) + { + Vec2 verts[4] = { + { x1, y2 - bottomInset }, + { x1 + leftInset, y2 - bottomInset }, + { x1 + leftInset, y2 - rBL }, + { x1, y2 - rBL } + }; + u32 inds[6] = QUAD_INDICES_ARR; + push_polygon(verts, nullptr, 4, inds, 6, color, nullptr); + } + + // Bottom-right gap + if (rBR < bottomInset) + { + Vec2 verts[4] = { + { x2 - rightInset, y2 - bottomInset }, + { x2, y2 - bottomInset }, + { x2, y2 - rBR }, + { x2 - rightInset, y2 - rBR } + }; + u32 inds[6] = QUAD_INDICES_ARR; + push_polygon(verts, nullptr, 4, inds, 6, color, nullptr); + } + + // Left-side gap (if rTL != rBL, leftInset is the max, smaller one needs a gap) + if (rTL < leftInset && rTL < topInset) + { + // Already covered by TL gap above + } + if (rBL < leftInset && rBL < bottomInset) + { + // Already covered by BL gap above + } + + // Corner fills + auto segments = [](f32 r) -> i32 { return std::max(6, i32(r * 0.75f)); }; + + 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)); } void BasicRenderer2D::fillCircle(const Vec2& center, f32 radius, const Color& color) @@ -714,9 +787,9 @@ namespace ogfx u32 inds[3] = { 0, 1, 2 }; push_polygon(verts, texCoords, 3, inds, 3, color, nullptr); } - - - + + + void BasicRenderer2D::outlinedRect(const Rectangle& rect, const Color& fillColor, const Color& outlineColor, i32 outlineThickness) { @@ -733,15 +806,25 @@ namespace ogfx void BasicRenderer2D::outlinedRoundRect(const Rectangle& rect, const Color& fillColor, const Color& outlineColor, f32 radius, i32 outlineThickness) { - if (!m_initialized) return; - Rectangle offset = { 1, 1, -2, -2 }; - fillRoundRect(rect + offset, fillColor, radius); - drawRoundRect(rect, outlineColor, radius, outlineThickness); + outlinedRoundRect(rect, fillColor, outlineColor, { radius, radius, radius, radius }, outlineThickness); } void BasicRenderer2D::outlinedRoundRect(const Vec2& center, const Vec2& size, const Color& fillColor, const Color& outlineColor, f32 radius, i32 outlineThickness) { - outlinedRoundRect({ center.x - size.x * 0.5f, center.y - size.y * 0.5f, size.x, size.y }, fillColor, outlineColor, radius, outlineThickness); + outlinedRoundRect({ center.x - size.x * 0.5f, center.y - size.y * 0.5f, size.x, size.y }, fillColor, outlineColor, { radius, radius, radius, radius }, outlineThickness); + } + + void BasicRenderer2D::outlinedRoundRect(const Vec2& center, const Vec2& size, const Color& fillColor, const Color& outlineColor, const Rectangle& radii, i32 outlineThickness) + { + outlinedRoundRect({ center.x - size.x * 0.5f, center.y - size.y * 0.5f, size.x, size.y }, fillColor, outlineColor, radii, outlineThickness); + } + + void BasicRenderer2D::outlinedRoundRect(const Rectangle& rect, const Color& fillColor, const Color& outlineColor, const Rectangle& radii, i32 outlineThickness) + { + if (!m_initialized) return; + Rectangle offset = { 1, 1, -2, -2 }; + fillRoundRect(rect + offset, fillColor, radii); + drawRoundRect(rect, outlineColor, radii, outlineThickness); } void BasicRenderer2D::outlinedCircle(const Vec2& center, f32 radius, const Color& fillColor, const Color& outlineColor, i32 outlineThickness) diff --git a/src/ogfx/render/BasicRenderer.hpp b/src/ogfx/render/BasicRenderer.hpp index 13eb052..203461c 100644 --- a/src/ogfx/render/BasicRenderer.hpp +++ b/src/ogfx/render/BasicRenderer.hpp @@ -93,6 +93,8 @@ namespace ogfx void drawRect(const Vec2& center, const Vec2& size, const Color& color, i32 thickness = 1); void drawRoundRect(const Vec2& center, const Vec2& size, const Color& color, f32 radius, i32 thickness = 1); void drawRoundRect(const Rectangle& rect, const Color& color, f32 radius, i32 thickness = 1); + void drawRoundRect(const Rectangle& rect, const Color& color, const Rectangle& radii, i32 thickness = 1); + void drawRoundRect(const Vec2& center, const Vec2& size, const Color& color, const Rectangle& radii, i32 thickness = 1); void drawCircle(const Vec2& center, f32 radius, const Color& color, i32 thickness = 1); void drawCircle(const Rectangle& rect, const Color& color, i32 thickness = 1); void drawEllipse(const Rectangle& rect, const Color& color, i32 thickness = 1); @@ -103,6 +105,8 @@ namespace ogfx void fillRect(const Vec2& center, const Vec2& size, const Color& color); void fillRoundRect(const Rectangle& rect, const Color& color, f32 radius); void fillRoundRect(const Vec2& center, const Vec2& size, const Color& color, f32 radius); + void fillRoundRect(const Rectangle& rect, const Color& color, const Rectangle& radii); + void fillRoundRect(const Vec2& center, const Vec2& size, const Color& color, const Rectangle& radii); void fillCircle(const Vec2& center, f32 radius, const Color& color); void fillCircle(const Rectangle& rect, const Color& color); void fillEllipse(const Rectangle& rect, const Color& color); @@ -113,6 +117,8 @@ namespace ogfx void outlinedRect(const Rectangle& rect, const Color& fillColor, const Color& outlineColor, i32 outlineThickness = 1); void outlinedRoundRect(const Rectangle& rect, const Color& fillColor, const Color& outlineColor, f32 radius, i32 outlineThickness = 1); void outlinedRoundRect(const Vec2& center, const Vec2& size, const Color& fillColor, const Color& outlineColor, f32 radius, i32 outlineThickness = 1); + void outlinedRoundRect(const Rectangle& rect, const Color& fillColor, const Color& outlineColor, const Rectangle& radii, i32 outlineThickness = 1); + void outlinedRoundRect(const Vec2& center, const Vec2& size, const Color& fillColor, const Color& outlineColor, const Rectangle& radii, i32 outlineThickness = 1); void outlinedCircle(const Vec2& center, f32 radius, const Color& fillColor, const Color& outlineColor, i32 outlineThickness = 1); void outlinedCircle(const Rectangle& rect, const Color& fillColor, const Color& outlineColor, i32 outlineThickness = 1); void outlinedEllipse(const Rectangle& rect, const Color& fillColor, const Color& outlineColor, i32 outlineThickness = 1); diff --git a/src/ostd/io/Stylesheet.cpp b/src/ostd/io/Stylesheet.cpp index 19f6b97..d36f8fc 100644 --- a/src/ostd/io/Stylesheet.cpp +++ b/src/ostd/io/Stylesheet.cpp @@ -217,7 +217,7 @@ custom_continue: { auto it = m_values.find(fullKey); // if (it != m_values.end()) - // std::cout << " GET: " << fullKey << "\n"; + // std::cout << " GET: " << fullKey << "\n"; return it != m_values.end() ? &it->second : nullptr; } @@ -231,6 +231,24 @@ custom_continue: if (key == "") return false; String themeID = ""; + + auto l_parseColor = [this](const String& _value) -> String { + String value = _value.new_toLower().trim(); + if (value.startsWith("color(") && value.endsWith(")")) + { + value.substr(6, value.len() - 1).trim(); + return value; + } + else if ((value.startsWith("#") && (value.len() == 7 || value.len() == 9)) || + (value.startsWith("rgb(") && value.endsWith(")")) || + (value.startsWith("rgba(") && value.endsWith(")"))) + { + + return value; + } + return ""; + }; + if (key.startsWith("@")) { if (key.indexOf(" ") < 2) @@ -249,10 +267,9 @@ custom_continue: valuePreserveCase.substr(1, value.len() - 1); set(key, valuePreserveCase, themeID); } - else if (value.startsWith("color(") && value.endsWith(")")) + else if (String v = l_parseColor(value); v != "") { - value.substr(6, value.len() - 1).trim(); - set(key, Color(value), themeID); + set(key, Color(v), themeID); } else if (value.startsWith("vec2(") && value.endsWith(")")) { @@ -284,6 +301,11 @@ custom_continue: } set(key, Rectangle(vec[0], vec[1], vec[2], vec[3]), themeID); } + else if (exitCondition) + { + //Defaulting anything that isn't recognized as any value_type/variable to String type + set(key, valuePreserveCase, themeID); + } else { if (exitCondition) diff --git a/src/test/GuiTest.cpp b/src/test/GuiTest.cpp index d2d9247..db38346 100644 --- a/src/test/GuiTest.cpp +++ b/src/test/GuiTest.cpp @@ -153,8 +153,7 @@ class Window : public ogfx::gui::Window void onRedraw(ogfx::BasicRenderer2D& gfx) override { gfx.drawAnimation(m_anim, { 200, 200 }); - gfx.outlinedTriangle({ 30, 30 }, { 100, 50 }, { 60, 90 }, Colors::Crimson, Colors::DarkBlue, 5); - gfx.endFrame(); + gfx.outlinedTriangle({ 30, 30 }, { 100, 50 }, { 60, 90 }, { 255, 0, 0, 80 }, Colors::DarkBlue, 5); } void onFixedUpdate(void) override