Skip to main content

rdi_core/
spec.rs

1//! Animation specification types.
2//!
3//! The engine consumes a `Vec<IconAnimationSpec>` and an
4//! [`AnimationOptions`]; both are plain data with no callbacks.
5
6use crate::curve::Curve;
7use crate::duration::Duration;
8use crate::geometry::Point;
9use crate::id::IconId;
10use crate::overlay_plan::OverlayRenderOptions;
11
12/// Reusable movement, effect-strength and duration recommendations.
13///
14/// Pass these fields to animation/effect constructors explicitly. A preset
15/// does not compile shaders, change their progress clock or start playback.
16/// The default is ease-in-out movement, smooth 15 percent strength fades and
17/// a fixed two-second duration. This value is available on every platform.
18#[derive(Clone, Debug)]
19pub struct AnimationPreset {
20    /// Suggested position interpolation, reusable for either axis.
21    pub movement: Curve,
22    /// Suggested effect strength; Effect still enforces clean endpoints.
23    pub envelope: Curve,
24    /// Suggested fixed or distance-based timing policy.
25    pub duration: Duration,
26}
27
28impl Default for AnimationPreset {
29    fn default() -> Self {
30        Self {
31            movement: Curve::ease_in_out(),
32            envelope: Curve::keyframes(
33                vec![
34                    crate::Keyframe::new(0.0, 0.0),
35                    crate::Keyframe::new(0.15, 1.0),
36                    crate::Keyframe::new(0.85, 1.0),
37                    crate::Keyframe::new(1.0, 0.0),
38                ],
39                crate::KeyframeInterp::SmoothStep,
40            ).expect("valid default preset envelope"),
41            duration: Duration::fixed(std::time::Duration::from_secs(2)),
42        }
43    }
44}
45
46/// Per-icon animation description.
47///
48/// X and Y are two independent axis animations that share the same
49/// normalised time `t = elapsed / duration ∈ [0, 1]`, so both axes tick in
50/// lock-step but can use different curves.
51#[derive(Clone, Debug)]
52pub struct IconAnimationSpec {
53    pub id: IconId,
54    pub target: Point,
55    pub duration: Duration,
56    pub curve_x: Curve,
57    pub curve_y: Curve,
58    pub effect: Option<crate::Effect>,
59}
60
61impl IconAnimationSpec {
62    /// Convenience constructor that reuses the same curve for both axes.
63    pub fn new(id: IconId, target: Point, duration: Duration, curve: Curve) -> Self {
64        Self {
65            id,
66            target,
67            duration,
68            curve_x: curve.clone(),
69            curve_y: curve,
70            effect: None,
71        }
72    }
73
74    /// Constructor with distinct X/Y curves.
75    pub fn with_axes(
76        id: IconId,
77        target: Point,
78        duration: Duration,
79        curve_x: Curve,
80        curve_y: Curve,
81    ) -> Self {
82        Self {
83            id,
84            target,
85            duration,
86            curve_x,
87            curve_y,
88            effect: None,
89        }
90    }
91}
92
93/// Global options that apply to every icon in an animation batch.
94#[derive(Clone, Copy, Debug, Default, PartialEq)]
95pub struct AnimationOptions {
96    /// Align destinations to available grid cells before preparation. Protects
97    /// stationary icons and resolves target conflicts; never changes Shell flags.
98    /// Defaults to false. Failures occur before artwork, flags or position writes.
99    pub snap_to_grid: bool,
100
101    /// Tick rate in Hz. Defaults to 100 (matching the legacy 10 ms step).
102    pub tick_hz: Option<u32>,
103
104    /// Distance threshold in pixels below which an icon is considered "at
105    /// target" and dropped from the active set. Defaults to `10`
106    /// (matching the legacy `POSITION_ERROR_VALUE`).
107    pub position_tolerance_px: Option<i32>,
108
109    /// Optional folder-flag operation applied before the animation starts.
110    pub before_flags: Option<FolderFlagOp>,
111
112    /// Optional folder-flag operation applied after the animation ends
113    /// (regardless of success or user-triggered stop).
114    pub after_flags: Option<FolderFlagOp>,
115
116    /// Force the engine to skip the overlay entirely and take the
117    /// "loud warning + direct teleport" fallback path.
118    ///
119    /// Lets tests and the `animate_direct` demo binary exercise the
120    /// fallback without needing a real overlay failure. Defaults to
121    /// `false` — normal callers get the overlay animation.
122    pub force_fallback: bool,
123
124    /// Feature toggles for the overlay renderer's decoration passes
125    /// (label text, shortcut-arrow overlay, admin shield overlay).
126    /// Defaults to [`OverlayRenderOptions::all_enabled`] — every
127    /// decoration Explorer draws is drawn on the overlay too.
128    pub render_options: OverlayRenderOptions,
129}
130
131/// Folder-flag mutation semantics, mirroring the legacy `ISF_*` modifiers.
132#[derive(Copy, Clone, Debug, PartialEq, Eq)]
133pub enum FolderFlagOp {
134    /// OR-set semantics — matches legacy `set_desktop_flags`.
135    ///
136    /// The engine will call `SetCurrentFolderFlags(flags, 0xFFFF_FFFF)`.
137    Set(u32),
138
139    /// Exactly-set semantics — matches legacy `exactly_set_desktop_flags`.
140    ///
141    /// The engine will call `SetCurrentFolderFlags(build_true_mask(flags), flags)`.
142    Exactly(u32),
143}
144
145#[cfg(test)]
146mod tests {
147    use super::*;
148    use crate::{Duration, IconId};
149
150    #[test]
151    fn animation_preset_defaults_are_independent() {
152        use crate::AnimationCurve;
153        let mut preset = AnimationPreset::default();
154        assert_eq!(preset.movement, Curve::ease_in_out());
155        assert_eq!(preset.duration.resolve(Point::ZERO, Point::ZERO).unwrap(), std::time::Duration::from_secs(2));
156        for (progress, expected) in [(0.0, 0.0), (0.15, 1.0), (0.5, 1.0), (0.85, 1.0), (1.0, 0.0)] {
157            assert_eq!(preset.envelope.eval(progress), expected);
158        }
159        preset.movement = Curve::linear();
160        assert_eq!(AnimationPreset::default().movement, Curve::ease_in_out());
161        fn assert_send_sync<Value: Send + Sync>() {}
162        assert_send_sync::<AnimationPreset>();
163    }
164
165    #[test]
166    fn spec_new_shares_curve_between_axes() {
167        let c = Curve::linear();
168        let s = IconAnimationSpec::new(
169            IconId::from("x"),
170            Point::new(10, 20),
171            Duration::fixed(std::time::Duration::from_millis(500)),
172            c.clone(),
173        );
174        assert_eq!(s.curve_x, c);
175        assert_eq!(s.curve_y, c);
176    }
177
178    #[test]
179    fn options_default_leaves_everything_none() {
180        let o = AnimationOptions::default();
181        assert!(o.tick_hz.is_none());
182        assert!(o.position_tolerance_px.is_none());
183        assert!(o.before_flags.is_none());
184        assert!(o.after_flags.is_none());
185        assert!(!o.force_fallback);
186        assert_eq!(o.render_options, OverlayRenderOptions::all_enabled());
187    }
188}