Skip to main content

ferroui_base/rendering/composition/interactions/
interaction_tracker.rs

1//! `InteractionTracker`: a position and a scale driven by input, inertia and
2//! requests, which expressions read to move visuals.
3
4use super::interaction_tracker_inertia_modifier::InteractionTrackerInertiaModifier;
5use super::visual_interaction_source::VisualInteractionSource;
6use crate::animation::{PlaybackDirection, TimeSpan};
7use crate::media::MediaContext;
8use crate::numerics::Vector3;
9use crate::rendering::composition::animations::{
10    AnimationIterationBehavior, ExpressionAnimation, KeyFrame, KeyFrameAnimation, PropertySetSnapshot,
11    ScalarKeyFrameAnimation, Vector3KeyFrameAnimation,
12};
13use crate::rendering::composition::expressions::{
14    BuiltInExpressionFfi, Expression, ExpressionEvaluationContext, ExpressionVariant, ExpressionVariantValue,
15    IExpressionObject,
16};
17use crate::rendering::composition::{AsCompositionObject, CompositionObject, CompositionPropertySet, Compositor};
18use std::cell::{Cell, RefCell};
19use std::collections::VecDeque;
20use std::rc::{Rc, Weak};
21use std::time::Duration;
22
23/// The decay rate of the position and of the scale while
24/// `PositionInertiaDecayRate` and `ScaleInertiaDecayRate` are not set.
25pub const DEFAULT_INERTIA_DECAY_RATE: f32 = 0.95;
26
27/// How far, in pixels, the position can be pulled past a boundary while
28/// interacting: the limit the rubber band approaches.
29const OVERPAN_LIMIT: f32 = 120.0;
30
31/// The distance (pixels, or scale units for the scale) under which an
32/// inertia is considered at rest.
33const REST_EPSILON: f32 = 0.01;
34
35/// The states of an [`InteractionTracker`].
36///
37/// WinUI has no public state property; the state is known through the
38/// owner callbacks. The port exposes it as [`InteractionTracker::state`].
39#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
40pub enum InteractionTrackerState {
41    /// Nothing moves the tracker.
42    #[default]
43    Idle,
44    /// A source drives the tracker with input.
45    Interacting,
46    /// The tracker coasts to its resting values.
47    Inertia,
48    /// A custom animation moves the tracker
49    /// (`TryUpdatePositionWithAnimation`, `TryUpdateScaleWithAnimation`).
50    CustomAnimation,
51}
52
53/// Whether a position request is clamped to the boundaries
54/// (`InteractionTrackerClampingOption`).
55#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
56pub enum InteractionTrackerClampingOption {
57    /// The value is clamped to `MinPosition` and `MaxPosition`.
58    #[default]
59    Auto,
60    /// The value is used as given, even outside the boundaries.
61    Disabled,
62}
63
64/// The arguments of [`IInteractionTrackerOwner::custom_animation_state_entered`].
65#[derive(Clone, Copy, Debug, PartialEq)]
66pub struct InteractionTrackerCustomAnimationStateEnteredArgs {
67    pub request_id: i32,
68    /// Always `false`: tracker binding is not ported.
69    pub is_from_binding: bool,
70}
71
72/// The arguments of [`IInteractionTrackerOwner::idle_state_entered`].
73#[derive(Clone, Copy, Debug, PartialEq)]
74pub struct InteractionTrackerIdleStateEnteredArgs {
75    pub request_id: i32,
76    /// Always `false`: tracker binding is not ported.
77    pub is_from_binding: bool,
78}
79
80/// The arguments of [`IInteractionTrackerOwner::inertia_state_entered`].
81#[derive(Clone, Copy, Debug, PartialEq)]
82pub struct InteractionTrackerInertiaStateEnteredArgs {
83    /// The resting position after the inertia modifiers and the boundaries.
84    pub modified_resting_position: Option<Vector3>,
85    /// The resting scale after the inertia modifiers and the boundaries.
86    pub modified_resting_scale: Option<f32>,
87    /// The resting position of the natural decay of the velocity.
88    pub natural_resting_position: Vector3,
89    /// The resting scale of the natural decay of the scale velocity.
90    pub natural_resting_scale: f32,
91    pub position_velocity_in_pixels_per_second: Vector3,
92    pub scale_velocity_in_percent_per_second: f32,
93    pub request_id: i32,
94    /// Whether the inertia comes from a `TryUpdate*WithAdditionalVelocity`
95    /// request rather than from the end of an interaction.
96    pub is_inertia_from_impulse: bool,
97    /// Always `false`: tracker binding is not ported.
98    pub is_from_binding: bool,
99}
100
101/// The arguments of [`IInteractionTrackerOwner::interacting_state_entered`].
102#[derive(Clone, Copy, Debug, PartialEq)]
103pub struct InteractionTrackerInteractingStateEnteredArgs {
104    pub request_id: i32,
105    /// Always `false`: tracker binding is not ported.
106    pub is_from_binding: bool,
107}
108
109/// The arguments of [`IInteractionTrackerOwner::request_ignored`].
110#[derive(Clone, Copy, Debug, PartialEq)]
111pub struct InteractionTrackerRequestIgnoredArgs {
112    pub request_id: i32,
113}
114
115/// The arguments of [`IInteractionTrackerOwner::values_changed`].
116#[derive(Clone, Copy, Debug, PartialEq)]
117pub struct InteractionTrackerValuesChangedArgs {
118    pub position: Vector3,
119    pub scale: f32,
120    pub request_id: i32,
121}
122
123/// The callbacks of an [`InteractionTracker`] (`IInteractionTrackerOwner`).
124///
125/// Every callback does nothing by default. The tracker holds its owner
126/// strongly, as WinUI does: an owner that keeps the tracker should keep it
127/// weakly (each callback gets the tracker as `sender`), or the two keep each
128/// other alive.
129///
130/// Callbacks run on the UI thread after the change they report, never
131/// nested: a callback that makes a request on the tracker gets the
132/// callbacks of that request after it returns.
133pub trait IInteractionTrackerOwner {
134    fn custom_animation_state_entered(
135        &self,
136        _sender: &Rc<InteractionTracker>,
137        _args: &InteractionTrackerCustomAnimationStateEnteredArgs,
138    ) {
139    }
140
141    fn idle_state_entered(&self, _sender: &Rc<InteractionTracker>, _args: &InteractionTrackerIdleStateEnteredArgs) {}
142
143    fn inertia_state_entered(
144        &self,
145        _sender: &Rc<InteractionTracker>,
146        _args: &InteractionTrackerInertiaStateEnteredArgs,
147    ) {
148    }
149
150    fn interacting_state_entered(
151        &self,
152        _sender: &Rc<InteractionTracker>,
153        _args: &InteractionTrackerInteractingStateEnteredArgs,
154    ) {
155    }
156
157    fn request_ignored(&self, _sender: &Rc<InteractionTracker>, _args: &InteractionTrackerRequestIgnoredArgs) {}
158
159    fn values_changed(&self, _sender: &Rc<InteractionTracker>, _args: &InteractionTrackerValuesChangedArgs) {}
160}
161
162/// A custom animation of the position or of the scale of a tracker: the
163/// `CompositionAnimation` that `TryUpdatePositionWithAnimation` and
164/// `TryUpdateScaleWithAnimation` take.
165///
166/// Deviation (winui-composition.md): the tracker runs on the UI thread, so
167/// it evaluates the animation itself rather than on the server: key frames
168/// (value and expression key frames, easing, delay, iterations, direction)
169/// and expressions. Reference parameters of these animations that are
170/// property sets (or trackers) are read; references to other composition
171/// objects read as missing, because their values live on the server.
172#[derive(Clone)]
173pub enum InteractionTrackerAnimation {
174    /// A key frame animation of the position.
175    Vector3KeyFrame(Rc<Vector3KeyFrameAnimation>),
176    /// A key frame animation of the scale.
177    ScalarKeyFrame(Rc<ScalarKeyFrameAnimation>),
178    /// An expression of the position (`Vector3`) or of the scale (scalar),
179    /// evaluated every frame until another request or an interaction.
180    Expression(Rc<ExpressionAnimation>),
181}
182
183impl From<Rc<Vector3KeyFrameAnimation>> for InteractionTrackerAnimation {
184    fn from(value: Rc<Vector3KeyFrameAnimation>) -> Self {
185        Self::Vector3KeyFrame(value)
186    }
187}
188
189impl From<Rc<ScalarKeyFrameAnimation>> for InteractionTrackerAnimation {
190    fn from(value: Rc<ScalarKeyFrameAnimation>) -> Self {
191        Self::ScalarKeyFrame(value)
192    }
193}
194
195impl From<Rc<ExpressionAnimation>> for InteractionTrackerAnimation {
196    fn from(value: Rc<ExpressionAnimation>) -> Self {
197        Self::Expression(value)
198    }
199}
200
201/// The values of a tracker: what expressions read as `tracker.Name` and
202/// `this.Target.Name`.
203#[derive(Clone, Copy, Debug, PartialEq)]
204pub(crate) struct TrackerValues {
205    pub position: Vector3,
206    pub position_velocity: Vector3,
207    pub scale: f32,
208    pub scale_velocity: f32,
209    pub min_position: Vector3,
210    pub max_position: Vector3,
211    pub min_scale: f32,
212    pub max_scale: f32,
213    pub natural_resting_position: Vector3,
214    pub natural_resting_scale: f32,
215    pub position_inertia_decay_rate: Option<Vector3>,
216    pub scale_inertia_decay_rate: Option<f32>,
217    pub is_position_rounding_suggested: bool,
218    pub is_inertia_from_impulse: bool,
219}
220
221impl Default for TrackerValues {
222    fn default() -> Self {
223        Self {
224            position: Vector3::ZERO,
225            position_velocity: Vector3::ZERO,
226            scale: 1.0,
227            scale_velocity: 0.0,
228            min_position: Vector3::ZERO,
229            max_position: Vector3::ZERO,
230            min_scale: 1.0,
231            max_scale: 1.0,
232            natural_resting_position: Vector3::ZERO,
233            natural_resting_scale: 1.0,
234            position_inertia_decay_rate: None,
235            scale_inertia_decay_rate: None,
236            is_position_rounding_suggested: true,
237            is_inertia_from_impulse: false,
238        }
239    }
240}
241
242impl TrackerValues {
243    fn effective_position_decay_rate(&self) -> Vector3 {
244        self.position_inertia_decay_rate.unwrap_or(Vector3::from_value(DEFAULT_INERTIA_DECAY_RATE))
245    }
246
247    fn effective_scale_decay_rate(&self) -> f32 {
248        self.scale_inertia_decay_rate.unwrap_or(DEFAULT_INERTIA_DECAY_RATE)
249    }
250
251    /// The value of the property with the given WinUI name.
252    pub fn get_property(&self, name: &str) -> ExpressionVariant {
253        match name {
254            "Position" => self.position.into(),
255            "PositionVelocityInPixelsPerSecond" => self.position_velocity.into(),
256            "Scale" => self.scale.into(),
257            "ScaleVelocityInPercentPerSecond" => self.scale_velocity.into(),
258            "MinPosition" => self.min_position.into(),
259            "MaxPosition" => self.max_position.into(),
260            "MinScale" => self.min_scale.into(),
261            "MaxScale" => self.max_scale.into(),
262            "NaturalRestingPosition" => self.natural_resting_position.into(),
263            "NaturalRestingScale" => self.natural_resting_scale.into(),
264            "PositionInertiaDecayRate" => self.effective_position_decay_rate().into(),
265            "ScaleInertiaDecayRate" => self.effective_scale_decay_rate().into(),
266            "IsPositionRoundingSuggested" => self.is_position_rounding_suggested.into(),
267            "IsInertiaFromImpulse" => self.is_inertia_from_impulse.into(),
268            _ => ExpressionVariant::Invalid,
269        }
270    }
271}
272
273/// The tracker as `this.Target` of the expressions it evaluates.
274struct TrackerTarget(TrackerValues);
275
276impl IExpressionObject for TrackerTarget {
277    fn get_property(&self, name: &str) -> ExpressionVariant {
278        self.0.get_property(name)
279    }
280}
281
282/// Evaluates the expression of `animation` against the tracker values.
283/// The parameters are taken from the animation at each evaluation, so a
284/// referenced property set is read as it is now. An empty or invalid
285/// expression gives an invalid variant.
286fn evaluate_animation(animation: &ExpressionAnimation, values: TrackerValues, current: ExpressionVariant) -> ExpressionVariant {
287    let Some(source) = animation.expression() else { return ExpressionVariant::Invalid };
288    let Ok(expression) = Expression::parse(&source) else { return ExpressionVariant::Invalid };
289    evaluate_parsed(&expression, animation.create_snapshot(), values, current, current)
290}
291
292fn evaluate_parsed(
293    expression: &Expression,
294    parameters: Rc<PropertySetSnapshot>,
295    values: TrackerValues,
296    starting: ExpressionVariant,
297    current: ExpressionVariant,
298) -> ExpressionVariant {
299    let target = TrackerTarget(values);
300    let ctx = ExpressionEvaluationContext {
301        starting_value: starting,
302        current_value: current,
303        final_value: starting,
304        target: Some(&target),
305        parameters: Some(&*parameters),
306        foreign_function_interface: Some(BuiltInExpressionFfi::instance()),
307    };
308    expression.evaluate(&ctx)
309}
310
311fn axis(v: Vector3, index: usize) -> f32 {
312    match index {
313        0 => v.x,
314        1 => v.y,
315        _ => v.z,
316    }
317}
318
319fn set_axis(v: &mut Vector3, index: usize, value: f32) {
320    match index {
321        0 => v.x = value,
322        1 => v.y = value,
323        _ => v.z = value,
324    }
325}
326
327/// Clamps to `[min, max]`; when `min > max`, `min` wins, as for
328/// `Math.Clamp` applied to a reversed range in WinUI's samples.
329fn clamp_scalar(value: f32, min: f32, max: f32) -> f32 {
330    let max = max.max(min);
331    if value < min {
332        min
333    } else if value > max {
334        max
335    } else {
336        value
337    }
338}
339
340fn clamp_vector(value: Vector3, min: Vector3, max: Vector3) -> Vector3 {
341    Vector3::new(
342        clamp_scalar(value.x, min.x, max.x),
343        clamp_scalar(value.y, min.y, max.y),
344        clamp_scalar(value.z, min.z, max.z),
345    )
346}
347
348/// The rubber band of an overpan: the displayed distance past a boundary
349/// for a pulled distance `x`, approaching [`OVERPAN_LIMIT`].
350fn rubber_band(x: f32) -> f32 {
351    OVERPAN_LIMIT * (1.0 - 1.0 / (1.0 + 0.55 * x / OVERPAN_LIMIT))
352}
353
354fn rubber_band_scalar(raw: f32, min: f32, max: f32) -> f32 {
355    let max = max.max(min);
356    if raw < min {
357        min - rubber_band(min - raw)
358    } else if raw > max {
359        max + rubber_band(raw - max)
360    } else {
361        raw
362    }
363}
364
365/// The share of the velocity kept after one second for a decay rate:
366/// `1 - decay_rate`, kept below 1 so that the inertia ends.
367///
368/// Deviation (winui-composition.md): WinUI documents the decay rate as the
369/// part of the velocity lost per second (0.95 keeps 5% after a second);
370/// the port uses that reading: `v(t) = v0 * (1 - rate)^t`.
371fn retention(decay_rate: f32) -> f32 {
372    (1.0 - decay_rate.clamp(0.0, 1.0)).min(0.9999)
373}
374
375/// The closed form of the exponential decay: where a value `p` moving at
376/// `v` per second comes to rest when the velocity keeps the share `r` of
377/// itself each second. `p(t) = p + v * (r^t - 1) / ln r`, so
378/// `p(inf) = p - v / ln r = p + v / ln(1 / r)`.
379pub(crate) fn natural_resting_value(p: f32, v: f32, r: f32) -> f32 {
380    if r <= 0.0 || v == 0.0 {
381        p
382    } else {
383        p - v / r.ln()
384    }
385}
386
387/// The value and velocity at `t` of the exponential motion from `start` to
388/// `target`: `target + (start - target) * r^t`. With the natural resting
389/// value as the target this is exactly the decay of the velocity; with a
390/// modified target the motion keeps the same shape and ends there.
391fn exponential_motion(start: f32, target: f32, r: f32, t: f32) -> (f32, f32, bool) {
392    if r <= 0.0 {
393        return (target, 0.0, true);
394    }
395    let k = r.powf(t);
396    let remaining = (start - target) * k;
397    let velocity = (target - start) * (-r.ln()) * k;
398    (target + remaining, velocity, remaining.abs() < REST_EPSILON)
399}
400
401/// The value types a custom animation runs on.
402trait TrackValue: ExpressionVariantValue {
403    fn interpolate(from: Self, to: Self, progress: f32) -> Self;
404}
405
406impl TrackValue for Vector3 {
407    fn interpolate(from: Self, to: Self, progress: f32) -> Self {
408        from + (to - from) * progress
409    }
410}
411
412impl TrackValue for f32 {
413    fn interpolate(from: Self, to: Self, progress: f32) -> Self {
414        from + (to - from) * progress
415    }
416}
417
418/// A key frame animation, read from its animation when the request is
419/// made, evaluated on the UI thread.
420struct KeyFrameTrack<T> {
421    frames: Vec<KeyFrame<T>>,
422    duration: f32,
423    delay: f32,
424    iteration_count: i32,
425    forever: bool,
426    direction: PlaybackDirection,
427    parameters: Rc<PropertySetSnapshot>,
428}
429
430impl<T: TrackValue> KeyFrameTrack<T> {
431    fn new(animation: &KeyFrameAnimation) -> Self {
432        let frames = animation.with_key_frames::<T, _>(|key_frames| key_frames.items().to_vec());
433        KeyFrameTrack {
434            frames,
435            duration: animation.duration().as_secs_f32().max(0.001),
436            delay: animation.delay_time().as_secs_f32(),
437            iteration_count: animation.iteration_count().max(1),
438            forever: animation.iteration_behavior() == AnimationIterationBehavior::Forever,
439            direction: animation.direction(),
440            parameters: animation.create_snapshot(),
441        }
442    }
443
444    /// The value at `elapsed` seconds, and whether the animation is over.
445    fn value_at(&self, elapsed: f32, starting: T, current: T, values: TrackerValues) -> (T, bool) {
446        let t = elapsed - self.delay;
447        if t < 0.0 {
448            return (starting, false);
449        }
450        let total = self.duration * self.iteration_count as f32;
451        let (iteration, local, finished) = if !self.forever && t >= total {
452            (self.iteration_count - 1, 1.0, true)
453        } else {
454            let iteration = (t / self.duration).floor();
455            (iteration as i32, (t - iteration * self.duration) / self.duration, false)
456        };
457        let even = iteration % 2 == 0;
458        let progress = match self.direction {
459            PlaybackDirection::Normal => local,
460            PlaybackDirection::Reverse => 1.0 - local,
461            PlaybackDirection::Alternate => {
462                if even {
463                    local
464                } else {
465                    1.0 - local
466                }
467            }
468            PlaybackDirection::AlternateReverse => {
469                if even {
470                    1.0 - local
471                } else {
472                    local
473                }
474            }
475        };
476        (self.interpolate(progress, starting, current, values), finished)
477    }
478
479    fn frame_value(&self, frame: &KeyFrame<T>, starting: T, current: T, values: TrackerValues) -> T {
480        match &frame.expression {
481            Some(expression) => evaluate_parsed(
482                expression,
483                self.parameters.clone(),
484                values,
485                ExpressionVariant::create(starting),
486                ExpressionVariant::create(current),
487            )
488            .try_cast::<T>()
489            .unwrap_or(frame.value),
490            None => frame.value,
491        }
492    }
493
494    fn interpolate(&self, progress: f32, starting: T, current: T, values: TrackerValues) -> T {
495        // The value before the first key frame is the starting value, as
496        // the server instance adds a key frame for it at 0.
497        let mut previous_key = 0.0f32;
498        let mut previous_value = starting;
499        for frame in &self.frames {
500            let value = self.frame_value(frame, starting, current, values);
501            if progress <= frame.normalized_progress_key {
502                let span = frame.normalized_progress_key - previous_key;
503                let local = if span <= 0.0 { 1.0 } else { (progress - previous_key) / span };
504                let eased = frame.easing_function.ease(local as f64) as f32;
505                return T::interpolate(previous_value, value, eased);
506            }
507            previous_key = frame.normalized_progress_key;
508            previous_value = value;
509        }
510        previous_value
511    }
512}
513
514/// What a custom animation evaluates.
515enum Track<T> {
516    KeyFrames(KeyFrameTrack<T>),
517    Expression { expression: Rc<Expression>, animation: Rc<ExpressionAnimation> },
518}
519
520impl<T: TrackValue> Track<T> {
521    fn value_at(&self, elapsed: f32, starting: T, current: T, values: TrackerValues) -> (T, bool) {
522        match self {
523            Track::KeyFrames(track) => track.value_at(elapsed, starting, current, values),
524            Track::Expression { expression, animation } => {
525                let value = evaluate_parsed(
526                    expression,
527                    animation.create_snapshot(),
528                    values,
529                    ExpressionVariant::create(starting),
530                    ExpressionVariant::create(current),
531                )
532                .try_cast::<T>()
533                .unwrap_or(current);
534                (value, false)
535            }
536        }
537    }
538}
539
540enum CustomKind {
541    Position { track: Track<Vector3>, starting: Vector3 },
542    Scale { track: Track<f32>, starting: f32, center: Vector3 },
543}
544
545struct CustomRun {
546    request_id: i32,
547    elapsed: f32,
548    kind: CustomKind,
549}
550
551struct InertiaRun {
552    request_id: i32,
553    elapsed: f32,
554    start_position: Vector3,
555    target_position: Vector3,
556    position_retention: Vector3,
557    start_scale: f32,
558    target_scale: f32,
559    scale_retention: f32,
560    scale_center: Vector3,
561    /// The motion modifiers of the X, Y and scale axes.
562    motions: [Option<Rc<ExpressionAnimation>>; 3],
563}
564
565enum Notification {
566    CustomAnimation(InteractionTrackerCustomAnimationStateEnteredArgs),
567    Idle(InteractionTrackerIdleStateEnteredArgs),
568    Inertia(InteractionTrackerInertiaStateEnteredArgs),
569    Interacting(InteractionTrackerInteractingStateEnteredArgs),
570    RequestIgnored(InteractionTrackerRequestIgnoredArgs),
571    ValuesChanged(InteractionTrackerValuesChangedArgs),
572}
573
574/// The sources of a tracker (`CompositionInteractionSourceCollection`).
575pub struct CompositionInteractionSourceCollection {
576    tracker: Weak<InteractionTracker>,
577    items: RefCell<Vec<Rc<VisualInteractionSource>>>,
578}
579
580impl CompositionInteractionSourceCollection {
581    /// Adds a source: its input drives the tracker from now on. Adding a
582    /// source twice does nothing.
583    pub fn add(&self, source: Rc<VisualInteractionSource>) {
584        if self.items.borrow().iter().any(|s| Rc::ptr_eq(s, &source)) {
585            return;
586        }
587        self.items.borrow_mut().push(source.clone());
588        source.attach_tracker(&self.tracker);
589    }
590
591    /// Removes a source.
592    pub fn remove(&self, source: &Rc<VisualInteractionSource>) {
593        let removed = {
594            let mut items = self.items.borrow_mut();
595            let index = items.iter().position(|s| Rc::ptr_eq(s, source));
596            index.map(|index| items.remove(index))
597        };
598        if let Some(removed) = removed {
599            removed.detach_tracker(&self.tracker);
600        }
601    }
602
603    /// Removes every source.
604    pub fn remove_all(&self) {
605        let removed = std::mem::take(&mut *self.items.borrow_mut());
606        for source in removed {
607            source.detach_tracker(&self.tracker);
608        }
609    }
610
611    /// The number of sources.
612    pub fn count(&self) -> usize {
613        self.items.borrow().len()
614    }
615
616    /// The sources, in the order they were added.
617    pub fn items(&self) -> Vec<Rc<VisualInteractionSource>> {
618        self.items.borrow().clone()
619    }
620}
621
622/// A position and a scale driven by input, inertia and requests
623/// (`Microsoft.UI.Composition.Interactions.InteractionTracker`).
624///
625/// The main use is to bind visuals to it: an expression animation that
626/// references the tracker (`set_reference_parameter("tracker", tracker)`)
627/// reads `tracker.Position`, `tracker.Scale` and the other values of the
628/// tracker, e.g. `-tracker.Position` for the `Offset` of a scrolled visual.
629///
630/// Deviation (winui-composition.md): WinUI runs the tracker in the
631/// compositor. Here it runs on the UI thread, ticking with the animation
632/// frames of the media context while it is in inertia or in a custom
633/// animation, and publishes its values into its property set
634/// ([`properties`](Self::properties)) after every change; a reference to
635/// the tracker in an expression is a reference to that property set.
636pub struct InteractionTracker {
637    this: Weak<InteractionTracker>,
638    properties: Rc<CompositionPropertySet>,
639    owner: Option<Rc<dyn IInteractionTrackerOwner>>,
640    state: Cell<InteractionTrackerState>,
641    values: Cell<TrackerValues>,
642    interaction_sources: CompositionInteractionSourceCollection,
643    position_x_modifiers: RefCell<Vec<InteractionTrackerInertiaModifier>>,
644    position_y_modifiers: RefCell<Vec<InteractionTrackerInertiaModifier>>,
645    scale_modifiers: RefCell<Vec<InteractionTrackerInertiaModifier>>,
646    next_request_id: Cell<i32>,
647    last_request_id: Cell<i32>,
648    /// The number of sources that currently drive the tracker.
649    active_interactions: Cell<u32>,
650    /// The position the input asked for while interacting, before the
651    /// rubber band of the boundaries.
652    raw_position: Cell<Vector3>,
653    inertia: RefCell<Option<InertiaRun>>,
654    custom: RefCell<Option<CustomRun>>,
655    frame_requested: Cell<bool>,
656    frame_requests_enabled: Cell<bool>,
657    last_frame_time: Cell<Option<f64>>,
658    notifications: RefCell<VecDeque<Notification>>,
659    notifying: Cell<bool>,
660}
661
662impl InteractionTracker {
663    /// `InteractionTracker.Create(compositor)`.
664    pub fn create(compositor: &Rc<Compositor>) -> Rc<InteractionTracker> {
665        Self::create_core(compositor, None)
666    }
667
668    /// `InteractionTracker.CreateWithOwner(compositor, owner)`.
669    pub fn create_with_owner(compositor: &Rc<Compositor>, owner: Rc<dyn IInteractionTrackerOwner>) -> Rc<InteractionTracker> {
670        Self::create_core(compositor, Some(owner))
671    }
672
673    fn create_core(compositor: &Rc<Compositor>, owner: Option<Rc<dyn IInteractionTrackerOwner>>) -> Rc<InteractionTracker> {
674        let tracker = Rc::new_cyclic(|this: &Weak<InteractionTracker>| InteractionTracker {
675            this: this.clone(),
676            properties: CompositionPropertySet::new(compositor),
677            owner,
678            state: Cell::new(InteractionTrackerState::Idle),
679            values: Cell::new(TrackerValues::default()),
680            interaction_sources: CompositionInteractionSourceCollection {
681                tracker: this.clone(),
682                items: RefCell::new(Vec::new()),
683            },
684            position_x_modifiers: RefCell::new(Vec::new()),
685            position_y_modifiers: RefCell::new(Vec::new()),
686            scale_modifiers: RefCell::new(Vec::new()),
687            next_request_id: Cell::new(0),
688            last_request_id: Cell::new(0),
689            active_interactions: Cell::new(0),
690            raw_position: Cell::new(Vector3::ZERO),
691            inertia: RefCell::new(None),
692            custom: RefCell::new(None),
693            frame_requested: Cell::new(false),
694            frame_requests_enabled: Cell::new(true),
695            last_frame_time: Cell::new(None),
696            notifications: RefCell::new(VecDeque::new()),
697            notifying: Cell::new(false),
698        });
699        tracker.publish();
700        tracker
701    }
702
703    /// The compositor of the tracker.
704    pub fn compositor(&self) -> &Rc<Compositor> {
705        self.properties.compositor()
706    }
707
708    /// The property set the tracker publishes its values into, under the
709    /// WinUI names (`Position`, `Scale`, `PositionVelocityInPixelsPerSecond`,
710    /// `ScaleVelocityInPercentPerSecond`, `MinPosition`, `MaxPosition`,
711    /// `MinScale`, `MaxScale`, `NaturalRestingPosition`,
712    /// `NaturalRestingScale`, `PositionInertiaDecayRate`,
713    /// `ScaleInertiaDecayRate`, `IsPositionRoundingSuggested`,
714    /// `IsInertiaFromImpulse`).
715    ///
716    /// Deviation (winui-composition.md): in WinUI, `Properties` of a tracker
717    /// is a property set of the application and the values are properties
718    /// of the tracker itself; here the values live in this set, so entries
719    /// the application adds must not use those names.
720    pub fn properties(&self) -> &Rc<CompositionPropertySet> {
721        &self.properties
722    }
723
724    /// The owner given at creation.
725    pub fn owner(&self) -> Option<Rc<dyn IInteractionTrackerOwner>> {
726        self.owner.clone()
727    }
728
729    /// The current state (not a WinUI member, see
730    /// [`InteractionTrackerState`]).
731    pub fn state(&self) -> InteractionTrackerState {
732        self.state.get()
733    }
734
735    /// The sources whose input drives the tracker.
736    pub fn interaction_sources(&self) -> &CompositionInteractionSourceCollection {
737        &self.interaction_sources
738    }
739
740    // --- values -------------------------------------------------------------
741
742    fn update_values(&self, f: impl FnOnce(&mut TrackerValues)) {
743        let mut values = self.values.get();
744        f(&mut values);
745        self.values.set(values);
746        self.publish();
747    }
748
749    /// Writes the values into the property set.
750    fn publish(&self) {
751        let v = self.values.get();
752        let p = &self.properties;
753        p.insert_vector3("Position", v.position);
754        p.insert_vector3("PositionVelocityInPixelsPerSecond", v.position_velocity);
755        p.insert_scalar("Scale", v.scale);
756        p.insert_scalar("ScaleVelocityInPercentPerSecond", v.scale_velocity);
757        p.insert_vector3("MinPosition", v.min_position);
758        p.insert_vector3("MaxPosition", v.max_position);
759        p.insert_scalar("MinScale", v.min_scale);
760        p.insert_scalar("MaxScale", v.max_scale);
761        p.insert_vector3("NaturalRestingPosition", v.natural_resting_position);
762        p.insert_scalar("NaturalRestingScale", v.natural_resting_scale);
763        p.insert_vector3("PositionInertiaDecayRate", v.effective_position_decay_rate());
764        p.insert_scalar("ScaleInertiaDecayRate", v.effective_scale_decay_rate());
765        p.insert_boolean("IsPositionRoundingSuggested", v.is_position_rounding_suggested);
766        p.insert_boolean("IsInertiaFromImpulse", v.is_inertia_from_impulse);
767    }
768
769    /// The position (`Position`).
770    pub fn position(&self) -> Vector3 {
771        self.values.get().position
772    }
773
774    /// The velocity of the position (`PositionVelocityInPixelsPerSecond`).
775    pub fn position_velocity_in_pixels_per_second(&self) -> Vector3 {
776        self.values.get().position_velocity
777    }
778
779    /// The scale (`Scale`).
780    pub fn scale(&self) -> f32 {
781        self.values.get().scale
782    }
783
784    /// The velocity of the scale (`ScaleVelocityInPercentPerSecond`), in
785    /// scale units per second.
786    pub fn scale_velocity_in_percent_per_second(&self) -> f32 {
787        self.values.get().scale_velocity
788    }
789
790    /// The lower boundary of the position (`MinPosition`).
791    pub fn min_position(&self) -> Vector3 {
792        self.values.get().min_position
793    }
794
795    /// Sets the lower boundary. The position is not moved by the change; the
796    /// next request or inertia honours it.
797    pub fn set_min_position(&self, value: Vector3) {
798        self.update_values(|v| v.min_position = value);
799    }
800
801    /// The upper boundary of the position (`MaxPosition`).
802    pub fn max_position(&self) -> Vector3 {
803        self.values.get().max_position
804    }
805
806    pub fn set_max_position(&self, value: Vector3) {
807        self.update_values(|v| v.max_position = value);
808    }
809
810    /// The lower boundary of the scale (`MinScale`).
811    pub fn min_scale(&self) -> f32 {
812        self.values.get().min_scale
813    }
814
815    pub fn set_min_scale(&self, value: f32) {
816        self.update_values(|v| v.min_scale = value);
817    }
818
819    /// The upper boundary of the scale (`MaxScale`).
820    pub fn max_scale(&self) -> f32 {
821        self.values.get().max_scale
822    }
823
824    pub fn set_max_scale(&self, value: f32) {
825        self.update_values(|v| v.max_scale = value);
826    }
827
828    /// Where the natural decay of the velocity of the last inertia comes to
829    /// rest, before the inertia modifiers and the boundaries
830    /// (`NaturalRestingPosition`).
831    pub fn natural_resting_position(&self) -> Vector3 {
832        self.values.get().natural_resting_position
833    }
834
835    /// The natural resting scale of the last inertia (`NaturalRestingScale`).
836    pub fn natural_resting_scale(&self) -> f32 {
837        self.values.get().natural_resting_scale
838    }
839
840    /// The decay rate of the position velocity per axis
841    /// (`PositionInertiaDecayRate`); `None` for the default
842    /// ([`DEFAULT_INERTIA_DECAY_RATE`]).
843    pub fn position_inertia_decay_rate(&self) -> Option<Vector3> {
844        self.values.get().position_inertia_decay_rate
845    }
846
847    pub fn set_position_inertia_decay_rate(&self, value: Option<Vector3>) {
848        self.update_values(|v| v.position_inertia_decay_rate = value);
849    }
850
851    /// The decay rate of the scale velocity (`ScaleInertiaDecayRate`).
852    pub fn scale_inertia_decay_rate(&self) -> Option<f32> {
853        self.values.get().scale_inertia_decay_rate
854    }
855
856    pub fn set_scale_inertia_decay_rate(&self, value: Option<f32>) {
857        self.update_values(|v| v.scale_inertia_decay_rate = value);
858    }
859
860    /// Whether the position should be rounded to whole pixels: `true` while
861    /// the tracker is idle (`IsPositionRoundingSuggested`).
862    pub fn is_position_rounding_suggested(&self) -> bool {
863        self.values.get().is_position_rounding_suggested
864    }
865
866    /// Whether the current or last inertia comes from an impulse
867    /// (`IsInertiaFromImpulse`).
868    pub fn is_inertia_from_impulse(&self) -> bool {
869        self.values.get().is_inertia_from_impulse
870    }
871
872    // --- inertia modifiers --------------------------------------------------
873
874    /// `ConfigurePositionXInertiaModifiers`: the modifiers of the inertia of
875    /// the X axis; an empty list removes them.
876    pub fn configure_position_x_inertia_modifiers(&self, modifiers: impl IntoIterator<Item = InteractionTrackerInertiaModifier>) {
877        *self.position_x_modifiers.borrow_mut() = modifiers.into_iter().collect();
878    }
879
880    /// `ConfigurePositionYInertiaModifiers`.
881    pub fn configure_position_y_inertia_modifiers(&self, modifiers: impl IntoIterator<Item = InteractionTrackerInertiaModifier>) {
882        *self.position_y_modifiers.borrow_mut() = modifiers.into_iter().collect();
883    }
884
885    /// `ConfigureScaleInertiaModifiers`.
886    pub fn configure_scale_inertia_modifiers(&self, modifiers: impl IntoIterator<Item = InteractionTrackerInertiaModifier>) {
887        *self.scale_modifiers.borrow_mut() = modifiers.into_iter().collect();
888    }
889
890    // --- requests -----------------------------------------------------------
891
892    fn new_request_id(&self) -> i32 {
893        let id = self.next_request_id.get() + 1;
894        self.next_request_id.set(id);
895        self.last_request_id.set(id);
896        id
897    }
898
899    /// Ignores a request while a source drives the tracker. Returns whether
900    /// it was ignored.
901    fn ignore_while_interacting(&self, request_id: i32) -> bool {
902        if self.state.get() == InteractionTrackerState::Interacting {
903            self.notify(Notification::RequestIgnored(InteractionTrackerRequestIgnoredArgs { request_id }));
904            return true;
905        }
906        false
907    }
908
909    /// `TryUpdatePosition(value)`: moves to `value`, clamped to the
910    /// boundaries, and stops any inertia or custom animation. Ignored while
911    /// interacting. Returns the id of the request.
912    pub fn try_update_position(&self, value: Vector3) -> i32 {
913        self.try_update_position_with_option(value, InteractionTrackerClampingOption::Auto)
914    }
915
916    /// `TryUpdatePosition(value, option)`.
917    pub fn try_update_position_with_option(&self, value: Vector3, option: InteractionTrackerClampingOption) -> i32 {
918        let id = self.new_request_id();
919        if !self.ignore_while_interacting(id) {
920            self.jump_to(value, option, id);
921        }
922        self.flush();
923        id
924    }
925
926    /// `TryUpdatePositionBy(amount)`: moves by `amount`, clamped.
927    pub fn try_update_position_by(&self, amount: Vector3) -> i32 {
928        let id = self.new_request_id();
929        if !self.ignore_while_interacting(id) {
930            self.jump_to(self.position() + amount, InteractionTrackerClampingOption::Auto, id);
931        }
932        self.flush();
933        id
934    }
935
936    fn jump_to(&self, value: Vector3, option: InteractionTrackerClampingOption, request_id: i32) {
937        self.stop_runs();
938        let values = self.values.get();
939        let position = match option {
940            InteractionTrackerClampingOption::Auto => clamp_vector(value, values.min_position, values.max_position),
941            InteractionTrackerClampingOption::Disabled => value,
942        };
943        self.update_values(|v| {
944            v.position = position;
945            v.position_velocity = Vector3::ZERO;
946            v.scale_velocity = 0.0;
947        });
948        self.values_changed(request_id);
949        self.enter_idle(request_id);
950    }
951
952    /// `TryUpdatePositionWithAnimation(animation)`: runs a custom animation
953    /// of the position (a `Vector3` key frame animation, or an expression).
954    /// Ignored while interacting, or for an animation of another type.
955    pub fn try_update_position_with_animation(&self, animation: impl Into<InteractionTrackerAnimation>) -> i32 {
956        let id = self.new_request_id();
957        if !self.ignore_while_interacting(id) {
958            let track = match animation.into() {
959                InteractionTrackerAnimation::Vector3KeyFrame(a) => Some(Track::KeyFrames(KeyFrameTrack::<Vector3>::new(&a))),
960                InteractionTrackerAnimation::Expression(a) => Self::expression_track::<Vector3>(a),
961                InteractionTrackerAnimation::ScalarKeyFrame(_) => None,
962            };
963            match track {
964                Some(track) => {
965                    let starting = self.position();
966                    self.start_custom(CustomRun {
967                        request_id: id,
968                        elapsed: 0.0,
969                        kind: CustomKind::Position { track, starting },
970                    });
971                }
972                None => self.notify(Notification::RequestIgnored(InteractionTrackerRequestIgnoredArgs { request_id: id })),
973            }
974        }
975        self.flush();
976        id
977    }
978
979    fn expression_track<T: TrackValue>(animation: Rc<ExpressionAnimation>) -> Option<Track<T>> {
980        let source = animation.expression()?;
981        let expression = Expression::parse(&source).ok()?;
982        Some(Track::Expression { expression: Rc::new(expression), animation })
983    }
984
985    /// `TryUpdatePositionWithAdditionalVelocity(velocity)`: adds `velocity`
986    /// (pixels per second) to the current velocity and coasts in inertia.
987    /// Ignored while interacting.
988    pub fn try_update_position_with_additional_velocity(&self, velocity_in_pixels_per_second: Vector3) -> i32 {
989        let id = self.new_request_id();
990        if !self.ignore_while_interacting(id) {
991            let values = self.values.get();
992            let base = match self.state.get() {
993                InteractionTrackerState::Idle => Vector3::ZERO,
994                _ => values.position_velocity,
995            };
996            let scale_velocity = match self.state.get() {
997                InteractionTrackerState::Idle => 0.0,
998                _ => values.scale_velocity,
999            };
1000            self.enter_inertia(base + velocity_in_pixels_per_second, scale_velocity, Vector3::ZERO, id, true);
1001        }
1002        self.flush();
1003        id
1004    }
1005
1006    /// `TryUpdateScale(value, centerPoint)`: scales to `value`, clamped to
1007    /// the scale boundaries, keeping `center_point` (in the coordinates of
1008    /// the viewport) in place. Ignored while interacting.
1009    pub fn try_update_scale(&self, value: f32, center_point: Vector3) -> i32 {
1010        let id = self.new_request_id();
1011        if !self.ignore_while_interacting(id) {
1012            self.stop_runs();
1013            self.apply_scale(value, center_point);
1014            self.update_values(|v| {
1015                v.position_velocity = Vector3::ZERO;
1016                v.scale_velocity = 0.0;
1017            });
1018            self.values_changed(id);
1019            self.enter_idle(id);
1020        }
1021        self.flush();
1022        id
1023    }
1024
1025    /// Sets the scale (clamped) and moves the position so that
1026    /// `center_point` stays in place.
1027    fn apply_scale(&self, value: f32, center_point: Vector3) {
1028        let values = self.values.get();
1029        let new_scale = clamp_scalar(value, values.min_scale, values.max_scale);
1030        let old_scale = values.scale;
1031        let position = if old_scale != 0.0 {
1032            let c = Vector3::new(center_point.x, center_point.y, 0.0);
1033            clamp_vector((values.position + c) * (new_scale / old_scale) - c, values.min_position, values.max_position)
1034        } else {
1035            values.position
1036        };
1037        self.update_values(|v| {
1038            v.scale = new_scale;
1039            v.position = position;
1040        });
1041    }
1042
1043    /// `TryUpdateScaleWithAnimation(animation, centerPoint)`: runs a custom
1044    /// animation of the scale (a scalar key frame animation, or an
1045    /// expression). Ignored while interacting, or for an animation of
1046    /// another type.
1047    pub fn try_update_scale_with_animation(&self, animation: impl Into<InteractionTrackerAnimation>, center_point: Vector3) -> i32 {
1048        let id = self.new_request_id();
1049        if !self.ignore_while_interacting(id) {
1050            let track = match animation.into() {
1051                InteractionTrackerAnimation::ScalarKeyFrame(a) => Some(Track::KeyFrames(KeyFrameTrack::<f32>::new(&a))),
1052                InteractionTrackerAnimation::Expression(a) => Self::expression_track::<f32>(a),
1053                InteractionTrackerAnimation::Vector3KeyFrame(_) => None,
1054            };
1055            match track {
1056                Some(track) => {
1057                    let starting = self.scale();
1058                    self.start_custom(CustomRun {
1059                        request_id: id,
1060                        elapsed: 0.0,
1061                        kind: CustomKind::Scale { track, starting, center: center_point },
1062                    });
1063                }
1064                None => self.notify(Notification::RequestIgnored(InteractionTrackerRequestIgnoredArgs { request_id: id })),
1065            }
1066        }
1067        self.flush();
1068        id
1069    }
1070
1071    /// `TryUpdateScaleWithAdditionalVelocity(velocity, centerPoint)`: adds
1072    /// `velocity` (scale units per second) to the scale velocity and coasts
1073    /// in inertia around `center_point`. Ignored while interacting.
1074    pub fn try_update_scale_with_additional_velocity(&self, velocity_in_percent_per_second: f32, center_point: Vector3) -> i32 {
1075        let id = self.new_request_id();
1076        if !self.ignore_while_interacting(id) {
1077            let values = self.values.get();
1078            let (position_velocity, scale_velocity) = match self.state.get() {
1079                InteractionTrackerState::Idle => (Vector3::ZERO, 0.0),
1080                _ => (values.position_velocity, values.scale_velocity),
1081            };
1082            self.enter_inertia(position_velocity, scale_velocity + velocity_in_percent_per_second, center_point, id, true);
1083        }
1084        self.flush();
1085        id
1086    }
1087
1088    // --- input from the sources ---------------------------------------------
1089
1090    /// A source starts driving the tracker: the tracker enters the
1091    /// interacting state, interrupting any inertia or custom animation.
1092    pub(crate) fn begin_interaction(&self) {
1093        self.active_interactions.set(self.active_interactions.get() + 1);
1094        if self.state.get() == InteractionTrackerState::Interacting {
1095            return;
1096        }
1097        self.stop_runs();
1098        self.raw_position.set(self.position());
1099        self.state.set(InteractionTrackerState::Interacting);
1100        self.update_values(|v| {
1101            v.is_position_rounding_suggested = false;
1102            v.position_velocity = Vector3::ZERO;
1103            v.scale_velocity = 0.0;
1104        });
1105        self.notify(Notification::Interacting(InteractionTrackerInteractingStateEnteredArgs {
1106            request_id: self.last_request_id.get(),
1107            is_from_binding: false,
1108        }));
1109        self.flush();
1110    }
1111
1112    /// A source moved the position by `delta` while interacting. Past a
1113    /// boundary the position follows with a rubber band.
1114    pub(crate) fn interaction_delta(&self, delta: Vector3, velocity: Vector3) {
1115        if self.state.get() != InteractionTrackerState::Interacting {
1116            return;
1117        }
1118        let raw = self.raw_position.get() + delta;
1119        self.raw_position.set(raw);
1120        let values = self.values.get();
1121        let position = Vector3::new(
1122            rubber_band_scalar(raw.x, values.min_position.x, values.max_position.x),
1123            rubber_band_scalar(raw.y, values.min_position.y, values.max_position.y),
1124            rubber_band_scalar(raw.z, values.min_position.z, values.max_position.z),
1125        );
1126        self.update_values(|v| {
1127            v.position = position;
1128            v.position_velocity = velocity;
1129        });
1130        self.values_changed(self.last_request_id.get());
1131        self.flush();
1132    }
1133
1134    /// A source stopped driving the tracker, releasing with `velocity`
1135    /// (pixels per second). When no source drives it any more, the tracker
1136    /// coasts in inertia.
1137    pub(crate) fn end_interaction(&self, velocity: Vector3) {
1138        let count = self.active_interactions.get().saturating_sub(1);
1139        self.active_interactions.set(count);
1140        if count > 0 || self.state.get() != InteractionTrackerState::Interacting {
1141            return;
1142        }
1143        self.enter_inertia(velocity, 0.0, Vector3::ZERO, self.last_request_id.get(), false);
1144        self.flush();
1145    }
1146
1147    /// The mouse wheel of a source scrolled by `delta`: the tracker coasts
1148    /// to the position `delta` away from where it is heading.
1149    pub(crate) fn wheel_delta(&self, delta: Vector3) {
1150        if self.state.get() == InteractionTrackerState::Interacting {
1151            return;
1152        }
1153        let values = self.values.get();
1154        let base = match (self.state.get(), &*self.inertia.borrow()) {
1155            (InteractionTrackerState::Inertia, Some(run)) => run.target_position,
1156            _ => values.position,
1157        };
1158        let target = clamp_vector(base + delta, values.min_position, values.max_position);
1159        let r = values.effective_position_decay_rate();
1160        let mut velocity = Vector3::ZERO;
1161        for i in 0..3 {
1162            let ri = retention(axis(r, i));
1163            let distance = axis(target, i) - axis(values.position, i);
1164            // The velocity whose natural resting value is the target.
1165            set_axis(&mut velocity, i, if ri > 0.0 { -distance * ri.ln() } else { 0.0 });
1166        }
1167        self.enter_inertia(velocity, values.scale_velocity, Vector3::ZERO, self.last_request_id.get(), false);
1168        self.flush();
1169    }
1170
1171    /// The mouse wheel of a source scaled by `factor` around
1172    /// `center_point`: applied at once.
1173    pub(crate) fn wheel_scale(&self, factor: f32, center_point: Vector3) {
1174        if self.state.get() == InteractionTrackerState::Interacting {
1175            return;
1176        }
1177        self.apply_scale(self.scale() * factor, center_point);
1178        self.values_changed(self.last_request_id.get());
1179        self.flush();
1180    }
1181
1182    // --- states -------------------------------------------------------------
1183
1184    fn stop_runs(&self) {
1185        drop(self.inertia.borrow_mut().take());
1186        drop(self.custom.borrow_mut().take());
1187    }
1188
1189    fn enter_idle(&self, request_id: i32) {
1190        self.stop_runs();
1191        self.update_values(|v| {
1192            v.position_velocity = Vector3::ZERO;
1193            v.scale_velocity = 0.0;
1194            v.is_position_rounding_suggested = true;
1195        });
1196        if self.state.get() == InteractionTrackerState::Idle {
1197            return;
1198        }
1199        self.state.set(InteractionTrackerState::Idle);
1200        self.last_frame_time.set(None);
1201        self.notify(Notification::Idle(InteractionTrackerIdleStateEnteredArgs { request_id, is_from_binding: false }));
1202    }
1203
1204    fn start_custom(&self, run: CustomRun) {
1205        self.stop_runs();
1206        let request_id = run.request_id;
1207        *self.custom.borrow_mut() = Some(run);
1208        self.state.set(InteractionTrackerState::CustomAnimation);
1209        self.last_frame_time.set(None);
1210        self.update_values(|v| v.is_position_rounding_suggested = false);
1211        self.notify(Notification::CustomAnimation(InteractionTrackerCustomAnimationStateEnteredArgs {
1212            request_id,
1213            is_from_binding: false,
1214        }));
1215        self.request_frame();
1216    }
1217
1218    /// The first modifier of `modifiers` whose condition holds.
1219    fn matching_modifier(
1220        modifiers: &[InteractionTrackerInertiaModifier],
1221        values: TrackerValues,
1222    ) -> Option<InteractionTrackerInertiaModifier> {
1223        modifiers
1224            .iter()
1225            .find(|modifier| {
1226                modifier.condition().is_some_and(|condition| {
1227                    evaluate_animation(&condition, values, ExpressionVariant::Invalid) == ExpressionVariant::Boolean(true)
1228                })
1229            })
1230            .cloned()
1231    }
1232
1233    /// Enters inertia with the given velocities: computes the natural
1234    /// resting values, applies the inertia modifiers and the boundaries.
1235    fn enter_inertia(&self, velocity: Vector3, scale_velocity: f32, scale_center: Vector3, request_id: i32, from_impulse: bool) {
1236        self.stop_runs();
1237        let values = self.values.get();
1238        let decay = values.effective_position_decay_rate();
1239        let position_retention = Vector3::new(retention(decay.x), retention(decay.y), retention(decay.z));
1240        let scale_retention = retention(values.effective_scale_decay_rate());
1241        let natural_position = Vector3::new(
1242            natural_resting_value(values.position.x, velocity.x, position_retention.x),
1243            natural_resting_value(values.position.y, velocity.y, position_retention.y),
1244            natural_resting_value(values.position.z, velocity.z, position_retention.z),
1245        );
1246        let natural_scale = natural_resting_value(values.scale, scale_velocity, scale_retention);
1247        self.update_values(|v| {
1248            v.position_velocity = velocity;
1249            v.scale_velocity = scale_velocity;
1250            v.natural_resting_position = natural_position;
1251            v.natural_resting_scale = natural_scale;
1252            v.is_inertia_from_impulse = from_impulse;
1253            v.is_position_rounding_suggested = false;
1254        });
1255        let values = self.values.get();
1256
1257        // The modifiers read the natural resting values.
1258        let mut target_position = natural_position;
1259        let mut target_scale = natural_scale;
1260        let mut motions: [Option<Rc<ExpressionAnimation>>; 3] = [None, None, None];
1261        let lists = [
1262            self.position_x_modifiers.borrow().clone(),
1263            self.position_y_modifiers.borrow().clone(),
1264            self.scale_modifiers.borrow().clone(),
1265        ];
1266        for (index, modifiers) in lists.iter().enumerate() {
1267            match Self::matching_modifier(modifiers, values) {
1268                Some(InteractionTrackerInertiaModifier::RestingValue(modifier)) => {
1269                    let resting = modifier
1270                        .resting_value()
1271                        .map(|expression| evaluate_animation(&expression, values, ExpressionVariant::Invalid))
1272                        .and_then(|value| value.try_cast::<f32>());
1273                    if let Some(resting) = resting {
1274                        if index < 2 {
1275                            set_axis(&mut target_position, index, resting);
1276                        } else {
1277                            target_scale = resting;
1278                        }
1279                    }
1280                }
1281                Some(InteractionTrackerInertiaModifier::Motion(modifier)) => motions[index] = modifier.motion(),
1282                None => {}
1283            }
1284        }
1285        let target_position = clamp_vector(target_position, values.min_position, values.max_position);
1286        let target_scale = clamp_scalar(target_scale, values.min_scale, values.max_scale);
1287
1288        *self.inertia.borrow_mut() = Some(InertiaRun {
1289            request_id,
1290            elapsed: 0.0,
1291            start_position: values.position,
1292            target_position,
1293            position_retention,
1294            start_scale: values.scale,
1295            target_scale,
1296            scale_retention,
1297            scale_center,
1298            motions,
1299        });
1300        self.state.set(InteractionTrackerState::Inertia);
1301        self.last_frame_time.set(None);
1302        self.notify(Notification::Inertia(InteractionTrackerInertiaStateEnteredArgs {
1303            modified_resting_position: Some(target_position),
1304            modified_resting_scale: Some(target_scale),
1305            natural_resting_position: natural_position,
1306            natural_resting_scale: natural_scale,
1307            position_velocity_in_pixels_per_second: velocity,
1308            scale_velocity_in_percent_per_second: scale_velocity,
1309            request_id,
1310            is_inertia_from_impulse: from_impulse,
1311            is_from_binding: false,
1312        }));
1313        self.request_frame();
1314    }
1315
1316    // --- frames -------------------------------------------------------------
1317
1318    /// Stops or resumes the requests of animation frames: a test drives the
1319    /// tracker with [`advance`](Self::advance) instead.
1320    #[cfg(test)]
1321    pub(crate) fn set_frame_requests_enabled(&self, value: bool) {
1322        self.frame_requests_enabled.set(value);
1323    }
1324
1325    fn request_frame(&self) {
1326        if self.frame_requested.get() || !self.frame_requests_enabled.get() {
1327            return;
1328        }
1329        if !matches!(self.state.get(), InteractionTrackerState::Inertia | InteractionTrackerState::CustomAnimation) {
1330            return;
1331        }
1332        self.frame_requested.set(true);
1333        let weak = self.this.clone();
1334        MediaContext::instance().request_animation_frame(move |now| {
1335            if let Some(this) = weak.upgrade() {
1336                this.on_animation_frame(now);
1337            }
1338        });
1339    }
1340
1341    fn on_animation_frame(&self, now: TimeSpan) {
1342        self.frame_requested.set(false);
1343        let now = now.total_seconds();
1344        let elapsed = match self.last_frame_time.replace(Some(now)) {
1345            Some(last) => (now - last).max(0.0),
1346            None => 0.0,
1347        };
1348        self.advance(Duration::from_secs_f64(elapsed));
1349        self.request_frame();
1350    }
1351
1352    /// Moves the inertia or the custom animation forward by `elapsed`. The
1353    /// animation frames call it with the time between two frames.
1354    pub(crate) fn advance(&self, elapsed: Duration) {
1355        let dt = elapsed.as_secs_f32();
1356        match self.state.get() {
1357            InteractionTrackerState::Inertia => self.advance_inertia(dt),
1358            InteractionTrackerState::CustomAnimation => self.advance_custom(dt),
1359            _ => {}
1360        }
1361        self.flush();
1362    }
1363
1364    fn advance_inertia(&self, dt: f32) {
1365        let Some(mut run) = self.inertia.borrow_mut().take() else { return };
1366        run.elapsed += dt;
1367        let t = run.elapsed;
1368        let values = self.values.get();
1369
1370        // The scale first: it moves the position around its center.
1371        let (scale, scale_velocity, scale_done) = match &run.motions[2] {
1372            Some(motion) => {
1373                let value = evaluate_animation(motion, values, values.scale.into()).try_cast::<f32>().unwrap_or(values.scale);
1374                let velocity = if dt > 0.0 { (value - values.scale) / dt } else { 0.0 };
1375                (value, velocity, dt > 0.0 && (value - values.scale).abs() < REST_EPSILON * 0.01)
1376            }
1377            None => exponential_motion(run.start_scale, run.target_scale, run.scale_retention, t),
1378        };
1379        if values.scale != 0.0 && scale != values.scale {
1380            let k = scale / values.scale;
1381            let c = Vector3::new(run.scale_center.x, run.scale_center.y, 0.0);
1382            run.start_position = (run.start_position + c) * k - c;
1383            run.target_position = (run.target_position + c) * k - c;
1384        }
1385
1386        let mut position = values.position;
1387        let mut velocity = Vector3::ZERO;
1388        let mut done = scale_done;
1389        for i in 0..3 {
1390            // The third motion is the one of the scale; Z has none.
1391            let motion = if i < 2 { run.motions[i].clone() } else { None };
1392            let (value, v, axis_done) = match &motion {
1393                Some(motion) => {
1394                    let current = axis(values.position, i);
1395                    let value = evaluate_animation(motion, values, current.into()).try_cast::<f32>().unwrap_or(current);
1396                    let v = if dt > 0.0 { (value - current) / dt } else { 0.0 };
1397                    (value, v, dt > 0.0 && (value - current).abs() < REST_EPSILON)
1398                }
1399                None => exponential_motion(
1400                    axis(run.start_position, i),
1401                    axis(run.target_position, i),
1402                    axis(run.position_retention, i),
1403                    t,
1404                ),
1405            };
1406            set_axis(&mut position, i, value);
1407            set_axis(&mut velocity, i, v);
1408            done &= axis_done;
1409        }
1410
1411        if done {
1412            let final_position = {
1413                let mut p = run.target_position;
1414                for i in 0..2 {
1415                    if run.motions[i].is_some() {
1416                        set_axis(&mut p, i, axis(position, i));
1417                    }
1418                }
1419                clamp_vector(p, values.min_position, values.max_position)
1420            };
1421            let final_scale = if run.motions[2].is_some() {
1422                clamp_scalar(scale, values.min_scale, values.max_scale)
1423            } else {
1424                run.target_scale
1425            };
1426            self.update_values(|v| {
1427                v.position = final_position;
1428                v.scale = final_scale;
1429            });
1430            self.values_changed(run.request_id);
1431            self.enter_idle(run.request_id);
1432        } else {
1433            let request_id = run.request_id;
1434            *self.inertia.borrow_mut() = Some(run);
1435            self.update_values(|v| {
1436                v.position = position;
1437                v.position_velocity = velocity;
1438                v.scale = scale;
1439                v.scale_velocity = scale_velocity;
1440            });
1441            self.values_changed(request_id);
1442        }
1443    }
1444
1445    fn advance_custom(&self, dt: f32) {
1446        let Some(mut run) = self.custom.borrow_mut().take() else { return };
1447        run.elapsed += dt;
1448        let values = self.values.get();
1449        let finished = match &run.kind {
1450            CustomKind::Position { track, starting } => {
1451                let (value, finished) = track.value_at(run.elapsed, *starting, values.position, values);
1452                let velocity = if dt > 0.0 { (value - values.position) / dt } else { values.position_velocity };
1453                self.update_values(|v| {
1454                    v.position = value;
1455                    v.position_velocity = velocity;
1456                });
1457                finished
1458            }
1459            CustomKind::Scale { track, starting, center } => {
1460                let (value, finished) = track.value_at(run.elapsed, *starting, values.scale, values);
1461                let velocity = if dt > 0.0 { (value - values.scale) / dt } else { values.scale_velocity };
1462                // Deviation (winui-composition.md): the boundaries of the
1463                // scale are applied to a custom animation of the scale.
1464                self.apply_scale(value, *center);
1465                self.update_values(|v| v.scale_velocity = velocity);
1466                finished
1467            }
1468        };
1469        let request_id = run.request_id;
1470        self.values_changed(request_id);
1471        if !finished {
1472            *self.custom.borrow_mut() = Some(run);
1473            return;
1474        }
1475        // A custom animation that ends past a boundary returns to it in
1476        // inertia; otherwise the tracker is idle.
1477        let values = self.values.get();
1478        if clamp_vector(values.position, values.min_position, values.max_position) != values.position {
1479            self.enter_inertia(Vector3::ZERO, 0.0, Vector3::ZERO, request_id, false);
1480        } else {
1481            self.enter_idle(request_id);
1482        }
1483    }
1484
1485    // --- notifications ------------------------------------------------------
1486
1487    fn values_changed(&self, request_id: i32) {
1488        let values = self.values.get();
1489        self.notify(Notification::ValuesChanged(InteractionTrackerValuesChangedArgs {
1490            position: values.position,
1491            scale: values.scale,
1492            request_id,
1493        }));
1494    }
1495
1496    fn notify(&self, notification: Notification) {
1497        if self.owner.is_some() {
1498            self.notifications.borrow_mut().push_back(notification);
1499        }
1500    }
1501
1502    /// Delivers the queued notifications to the owner, unless they are
1503    /// being delivered already (a callback that makes a request).
1504    fn flush(&self) {
1505        if self.notifying.get() {
1506            return;
1507        }
1508        let (Some(owner), Some(this)) = (self.owner.clone(), self.this.upgrade()) else {
1509            self.notifications.borrow_mut().clear();
1510            return;
1511        };
1512        self.notifying.set(true);
1513        loop {
1514            let next = self.notifications.borrow_mut().pop_front();
1515            let Some(next) = next else { break };
1516            match next {
1517                Notification::CustomAnimation(args) => owner.custom_animation_state_entered(&this, &args),
1518                Notification::Idle(args) => owner.idle_state_entered(&this, &args),
1519                Notification::Inertia(args) => owner.inertia_state_entered(&this, &args),
1520                Notification::Interacting(args) => owner.interacting_state_entered(&this, &args),
1521                Notification::RequestIgnored(args) => owner.request_ignored(&this, &args),
1522                Notification::ValuesChanged(args) => owner.values_changed(&this, &args),
1523            }
1524        }
1525        self.notifying.set(false);
1526    }
1527}
1528
1529impl AsCompositionObject for InteractionTracker {
1530    /// The composition object of the property set of the tracker: a
1531    /// reference to the tracker is a reference to its values.
1532    fn as_composition_object(&self) -> &CompositionObject {
1533        self.properties.as_composition_object()
1534    }
1535
1536    fn composition_type_name(&self) -> &'static str {
1537        "InteractionTracker"
1538    }
1539
1540    fn as_property_set(self: Rc<Self>) -> Option<Rc<CompositionPropertySet>> {
1541        Some(self.properties.clone())
1542    }
1543}