From cda204aec96106d35d75b414fcc6969d1d061b85 Mon Sep 17 00:00:00 2001 From: OmniaX-Dev Date: Tue, 21 Apr 2026 05:46:45 +0200 Subject: [PATCH] Added TabPanel widget --- extra/DefaultTheme.oss | 17 +++-- other/TODO.txt | 1 + src/ogfx/gui/widgets/Containers.cpp | 99 +++++++++++++++++++++-------- src/ogfx/gui/widgets/Containers.hpp | 15 ++++- src/ogfx/gui/widgets/Scrollbar.cpp | 36 +++++++++++ src/ogfx/gui/widgets/Scrollbar.hpp | 7 +- src/ogfx/gui/widgets/Widget.hpp | 2 + src/test/GuiTest.cpp | 24 ++++--- 8 files changed, 153 insertions(+), 48 deletions(-) diff --git a/extra/DefaultTheme.oss b/extra/DefaultTheme.oss index 383dcb0..b965f0a 100644 --- a/extra/DefaultTheme.oss +++ b/extra/DefaultTheme.oss @@ -21,7 +21,7 @@ window.backgroundColor = rgba(160, 160, 160, 255) titlebarColor = #999999FF titlebarBorderColor = #909090FF borderColor = #909090FF - borderRadius = 10 + borderRadius = 0 borderWidth = 2 titlebarBorderWidth = 2 showBorder = true @@ -39,30 +39,29 @@ window.backgroundColor = rgba(160, 160, 160, 255) -% ====== Panel ====== +% ====== TabPanel ====== (tabPanel) { backgroundColor = #AAAAAAFF - borderColor = #909090FF + borderColor = #666666FF borderRadius = 0 borderWidth = 2 showBorder = true showBackground = true - padding = rect(0, 0, 0, 0) margin = rect(0, 0, 0, 0) textColor = $textColor + fontSize = 18 (tabBar) { height = 35.0 - fontSize = 18 - backgroundColor = #999999FF + backgroundColor = #888888FF borderColor = #909090FF - borderWidth = 2 + borderWidth = 1 + sidePadding = 20.0 } } (@panel_tab panel) { - backgroundColor = color_crimson } -% =================== +% ====================== diff --git a/other/TODO.txt b/other/TODO.txt index 5639c2a..0015214 100644 --- a/other/TODO.txt +++ b/other/TODO.txt @@ -55,3 +55,4 @@ Implement following widgets: Vertical Horizontal Grid + Tooltips diff --git a/src/ogfx/gui/widgets/Containers.cpp b/src/ogfx/gui/widgets/Containers.cpp index 6d44f3f..5828924 100644 --- a/src/ogfx/gui/widgets/Containers.cpp +++ b/src/ogfx/gui/widgets/Containers.cpp @@ -146,23 +146,51 @@ namespace ogfx void TabPanel::applyTheme(const ostd::Stylesheet& theme) { setTabBarHeight(getThemeValue(theme, "tabBar.height", m_tabBarHeight)); + setTabBarBackgroundColor(getThemeValue(theme, "tabBar.backgroundColor", m_tabBarBackgroundColor)); + setTabBarBorderColor(getThemeValue(theme, "tabBar.borderColor", m_tabBarBorderColor)); + setTabBarBorderColor(getThemeValue(theme, "tabBar.borderWidth", m_tabBarBorderWidth)); + setTabBarSidePadding(getThemeValue(theme, "tabBar.sidePadding", m_tabSidePadding)); + } - for (auto& tab : m_tabs) + void TabPanel::onMousePressed(const Event& event) + { + if (!isMouseInsideTabBar({ event.mouse->position_x, event.mouse->position_y })) + return; + const f32 extra_offset = 5; + i32 index = 0; + for (auto& b : m_tabBoundsList) { - if (tab == nullptr) - continue; - tab->setBackgroundColor(getBackgroundColor()); + if (b.contains(event.mouse->position_x, event.mouse->position_y)) + { + setCurrentTab(*m_tabs[index]); + Rectangle tabBounds = m_tabBoundsList[index]; + f32 tabLocalLeft = tabBounds.x - getGlobalPosition().x; + f32 tabLocalRight = tabLocalLeft + tabBounds.w; + + if (tabLocalLeft < 0) + m_tabScrollOffset += tabLocalLeft - extra_offset; + else if (tabLocalRight > getw()) + m_tabScrollOffset += (tabLocalRight - getw()) + extra_offset; + break; + } + index++; } } - void TabPanel::onMouseReleased(const Event& event) + void TabPanel::onMouseScrolled(const Event& event) { - + if (!isMouseInsideTabBar({ event.mouse->position_x, event.mouse->position_y })) + return; + if (m_tabs.size() == 0) + return; + const f32 extra_offset = 5; + m_tabScrollOffset += (event.mouse->scrollAmount.y * -40.0f); + m_tabScrollOffset = std::clamp(m_tabScrollOffset, -extra_offset, m_totalTabWidth - getw() + extra_offset); } void TabPanel::onDraw(ogfx::BasicRenderer2D& gfx) { - gfx.outlinedRoundRect({ getGlobalPosition(), { getw(), m_tabBarHeight } }, m_tabBarBackgroundColor, m_tabBarBorderColor, m_tabBarBorderRadii, getBorderWidth()); + gfx.outlinedRect({ getGlobalPosition(), { getw(), getTabBarHeight() } }, getTabBarBackgroundColor(), getTabBarBorderColor(), getTabBarBorderWidth(), false, false, true, false); draw_tabs(gfx); } @@ -172,10 +200,13 @@ namespace ogfx auto& tab = *m_tabs.back(); tab.setTitle(title); tab.setTitlebarType(Panel::TitleBarTypes::None); - if (m_currentTab == nullptr && m_tabs.size() == 2) + tab.addThemeOverride("@panel_tab.panel.titlebarType", "none"); + tab.addThemeOverride("@panel_tab.panel.showBorder", false); + tab.addThemeOverride("@panel_tab.panel.borderRadius", 0); + tab.setBackgroundColor(getBackgroundColor()); + if (m_currentTab == nullptr && m_tabs.size() == 1) setCurrentTab(tab); tab.addThemeID("panel_tab"); - // Initialization code here return tab; } @@ -215,27 +246,42 @@ namespace ogfx auto it = std::find_if(m_tabs.begin(), m_tabs.end(), [&](const std::unique_ptr& p) { return p.get() == &tab; }); if (it == m_tabs.end()) return false; - m_currentTab = it->get(); - return true; + i32 index = it - m_tabs.begin(); + return setCurrentTab(index); } bool TabPanel::setCurrentTab(i32 index) { if (index < 0 || index >= (i32)m_tabs.size()) return false; - m_currentTab = m_tabs[index].get(); - return true; + + auto curr = m_tabs[index].get(); + if (curr == m_currentTab && m_currentTab != nullptr) + return false; + removeWidget(*m_currentTab); + m_currentTabIndex = index; + m_currentTab = curr; + m_currentTab->setMargin({ 0, 0, 0, 0 }); + m_currentTab->setSize(getPureContentBounds().getSize()); + m_currentTab->setPosition({ 0, 0 }); + m_currentTab->reloadTheme(true); + m_currentTab->resetScroll(); + return addWidget(*m_currentTab); } 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 }); } + bool TabPanel::isMouseInsideTabBar(const Vec2& mousePos) + { + Rectangle bounds { getGlobalPosition(), { getw(), getTabBarHeight() } }; + return bounds.contains(mousePos, true); + } + void TabPanel::prepare_for_current_tab_removal(void) { if (m_currentTab == nullptr) @@ -258,34 +304,31 @@ namespace ogfx void TabPanel::draw_tabs(ogfx::BasicRenderer2D& gfx) { - f32 nextTabX = 2; + f32 nextTabX = 1 - m_tabScrollOffset; + m_tabBoundsList.clear(); + i32 index = -1; for (const auto& _tab : m_tabs) { + index++; 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 } }; + Rectangle tabBounds { glob + Vec2 { nextTabX, 2}, { titleBounds.x + (m_tabSidePadding * 2), m_tabBarHeight } }; if (m_currentTab == _tab.get()) { - gfx.outlinedRect(tabBounds, tab.getBackgroundColor(), Colors::Black, 1, true, true, false, true); + gfx.outlinedRect(tabBounds, tab.getBackgroundColor(), getTabBarBorderColor(), getTabBarBorderWidth(), true, true, true, true); } else { - gfx.drawRect(tabBounds, Colors::Black, 1, true, true, false, true); + bool draw_right_edge = !(m_currentTabIndex == (index + 1)); + gfx.drawRect(tabBounds - Rectangle { 0, 0, 0, 2 }, getTabBarBorderColor(), getTabBarBorderWidth(), false, draw_right_edge, true, false); } 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); + m_tabBoundsList.push_back(tabBounds); } + m_totalTabWidth = nextTabX + m_tabScrollOffset - 1; } } } diff --git a/src/ogfx/gui/widgets/Containers.hpp b/src/ogfx/gui/widgets/Containers.hpp index 4204eaa..db76967 100644 --- a/src/ogfx/gui/widgets/Containers.hpp +++ b/src/ogfx/gui/widgets/Containers.hpp @@ -74,7 +74,8 @@ namespace ogfx 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 onMousePressed(const Event& event) override; + void onMouseScrolled(const Event& event) override; void onDraw(ogfx::BasicRenderer2D& gfx) override; Panel& addTab(const String& title); bool removeTab(Panel& tab); @@ -83,6 +84,13 @@ namespace ogfx bool setCurrentTab(Panel& tab); bool setCurrentTab(i32 index); void setTabBarHeight(f32 height); + bool isMouseInsideTabBar(const Vec2& mousePos); + + inline f32 getTabBarHeight(void) const { return m_tabBarHeight; } + OSTD_PARAM_GETSET(Color, TabBarBackgroundColor, m_tabBarBackgroundColor); + OSTD_PARAM_GETSET(Color, TabBarBorderColor, m_tabBarBorderColor); + OSTD_PARAM_GETSET(f32, TabBarSidePadding, m_tabSidePadding); + OSTD_PARAM_GETSET(i32, TabBarBorderWidth, m_tabBarBorderWidth); private: void prepare_for_current_tab_removal(void); @@ -90,13 +98,18 @@ namespace ogfx private: stdvec> m_tabs; + stdvec m_tabBoundsList; Panel* m_currentTab { nullptr }; + i32 m_currentTabIndex { -1 }; + f32 m_tabScrollOffset { 0.0f }; + f32 m_totalTabWidth { 0.0f }; Rectangle m_tabBarBorderRadii { 0, 0, 0, 0 }; f32 m_tabBarHeight { 35 }; Color m_tabBarBackgroundColor { 120, 120, 120 }; Color m_tabBarBorderColor { 170, 170, 170 }; f32 m_tabSidePadding { 20 }; + i32 m_tabBarBorderWidth { 2 }; }; } } diff --git a/src/ogfx/gui/widgets/Scrollbar.cpp b/src/ogfx/gui/widgets/Scrollbar.cpp index 00b3282..91475c3 100644 --- a/src/ogfx/gui/widgets/Scrollbar.cpp +++ b/src/ogfx/gui/widgets/Scrollbar.cpp @@ -306,6 +306,26 @@ namespace ogfx void ScrollableWidget::onUpdate(void) { m_smoothScrollTimer.update(); + if (!needsVScroll() && m_vScrollbarAdded) + { + removeWidget(m_vScrollbar); + m_vScrollbarAdded = false; + } + else if (!m_vScrollbarAdded) + { + addWidget(m_vScrollbar); + m_vScrollbarAdded = true; + } + if (!needsHScroll() && m_hScrollbarAdded) + { + removeWidget(m_hScrollbar); + m_hScrollbarAdded = false; + } + else if (!m_hScrollbarAdded) + { + addWidget(m_hScrollbar); + m_hScrollbarAdded = true; + } } void ScrollableWidget::drawScrollbars(ogfx::BasicRenderer2D& gfx) @@ -319,6 +339,22 @@ namespace ogfx m_vScrollbar.setMargin({ 0, getPureContentBounds().y, 0, 0 }); } + void ScrollableWidget::resetScroll(bool horizontal, bool vertical, bool propagate) + { + if (vertical) + m_scrollOffset.y = 0; + if (horizontal) + m_scrollOffset.x = 0; + if (propagate) + { + for (auto& w : getChildren().getWidgets()) + { + if (w == nullptr) continue; + w->resetScroll(horizontal, vertical, propagate); + } + } + } + void ScrollableWidget::onMouseScrolled(const Event& event) { if (isVScrollEnabled()) diff --git a/src/ogfx/gui/widgets/Scrollbar.hpp b/src/ogfx/gui/widgets/Scrollbar.hpp index 3fa495f..4e30a0a 100644 --- a/src/ogfx/gui/widgets/Scrollbar.hpp +++ b/src/ogfx/gui/widgets/Scrollbar.hpp @@ -60,7 +60,7 @@ namespace ogfx f32 m_dragGrabOffset { 0 }; Rectangle m_correctionOffset { 1, 1, 0, 0 }; - Rectangle m_trackBorderRadii { 0, 0, 10, 0 }; + Rectangle m_trackBorderRadii { 0, 0, 0, 0 }; f32 m_thumbBorderRadius { 16 }; Color m_trackColor { 70, 70, 70 }; Color m_thumbColor { 120, 120, 120 }; @@ -98,7 +98,7 @@ namespace ogfx f32 m_dragGrabOffset { 0 }; Rectangle m_correctionOffset { 0, 0, 0, 0 }; - Rectangle m_trackBorderRadii { 0, 0, 0, 10 }; + Rectangle m_trackBorderRadii { 0, 0, 0, 0 }; f32 m_thumbBorderRadius { 16 }; Color m_trackColor { 70, 70, 70 }; Color m_thumbColor { 120, 120, 120 }; @@ -113,6 +113,7 @@ namespace ogfx virtual void onUpdate(void) override; void drawScrollbars(ogfx::BasicRenderer2D& gfx); void updateScrollbarsSize(void); + void resetScroll(bool horizontal = true, bool vertical = true, bool propagate = true) override; virtual void onMouseScrolled(const Event& event) override; virtual void setScrollOffset(const Vec2& offset) override; virtual void addScrollOffset(const Vec2& offset) override; @@ -137,6 +138,8 @@ namespace ogfx Vec2 m_scrollVelocity { 0.0f, 0.0f }; f32 m_scrollSmoothFactor { 0.7f }; f32 m_scrollSpeedMultiplier { 15.0f }; + bool m_hScrollbarAdded { false }; + bool m_vScrollbarAdded { false }; }; } } diff --git a/src/ogfx/gui/widgets/Widget.hpp b/src/ogfx/gui/widgets/Widget.hpp index d4b1f76..fb21329 100644 --- a/src/ogfx/gui/widgets/Widget.hpp +++ b/src/ogfx/gui/widgets/Widget.hpp @@ -129,6 +129,7 @@ namespace ogfx inline const stdvec& getThemeIDList(void) const { return m_themeIDList; } inline const ostd::Stylesheet::QualifierList& getThemeQualifierList(void) const { return m_qualifierList; } inline String getStylesheetCategoryName(void) const { return m_stylesheetCategoryName; } + inline WidgetManager& getChildren(void) { return m_widgets; } OSTD_PARAM_GETSET(Color, TextColor, m_textColor); OSTD_PARAM_GETSET(Color, BackgroundColor, m_backgroundColor); OSTD_PARAM_GETSET(Color, BorderColor, m_borderColor); @@ -274,6 +275,7 @@ namespace ogfx inline virtual void addScrollOffset(const Vec2& offset) { } inline virtual bool needsVScroll(void) const { return false; } inline virtual bool needsHScroll(void) const { return false; } + inline virtual void resetScroll(bool horizontal = true, bool vertical = true, bool propagate = true) { } inline virtual f32 getVScrollbarSize(void) const { return 0; } inline virtual f32 getHScrollbarSize(void) const { return 0; } // ===================================================== diff --git a/src/test/GuiTest.cpp b/src/test/GuiTest.cpp index 1d9a700..cacc011 100644 --- a/src/test/GuiTest.cpp +++ b/src/test/GuiTest.cpp @@ -90,10 +90,18 @@ 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_tabs.setSize(900, 700); + auto& t1 = m_tabs.addTab("Tab1"); + auto& t2 = m_tabs.addTab("Tab2 Test"); + auto& t3 = m_tabs.addTab("Long Tab Test"); + for (i32 i = 3; i < 15; i++) + m_tabs.addTab(String("Tab").add(i)); + + t2.addThemeOverride("@panel_tab.panel.backgroundColor", Colors::SkyBlue); + t3.addThemeOverride("@panel_tab.panel.backgroundColor", Colors::Orange); + + t1.addWidget(m_check1, { 30, 30 }); + t2.addWidget(m_panel2, { 500, 100 }); m_panel3.addWidget(m_label4); @@ -107,9 +115,9 @@ class Window : public ogfx::gui::Window m_panel2.addWidget(m_btn1, { 0, 300 }); m_panel2.addWidget(m_img, { 20, 50 }); - addWidget(m_check1, { 30, 30 }); - addWidget(m_panel2, { 500, 100 }); - addWidget(m_tabs, { 20, 120 }); + + + addWidget(m_tabs, { 0, 0 }); m_theme.loadFromFile("./DefaultTheme.oss", true, getDefaultStylesheetVariableList()); setTheme(m_theme); @@ -127,7 +135,7 @@ class Window : public ogfx::gui::Window void onRedraw(ogfx::BasicRenderer2D& gfx) override { - gfx.fillRect(m_tabs.getGlobalPureContentBounds(), { 0, 255, 0, 100 }); + // gfx.fillRect(m_tabs.getGlobalPureContentBounds(), { 0, 255, 0, 100 }); } void onFixedUpdate(void) override