/* OmniaFramework - A collection of useful functionality Copyright (C) 2025 OmniaX-Dev This file is part of OmniaFramework. OmniaFramework is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. OmniaFramework is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OmniaFramework. If not, see . */ #include "Scrollbar.hpp" #include "../../render/BasicRenderer.hpp" namespace ogfx { namespace gui { namespace widgets { VerticalScrollBar& VerticalScrollBar::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 VerticalScrollBar::applyTheme(const ostd::Stylesheet& theme) { w = 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 }); } void VerticalScrollBar::afterDraw(ogfx::BasicRenderer2D& gfx) { if (!getParent()->needsVScroll()) return; gfx.fillRoundRect(getGlobalBounds() - m_correctionOffset, m_trackColor, m_trackBorderRadii); gfx.outlinedRoundRect(m_thumbGlobalBounds, m_thumbColor, m_thumbBorderColor, m_thumbBorderRadius, 1); } void VerticalScrollBar::onMouseDragged(const Event& event) { if (!m_mousePressed) return; if (isMouseInsideThumb({ event.mouse->position_x, event.mouse->position_y })) m_mouseDragged = true; if (m_mouseDragged) { auto bounds = getGlobalBounds() - m_correctionOffset; f32 newThumbY = (event.mouse->position_y - bounds.y) - m_dragGrabOffset; set_thumb_y(newThumbY); } } void VerticalScrollBar::onMousePressed(const Event& event) { m_mousePressed = true; m_dragGrabOffset = event.mouse->position_y - m_thumbGlobalBounds.y; } void VerticalScrollBar::onMouseReleased(const Event& event) { m_mousePressed = false; if (event.mouse->mousePressedOnWidget != this) return; if (!m_mouseDragged) { auto bounds = getGlobalBounds() - m_correctionOffset; f32 newThumbY = (event.mouse->position_y - bounds.y); newThumbY -= m_thumbHeight / 2.0f; set_thumb_y(newThumbY); } m_mouseDragged = false; } void VerticalScrollBar::onUpdate(void) { 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()->getGlobalPureContentBounds().h; auto ext = getParent()->getContentExtents(); auto cont = getParent()->getContentBounds(); auto scrollOfset = getParent()->getScrollOffset(); // Thumb size is proportional to visible/total ratio f32 visibleRatio = cont.h / ext.h; // e.g. 0.4 means 40% visible m_thumbHeight = std::max(20.0f, geth() * visibleRatio); // min 20px for usability // Thumb position is proportional to scroll progress f32 scrollProgress = -scrollOfset.y / (ext.h - cont.h); // 0 to 1 m_thumbY = gety() + ((geth() - m_thumbHeight) * scrollProgress); auto bounds = getGlobalBounds() - m_correctionOffset; m_thumbGlobalBounds = { bounds.x + 2, bounds.y + gety() + m_thumbY, getw() - 4, m_thumbHeight }; } void VerticalScrollBar::set_thumb_y(f32 thumby) { if (!getParent()->needsVScroll()) return; auto bounds = getGlobalBounds() - m_correctionOffset; auto ext = getParent()->getContentExtents(); auto cont = getParent()->getContentBounds(); thumby = std::clamp(thumby, 0.0f, h - m_thumbHeight); m_thumbY = thumby; f32 scrollProgress = (bounds.y + gety() + m_thumbY - bounds.y) / (bounds.h - m_thumbHeight); f32 maxScroll = -(ext.h - cont.h); auto parentScrollOffset = getParent()->getScrollOffset(); parentScrollOffset.y = maxScroll * scrollProgress; getParent()->setScrollOffset(parentScrollOffset); } 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 }); } 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 (isMouseInsideThumb({ 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(); } 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 - getParent()->getVScrollbarSize(); 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 }; 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(); 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); } } } }