From 5491e5d4b362fa91b118a4624661a40f915fa99b Mon Sep 17 00:00:00 2001 From: OmniaX-Dev Date: Mon, 20 Apr 2026 14:45:40 +0200 Subject: [PATCH] Started work on TabPanel --- extra/DefaultTheme.oss | 46 ++++++++----- other/build.nr | 2 +- src/ogfx/gui/widgets/Containers.cpp | 59 +++++++++++++++- src/ogfx/gui/widgets/Containers.hpp | 8 ++- src/ogfx/render/BasicRenderer.cpp | 26 ++++--- src/ogfx/render/BasicRenderer.hpp | 8 +-- src/ostd/data/Color.cpp | 101 +++++++++++++++++----------- src/ostd/data/Color.hpp | 7 ++ src/test/GuiTest.cpp | 24 ++++++- 9 files changed, 208 insertions(+), 73 deletions(-) diff --git a/extra/DefaultTheme.oss b/extra/DefaultTheme.oss index 4a5a804..383dcb0 100644 --- a/extra/DefaultTheme.oss +++ b/extra/DefaultTheme.oss @@ -39,6 +39,33 @@ window.backgroundColor = rgba(160, 160, 160, 255) +% ====== Panel ====== +(tabPanel) { + backgroundColor = #AAAAAAFF + borderColor = #909090FF + borderRadius = 0 + borderWidth = 2 + showBorder = true + showBackground = true + padding = rect(0, 0, 0, 0) + margin = rect(0, 0, 0, 0) + textColor = $textColor + + (tabBar) { + height = 35.0 + fontSize = 18 + backgroundColor = #999999FF + borderColor = #909090FF + borderWidth = 2 + } +} +(@panel_tab panel) { + backgroundColor = color_crimson +} +% =================== + + + % ====== Scrollbar ====== (scrollbar) { (thumb) { @@ -156,20 +183,9 @@ window.backgroundColor = rgba(160, 160, 160, 255) } % ==================== + + +% ====== Image ====== (image) { - path = file("img.png") - animated = true - animation = anim(frameCount: 36, \ - fps: 60, \ - columns: 9, \ - rows: 4, \ - frameWidth: 256, \ - frameHeight: 256) - useBackgroundGradient = true - backgroundGradient = gradientV(color_crimson - $accentColorDark) - showBorder = true - borderWidth = 1 - borderColor = $accentColorDark - borderRadius = 0 - padding = rect(15, 15, 15, 15) } +% =================== diff --git a/other/build.nr b/other/build.nr index 4adb23b..0bbd854 100644 --- a/other/build.nr +++ b/other/build.nr @@ -1 +1 @@ -2072 +2073 diff --git a/src/ogfx/gui/widgets/Containers.cpp b/src/ogfx/gui/widgets/Containers.cpp index 69bc7f1..6d44f3f 100644 --- a/src/ogfx/gui/widgets/Containers.cpp +++ b/src/ogfx/gui/widgets/Containers.cpp @@ -136,13 +136,23 @@ namespace ogfx disableFocus(); enableStopEvents(); setStylesheetCategoryName("tabPanel"); + enableBackground(); + enableBorder(); + setTabBarHeight(m_tabBarHeight); validate(); return *this; } void TabPanel::applyTheme(const ostd::Stylesheet& theme) { + setTabBarHeight(getThemeValue(theme, "tabBar.height", m_tabBarHeight)); + for (auto& tab : m_tabs) + { + if (tab == nullptr) + continue; + tab->setBackgroundColor(getBackgroundColor()); + } } void TabPanel::onMouseReleased(const Event& event) @@ -152,13 +162,19 @@ namespace ogfx void TabPanel::onDraw(ogfx::BasicRenderer2D& gfx) { - + gfx.outlinedRoundRect({ getGlobalPosition(), { getw(), m_tabBarHeight } }, m_tabBarBackgroundColor, m_tabBarBorderColor, m_tabBarBorderRadii, getBorderWidth()); + draw_tabs(gfx); } Panel& TabPanel::addTab(const String& title) { m_tabs.push_back(std::make_unique(getWindow())); auto& tab = *m_tabs.back(); + tab.setTitle(title); + tab.setTitlebarType(Panel::TitleBarTypes::None); + if (m_currentTab == nullptr && m_tabs.size() == 2) + setCurrentTab(tab); + tab.addThemeID("panel_tab"); // Initialization code here return tab; } @@ -211,6 +227,15 @@ namespace ogfx return true; } + void TabPanel::setTabBarHeight(f32 height) + { + if (height == m_tabBarHeight) + return; + m_tabBarHeight = height; + m_tabBarBorderRadii = { cast(getBorderRadius()), cast(getBorderRadius()), 0, 0 }; + setContentOffset({ 0, m_tabBarHeight }); + } + void TabPanel::prepare_for_current_tab_removal(void) { if (m_currentTab == nullptr) @@ -230,6 +255,38 @@ namespace ogfx else m_currentTab = (it + 1)->get(); // first tab being removed, go right } + + void TabPanel::draw_tabs(ogfx::BasicRenderer2D& gfx) + { + f32 nextTabX = 2; + for (const auto& _tab : m_tabs) + { + if (_tab == nullptr) continue; + const auto& tab = *_tab; + auto titleBounds = gfx.getStringDimensions(tab.getTitle(), getFontSize()); + auto glob = getGlobalPosition(); + Rectangle tabBounds { glob + Vec2 { nextTabX, 2}, { titleBounds.x + (m_tabSidePadding * 2), m_tabBarHeight - 3 } }; + if (m_currentTab == _tab.get()) + { + gfx.outlinedRect(tabBounds, tab.getBackgroundColor(), Colors::Black, 1, true, true, false, true); + } + else + { + gfx.drawRect(tabBounds, Colors::Black, 1, true, true, false, true); + } + gfx.drawCenteredString(tab.getTitle(), tabBounds, getTextColor(), getFontSize()); + nextTabX += titleBounds.x + (m_tabSidePadding * 2); + + // f32 titleY = (m_tabBarHeight / 2.0f) - (titleBounds.y / 2.0f); + // if (m_currentTab == _tab.get()) + // { + // gfx.fillRect({ getGlobalPosition() + Vec2 { nextTabX, 0 }, { (titleBounds.x * 2), m_tabBarHeight } }, tab.getBackgroundColor()); + // } + // gfx.drawString(tab.getTitle(), getGlobalPosition() + Vec2 { nextTabX + m_tabSidePadding, titleY }, getTextColor(), getFontSize()); + // nextTabX += titleBounds.x + (m_tabSidePadding * 2); + // gfx.drawLine({ getGlobalPosition() + Vec2 { nextTabX, 0 }, getGlobalPosition() + Vec2 { nextTabX, m_tabBarHeight - 3 } }, Colors::Black, 2); + } + } } } } diff --git a/src/ogfx/gui/widgets/Containers.hpp b/src/ogfx/gui/widgets/Containers.hpp index 93b1909..4204eaa 100644 --- a/src/ogfx/gui/widgets/Containers.hpp +++ b/src/ogfx/gui/widgets/Containers.hpp @@ -82,15 +82,21 @@ namespace ogfx bool removeTab(const String& title); bool setCurrentTab(Panel& tab); bool setCurrentTab(i32 index); + void setTabBarHeight(f32 height); private: void prepare_for_current_tab_removal(void); + void draw_tabs(ogfx::BasicRenderer2D& gfx); private: stdvec> m_tabs; Panel* m_currentTab { nullptr }; + Rectangle m_tabBarBorderRadii { 0, 0, 0, 0 }; - f32 m_tabBarHeight { 30 }; + f32 m_tabBarHeight { 35 }; + Color m_tabBarBackgroundColor { 120, 120, 120 }; + Color m_tabBarBorderColor { 170, 170, 170 }; + f32 m_tabSidePadding { 20 }; }; } } diff --git a/src/ogfx/render/BasicRenderer.cpp b/src/ogfx/render/BasicRenderer.cpp index 9f4d07d..a96c972 100644 --- a/src/ogfx/render/BasicRenderer.cpp +++ b/src/ogfx/render/BasicRenderer.cpp @@ -477,7 +477,7 @@ namespace ogfx 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, bool topEdge, bool rightEdge, bool bottomEdge, bool leftEdge) { if (!m_initialized || thickness <= 0) return; @@ -491,21 +491,25 @@ namespace ogfx f32 y2 = rect.y + rect.h - half; // Top - drawLine({ {x1, y1}, {x2, y1} }, color, thickness, false); + if (topEdge) + drawLine({ {x1, y1}, {x2, y1} }, color, thickness, false); // Right - drawLine({ {x2, y1 - half}, {x2, y2 + half} }, color, thickness, false); + if (rightEdge) + drawLine({ {x2, y1 - half}, {x2, y2 + half} }, color, thickness, false); // Bottom - drawLine({ {x2, y2}, {x1, y2} }, color, thickness, false); + if (bottomEdge) + drawLine({ {x2, y2}, {x1, y2} }, color, thickness, false); // Left - drawLine({ {x1, y2 + half}, {x1, y1 - half} }, color, thickness, false); + if (leftEdge) + drawLine({ {x1, y2 + half}, {x1, y1 - half} }, color, thickness, false); } - void BasicRenderer2D::drawRect(const Vec2& center, const Vec2& size, const Color& color, i32 thickness) + void BasicRenderer2D::drawRect(const Vec2& center, const Vec2& size, const Color& color, i32 thickness, bool topEdge, bool rightEdge, bool bottomEdge, bool leftEdge) { - drawRect({ center.x - size.x * 0.5f, center.y - size.y * 0.5f, size.x, size.y }, color, thickness); + drawRect({ center.x - size.x * 0.5f, center.y - size.y * 0.5f, size.x, size.y }, color, thickness, topEdge, rightEdge, bottomEdge, leftEdge); } void BasicRenderer2D::drawRoundRect(const Rectangle& rect, const Color& color, f32 radius, i32 thickness) @@ -864,17 +868,17 @@ namespace ogfx - void BasicRenderer2D::outlinedRect(const Rectangle& rect, const Color& fillColor, const Color& outlineColor, i32 outlineThickness) + void BasicRenderer2D::outlinedRect(const Rectangle& rect, const Color& fillColor, const Color& outlineColor, i32 outlineThickness, bool topEdge, bool rightEdge, bool bottomEdge, bool leftEdge) { if (!m_initialized) return; Rectangle offset = { 1, 1, -2, -2 }; fillRect(rect + offset, fillColor); - drawRect(rect, outlineColor, outlineThickness); + drawRect(rect, outlineColor, outlineThickness, topEdge, rightEdge, bottomEdge, leftEdge); } - void BasicRenderer2D::outlinedRect(const Vec2& center, const Vec2& size, const Color& fillColor, const Color& outlineColor, i32 outlineThickness) + void BasicRenderer2D::outlinedRect(const Vec2& center, const Vec2& size, const Color& fillColor, const Color& outlineColor, i32 outlineThickness, bool topEdge, bool rightEdge, bool bottomEdge, bool leftEdge) { - outlinedRect({ center.x - size.x * 0.5f, center.y - size.y * 0.5f, size.x, size.y }, fillColor, outlineColor, outlineThickness); + outlinedRect({ center.x - size.x * 0.5f, center.y - size.y * 0.5f, size.x, size.y }, fillColor, outlineColor, outlineThickness, topEdge, rightEdge, bottomEdge, leftEdge); } void BasicRenderer2D::outlinedRoundRect(const Rectangle& rect, const Color& fillColor, const Color& outlineColor, f32 radius, i32 outlineThickness) diff --git a/src/ogfx/render/BasicRenderer.hpp b/src/ogfx/render/BasicRenderer.hpp index 3cef174..9f7297d 100644 --- a/src/ogfx/render/BasicRenderer.hpp +++ b/src/ogfx/render/BasicRenderer.hpp @@ -88,8 +88,8 @@ namespace ogfx void drawCenteredString(const String& str, const Rectangle& bounds, const Color& color, i32 fontSize = 0, f32 scale = 1.0f); void drawLine(const FLine& line, const Color& color, i32 thickness = 1, bool rounded = true); - void drawRect(const Rectangle& rect, const Color& color, i32 thickness = 1); - void drawRect(const Vec2& center, const Vec2& size, const Color& color, i32 thickness = 1); + void drawRect(const Rectangle& rect, const Color& color, i32 thickness = 1, bool topEdge = true, bool rightEdge = true, bool bottomEdge = true, bool leftEdge = true); + void drawRect(const Vec2& center, const Vec2& size, const Color& color, i32 thickness = 1, bool topEdge = true, bool rightEdge = true, bool bottomEdge = true, bool leftEdge = true); 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); @@ -112,8 +112,8 @@ namespace ogfx void fillTriangle(const Triangle& tri, const Color& color); void fillTriangle(const Vec2& A, const Vec2& B, const Vec2& C, const Color& color); - void outlinedRect(const Vec2& center, const Vec2& size, const Color& fillColor, const Color& outlineColor, i32 outlineThickness = 1); - void outlinedRect(const Rectangle& rect, const Color& fillColor, const Color& outlineColor, i32 outlineThickness = 1); + void outlinedRect(const Vec2& center, const Vec2& size, const Color& fillColor, const Color& outlineColor, i32 outlineThickness = 1, bool topEdge = true, bool rightEdge = true, bool bottomEdge = true, bool leftEdge = true); + void outlinedRect(const Rectangle& rect, const Color& fillColor, const Color& outlineColor, i32 outlineThickness = 1, bool topEdge = true, bool rightEdge = true, bool bottomEdge = true, bool leftEdge = true); 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); diff --git a/src/ostd/data/Color.cpp b/src/ostd/data/Color.cpp index 7c9ba51..b2d9336 100755 --- a/src/ostd/data/Color.cpp +++ b/src/ostd/data/Color.cpp @@ -208,15 +208,40 @@ namespace ostd { if (m_dirty) { - m_cachedFloat = { - r.value * (1.0f / 255.0f), - g.value * (1.0f / 255.0f), - b.value * (1.0f / 255.0f), - a.value * (1.0f / 255.0f) - }; - m_dirty = false; - } - return m_cachedFloat; + m_cachedFloat = { + r.value * (1.0f / 255.0f), + g.value * (1.0f / 255.0f), + b.value * (1.0f / 255.0f), + a.value * (1.0f / 255.0f) + }; + m_dirty = false; + } + return m_cachedFloat; + } + + Color Color::darkened(f32 amount) const + { + f32 h, s, v; + RGBtoHSV(r.value / 255.0f, g.value / 255.0f, b.value / 255.0f, h, s, v); + v = std::clamp(v - amount, 0.0f, 1.0f); + f32 nr, ng, nb; + HSVtoRGB(h, s, v, nr, ng, nb); + return { + cast(nr * 255), + cast(ng * 255), + cast(nb * 255), + cast(a.value) + }; + } + + Color Color::scaled(f32 factor) const + { + return { + cast(std::clamp(r.value * factor, 0.0f, 255.0f)), + cast(std::clamp(g.value * factor, 0.0f, 255.0f)), + cast(std::clamp(b.value * factor, 0.0f, 255.0f)), + cast(a.value) + }; } String Color::toString(void) const @@ -237,45 +262,45 @@ namespace ostd void Color::RGBtoHSV(f32 r, f32 g, f32 b, f32& h, f32& s, f32& v) { - f32 max = std::max({r, g, b}); - f32 min = std::min({r, g, b}); - f32 d = max - min; + f32 max = std::max({r, g, b}); + f32 min = std::min({r, g, b}); + f32 d = max - min; - v = max; + v = max; - if (max == 0) - { - s = 0; - h = 0; - return; - } + if (max == 0) + { + s = 0; + h = 0; + return; + } - s = d / max; + s = d / max; - if (max == r) h = std::fmod((g - b) / d + 6.0f, 6.0f); - else if (max == g) h = (b - r) / d + 2.0f; - else h = (r - g) / d + 4.0f; + if (max == r) h = std::fmod((g - b) / d + 6.0f, 6.0f); + else if (max == g) h = (b - r) / d + 2.0f; + else h = (r - g) / d + 4.0f; - h /= 6.0f; + h /= 6.0f; } void Color::HSVtoRGB(f32 h, f32 s, f32 v, f32& r, f32& g, f32& b) { - i32 i = cast(h * 6); - f32 f = h * 6 - i; - f32 p = v * (1 - s); - f32 q = v * (1 - f * s); - f32 t = v * (1 - (1 - f) * s); + i32 i = cast(h * 6); + f32 f = h * 6 - i; + f32 p = v * (1 - s); + f32 q = v * (1 - f * s); + f32 t = v * (1 - (1 - f) * s); - switch (i % 6) - { - case 0: r = v, g = t, b = p; break; - case 1: r = q, g = v, b = p; break; - case 2: r = p, g = v, b = t; break; - case 3: r = p, g = q, b = v; break; - case 4: r = t, g = p, b = v; break; - case 5: r = v, g = p, b = q; break; - } + switch (i % 6) + { + case 0: r = v, g = t, b = p; break; + case 1: r = q, g = v, b = p; break; + case 2: r = p, g = v, b = t; break; + case 3: r = p, g = q, b = v; break; + case 4: r = t, g = p, b = v; break; + case 5: r = v, g = p, b = q; break; + } } } diff --git a/src/ostd/data/Color.hpp b/src/ostd/data/Color.hpp index 6c10cc1..6c7c105 100755 --- a/src/ostd/data/Color.hpp +++ b/src/ostd/data/Color.hpp @@ -79,6 +79,13 @@ namespace ostd u32 asInteger(eColorFormat format = eColorFormat::RGBA) const; FloatCol getNormalizedColor(void) const; + inline Color& darken(f32 amount) { Color c = darkened(amount); set(c.r, c.g, c.b, c.a); return *this; } + inline Color& lighten(f32 amount) { Color c = lightened(amount); set(c.r, c.g, c.b, c.a); return *this; } + inline Color& scale(f32 amount) { Color c = scaled(amount); set(c.r, c.g, c.b, c.a); return *this; } + inline Color lightened(f32 amount) const { return darkened(amount); } + Color darkened(f32 amount) const; + Color scaled(f32 factor) const; + String toString(void) const override; void print(bool newLine = true, OutputHandlerBase* __destination = nullptr) const override; inline void invalidate(void) override { } diff --git a/src/test/GuiTest.cpp b/src/test/GuiTest.cpp index 9cd07e8..1d9a700 100644 --- a/src/test/GuiTest.cpp +++ b/src/test/GuiTest.cpp @@ -32,6 +32,18 @@ class Window : public ogfx::gui::Window inline Window(void) { } inline void onInitialize(void) override { + ogfx::AnimationData ad; + ad.frameCount = 36; + ad.fps = 60; + ad.columns = 9; + ad.rows = 4; + ad.frameWidth = 256; + ad.frameHeight = 256; + m_img.setAnimationData(ad); + m_img.setImage("./img.png"); + m_img.enableAnimated(); + m_img.setSize(256, 256); + m_label1.setText("Show Panel2"); m_label1.setCallback(ogfx::gui::Widget::eCallback::MousePressed, [&](const ogfx::gui::Event& event) -> void { m_check1.setChecked(!m_check1.isChecked()); @@ -78,6 +90,11 @@ class Window : public ogfx::gui::Window m_panel2.setSize(600, 400); m_panel2.setTitle("Panel 2"); + m_tabs.setSize(400, 400); + m_tabs.addTab("Tab1"); + m_tabs.addTab("Tab2 Test"); + m_tabs.addTab("Long Tab Test"); + m_panel3.addWidget(m_label4); m_panel1.addWidget(m_label2); @@ -88,10 +105,11 @@ class Window : public ogfx::gui::Window m_panel2.addWidget(m_panel1, { 400, 50 }); m_panel2.addWidget(m_label1, { 0, 500 }); m_panel2.addWidget(m_btn1, { 0, 300 }); - m_panel2.addWidget(m_img, { 20, 150 }); + m_panel2.addWidget(m_img, { 20, 50 }); addWidget(m_check1, { 30, 30 }); addWidget(m_panel2, { 500, 100 }); + addWidget(m_tabs, { 20, 120 }); m_theme.loadFromFile("./DefaultTheme.oss", true, getDefaultStylesheetVariableList()); setTheme(m_theme); @@ -109,6 +127,7 @@ class Window : public ogfx::gui::Window void onRedraw(ogfx::BasicRenderer2D& gfx) override { + gfx.fillRect(m_tabs.getGlobalPureContentBounds(), { 0, 255, 0, 100 }); } void onFixedUpdate(void) override @@ -127,6 +146,7 @@ class Window : public ogfx::gui::Window CheckBox m_check1 { *this }; Button m_btn1 { *this }; ImageLabel m_img { *this }; + TabPanel m_tabs { *this }; ostd::Stylesheet m_theme; }; @@ -138,7 +158,7 @@ i32 main(i32 argc, char** argv) window.initialize(1200, 800, "OmniaFramework - Test Window"); window.setClearColor({ 0, 0, 0 }); window.setPosition({ 50, 50 }); - window.setWindowState(ogfx::WindowCore::eWindowState::Maximized); + // window.setWindowState(ogfx::WindowCore::eWindowState::Maximized); window.mainLoop(); return 0; }