Browse Source

LibGUI: Animated smooth scroll interpolation

ForLoveOfCats 3 years ago
parent
commit
c204885a94

+ 6 - 6
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<void(int)> on_change;
 

+ 52 - 1
Userland/Libraries/LibGUI/Scrollbar.cpp

@@ -12,6 +12,9 @@
 #include <LibGfx/Palette.h>
 #include <LibGfx/StylePainter.h>
 
+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<Core::Timer>();
+        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);
+}
+
 }

+ 17 - 0
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<Core::Timer> m_automatic_scrolling_timer;
+    RefPtr<Core::Timer> m_animated_scrolling_timer;
 };
 
 }