From c204885a94cf158b009befc32bab61deef4ab452 Mon Sep 17 00:00:00 2001 From: ForLoveOfCats Date: Mon, 7 Mar 2022 14:19:35 -0500 Subject: [PATCH] LibGUI: Animated smooth scroll interpolation --- Userland/Libraries/LibGUI/AbstractSlider.h | 12 ++--- Userland/Libraries/LibGUI/Scrollbar.cpp | 53 +++++++++++++++++++++- Userland/Libraries/LibGUI/Scrollbar.h | 17 +++++++ 3 files changed, 75 insertions(+), 7 deletions(-) diff --git a/Userland/Libraries/LibGUI/AbstractSlider.h b/Userland/Libraries/LibGUI/AbstractSlider.h index 7d22aa7cc3..10e3fc829d 100644 --- a/Userland/Libraries/LibGUI/AbstractSlider.h +++ b/Userland/Libraries/LibGUI/AbstractSlider.h @@ -38,12 +38,12 @@ public: void set_page_step(int page_step); void set_jump_to_cursor(bool b) { m_jump_to_cursor = b; } - void increase_slider_by(int delta) { set_value(value() + delta); } - void decrease_slider_by(int delta) { set_value(value() - delta); } - void increase_slider_by_page_steps(int page_steps) { set_value(value() + page_step() * page_steps); } - void decrease_slider_by_page_steps(int page_steps) { set_value(value() - page_step() * page_steps); } - void increase_slider_by_steps(int steps) { set_value(value() + step() * steps); } - void decrease_slider_by_steps(int steps) { set_value(value() - step() * steps); } + virtual void increase_slider_by(int delta) { set_value(value() + delta); } + virtual void decrease_slider_by(int delta) { set_value(value() - delta); } + virtual void increase_slider_by_page_steps(int page_steps) { set_value(value() + page_step() * page_steps); } + virtual void decrease_slider_by_page_steps(int page_steps) { set_value(value() - page_step() * page_steps); } + virtual void increase_slider_by_steps(int steps) { set_value(value() + step() * steps); } + virtual void decrease_slider_by_steps(int steps) { set_value(value() - step() * steps); } Function on_change; diff --git a/Userland/Libraries/LibGUI/Scrollbar.cpp b/Userland/Libraries/LibGUI/Scrollbar.cpp index 25f0cf86d1..c6d67b1013 100644 --- a/Userland/Libraries/LibGUI/Scrollbar.cpp +++ b/Userland/Libraries/LibGUI/Scrollbar.cpp @@ -12,6 +12,9 @@ #include #include +static constexpr int ANIMATION_INTERVAL = 16; // Milliseconds +static constexpr double ANIMATION_TIME = 0.18; // Seconds + REGISTER_WIDGET(GUI, Scrollbar) namespace GUI { @@ -115,6 +118,39 @@ bool Scrollbar::has_scrubber() const return max() != min(); } +void Scrollbar::set_value(int value, AllowCallback allow_callback) +{ + m_target_value = value; + if (!(m_animated_scrolling_timer.is_null())) + m_animated_scrolling_timer->stop(); + + AbstractSlider::set_value(value, allow_callback); +} + +void Scrollbar::set_target_value(int new_target_value) +{ + new_target_value = clamp(new_target_value, min(), max()); + + // If we are already at or scrolling to the new target then don't touch anything + if (m_target_value == new_target_value) + return; + + m_animation_time_elapsed = 0; + m_start_value = value(); + m_target_value = new_target_value; + + if (m_animated_scrolling_timer.is_null()) { + m_animated_scrolling_timer = add(); + m_animated_scrolling_timer->set_interval(ANIMATION_INTERVAL); + m_animated_scrolling_timer->on_timeout = [this]() { + m_animation_time_elapsed += (double)ANIMATION_INTERVAL / 1'000; // ms -> sec + update_animated_scroll(); + }; + } + + m_animated_scrolling_timer->start(); +} + float Scrollbar::unclamped_scrubber_size() const { float pixel_range = length(orientation()) - button_size() * 2; @@ -335,7 +371,7 @@ void Scrollbar::scroll_to_position(const Gfx::IntPoint& click_position) float x_or_y = ::max(0, click_position.primary_offset_for_orientation(orientation()) - button_width() - button_width() / 2); float rel_x_or_y = x_or_y / available; - set_value(min() + rel_x_or_y * range_size); + set_target_value(min() + rel_x_or_y * range_size); } Scrollbar::Component Scrollbar::component_at_position(const Gfx::IntPoint& position) @@ -391,4 +427,19 @@ void Scrollbar::change_event(Event& event) return Widget::change_event(event); } +void Scrollbar::update_animated_scroll() +{ + if (value() == m_target_value) { + m_animated_scrolling_timer->stop(); + return; + } + + double time_percent = m_animation_time_elapsed / ANIMATION_TIME; + double ease_percent = 1.0 - pow(1.0 - time_percent, 5.0); // Ease out quint + double initial_distance = m_target_value - m_start_value; + double new_distance = initial_distance * ease_percent; + int new_value = m_start_value + (int)round(new_distance); + AbstractSlider::set_value(new_value); +} + } diff --git a/Userland/Libraries/LibGUI/Scrollbar.h b/Userland/Libraries/LibGUI/Scrollbar.h index a7330c45e1..c713314f6b 100644 --- a/Userland/Libraries/LibGUI/Scrollbar.h +++ b/Userland/Libraries/LibGUI/Scrollbar.h @@ -21,6 +21,16 @@ public: bool has_scrubber() const; + virtual void set_value(int, AllowCallback = AllowCallback::Yes) override; + void set_target_value(int); + + virtual void increase_slider_by(int delta) override { set_target_value(m_target_value + delta); } + virtual void decrease_slider_by(int delta) override { set_target_value(m_target_value - delta); } + virtual void increase_slider_by_page_steps(int page_steps) override { set_target_value(m_target_value + page_step() * page_steps); } + virtual void decrease_slider_by_page_steps(int page_steps) override { set_target_value(m_target_value - page_step() * page_steps); } + virtual void increase_slider_by_steps(int steps) override { set_target_value(m_target_value + step() * steps); } + virtual void decrease_slider_by_steps(int steps) override { set_target_value(m_target_value - step() * steps); } + enum Component { None, DecrementButton, @@ -66,6 +76,12 @@ private: Component component_at_position(const Gfx::IntPoint&); + void update_animated_scroll(); + + int m_target_value { 0 }; + int m_start_value { 0 }; + double m_animation_time_elapsed { 0 }; + int m_scrub_start_value { 0 }; Gfx::IntPoint m_scrub_origin; @@ -74,6 +90,7 @@ private: Gfx::IntPoint m_last_mouse_position; RefPtr m_automatic_scrolling_timer; + RefPtr m_animated_scrolling_timer; }; }