diff --git a/other/build.nr b/other/build.nr index 5208b65..523bd11 100644 --- a/other/build.nr +++ b/other/build.nr @@ -1 +1 @@ -2069 +2070 diff --git a/src/ogfx/gui/Window.cpp b/src/ogfx/gui/Window.cpp index 6db5d98..ef88fa3 100644 --- a/src/ogfx/gui/Window.cpp +++ b/src/ogfx/gui/Window.cpp @@ -463,7 +463,7 @@ namespace ogfx { MouseEventData mmd = get_mouse_state(event); f32 deltaY = event.wheel.y; - f32 deltaX = event.wheel.x; + f32 deltaX = m_invertHorizontalScroll ? -event.wheel.x : event.wheel.x; if (event.wheel.direction == SDL_MOUSEWHEEL_FLIPPED) deltaY = -deltaY; if (deltaY < 0) diff --git a/src/ogfx/gui/Window.hpp b/src/ogfx/gui/Window.hpp index 91eaba2..0bedeec 100644 --- a/src/ogfx/gui/Window.hpp +++ b/src/ogfx/gui/Window.hpp @@ -156,6 +156,7 @@ namespace ogfx bool m_blockingEvents { false }; bool m_resizeable { true }; bool m_refreshScreen { true }; + bool m_invertHorizontalScroll { true }; f32 m_systemScale { 1.0f }; f32 m_userScale { 1.0f }; diff --git a/src/ogfx/gui/widgets/Containers.cpp b/src/ogfx/gui/widgets/Containers.cpp index a079196..eb77cac 100644 --- a/src/ogfx/gui/widgets/Containers.cpp +++ b/src/ogfx/gui/widgets/Containers.cpp @@ -36,9 +36,12 @@ namespace ogfx disableFocus(); enableStopEvents(); allowScroll(true); - m_scrollbar.setMargin({ 0, getTitlebarHeight(), 0, 0 }); - m_scrollbar.enableManualDraw(true); - addWidget(m_scrollbar); + m_vScrollbar.setMargin({ 0, getTitlebarHeight(), 0, 0 }); + m_vScrollbar.enableManualDraw(true); + addWidget(m_vScrollbar); + m_hScrollbar.setMargin({ 0, 0, 0, 0 }); + m_hScrollbar.enableManualDraw(true); + addWidget(m_hScrollbar); m_smoothScrollTimer.create(60.0f, [&](f64 dt) -> void { f32 stepX = m_scrollVelocity.x * (1.0f - m_scrollSmoothFactor); f32 stepY = m_scrollVelocity.y * (1.0f - m_scrollSmoothFactor); @@ -94,7 +97,8 @@ namespace ogfx void Panel::afterDraw(ogfx::BasicRenderer2D& gfx) { draw_titlebar(gfx); - m_scrollbar.__draw(gfx); + m_vScrollbar.__draw(gfx); + m_hScrollbar.__draw(gfx); } void Panel::onMouseScrolled(const Event& event) @@ -174,15 +178,22 @@ namespace ogfx if (m_scrollOffset.x > 0) m_scrollOffset.x = 0; } - bool Panel::needsScroll(void) const + bool Panel::needsVScroll(void) const { return isScrollAllowed() && getContentExtents().h > getContentBounds().h; } + bool Panel::needsHScroll(void) const + { + return isScrollAllowed() && getContentExtents().w > getContentBounds().w; + } + void Panel::onWidgetAdded(Widget& child) { - removeWidget(m_scrollbar); - addWidget(m_scrollbar, { 0, 0 }, true); + removeWidget(m_vScrollbar); + removeWidget(m_hScrollbar); + addWidget(m_vScrollbar, { 0, 0 }, true); + addWidget(m_hScrollbar, { 0, 0 }, true); } void Panel::draw_titlebar(BasicRenderer2D& gfx) diff --git a/src/ogfx/gui/widgets/Containers.hpp b/src/ogfx/gui/widgets/Containers.hpp index d3d92ab..01b6085 100644 --- a/src/ogfx/gui/widgets/Containers.hpp +++ b/src/ogfx/gui/widgets/Containers.hpp @@ -54,7 +54,8 @@ namespace ogfx String getTitlebarType(void) const; void setScrollOffset(const Vec2& offset) override; void addScrollOffset(const Vec2& offset) override; - bool needsScroll(void) const override; + bool needsVScroll(void) const override; + bool needsHScroll(void) const override; void onWidgetAdded(Widget& child) override; inline void setBackGroundColor(const Color& color) { m_backgroundColor = color; } inline Color getBackgroundColor(void) { return m_backgroundColor; } @@ -69,7 +70,8 @@ namespace ogfx private: String m_title { "Panel" }; Vec2 m_scrollOffset { 0, 0 }; - VerticalScrollBar m_scrollbar { getWindow() }; + VerticalScrollBar m_vScrollbar { getWindow() }; + HorizontalScrollbar m_hScrollbar { getWindow() }; ostd::StepTimer m_smoothScrollTimer; Color m_titleColor { Colors::Black }; diff --git a/src/ogfx/gui/widgets/Scrollbar.cpp b/src/ogfx/gui/widgets/Scrollbar.cpp index 7d22e53..8ed35df 100644 --- a/src/ogfx/gui/widgets/Scrollbar.cpp +++ b/src/ogfx/gui/widgets/Scrollbar.cpp @@ -27,7 +27,7 @@ namespace ogfx { namespace widgets { - VerticalScrollBar& VerticalScrollBar::create(const String& text) + VerticalScrollBar& VerticalScrollBar::create(void) { setPadding({ 0, 0, 0, 0 }); setMargin({ 0, 0, 0, 0 }); @@ -55,7 +55,7 @@ namespace ogfx void VerticalScrollBar::afterDraw(ogfx::BasicRenderer2D& gfx) { - if (!getParent()->needsScroll()) + if (!getParent()->needsVScroll()) return; gfx.fillRoundRect(getGlobalBounds() - m_correctionOffset, m_trackColor, m_trackBorderRadii); gfx.outlinedRoundRect(m_thumbGlobalBounds, m_thumbColor, m_thumbBorderColor, m_thumbBorderRadius, 1); @@ -143,6 +143,129 @@ namespace ogfx { return m_thumbGlobalBounds.contains(mouse_pos, true); } + + + + + + + HorizontalScrollbar& HorizontalScrollbar::create(void) + { + setPadding({ 0, 0, 0, 0 }); + setMargin({ 0, 0, 0, 0 }); + setTypeName("ogfx::gui::widgets::Label"); + disableDrawBox(); + disableChildren(); + enableBackground(true); + enableBorder(false); + enableTopMost(true); + allowIgnoreScroll(true); + validate(); + return *this; + } + + void HorizontalScrollbar::applyTheme(const ostd::Stylesheet& theme) + { + h = getThemeValue(theme, "scrollbar.width", 15); + m_thumbColor = getThemeValue(theme, "scrollbar.thumb.color", { 120, 120, 120 }); + m_thumbBorderRadius = getThemeValue(theme, "scrollbar.thumb.borderRadius", 16); + 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) + { + if (!getParent()->needsHScroll()) + return; + gfx.fillRoundRect(getGlobalBounds() - m_correctionOffset, m_trackColor, m_trackBorderRadii); + gfx.outlinedRoundRect(m_thumbGlobalBounds, m_thumbColor, m_thumbBorderColor, m_thumbBorderRadius, 1); + } + + void HorizontalScrollbar::onMouseDragged(const Event& event) + { + if (!m_mousePressed) + return; + + if (is_mouse_in_thumb({ event.mouse->position_x, event.mouse->position_y })) + m_mouseDragged = true; + + if (m_mouseDragged) + { + auto bounds = getGlobalBounds() - m_correctionOffset; + f32 newThumbX = (event.mouse->position_x - bounds.x) - m_dragGrabOffset; + set_thumb_x(newThumbX); + } + } + + void HorizontalScrollbar::onMousePressed(const Event& event) + { + m_mousePressed = true; + m_dragGrabOffset = event.mouse->position_x - m_thumbGlobalBounds.x; + } + + void HorizontalScrollbar::onMouseReleased(const Event& event) + { + m_mousePressed = false; + if (event.mouse->mousePressedOnWidget != this) + return; + if (!m_mouseDragged) + { + auto bounds = getGlobalBounds() - m_correctionOffset; + f32 newThumbX = (event.mouse->position_x - bounds.x); + newThumbX -= m_thumbWidth / 2.0f; + set_thumb_x(newThumbX); + } + m_mouseDragged = false; + } + + void HorizontalScrollbar::onUpdate(void) + { + update_thumb(); + } + + void HorizontalScrollbar::update_thumb(void) + { + x = 0; + y = getParent()->getSize().y - h; + w = getParent()->getGlobalBounds().w; + + auto ext = getParent()->getContentExtents(); + auto cont = getParent()->getContentBounds(); + auto scrollOfset = getParent()->getScrollOffset(); + + // Thumb size is proportional to visible/total ratio + f32 visibleRatio = cont.w / ext.w; // e.g. 0.4 means 40% visible + m_thumbWidth = std::max(20.0f, getw() * visibleRatio); // min 20px for usability + + // Thumb position is proportional to scroll progress + f32 scrollProgress = -scrollOfset.x / (ext.w - cont.w); // 0 to 1 + m_thumbX = getx() + ((getw() - m_thumbWidth) * scrollProgress); + + auto bounds = getGlobalBounds() - m_correctionOffset; + m_thumbGlobalBounds = { bounds.x + 2 + m_thumbX, bounds.y + gety(), m_thumbWidth, geth() - 4 }; + std::cout << Rectangle::toString() << "\n"; + } + + void HorizontalScrollbar::set_thumb_x(f32 thumbx) + { + auto bounds = getGlobalBounds() - m_correctionOffset; + auto ext = getParent()->getContentExtents(); + auto cont = getParent()->getContentBounds(); + thumbx = std::clamp(thumbx, 0.0f, w - m_thumbWidth); + m_thumbX = thumbx; + f32 scrollProgress = (bounds.x + getx() + m_thumbX - bounds.x) / (bounds.w - m_thumbWidth); + f32 maxScroll = -(ext.w - cont.w); + auto parentScrollOffset = getParent()->getScrollOffset(); + 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 8fb976c..7d76592 100644 --- a/src/ogfx/gui/widgets/Scrollbar.hpp +++ b/src/ogfx/gui/widgets/Scrollbar.hpp @@ -31,9 +31,8 @@ namespace ogfx class VerticalScrollBar : public Widget { public: - inline VerticalScrollBar(WindowCore& window) : Widget({ 0, 0, 0, 0 }, window) { create(""); } - inline VerticalScrollBar(WindowCore& window, const String& text) : Widget({ 0, 0, 0, 0 }, window) { create(text); } - VerticalScrollBar& create(const String& text); + inline VerticalScrollBar(WindowCore& window) : Widget({ 0, 0, 0, 0 }, window) { create(); } + VerticalScrollBar& create(void); void applyTheme(const ostd::Stylesheet& theme) override; void afterDraw(ogfx::BasicRenderer2D& gfx) override; void onMouseDragged(const Event& event) override; @@ -67,6 +66,44 @@ namespace ogfx Color m_thumbBorderColor { 150, 150, 150 }; bool m_thumbShowBorder { true }; }; + class HorizontalScrollbar : public Widget + { + public: + inline HorizontalScrollbar(WindowCore& window) : Widget({ 0, 0, 0, 0 }, window) { create(); } + HorizontalScrollbar& create(void); + void applyTheme(const ostd::Stylesheet& theme) override; + void afterDraw(ogfx::BasicRenderer2D& gfx) override; + void onMouseDragged(const Event& event) override; + void onMousePressed(const Event& event) override; + void onMouseReleased(const Event& event) override; + void onUpdate(void) override; + + inline void setx(f32 xx) override { } + inline void sety(f32 yy) override { } + inline void setw(f32 ww) override { } + inline void seth(f32 hh) override { } + + 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 }; + f32 m_thumbX { 0 }; + Rectangle m_thumbGlobalBounds { 0, 0, 0, 0 }; + bool m_mousePressed { false }; + bool m_mouseDragged { false }; + f32 m_dragGrabOffset { 0 }; + Rectangle m_correctionOffset { 0, 0, 0, 0 }; + + Rectangle m_trackBorderRadii { 0, 0, 10, 10 }; + f32 m_thumbBorderRadius { 16 }; + Color m_trackColor { 70, 70, 70 }; + Color m_thumbColor { 120, 120, 120 }; + Color m_thumbBorderColor { 150, 150, 150 }; + bool m_thumbShowBorder { true }; + }; } } } diff --git a/src/ogfx/gui/widgets/Widget.cpp b/src/ogfx/gui/widgets/Widget.cpp index d460c71..00942b8 100644 --- a/src/ogfx/gui/widgets/Widget.cpp +++ b/src/ogfx/gui/widgets/Widget.cpp @@ -211,6 +211,7 @@ namespace ogfx gfx.fillRect({ getGlobalPosition(), getSize() }, getDrawBoxColor()); else { + beforeDraw(gfx); if (m_showBackground && m_showBorder) gfx.outlinedRoundRect({ getGlobalPosition(), getSize() }, m_backgroundColor, m_borderColor, m_borderRadius, m_borderWidth); else if (m_showBackground) diff --git a/src/ogfx/gui/widgets/Widget.hpp b/src/ogfx/gui/widgets/Widget.hpp index 008ff3a..7d28c0c 100644 --- a/src/ogfx/gui/widgets/Widget.hpp +++ b/src/ogfx/gui/widgets/Widget.hpp @@ -56,7 +56,8 @@ namespace ogfx inline virtual Vec2 getScrollOffset(void) const { return { 0, 0 }; } inline virtual void setScrollOffset(const Vec2& offset) { } inline virtual void addScrollOffset(const Vec2& offset) { } - inline virtual bool needsScroll(void) const { return false; } + inline virtual bool needsVScroll(void) const { return false; } + inline virtual bool needsHScroll(void) const { return false; } void addThemeOverride(const String& fullKey, ostd::Stylesheet::TypeVariant value); void reloadTheme(bool propagate = false); void setThemeQualifier(const String& qualifier, bool value = true); @@ -68,6 +69,7 @@ namespace ogfx inline virtual void onDraw(ogfx::BasicRenderer2D& gfx) { } inline virtual void afterDraw(ogfx::BasicRenderer2D& gfx) { } + inline virtual void beforeDraw(ogfx::BasicRenderer2D& gfx) { } inline virtual void onUpdate(void) { } inline virtual void onWidgetAdded(Widget& child) { } diff --git a/src/test/GuiTest.cpp b/src/test/GuiTest.cpp index 7141f28..4fd5dcc 100644 --- a/src/test/GuiTest.cpp +++ b/src/test/GuiTest.cpp @@ -104,6 +104,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 }); } void onFixedUpdate(void) override