diff --git a/extra/DefaultTheme.oss b/extra/DefaultTheme.oss index 2dc031a..751a937 100644 --- a/extra/DefaultTheme.oss +++ b/extra/DefaultTheme.oss @@ -30,7 +30,7 @@ window.backgroundColor = rgba(160, 160, 160, 255) titlebarHeight = 18 titlebarFontSize = 18 titlebarTextAlign = text_left - scrollSpeed = vec2(0.8, 0.8) + scrollSpeed = 15.0 padding = rect(15, 15, 15, 15) margin = rect(0, 0, 0, 0) } diff --git a/src/ogfx/gui/Events.hpp b/src/ogfx/gui/Events.hpp index 696bc9a..456aad6 100644 --- a/src/ogfx/gui/Events.hpp +++ b/src/ogfx/gui/Events.hpp @@ -21,6 +21,7 @@ #pragma once #include +#include namespace ogfx { @@ -48,7 +49,7 @@ namespace ogfx class MouseEventData : public ostd::BaseObject { public: enum class eButton { None = 0, Left, Middle, Right }; - public: enum class eScrollDirection { None = 0, Up, Down }; + public: enum class eScrollDirection { None = 0, Up, Down, Left, Right }; public: inline MouseEventData(WindowCore& parent, f32 mousex, f32 mousey, eButton btn) : parentWindow(parent), position_x(mousex), position_y(mousey), button(btn) { @@ -61,7 +62,7 @@ namespace ogfx f32 position_y; eButton button; eScrollDirection scroll { eScrollDirection::None }; - f32 scrollAmount { 0 }; + Vec2 scrollAmount { 0 }; gui::Widget* mousePressedOnWidget { nullptr }; WindowCore& parentWindow; }; diff --git a/src/ogfx/gui/Window.cpp b/src/ogfx/gui/Window.cpp index 2634a3d..6db5d98 100644 --- a/src/ogfx/gui/Window.cpp +++ b/src/ogfx/gui/Window.cpp @@ -462,16 +462,36 @@ namespace ogfx else if (event.type == SDL_EVENT_MOUSE_WHEEL) { MouseEventData mmd = get_mouse_state(event); - f32 delta = event.wheel.y; + f32 deltaY = event.wheel.y; + f32 deltaX = event.wheel.x; if (event.wheel.direction == SDL_MOUSEWHEEL_FLIPPED) - delta = -delta; - if (delta < 0) + deltaY = -deltaY; + if (deltaY < 0) + { mmd.scroll = MouseEventData::eScrollDirection::Down; - else if (delta > 0) + mmd.scrollAmount = { 0.0f, deltaY }; + } + else if (deltaY > 0) + { mmd.scroll = MouseEventData::eScrollDirection::Up; + mmd.scrollAmount = { 0.0f, deltaY }; + } + else if (deltaX < 0) + { + mmd.scroll = MouseEventData::eScrollDirection::Right; + mmd.scrollAmount = { deltaX, 0.0f }; + } + else if (deltaX > 0) + { + mmd.scroll = MouseEventData::eScrollDirection::Left; + mmd.scrollAmount = { deltaX, 0.0f }; + } else + { mmd.scroll = MouseEventData::eScrollDirection::None; - mmd.scrollAmount = delta; + mmd.scrollAmount = { 0.0f, 0.0f }; + } + ostd::SignalHandler::emitSignal(ostd::BuiltinSignals::MouseScrolled, ostd::Signal::Priority::RealTime, mmd); } else if (event.type == SDL_EVENT_MOUSE_BUTTON_DOWN) diff --git a/src/ogfx/gui/widgets/Containers.cpp b/src/ogfx/gui/widgets/Containers.cpp index fcc2c10..f4bfe21 100644 --- a/src/ogfx/gui/widgets/Containers.cpp +++ b/src/ogfx/gui/widgets/Containers.cpp @@ -39,6 +39,15 @@ namespace ogfx m_scrollbar.setMargin({ 0, getTitlebarHeight(), 0, 0 }); m_scrollbar.enableManualDraw(true); addWidget(m_scrollbar); + m_smoothScrollTimer.create(30.0f, [&](f64 dt) -> void { + f32 scrollStep = m_scrollTarget / 60.0f; + m_currentScroll += scrollStep; + addScrollOffset({ 0, scrollStep }); + if (m_currentScroll > m_scrollTarget) + m_currentScroll = m_scrollTarget; + }, [&](void) -> bool { + return m_currentScroll == m_scrollTarget; + }, true); validate(); return *this; } @@ -53,7 +62,7 @@ namespace ogfx setBorderColor(getThemeValue(theme, "panel.borderColor", Colors::Black)); setPadding(getThemeValue(theme, "panel.padding", { 15, 15, 15, 15 })); setMargin(getThemeValue(theme, "panel.margin", { 0, 0, 0, 0 })); - m_scrollSpeed = getThemeValue(theme, "panel.scrollSpeed", { 0.0f, 0.8f }); + m_scrollSpeed = getThemeValue(theme, "panel.scrollSpeed", 0.8f); m_basePadding = getPadding(); m_titleColor = getThemeValue(theme, "panel.titleColor", Colors::Black); m_titlebarColor = getThemeValue(theme, "panel.titlebarColor", Colors::Transparent); @@ -71,6 +80,11 @@ namespace ogfx // gfx.fillRect(getContentExtents() + Rectangle { getGlobalPosition(), 0, 0 }, { 80, 0, 0, 30 }); } + void Panel::onUpdate(void) + { + m_smoothScrollTimer.update(); + } + void Panel::afterDraw(ogfx::BasicRenderer2D& gfx) { draw_titlebar(gfx); @@ -81,12 +95,19 @@ namespace ogfx { if (!isScrollAllowed()) return; + if (m_smoothScrollTimer.isStopped()) + m_smoothScrollTimer.restart(); f32 offset_y = 0; if (event.mouse->scroll == MouseEventData::eScrollDirection::Down) - offset_y = (m_scrollSpeed.y * event.mouse->scrollAmount * 15.0f); + { + + m_scrollTarget += (m_scrollSpeed * event.mouse->scrollAmount.y); + } else if (event.mouse->scroll == MouseEventData::eScrollDirection::Up) - offset_y = (m_scrollSpeed.y * event.mouse->scrollAmount * 15.0f); - addScrollOffset({ 0, offset_y }); + { + + m_scrollTarget += (m_scrollSpeed * event.mouse->scrollAmount.y); + } event.handle(); } diff --git a/src/ogfx/gui/widgets/Containers.hpp b/src/ogfx/gui/widgets/Containers.hpp index 3d5e6f5..6c59c3f 100644 --- a/src/ogfx/gui/widgets/Containers.hpp +++ b/src/ogfx/gui/widgets/Containers.hpp @@ -22,6 +22,7 @@ #include #include +#include namespace ogfx { @@ -46,6 +47,7 @@ namespace ogfx Panel& create(void); void applyTheme(const ostd::Stylesheet& theme) override; void onDraw(ogfx::BasicRenderer2D& gfx) override; + void onUpdate(void) override; void afterDraw(ogfx::BasicRenderer2D& gfx) override; void onMouseScrolled(const Event& event) override; void setTitlebarType(const String& type); @@ -68,6 +70,7 @@ namespace ogfx String m_title { "Panel" }; Vec2 m_scrollOffset { 0, 0 }; VerticalScrollBar m_scrollbar { getWindow() }; + ostd::StepTimer m_smoothScrollTimer; Color m_titleColor { Colors::Black }; i32 m_titlebarType = TitleBarTypes::NoneValue; @@ -78,7 +81,10 @@ namespace ogfx Color m_titlebarBorderColor { Colors::Black }; Rectangle m_basePadding { 0, 0, 0, 0 }; i32 m_titleTextAlign { 0 }; - Vec2 m_scrollSpeed { 0.8f, 0.8f }; + f32 m_scrollSpeed { 0.8f }; + + f32 m_scrollTarget { 0.0f }; + f32 m_currentScroll { 0.0f }; }; } } diff --git a/src/ostd/utils/Time.cpp b/src/ostd/utils/Time.cpp index c4f96c8..61781b8 100644 --- a/src/ostd/utils/Time.cpp +++ b/src/ostd/utils/Time.cpp @@ -597,19 +597,26 @@ namespace ostd - StepTimer& StepTimer::create(f64 updatesPerSecond, StepTimer::Callback callback) + StepTimer& StepTimer::create(f64 updatesPerSecond, StepTimer::Callback callback, StopConditionCallback stopCondition, bool stopped) { m_targetDt = 1.0 / updatesPerSecond; m_callback = std::move(callback); + m_stopCondition = std::move(stopCondition); m_prevTime = Clock::now(); m_accumulator = 0.0; + m_stopped = stopped; m_valid = true; return *this; } void StepTimer::update(void) { - if (!m_valid) return; + if (!m_valid || m_stopped || !m_callback) return; + if (m_stopCondition && m_stopCondition()) + { + m_stopped = true; + return; + } TimePoint currentTime = Clock::now(); Duration frameDuration = currentTime - m_prevTime; @@ -638,6 +645,7 @@ namespace ostd { m_accumulator = 0.0; m_prevTime = Clock::now(); + m_stopped = false; } } diff --git a/src/ostd/utils/Time.hpp b/src/ostd/utils/Time.hpp index ec576d4..1148260 100644 --- a/src/ostd/utils/Time.hpp +++ b/src/ostd/utils/Time.hpp @@ -1,21 +1,21 @@ /* - OmniaFramework - A collection of useful functionality - Copyright (C) 2025 OmniaX-Dev + OmniaFramework - A collection of useful functionality + Copyright (C) 2025 OmniaX-Dev - This file is part of OmniaFramework. + 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 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. + 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 . + You should have received a copy of the GNU General Public License + along with OmniaFramework. If not, see . */ #pragma once @@ -118,27 +118,37 @@ namespace ostd class StepTimer { public: - using Callback = std::function; + using Callback = std::function; + using StopConditionCallback = std::function; + using StopCallback = std::function; using Clock = std::chrono::high_resolution_clock; - using TimePoint = Clock::time_point; - using Duration = Clock::duration; + using TimePoint = Clock::time_point; + using Duration = Clock::duration; public: - StepTimer() = default; - inline StepTimer(f64 updatesPerSecond, Callback callback) { create(updatesPerSecond, callback); } - StepTimer& create(f64 updatesPerSecond, Callback callback); - void update(void); - void reset(void); + StepTimer() = default; + inline StepTimer(f64 updatesPerSecond, Callback callback, bool stopped = false) { create(updatesPerSecond, callback, stopped); } + StepTimer& create(f64 updatesPerSecond, Callback callback, bool stopped = false); + void update(void); + void reset(void); + inline void setStopCondition(StopConditionCallback stopCondition) { m_stopCondition = stopCondition; } + inline void setStopCallback(StopCallback callback) { m_stopCallback = callback; } + inline void restart(void) { reset(); } + inline void stop(void) { m_stopped = true; } inline f64 getInterpolationAlpha(void) const { return m_valid && m_targetDt > 0.0 ? m_accumulator / m_targetDt : 0.0; } - inline void invalidate(void) { m_valid = false; } - inline bool isValid(void) const { return m_valid; } + inline void invalidate(void) { m_valid = false; } + inline bool isValid(void) const { return m_valid; } + inline bool isStopped(void) const { return m_stopped; } private: TimePoint m_prevTime; - f64 m_targetDt { 0.0 }; - Callback m_callback { nullptr }; - f64 m_accumulator { 0.0 }; - bool m_valid { false }; + f64 m_targetDt { 0.0 }; + Callback m_callback { nullptr }; + StopConditionCallback m_stopCondition { nullptr }; + StopCallback m_stopCallback { nullptr }; + f64 m_accumulator { 0.0 }; + bool m_valid { false }; + bool m_stopped { false }; }; struct GameClock