Started work on horizontal scrollbar

This commit is contained in:
OmniaX-Dev 2026-04-18 22:08:05 +02:00
parent d8475ecb48
commit 891e17948a
10 changed files with 195 additions and 17 deletions

View file

@ -1 +1 @@
2069 2070

View file

@ -463,7 +463,7 @@ namespace ogfx
{ {
MouseEventData mmd = get_mouse_state(event); MouseEventData mmd = get_mouse_state(event);
f32 deltaY = event.wheel.y; 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) if (event.wheel.direction == SDL_MOUSEWHEEL_FLIPPED)
deltaY = -deltaY; deltaY = -deltaY;
if (deltaY < 0) if (deltaY < 0)

View file

@ -156,6 +156,7 @@ namespace ogfx
bool m_blockingEvents { false }; bool m_blockingEvents { false };
bool m_resizeable { true }; bool m_resizeable { true };
bool m_refreshScreen { true }; bool m_refreshScreen { true };
bool m_invertHorizontalScroll { true };
f32 m_systemScale { 1.0f }; f32 m_systemScale { 1.0f };
f32 m_userScale { 1.0f }; f32 m_userScale { 1.0f };

View file

@ -36,9 +36,12 @@ namespace ogfx
disableFocus(); disableFocus();
enableStopEvents(); enableStopEvents();
allowScroll(true); allowScroll(true);
m_scrollbar.setMargin({ 0, getTitlebarHeight(), 0, 0 }); m_vScrollbar.setMargin({ 0, getTitlebarHeight(), 0, 0 });
m_scrollbar.enableManualDraw(true); m_vScrollbar.enableManualDraw(true);
addWidget(m_scrollbar); 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 { m_smoothScrollTimer.create(60.0f, [&](f64 dt) -> void {
f32 stepX = m_scrollVelocity.x * (1.0f - m_scrollSmoothFactor); f32 stepX = m_scrollVelocity.x * (1.0f - m_scrollSmoothFactor);
f32 stepY = m_scrollVelocity.y * (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) void Panel::afterDraw(ogfx::BasicRenderer2D& gfx)
{ {
draw_titlebar(gfx); draw_titlebar(gfx);
m_scrollbar.__draw(gfx); m_vScrollbar.__draw(gfx);
m_hScrollbar.__draw(gfx);
} }
void Panel::onMouseScrolled(const Event& event) void Panel::onMouseScrolled(const Event& event)
@ -174,15 +178,22 @@ namespace ogfx
if (m_scrollOffset.x > 0) m_scrollOffset.x = 0; 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; return isScrollAllowed() && getContentExtents().h > getContentBounds().h;
} }
bool Panel::needsHScroll(void) const
{
return isScrollAllowed() && getContentExtents().w > getContentBounds().w;
}
void Panel::onWidgetAdded(Widget& child) void Panel::onWidgetAdded(Widget& child)
{ {
removeWidget(m_scrollbar); removeWidget(m_vScrollbar);
addWidget(m_scrollbar, { 0, 0 }, true); removeWidget(m_hScrollbar);
addWidget(m_vScrollbar, { 0, 0 }, true);
addWidget(m_hScrollbar, { 0, 0 }, true);
} }
void Panel::draw_titlebar(BasicRenderer2D& gfx) void Panel::draw_titlebar(BasicRenderer2D& gfx)

View file

@ -54,7 +54,8 @@ namespace ogfx
String getTitlebarType(void) const; String getTitlebarType(void) const;
void setScrollOffset(const Vec2& offset) override; void setScrollOffset(const Vec2& offset) override;
void addScrollOffset(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; void onWidgetAdded(Widget& child) override;
inline void setBackGroundColor(const Color& color) { m_backgroundColor = color; } inline void setBackGroundColor(const Color& color) { m_backgroundColor = color; }
inline Color getBackgroundColor(void) { return m_backgroundColor; } inline Color getBackgroundColor(void) { return m_backgroundColor; }
@ -69,7 +70,8 @@ namespace ogfx
private: private:
String m_title { "Panel" }; String m_title { "Panel" };
Vec2 m_scrollOffset { 0, 0 }; Vec2 m_scrollOffset { 0, 0 };
VerticalScrollBar m_scrollbar { getWindow() }; VerticalScrollBar m_vScrollbar { getWindow() };
HorizontalScrollbar m_hScrollbar { getWindow() };
ostd::StepTimer m_smoothScrollTimer; ostd::StepTimer m_smoothScrollTimer;
Color m_titleColor { Colors::Black }; Color m_titleColor { Colors::Black };

View file

@ -27,7 +27,7 @@ namespace ogfx
{ {
namespace widgets namespace widgets
{ {
VerticalScrollBar& VerticalScrollBar::create(const String& text) VerticalScrollBar& VerticalScrollBar::create(void)
{ {
setPadding({ 0, 0, 0, 0 }); setPadding({ 0, 0, 0, 0 });
setMargin({ 0, 0, 0, 0 }); setMargin({ 0, 0, 0, 0 });
@ -55,7 +55,7 @@ namespace ogfx
void VerticalScrollBar::afterDraw(ogfx::BasicRenderer2D& gfx) void VerticalScrollBar::afterDraw(ogfx::BasicRenderer2D& gfx)
{ {
if (!getParent()->needsScroll()) if (!getParent()->needsVScroll())
return; return;
gfx.fillRoundRect(getGlobalBounds() - m_correctionOffset, m_trackColor, m_trackBorderRadii); gfx.fillRoundRect(getGlobalBounds() - m_correctionOffset, m_trackColor, m_trackBorderRadii);
gfx.outlinedRoundRect(m_thumbGlobalBounds, m_thumbColor, m_thumbBorderColor, m_thumbBorderRadius, 1); gfx.outlinedRoundRect(m_thumbGlobalBounds, m_thumbColor, m_thumbBorderColor, m_thumbBorderRadius, 1);
@ -143,6 +143,129 @@ namespace ogfx
{ {
return m_thumbGlobalBounds.contains(mouse_pos, true); 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<f32>(theme, "scrollbar.width", 15);
m_thumbColor = getThemeValue<Color>(theme, "scrollbar.thumb.color", { 120, 120, 120 });
m_thumbBorderRadius = getThemeValue<f32>(theme, "scrollbar.thumb.borderRadius", 16);
m_thumbBorderColor = getThemeValue<Color>(theme, "scrollbar.thumb.borderColor", { 150, 150, 150 });
m_thumbShowBorder = getThemeValue<bool>(theme, "scrollbar.thumb.showBorder", true);
m_trackColor = getThemeValue<Color>(theme, "scrollbar.track.color", { 70, 70, 70 });
m_trackBorderRadii = getThemeValue<Rectangle>(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);
}
} }
} }
} }

View file

@ -31,9 +31,8 @@ namespace ogfx
class VerticalScrollBar : public Widget class VerticalScrollBar : public Widget
{ {
public: public:
inline VerticalScrollBar(WindowCore& window) : Widget({ 0, 0, 0, 0 }, window) { create(""); } 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(void);
VerticalScrollBar& create(const String& text);
void applyTheme(const ostd::Stylesheet& theme) override; void applyTheme(const ostd::Stylesheet& theme) override;
void afterDraw(ogfx::BasicRenderer2D& gfx) override; void afterDraw(ogfx::BasicRenderer2D& gfx) override;
void onMouseDragged(const Event& event) override; void onMouseDragged(const Event& event) override;
@ -67,6 +66,44 @@ namespace ogfx
Color m_thumbBorderColor { 150, 150, 150 }; Color m_thumbBorderColor { 150, 150, 150 };
bool m_thumbShowBorder { true }; 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 };
};
} }
} }
} }

View file

@ -211,6 +211,7 @@ namespace ogfx
gfx.fillRect({ getGlobalPosition(), getSize() }, getDrawBoxColor()); gfx.fillRect({ getGlobalPosition(), getSize() }, getDrawBoxColor());
else else
{ {
beforeDraw(gfx);
if (m_showBackground && m_showBorder) if (m_showBackground && m_showBorder)
gfx.outlinedRoundRect({ getGlobalPosition(), getSize() }, m_backgroundColor, m_borderColor, m_borderRadius, m_borderWidth); gfx.outlinedRoundRect({ getGlobalPosition(), getSize() }, m_backgroundColor, m_borderColor, m_borderRadius, m_borderWidth);
else if (m_showBackground) else if (m_showBackground)

View file

@ -56,7 +56,8 @@ namespace ogfx
inline virtual Vec2 getScrollOffset(void) const { return { 0, 0 }; } inline virtual Vec2 getScrollOffset(void) const { return { 0, 0 }; }
inline virtual void setScrollOffset(const Vec2& offset) { } inline virtual void setScrollOffset(const Vec2& offset) { }
inline virtual void addScrollOffset(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 addThemeOverride(const String& fullKey, ostd::Stylesheet::TypeVariant value);
void reloadTheme(bool propagate = false); void reloadTheme(bool propagate = false);
void setThemeQualifier(const String& qualifier, bool value = true); void setThemeQualifier(const String& qualifier, bool value = true);
@ -68,6 +69,7 @@ namespace ogfx
inline virtual void onDraw(ogfx::BasicRenderer2D& gfx) { } inline virtual void onDraw(ogfx::BasicRenderer2D& gfx) { }
inline virtual void afterDraw(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 onUpdate(void) { }
inline virtual void onWidgetAdded(Widget& child) { } inline virtual void onWidgetAdded(Widget& child) { }

View file

@ -104,6 +104,7 @@ class Window : public ogfx::gui::Window
void onRedraw(ogfx::BasicRenderer2D& gfx) override void onRedraw(ogfx::BasicRenderer2D& gfx) override
{ {
gfx.drawAnimation(m_anim, { 200, 200 }); gfx.drawAnimation(m_anim, { 200, 200 });
// gfx.fillRect(m_panel2.getGlobalContentBounds(), { 0, 255, 0, 120 });
} }
void onFixedUpdate(void) override void onFixedUpdate(void) override