From 510ce50684dbdb618d6e758d7ee8d642b77a6100 Mon Sep 17 00:00:00 2001 From: OmniaX-Dev Date: Sun, 19 Apr 2026 05:42:03 +0200 Subject: [PATCH] Added Horizontal scrollbar --- extra/DefaultTheme.oss | 5 +- src/ogfx/gui/widgets/Containers.cpp | 72 +++++++++++++++++++++-------- src/ogfx/gui/widgets/Containers.hpp | 4 +- src/ogfx/gui/widgets/Scrollbar.cpp | 38 +++++++-------- src/ogfx/gui/widgets/Scrollbar.hpp | 6 +-- src/ogfx/gui/widgets/Widget.cpp | 18 +++++++- src/ogfx/gui/widgets/Widget.hpp | 16 +++++-- src/ogfx/render/BasicRenderer.cpp | 19 +++++--- src/test/GuiTest.cpp | 23 +++++++-- 9 files changed, 143 insertions(+), 58 deletions(-) diff --git a/extra/DefaultTheme.oss b/extra/DefaultTheme.oss index f7ebbaa..adf359a 100644 --- a/extra/DefaultTheme.oss +++ b/extra/DefaultTheme.oss @@ -43,13 +43,12 @@ window.backgroundColor = rgba(160, 160, 160, 255) (scrollbar) { (thumb) { color = rgb(140, 140, 140) - borderRadius = 16 + borderRadius = 0.0 borderColor = rgb(150, 150, 150) showBorder = true } (track) { color = rgb(90, 90, 90) - borderRadii = rect(0, 0, 10, 0) } width = 18.0 } @@ -145,7 +144,7 @@ window.backgroundColor = rgba(160, 160, 160, 255) } (button:pressed) { borderColor = $accentColorDark - textColor = #DDDDDDFF + textColor = #BBBBBBFF backgroundGradient = gradientV(rgb(120, 120, 120) - rgb(160, 160, 160)) } % ==================== diff --git a/src/ogfx/gui/widgets/Containers.cpp b/src/ogfx/gui/widgets/Containers.cpp index eb77cac..231c26c 100644 --- a/src/ogfx/gui/widgets/Containers.cpp +++ b/src/ogfx/gui/widgets/Containers.cpp @@ -35,7 +35,8 @@ namespace ogfx disableDrawBox(); disableFocus(); enableStopEvents(); - allowScroll(true); + allowVScroll(true); + allowHScroll(true); m_vScrollbar.setMargin({ 0, getTitlebarHeight(), 0, 0 }); m_vScrollbar.enableManualDraw(true); addWidget(m_vScrollbar); @@ -72,7 +73,6 @@ namespace ogfx setMargin(getThemeValue(theme, "panel.margin", { 0, 0, 0, 0 })); m_scrollSpeed = getThemeValue(theme, "panel.scrollSpeed", 0.8f); m_scrollSmoothFactor = std::clamp(getThemeValue(theme, "panel.scrollSmoothFactor", 0.7f), 0.0f, 1.0f); - 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); @@ -103,18 +103,34 @@ namespace ogfx void Panel::onMouseScrolled(const Event& event) { - if (!isScrollAllowed()) - return; - - if (std::abs(event.mouse->scrollAmount.y) > 0) - m_scrollVelocity.y += m_scrollSpeed * event.mouse->scrollAmount.y * 15.0f; - if (std::abs(event.mouse->scrollAmount.x) > 0) - m_scrollVelocity.x += m_scrollSpeed * event.mouse->scrollAmount.x * 15.0f; - - if (m_smoothScrollTimer.isStopped()) - m_smoothScrollTimer.restart(); - - event.handle(); + if (isVScrollAllowed()) + { + bool mouseInsideHScrollbar = m_hScrollbar.isMouseInsideThumb({ event.mouse->position_x, event.mouse->position_y }); + if (std::abs(event.mouse->scrollAmount.y) > 0 && !mouseInsideHScrollbar) + { + m_scrollVelocity.y += m_scrollSpeed * event.mouse->scrollAmount.y * m_scrollSpeedMultiplier; + if (m_smoothScrollTimer.isStopped()) + m_smoothScrollTimer.restart(); + event.handle(); + } + else if (std::abs(event.mouse->scrollAmount.y) > 0) + { + m_scrollVelocity.x += m_scrollSpeed * event.mouse->scrollAmount.y * m_scrollSpeedMultiplier; + if (m_smoothScrollTimer.isStopped()) + m_smoothScrollTimer.restart(); + event.handle(); + } + } + if (isHScrollAllowed()) + { + if (std::abs(event.mouse->scrollAmount.x) > 0) + { + m_scrollVelocity.x += m_scrollSpeed * event.mouse->scrollAmount.x * m_scrollSpeedMultiplier; + if (m_smoothScrollTimer.isStopped()) + m_smoothScrollTimer.restart(); + event.handle(); + } + } } void Panel::setTitlebarType(const String& type) @@ -122,17 +138,17 @@ namespace ogfx String t = type.new_toLower().trim(); if (t == TitleBarTypes::None) { - setPadding(m_basePadding); + setContentOffset({ 0, 0 }); m_titlebarType = TitleBarTypes::NoneValue; } else if (t == TitleBarTypes::Minimal) { - setPadding(m_basePadding + Rectangle { 0, m_titlebarHeight, 0, 0 }); + setContentOffset({ 0, m_titlebarHeight }); m_titlebarType = TitleBarTypes::MinimalValue; } else if (t == TitleBarTypes::Full) { - setPadding(m_basePadding + Rectangle { 0, m_titlebarHeight, 0, 0 }); + setContentOffset({ 0, m_titlebarHeight }); m_titlebarType = TitleBarTypes::FullValue; } } @@ -180,12 +196,12 @@ namespace ogfx bool Panel::needsVScroll(void) const { - return isScrollAllowed() && getContentExtents().h > getContentBounds().h; + return isVScrollAllowed() && getContentExtents().h > getContentBounds().h; } bool Panel::needsHScroll(void) const { - return isScrollAllowed() && getContentExtents().w > getContentBounds().w; + return isHScrollAllowed() && getContentExtents().w > getContentBounds().w; } void Panel::onWidgetAdded(Widget& child) @@ -196,6 +212,24 @@ namespace ogfx addWidget(m_hScrollbar, { 0, 0 }, true); } + f32 Panel::getVScrollbarSize(void) const + { + if (!isVScrollAllowed()) + return 0; + if (!needsVScroll()) + return 0; + return m_vScrollbar.getw(); + } + + f32 Panel::getHScrollbarSize(void) const + { + if (!isHScrollAllowed()) + return 0; + if (!needsHScroll()) + return 0; + return m_hScrollbar.geth(); + } + void Panel::draw_titlebar(BasicRenderer2D& gfx) { f32 br = cast(getBorderWidth()); diff --git a/src/ogfx/gui/widgets/Containers.hpp b/src/ogfx/gui/widgets/Containers.hpp index 01b6085..334f40b 100644 --- a/src/ogfx/gui/widgets/Containers.hpp +++ b/src/ogfx/gui/widgets/Containers.hpp @@ -57,6 +57,8 @@ namespace ogfx bool needsVScroll(void) const override; bool needsHScroll(void) const override; void onWidgetAdded(Widget& child) override; + f32 getVScrollbarSize(void) const override; + f32 getHScrollbarSize(void) const override; inline void setBackGroundColor(const Color& color) { m_backgroundColor = color; } inline Color getBackgroundColor(void) { return m_backgroundColor; } inline String getTitle(void) const { return m_title; } @@ -81,12 +83,12 @@ namespace ogfx 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 }; f32 m_scrollSpeed { 0.8f }; Vec2 m_scrollVelocity { 0.0f, 0.0f }; f32 m_scrollSmoothFactor { 0.7f }; + f32 m_scrollSpeedMultiplier { 15.0f }; }; } } diff --git a/src/ogfx/gui/widgets/Scrollbar.cpp b/src/ogfx/gui/widgets/Scrollbar.cpp index 8ed35df..b205de0 100644 --- a/src/ogfx/gui/widgets/Scrollbar.cpp +++ b/src/ogfx/gui/widgets/Scrollbar.cpp @@ -50,7 +50,6 @@ namespace ogfx m_thumbBorderColor = getThemeValue(theme, "scrollbar.thumb.borderColor", { 150, 150, 150 }); m_thumbShowBorder = getThemeValue(theme, "scrollbar.thumb.showBorder", true); m_trackColor = getThemeValue(theme, "scrollbar.track.color", { 70, 70, 70 }); - m_trackBorderRadii = getThemeValue(theme, "scrollbar.track.borderRadii", { 0, 0, 10, 0 }); } void VerticalScrollBar::afterDraw(ogfx::BasicRenderer2D& gfx) @@ -66,7 +65,7 @@ namespace ogfx if (!m_mousePressed) return; - if (is_mouse_in_thumb({ event.mouse->position_x, event.mouse->position_y })) + if (isMouseInsideThumb({ event.mouse->position_x, event.mouse->position_y })) m_mouseDragged = true; if (m_mouseDragged) @@ -103,11 +102,16 @@ namespace ogfx update_thumb(); } + bool VerticalScrollBar::isMouseInsideThumb(const Vec2& mouse_pos) + { + return m_thumbGlobalBounds.contains(mouse_pos, true); + } + void VerticalScrollBar::update_thumb(void) { x = getParent()->getSize().x - w; y = 0; - h = getParent()->getGlobalContentBounds().h; + h = getParent()->getGlobalPureContentBounds().h; auto ext = getParent()->getContentExtents(); auto cont = getParent()->getContentBounds(); @@ -127,6 +131,8 @@ namespace ogfx void VerticalScrollBar::set_thumb_y(f32 thumby) { + if (!getParent()->needsVScroll()) + return; auto bounds = getGlobalBounds() - m_correctionOffset; auto ext = getParent()->getContentExtents(); auto cont = getParent()->getContentBounds(); @@ -139,11 +145,6 @@ namespace ogfx getParent()->setScrollOffset(parentScrollOffset); } - bool VerticalScrollBar::is_mouse_in_thumb(const Vec2& mouse_pos) - { - return m_thumbGlobalBounds.contains(mouse_pos, true); - } - @@ -172,8 +173,7 @@ namespace ogfx m_thumbBorderColor = getThemeValue(theme, "scrollbar.thumb.borderColor", { 150, 150, 150 }); m_thumbShowBorder = getThemeValue(theme, "scrollbar.thumb.showBorder", true); m_trackColor = getThemeValue(theme, "scrollbar.track.color", { 70, 70, 70 }); - m_trackBorderRadii = getThemeValue(theme, "scrollbar.track.borderRadii", { 0, 0, 10, 10 }); - } + } void HorizontalScrollbar::afterDraw(ogfx::BasicRenderer2D& gfx) { @@ -188,7 +188,7 @@ namespace ogfx if (!m_mousePressed) return; - if (is_mouse_in_thumb({ event.mouse->position_x, event.mouse->position_y })) + if (isMouseInsideThumb({ event.mouse->position_x, event.mouse->position_y })) m_mouseDragged = true; if (m_mouseDragged) @@ -225,11 +225,16 @@ namespace ogfx update_thumb(); } + bool HorizontalScrollbar::isMouseInsideThumb(const Vec2& mouse_pos) + { + return m_thumbGlobalBounds.contains(mouse_pos, true); + } + void HorizontalScrollbar::update_thumb(void) { x = 0; y = getParent()->getSize().y - h; - w = getParent()->getGlobalBounds().w; + w = getParent()->getGlobalBounds().w - getParent()->getVScrollbarSize(); auto ext = getParent()->getContentExtents(); auto cont = getParent()->getContentBounds(); @@ -245,11 +250,13 @@ namespace ogfx auto bounds = getGlobalBounds() - m_correctionOffset; m_thumbGlobalBounds = { bounds.x + 2 + m_thumbX, bounds.y + gety(), m_thumbWidth, geth() - 4 }; - std::cout << Rectangle::toString() << "\n"; + m_thumbGlobalBounds = { bounds.x + getx() + m_thumbX, bounds.y + 2, m_thumbWidth, geth() - 4 }; } void HorizontalScrollbar::set_thumb_x(f32 thumbx) { + if (!getParent()->needsHScroll()) + return; auto bounds = getGlobalBounds() - m_correctionOffset; auto ext = getParent()->getContentExtents(); auto cont = getParent()->getContentBounds(); @@ -261,11 +268,6 @@ namespace ogfx parentScrollOffset.x = maxScroll * scrollProgress; getParent()->setScrollOffset(parentScrollOffset); } - - bool HorizontalScrollbar::is_mouse_in_thumb(const Vec2& mouse_pos) - { - return m_thumbGlobalBounds.contains(mouse_pos, true); - } } } } diff --git a/src/ogfx/gui/widgets/Scrollbar.hpp b/src/ogfx/gui/widgets/Scrollbar.hpp index 7d76592..8e0e9b2 100644 --- a/src/ogfx/gui/widgets/Scrollbar.hpp +++ b/src/ogfx/gui/widgets/Scrollbar.hpp @@ -39,6 +39,7 @@ namespace ogfx void onMousePressed(const Event& event) override; void onMouseReleased(const Event& event) override; void onUpdate(void) override; + bool isMouseInsideThumb(const Vec2& mouse_pos); inline void setx(f32 xx) override { } inline void sety(f32 yy) override { } @@ -48,7 +49,6 @@ namespace ogfx private: void update_thumb(void); void set_thumb_y(f32 thumby); - bool is_mouse_in_thumb(const Vec2& mouse_pos); private: f32 m_thumbHeight { 0 }; @@ -77,6 +77,7 @@ namespace ogfx void onMousePressed(const Event& event) override; void onMouseReleased(const Event& event) override; void onUpdate(void) override; + bool isMouseInsideThumb(const Vec2& mouse_pos); inline void setx(f32 xx) override { } inline void sety(f32 yy) override { } @@ -86,7 +87,6 @@ namespace ogfx private: void update_thumb(void); void set_thumb_x(f32 thumbx); - bool is_mouse_in_thumb(const Vec2& mouse_pos); private: f32 m_thumbWidth { 0 }; @@ -97,7 +97,7 @@ namespace ogfx f32 m_dragGrabOffset { 0 }; Rectangle m_correctionOffset { 0, 0, 0, 0 }; - Rectangle m_trackBorderRadii { 0, 0, 10, 10 }; + Rectangle m_trackBorderRadii { 0, 0, 0, 10 }; f32 m_thumbBorderRadius { 16 }; Color m_trackColor { 70, 70, 70 }; Color m_thumbColor { 120, 120, 120 }; diff --git a/src/ogfx/gui/widgets/Widget.cpp b/src/ogfx/gui/widgets/Widget.cpp index 00942b8..12abe69 100644 --- a/src/ogfx/gui/widgets/Widget.cpp +++ b/src/ogfx/gui/widgets/Widget.cpp @@ -64,6 +64,7 @@ namespace ogfx if (!isIgnoreScrollAllowed()) { glob += m_parent->getPadding().getPosition(); + glob += m_parent->getContentOffset(); glob += m_parent->getScrollOffset(); } } @@ -84,15 +85,24 @@ namespace ogfx Rectangle Widget::getContentBounds(void) const { auto pad = getPadding(); - return { pad.getPosition(), (getSize() - (pad.getSize() * 2)) }; + return { Vec2 { m_contentOffset.x, m_contentOffset.y } + pad.getPosition(), (getSize() - m_contentOffset - (pad.getSize() * 2)) }; + } + + Rectangle Widget::getPureContentBounds(void) const + { + return { { m_contentOffset.x, m_contentOffset.y }, (getSize() - m_contentOffset) }; } Rectangle Widget::getGlobalContentBounds(void) const { - auto pad = getPadding(); return { getGlobalContentPosition(), getContentBounds().getSize() }; } + Rectangle Widget::getGlobalPureContentBounds(void) const + { + return { getGlobalPosition() + getContentOffset(), getPureContentBounds().getSize() }; + } + Rectangle Widget::getContentExtents(void) const { f32 maxX = 0, maxY = 0; @@ -218,8 +228,12 @@ namespace ogfx gfx.fillRoundRect({ getGlobalPosition(), getSize() }, m_backgroundColor, m_borderRadius); } onDraw(gfx); + + // gfx.fillRect(getGlobalPureContentBounds(), { 0, 255, 0, 120 }); + gfx.pushClippingRect(getGlobalPureContentBounds(), true); if (hasChildren()) m_widgets.draw(gfx); + gfx.popClippingRect(); if (m_showBorder) gfx.drawRoundRect({ getGlobalPosition(), getSize() }, m_borderColor, m_borderRadius, m_borderWidth); afterDraw(gfx); diff --git a/src/ogfx/gui/widgets/Widget.hpp b/src/ogfx/gui/widgets/Widget.hpp index 7d28c0c..556156e 100644 --- a/src/ogfx/gui/widgets/Widget.hpp +++ b/src/ogfx/gui/widgets/Widget.hpp @@ -47,7 +47,9 @@ namespace ogfx virtual Vec2 getGlobalContentPosition(void) const; virtual Rectangle getGlobalBounds(void) const; virtual Rectangle getContentBounds(void) const; + virtual Rectangle getPureContentBounds(void) const; virtual Rectangle getGlobalContentBounds(void) const; + virtual Rectangle getGlobalPureContentBounds(void) const; virtual Rectangle getContentExtents(void) const; using Rectangle::contains; bool contains(Vec2 p, bool includeBounds = false) const override; @@ -58,6 +60,8 @@ 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 f32 getVScrollbarSize(void) const { return 0; } + inline virtual f32 getHScrollbarSize(void) const { return 0; } void addThemeOverride(const String& fullKey, ostd::Stylesheet::TypeVariant value); void reloadTheme(bool propagate = false); void setThemeQualifier(const String& qualifier, bool value = true); @@ -137,8 +141,10 @@ namespace ogfx inline bool isChildrenEnabled(void) const { return m_allowChildren; } inline void setPadding(const Rectangle& pad) { m_padding = pad; } inline void setMargin(const Rectangle& margin) { m_margin = margin; } + inline void setContentOffset(const Vec2& offset) { m_contentOffset = offset; } inline Rectangle getPadding(void) const { return m_padding; } inline Rectangle getMargin(void) const { return m_margin; } + inline Vec2 getContentOffset(void) const { return m_contentOffset; } inline bool isMouseInside(void) const { return m_mouseInside; } inline ogfx::MouseEventData::eButton getPressedMouseButton(void) const { return m_pressedButton; } inline const stdvec& getThemeIDList(void) const { return m_themeIDList; } @@ -170,8 +176,10 @@ namespace ogfx inline bool isVisible(void) const { return m_visible; } inline void show(void) { setVisible(true); } inline void hide(void) { setVisible(false); } - inline void allowScroll(bool allow = true) { m_allowScroll = allow; } - inline bool isScrollAllowed(void) const { return m_allowScroll; } + inline void allowVScroll(bool allow = true) { m_vScrollEnabled = allow; } + inline bool isVScrollAllowed(void) const { return m_vScrollEnabled; } + inline void allowHScroll(bool allow = true) { m_hScrollEnabled = allow; } + inline bool isHScrollAllowed(void) const { return m_hScrollEnabled; } inline void allowIgnoreScroll(bool allow = true) { m_ignoreScroll = allow; } inline bool isIgnoreScrollAllowed(void) const { return m_ignoreScroll; } @@ -233,10 +241,11 @@ namespace ogfx bool m_clipContents { true }; bool m_acceptDragAndDrop { false }; bool m_visible { true }; - bool m_allowScroll { false }; bool m_ignoreScroll { false}; bool m_manualDraw { false }; bool m_topMost { false }; + bool m_vScrollEnabled { false }; + bool m_hScrollEnabled { false }; MouseEventData::eButton m_pressedButton { MouseEventData::eButton::None }; stdvec m_themeIDList; @@ -254,6 +263,7 @@ namespace ogfx Rectangle m_padding { 0, 0, 0, 0 }; Rectangle m_margin { 0, 0, 0, 0 }; + Vec2 m_contentOffset { 0, 0 }; static ostd::BaseObject* s_dragAndDropData; static bool s_hasDragAndDropData; diff --git a/src/ogfx/render/BasicRenderer.cpp b/src/ogfx/render/BasicRenderer.cpp index 18be3f9..584133d 100644 --- a/src/ogfx/render/BasicRenderer.cpp +++ b/src/ogfx/render/BasicRenderer.cpp @@ -102,7 +102,10 @@ namespace ogfx finalRect = m_clipStack.back().getIntersection(rect, false); if (!m_clipStack.empty() && m_clipStack.back() == finalRect) + { + m_clipStack.push_back(finalRect); return; + } flushBatch(); m_clipStack.push_back(finalRect); @@ -121,22 +124,26 @@ namespace ogfx if (!m_initialized) return; if (m_clipStack.empty()) return; + Rectangle popped = m_clipStack.back(); m_clipStack.pop_back(); - flushBatch(); if (m_clipStack.empty()) { + flushBatch(); SDL_SetRenderClipRect(m_window->getSDLRenderer(), nullptr); return; } - const auto& rect = m_clipStack.back(); + const auto& prev = m_clipStack.back(); + if (prev == popped) + return; + flushBatch(); SDL_Rect r; - r.x = (i32)std::round(rect.x); - r.y = (i32)std::round(rect.y); - r.w = (i32)std::round(rect.w); - r.h = (i32)std::round(rect.h); + r.x = (i32)std::round(prev.x); + r.y = (i32)std::round(prev.y); + r.w = (i32)std::round(prev.w); + r.h = (i32)std::round(prev.h); SDL_SetRenderClipRect(m_window->getSDLRenderer(), &r); } diff --git a/src/test/GuiTest.cpp b/src/test/GuiTest.cpp index 4fd5dcc..0d051b4 100644 --- a/src/test/GuiTest.cpp +++ b/src/test/GuiTest.cpp @@ -71,13 +71,27 @@ class Window : public ogfx::gui::Window m_label2.setText("Label2"); m_label3.setText("Label3"); + m_label4.setText("Label4"); + m_label5.setText("Label5"); - m_panel1.setSize(300, 140); - m_panel1.allowScroll(false); + m_panel1.setSize(300, 300); + m_panel1.allowVScroll(false); + m_panel1.allowHScroll(false); + m_panel1.setTitle("Panel 1"); + + m_panel3.setSize(150, 150); + m_panel3.allowVScroll(false); + m_panel3.allowHScroll(false); + m_panel3.setTitle("Panel 3"); m_panel2.setSize(600, 400); + m_panel2.setTitle("Panel 2"); + + m_panel3.addWidget(m_label4); m_panel1.addWidget(m_label2); + m_panel1.addWidget(m_panel3, { 100, 50 }); + m_panel1.addWidget(m_label5, { 0, 60 }); m_panel2.addWidget(m_label3); m_panel2.addWidget(m_panel1, { 400, 50 }); @@ -104,7 +118,7 @@ class Window : public ogfx::gui::Window void onRedraw(ogfx::BasicRenderer2D& gfx) override { gfx.drawAnimation(m_anim, { 200, 200 }); - // gfx.fillRect(m_panel2.getGlobalContentBounds(), { 0, 255, 0, 120 }); + // gfx.fillRect(m_panel2.getGlobalPureContentBounds(), { 0, 255, 0, 120 }); } void onFixedUpdate(void) override @@ -116,8 +130,11 @@ class Window : public ogfx::gui::Window Label m_label1 { *this }; Label m_label2 { *this }; Label m_label3 { *this }; + Label m_label4 { *this }; + Label m_label5 { *this }; Panel m_panel1 { *this }; Panel m_panel2 { *this }; + Panel m_panel3 { *this }; CheckBox m_check1 { *this }; Button m_btn1 { *this };