Repository: zng-ui/zng (language: rust)

Issue:
Auto scroll does not work if animations are disabled on the system
This feature is implemented using animations, but if `VARS.animations_enabled()` is false the current animation would just jump the scroll to the end immediately.

See [Code](https://github.com/zng-ui/zng/blob/eba0173e6acd756a4214d803c1954ba56bc0ba12/crates/zng-wgt-scroll/src/types.rs#L400) for more details.


Discussion / hints:

Fix patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ee3bb1b77..5144ccfc1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,8 @@
 * Improve iterators in `InteractionPath`, `interaction_path` and `zip` are now double ended and exact sized.
 * Add `ZOOM_TO_FIT_CMD`.
     - The `CTRL+'0'` shortcut is now used for this command, not `ZOOM_RESET_CMD`.
+* Deprecate `NilAnimationObserver`, use `()` now.
+* Add `ForceAnimationController` to force important animations to run when animations are disabled on the system.
 
 # 0.5.0
 
diff --git a/crates/zng-var/src/animation.rs b/crates/zng-var/src/animation.rs
index f114ea0e7..cad41aad1 100644
--- a/crates/zng-var/src/animation.rs
+++ b/crates/zng-var/src/animation.rs
@@ -122,6 +122,7 @@ struct AnimationData {
     stop: bool,
     sleep: Option<Deadline>,
     animations_enabled: bool,
+    force_enabled: bool,
     now: DInstant,
     time_scale: Factor,
 }
@@ -142,6 +143,7 @@ impl Animation {
             now,
             sleep: None,
             animations_enabled,
+            force_enabled: false,
             time_scale,
         })))
     }
@@ -168,7 +170,9 @@ impl Animation {
 
     pub(crate) fn reset_state(&self, enabled: bool, now: DInstant, time_scale: Factor) {
         let mut m = self.0.lock();
-        m.animations_enabled = enabled;
+        if !m.force_enabled {
+            m.animations_enabled = enabled;
+        }
         m.now = now;
         m.time_scale = time_scale;
         m.sleep = None;
@@ -200,6 +204,17 @@ impl Animation {
         self.0.lock().animations_enabled
     }
 
+    /// Set [`animations_enabled`] to `true`.
+    ///
+    /// This should only be used for animations that are component of an app feature, cosmetic animations must not force enable.
+    ///
+    /// [`animations_enabled`]: VARS::animations_enabled
+    pub fn force_enable(&self) {
+        let mut me = self.0.lock();
+        me.force_enabled = true;
+        me.animations_enabled = true;
+    }
+
     /// Compute the time elapsed from [`start_time`] to [`now`].
     ///
     /// [`start_time`]: Self::start_time
@@ -971,12 +986,24 @@ pub trait AnimationController: Send + Sync + Any {
 }
 
 /// An [`AnimationController`] that does nothing.
+#[deprecated(since = "0.3.1", note = "please use `()` instead")]
 pub struct NilAnimationObserver;
+#[allow(deprecated)] // really
 impl AnimationController for NilAnimationObserver {}
 
+impl AnimationController for () {}
+
+/// An [`AnimationController`] that forces animations to run even if animations are not enabled.
+pub struct ForceAnimationController;
+impl AnimationController for ForceAnimationController {
+    fn on_start(&self, animation: &Animation) {
+        animation.force_enable();
+    }
+}
+
 context_local! {
     pub(crate) static VARS_ANIMATION_CTRL_CTX: Box<dyn AnimationController> = {
-        let r: Box<dyn AnimationController> = Box::new(NilAnimationObserver);
+        let r: Box<dyn AnimationController> = Box::new(());
         r
     };
 }
diff --git a/crates/zng-wgt-scroll/src/types.rs b/crates/zng-wgt-scroll/src/types.rs
index a1ece3dcf..ee0e56702 100644
--- a/crates/zng-wgt-scroll/src/types.rs
+++ b/crates/zng-wgt-scroll/src/types.rs
@@ -396,12 +396,10 @@ impl SCROLL {
                 }
                 let time = (travel.0 as f32 / velocity.0.abs() as f32).secs();
 
-                if !VARS.animations_enabled().get() {
-                    // !!: TODO, scroll by line with timer
-                } else {
+                VARS.with_animation_controller(zng_var::animation::ForceAnimationController, || {
                     let handle = offset_var.ease(target, time, easing::linear);
                     mem::replace(&mut *SCROLL_CONFIG.get().auto[dimension].lock(), handle).stop();
-                }
+                });
             }
         }
         scroll(0, velocity.x, max_scroll.width, &SCROLL_HORIZONTAL_OFFSET_VAR);
diff --git a/crates/zng/src/var.rs b/crates/zng/src/var.rs
index 985e27c38..8ffa316a9 100644
--- a/crates/zng/src/var.rs
+++ b/crates/zng/src/var.rs
@@ -328,10 +328,14 @@ pub use zng_app::widget::{AnyVarSubscribe, VarLayout, VarSubscribe};
 /// Var animation types and functions.
 pub mod animation {
     pub use zng_var::animation::{
-        Animation, AnimationController, AnimationHandle, ChaseAnimation, ModifyInfo, NilAnimationObserver, Transition, TransitionKeyed,
+        Animation, AnimationController, AnimationHandle, ChaseAnimation, ForceAnimationController, ModifyInfo, Transition, TransitionKeyed,
         Transitionable, WeakAnimationHandle,
     };
 
+    #[deprecated(since = "0.5.1", note = "please use `()` instead")]
+    #[allow(deprecated)] // really
+    pub use zng_var::animation::NilAnimationObserver;
+
     /// Common easing functions.
     pub mod easing {
         pub use zng_var::animation::easing::{


Test patch: