Added TabPanel widget

This commit is contained in:
OmniaX-Dev 2026-04-21 05:46:45 +02:00
parent 5491e5d4b3
commit cda204aec9
8 changed files with 153 additions and 48 deletions

View file

@ -21,7 +21,7 @@ window.backgroundColor = rgba(160, 160, 160, 255)
titlebarColor = #999999FF titlebarColor = #999999FF
titlebarBorderColor = #909090FF titlebarBorderColor = #909090FF
borderColor = #909090FF borderColor = #909090FF
borderRadius = 10 borderRadius = 0
borderWidth = 2 borderWidth = 2
titlebarBorderWidth = 2 titlebarBorderWidth = 2
showBorder = true showBorder = true
@ -39,30 +39,29 @@ window.backgroundColor = rgba(160, 160, 160, 255)
% ====== Panel ====== % ====== TabPanel ======
(tabPanel) { (tabPanel) {
backgroundColor = #AAAAAAFF backgroundColor = #AAAAAAFF
borderColor = #909090FF borderColor = #666666FF
borderRadius = 0 borderRadius = 0
borderWidth = 2 borderWidth = 2
showBorder = true showBorder = true
showBackground = true showBackground = true
padding = rect(0, 0, 0, 0)
margin = rect(0, 0, 0, 0) margin = rect(0, 0, 0, 0)
textColor = $textColor textColor = $textColor
fontSize = 18
(tabBar) { (tabBar) {
height = 35.0 height = 35.0
fontSize = 18 backgroundColor = #888888FF
backgroundColor = #999999FF
borderColor = #909090FF borderColor = #909090FF
borderWidth = 2 borderWidth = 1
sidePadding = 20.0
} }
} }
(@panel_tab panel) { (@panel_tab panel) {
backgroundColor = color_crimson
} }
% =================== % ======================

View file

@ -55,3 +55,4 @@ Implement following widgets:
Vertical Vertical
Horizontal Horizontal
Grid Grid
Tooltips

View file

@ -146,23 +146,51 @@ namespace ogfx
void TabPanel::applyTheme(const ostd::Stylesheet& theme) void TabPanel::applyTheme(const ostd::Stylesheet& theme)
{ {
setTabBarHeight(getThemeValue<f32>(theme, "tabBar.height", m_tabBarHeight)); setTabBarHeight(getThemeValue<f32>(theme, "tabBar.height", m_tabBarHeight));
setTabBarBackgroundColor(getThemeValue<Color>(theme, "tabBar.backgroundColor", m_tabBarBackgroundColor));
setTabBarBorderColor(getThemeValue<Color>(theme, "tabBar.borderColor", m_tabBarBorderColor));
setTabBarBorderColor(getThemeValue<i32>(theme, "tabBar.borderWidth", m_tabBarBorderWidth));
setTabBarSidePadding(getThemeValue<f32>(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) if (b.contains(event.mouse->position_x, event.mouse->position_y))
continue; {
tab->setBackgroundColor(getBackgroundColor()); 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) 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); draw_tabs(gfx);
} }
@ -172,10 +200,13 @@ namespace ogfx
auto& tab = *m_tabs.back(); auto& tab = *m_tabs.back();
tab.setTitle(title); tab.setTitle(title);
tab.setTitlebarType(Panel::TitleBarTypes::None); 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); setCurrentTab(tab);
tab.addThemeID("panel_tab"); tab.addThemeID("panel_tab");
// Initialization code here
return tab; return tab;
} }
@ -215,27 +246,42 @@ namespace ogfx
auto it = std::find_if(m_tabs.begin(), m_tabs.end(), [&](const std::unique_ptr<Panel>& p) { return p.get() == &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()) if (it == m_tabs.end())
return false; return false;
m_currentTab = it->get(); i32 index = it - m_tabs.begin();
return true; return setCurrentTab(index);
} }
bool TabPanel::setCurrentTab(i32 index) bool TabPanel::setCurrentTab(i32 index)
{ {
if (index < 0 || index >= (i32)m_tabs.size()) if (index < 0 || index >= (i32)m_tabs.size())
return false; 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) void TabPanel::setTabBarHeight(f32 height)
{ {
if (height == m_tabBarHeight)
return;
m_tabBarHeight = height; m_tabBarHeight = height;
m_tabBarBorderRadii = { cast<f32>(getBorderRadius()), cast<f32>(getBorderRadius()), 0, 0 }; m_tabBarBorderRadii = { cast<f32>(getBorderRadius()), cast<f32>(getBorderRadius()), 0, 0 };
setContentOffset({ 0, m_tabBarHeight }); 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) void TabPanel::prepare_for_current_tab_removal(void)
{ {
if (m_currentTab == nullptr) if (m_currentTab == nullptr)
@ -258,34 +304,31 @@ namespace ogfx
void TabPanel::draw_tabs(ogfx::BasicRenderer2D& gfx) 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) for (const auto& _tab : m_tabs)
{ {
index++;
if (_tab == nullptr) continue; if (_tab == nullptr) continue;
const auto& tab = *_tab; const auto& tab = *_tab;
auto titleBounds = gfx.getStringDimensions(tab.getTitle(), getFontSize()); auto titleBounds = gfx.getStringDimensions(tab.getTitle(), getFontSize());
auto glob = getGlobalPosition(); 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()) 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 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()); gfx.drawCenteredString(tab.getTitle(), tabBounds, getTextColor(), getFontSize());
nextTabX += titleBounds.x + (m_tabSidePadding * 2); nextTabX += titleBounds.x + (m_tabSidePadding * 2);
m_tabBoundsList.push_back(tabBounds);
// 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_totalTabWidth = nextTabX + m_tabScrollOffset - 1;
} }
} }
} }

View file

@ -74,7 +74,8 @@ namespace ogfx
inline TabPanel(WindowCore& window) : Widget({ 0, 0, 0, 0 }, window) { create(); } inline TabPanel(WindowCore& window) : Widget({ 0, 0, 0, 0 }, window) { create(); }
TabPanel& create(void); TabPanel& create(void);
void applyTheme(const ostd::Stylesheet& theme) override; 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; void onDraw(ogfx::BasicRenderer2D& gfx) override;
Panel& addTab(const String& title); Panel& addTab(const String& title);
bool removeTab(Panel& tab); bool removeTab(Panel& tab);
@ -83,6 +84,13 @@ namespace ogfx
bool setCurrentTab(Panel& tab); bool setCurrentTab(Panel& tab);
bool setCurrentTab(i32 index); bool setCurrentTab(i32 index);
void setTabBarHeight(f32 height); 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: private:
void prepare_for_current_tab_removal(void); void prepare_for_current_tab_removal(void);
@ -90,13 +98,18 @@ namespace ogfx
private: private:
stdvec<std::unique_ptr<Panel>> m_tabs; stdvec<std::unique_ptr<Panel>> m_tabs;
stdvec<Rectangle> m_tabBoundsList;
Panel* m_currentTab { nullptr }; 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 }; Rectangle m_tabBarBorderRadii { 0, 0, 0, 0 };
f32 m_tabBarHeight { 35 }; f32 m_tabBarHeight { 35 };
Color m_tabBarBackgroundColor { 120, 120, 120 }; Color m_tabBarBackgroundColor { 120, 120, 120 };
Color m_tabBarBorderColor { 170, 170, 170 }; Color m_tabBarBorderColor { 170, 170, 170 };
f32 m_tabSidePadding { 20 }; f32 m_tabSidePadding { 20 };
i32 m_tabBarBorderWidth { 2 };
}; };
} }
} }

View file

@ -306,6 +306,26 @@ namespace ogfx
void ScrollableWidget::onUpdate(void) void ScrollableWidget::onUpdate(void)
{ {
m_smoothScrollTimer.update(); 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) void ScrollableWidget::drawScrollbars(ogfx::BasicRenderer2D& gfx)
@ -319,6 +339,22 @@ namespace ogfx
m_vScrollbar.setMargin({ 0, getPureContentBounds().y, 0, 0 }); 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) void ScrollableWidget::onMouseScrolled(const Event& event)
{ {
if (isVScrollEnabled()) if (isVScrollEnabled())

View file

@ -60,7 +60,7 @@ namespace ogfx
f32 m_dragGrabOffset { 0 }; f32 m_dragGrabOffset { 0 };
Rectangle m_correctionOffset { 1, 1, 0, 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 }; f32 m_thumbBorderRadius { 16 };
Color m_trackColor { 70, 70, 70 }; Color m_trackColor { 70, 70, 70 };
Color m_thumbColor { 120, 120, 120 }; Color m_thumbColor { 120, 120, 120 };
@ -98,7 +98,7 @@ namespace ogfx
f32 m_dragGrabOffset { 0 }; f32 m_dragGrabOffset { 0 };
Rectangle m_correctionOffset { 0, 0, 0, 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 }; f32 m_thumbBorderRadius { 16 };
Color m_trackColor { 70, 70, 70 }; Color m_trackColor { 70, 70, 70 };
Color m_thumbColor { 120, 120, 120 }; Color m_thumbColor { 120, 120, 120 };
@ -113,6 +113,7 @@ namespace ogfx
virtual void onUpdate(void) override; virtual void onUpdate(void) override;
void drawScrollbars(ogfx::BasicRenderer2D& gfx); void drawScrollbars(ogfx::BasicRenderer2D& gfx);
void updateScrollbarsSize(void); void updateScrollbarsSize(void);
void resetScroll(bool horizontal = true, bool vertical = true, bool propagate = true) override;
virtual void onMouseScrolled(const Event& event) override; virtual void onMouseScrolled(const Event& event) override;
virtual void setScrollOffset(const Vec2& offset) override; virtual void setScrollOffset(const Vec2& offset) override;
virtual void addScrollOffset(const Vec2& offset) override; virtual void addScrollOffset(const Vec2& offset) override;
@ -137,6 +138,8 @@ namespace ogfx
Vec2 m_scrollVelocity { 0.0f, 0.0f }; Vec2 m_scrollVelocity { 0.0f, 0.0f };
f32 m_scrollSmoothFactor { 0.7f }; f32 m_scrollSmoothFactor { 0.7f };
f32 m_scrollSpeedMultiplier { 15.0f }; f32 m_scrollSpeedMultiplier { 15.0f };
bool m_hScrollbarAdded { false };
bool m_vScrollbarAdded { false };
}; };
} }
} }

View file

@ -129,6 +129,7 @@ namespace ogfx
inline const stdvec<String>& getThemeIDList(void) const { return m_themeIDList; } inline const stdvec<String>& getThemeIDList(void) const { return m_themeIDList; }
inline const ostd::Stylesheet::QualifierList& getThemeQualifierList(void) const { return m_qualifierList; } inline const ostd::Stylesheet::QualifierList& getThemeQualifierList(void) const { return m_qualifierList; }
inline String getStylesheetCategoryName(void) const { return m_stylesheetCategoryName; } 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, TextColor, m_textColor);
OSTD_PARAM_GETSET(Color, BackgroundColor, m_backgroundColor); OSTD_PARAM_GETSET(Color, BackgroundColor, m_backgroundColor);
OSTD_PARAM_GETSET(Color, BorderColor, m_borderColor); OSTD_PARAM_GETSET(Color, BorderColor, m_borderColor);
@ -274,6 +275,7 @@ namespace ogfx
inline virtual void addScrollOffset(const Vec2& offset) { } inline virtual void addScrollOffset(const Vec2& offset) { }
inline virtual bool needsVScroll(void) const { return false; } inline virtual bool needsVScroll(void) const { return false; }
inline virtual bool needsHScroll(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 getVScrollbarSize(void) const { return 0; }
inline virtual f32 getHScrollbarSize(void) const { return 0; } inline virtual f32 getHScrollbarSize(void) const { return 0; }
// ===================================================== // =====================================================

View file

@ -90,10 +90,18 @@ class Window : public ogfx::gui::Window
m_panel2.setSize(600, 400); m_panel2.setSize(600, 400);
m_panel2.setTitle("Panel 2"); m_panel2.setTitle("Panel 2");
m_tabs.setSize(400, 400); m_tabs.setSize(900, 700);
m_tabs.addTab("Tab1"); auto& t1 = m_tabs.addTab("Tab1");
m_tabs.addTab("Tab2 Test"); auto& t2 = m_tabs.addTab("Tab2 Test");
m_tabs.addTab("Long Tab 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); 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_btn1, { 0, 300 });
m_panel2.addWidget(m_img, { 20, 50 }); 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()); m_theme.loadFromFile("./DefaultTheme.oss", true, getDefaultStylesheetVariableList());
setTheme(m_theme); setTheme(m_theme);
@ -127,7 +135,7 @@ class Window : public ogfx::gui::Window
void onRedraw(ogfx::BasicRenderer2D& gfx) override 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 void onFixedUpdate(void) override