Skip to main content

inset_rendering/
animated_size.rs

1//! Flutter counterpart: `rendering/animated_size.dart`.
2
3use std::rc::Rc;
4use std::time::Duration;
5
6use inset_animation::{
7    Animatable, Animation, AnimationBehavior, AnimationController, AnimationStatus,
8    AnimationStatusListener, Curve, CurvedAnimation, SizeTween,
9};
10use inset_embedder::{Clip, Offset, Size, TextBaseline, TextDirection};
11use inset_foundation::{App, Handle, Listener};
12use inset_painting::AlignmentGeometry;
13
14use crate::box_::{AnyRenderBox, BoxConstraints, BoxHitTestResult, RenderBox, RenderBoxData};
15use crate::layer::{ClipRectLayer, LayerHandle};
16use crate::object::{
17    AnyRenderObject, Constraints, RenderHandle, RenderObject, RenderObjectBase, RenderObjectData,
18    RenderObjectWithChildData, RenderObjectWithChildMixin,
19};
20use crate::painting_context::PaintingContext;
21use crate::pipeline_owner::PipelineOwner;
22use crate::shifted_box::{
23    RenderAligningShiftedBox, RenderAligningShiftedBoxData, RenderShiftedBox,
24};
25use crate::sliver_persistent_header::TickerProviderRef;
26
27/// A [`RenderAnimatedSize`] can be in exactly one of these states.
28#[derive(Clone, Copy, Debug, Eq, PartialEq)]
29pub enum RenderAnimatedSizeState {
30    /// The initial state, when we do not yet know what the starting and target
31    /// sizes are to animate.
32    ///
33    /// The next state is [`Stable`](Self::Stable).
34    Start,
35    /// At this state the child's size is assumed to be stable and we are either
36    /// animating, or waiting for the child's size to change.
37    ///
38    /// If the child's size changes, the state will become [`Changed`](Self::Changed).
39    /// Otherwise, it remains [`Stable`](Self::Stable).
40    Stable,
41    /// At this state we know that the child has changed once after being assumed
42    /// [`Stable`](Self::Stable).
43    Changed,
44    /// At this state the child's size is assumed to be unstable (changing each
45    /// frame).
46    Unstable,
47}
48
49/// A render object that animates its size to its child's size over a given
50/// `duration` and with a given `curve`.
51pub struct RenderAnimatedSize {
52    render_object: RenderObjectData,
53    render_box: RenderBoxData,
54    child: RenderObjectWithChildData<AnyRenderBox>,
55    aligning: RenderAligningShiftedBoxData,
56    vsync: TickerProviderRef,
57    clip_behavior: Clip,
58    controller: Handle<AnimationController>,
59    animation: Handle<CurvedAnimation>,
60    size_tween: Handle<SizeTween>,
61    has_visual_overflow: bool,
62    last_value: Option<f64>,
63    state: RenderAnimatedSizeState,
64    current_size: Size,
65    on_end: Option<Listener>,
66    clip_rect_layer: LayerHandle<Handle<ClipRectLayer>>,
67}
68
69impl RenderAnimatedSize {
70    /// Creates a render object that animates its size to match its child.
71    #[allow(clippy::too_many_arguments)]
72    pub fn new(
73        app: &mut App,
74        vsync: TickerProviderRef,
75        duration: Duration,
76        reverse_duration: Option<Duration>,
77        curve: Rc<dyn Curve>,
78        alignment: AlignmentGeometry,
79        text_direction: Option<TextDirection>,
80        clip_behavior: Clip,
81        on_end: Option<Listener>,
82        child: Option<AnyRenderBox>,
83    ) -> RenderHandle<Self> {
84        let controller = AnimationController::create(
85            app,
86            None,
87            Some(duration),
88            reverse_duration,
89            0.0,
90            1.0,
91            AnimationBehavior::Normal,
92            vsync.clone(),
93        );
94        let animation = CurvedAnimation::create(app, controller.view(), curve, None);
95        let size_tween = SizeTween::new(app, None, None);
96        let this = RenderHandle::new_box(
97            app,
98            RenderAnimatedSize {
99                render_object: RenderObjectData::new(),
100                render_box: RenderBoxData::new(),
101                child: RenderObjectWithChildData::new(),
102                aligning: RenderAligningShiftedBoxData::new(alignment, text_direction),
103                vsync,
104                clip_behavior,
105                controller,
106                animation,
107                size_tween,
108                has_visual_overflow: false,
109                last_value: None,
110                state: RenderAnimatedSizeState::Start,
111                current_size: Size::ZERO,
112                on_end,
113                clip_rect_layer: LayerHandle::new(),
114            },
115        );
116        controller.add_listener(
117            app,
118            Listener::handle_method(this.handle(), on_controller_tick),
119        );
120        this.set_child(app, child);
121        this
122    }
123
124    /// The duration of the animation.
125    pub fn duration(self: RenderHandle<Self>, app: &App) -> Duration {
126        app.get(self.get(app).controller)
127            .duration
128            .expect("duration")
129    }
130
131    /// Sets [`duration`](Self::duration).
132    pub fn set_duration(self: RenderHandle<Self>, app: &mut App, value: Duration) {
133        let controller = self.get(app).controller;
134        if app.get(controller).duration == Some(value) {
135            return;
136        }
137        app.get_mut(controller).duration = Some(value);
138    }
139
140    /// The duration of the animation when running in reverse.
141    pub fn reverse_duration(self: RenderHandle<Self>, app: &App) -> Option<Duration> {
142        app.get(self.get(app).controller).reverse_duration
143    }
144
145    /// Sets [`reverse_duration`](Self::reverse_duration).
146    pub fn set_reverse_duration(self: RenderHandle<Self>, app: &mut App, value: Option<Duration>) {
147        let controller = self.get(app).controller;
148        if app.get(controller).reverse_duration == value {
149            return;
150        }
151        app.get_mut(controller).reverse_duration = value;
152    }
153
154    /// The curve of the animation.
155    pub fn curve(self: RenderHandle<Self>, app: &App) -> Rc<dyn Curve> {
156        app.get(self.get(app).animation).curve.clone()
157    }
158
159    /// Sets [`curve`](Self::curve).
160    pub fn set_curve(self: RenderHandle<Self>, app: &mut App, value: Rc<dyn Curve>) {
161        let animation = self.get(app).animation;
162        if Rc::ptr_eq(&app.get(animation).curve, &value) {
163            return;
164        }
165        app.get_mut(animation).curve = value;
166    }
167
168    /// {@macro flutter.material.Material.clipBehavior}
169    pub fn clip_behavior(self: RenderHandle<Self>, app: &App) -> Clip {
170        self.get(app).clip_behavior
171    }
172
173    /// Sets [`clip_behavior`](Self::clip_behavior).
174    pub fn set_clip_behavior(self: RenderHandle<Self>, app: &mut App, value: Clip) {
175        if self.get(app).clip_behavior == value {
176            return;
177        }
178        self.get_mut(app).clip_behavior = value;
179        RenderBox::mark_needs_paint(self, app);
180    }
181
182    /// Whether the size is being currently animated towards the child's size.
183    pub fn is_animating(self: RenderHandle<Self>, app: &App) -> bool {
184        self.get(app).controller.as_animation().is_animating(app)
185    }
186
187    /// The [`TickerProvider`] for the [`AnimationController`] that runs the animation.
188    pub fn vsync(self: RenderHandle<Self>, app: &App) -> TickerProviderRef {
189        self.get(app).vsync.clone()
190    }
191
192    /// Sets [`vsync`](Self::vsync).
193    pub fn set_vsync(self: RenderHandle<Self>, app: &mut App, value: TickerProviderRef) {
194        if self.get(app).vsync == value {
195            return;
196        }
197        self.get_mut(app).vsync = value.clone();
198        let controller = self.get(app).controller;
199        controller.resync(app, value);
200    }
201
202    /// Called every time an animation completes.
203    pub fn on_end(self: RenderHandle<Self>, app: &App) -> Option<Listener> {
204        self.get(app).on_end.clone()
205    }
206
207    /// Sets [`on_end`](Self::on_end).
208    pub fn set_on_end(self: RenderHandle<Self>, app: &mut App, value: Option<Listener>) {
209        self.get_mut(app).on_end = value;
210    }
211
212    /// The state this size animation is in.
213    pub fn state(self: RenderHandle<Self>, app: &App) -> RenderAnimatedSizeState {
214        self.get(app).state
215    }
216
217    fn animated_size(self: RenderHandle<Self>, app: &App) -> Option<Size> {
218        let size_tween = self.get(app).size_tween;
219        let animation = self.get(app).animation;
220        Animatable::evaluate(&size_tween, app, animation.as_animation())
221    }
222
223    fn restart_animation(self: RenderHandle<Self>, app: &mut App) {
224        self.get_mut(app).last_value = Some(0.0);
225        let controller = self.get(app).controller;
226        controller.forward(app, Some(0.0));
227    }
228
229    fn layout_start(self: RenderHandle<Self>, app: &mut App) {
230        let child_size = self.child(app).expect("child").size(app);
231        let size_tween = self.get(app).size_tween;
232        app.get_mut(size_tween).begin = Some(child_size);
233        app.get_mut(size_tween).end = Some(child_size);
234        self.get_mut(app).state = RenderAnimatedSizeState::Stable;
235    }
236
237    fn layout_stable(self: RenderHandle<Self>, app: &mut App) {
238        let child_size = self.child(app).expect("child").size(app);
239        let size_tween = self.get(app).size_tween;
240        let end = app.get(size_tween).end;
241        if end != Some(child_size) {
242            let size = self.size(app);
243            app.get_mut(size_tween).begin = Some(size);
244            app.get_mut(size_tween).end = Some(child_size);
245            self.restart_animation(app);
246            self.get_mut(app).state = RenderAnimatedSizeState::Changed;
247        } else {
248            let controller = self.get(app).controller;
249            let value = controller.value(app);
250            let upper = app.get(controller).upper_bound;
251            if value == upper {
252                app.get_mut(size_tween).begin = Some(child_size);
253                app.get_mut(size_tween).end = Some(child_size);
254            } else if !controller.is_animating(app) {
255                controller.forward(app, None);
256            }
257        }
258    }
259
260    fn layout_changed(self: RenderHandle<Self>, app: &mut App) {
261        let child_size = self.child(app).expect("child").size(app);
262        let size_tween = self.get(app).size_tween;
263        if app.get(size_tween).end != Some(child_size) {
264            app.get_mut(size_tween).begin = Some(child_size);
265            app.get_mut(size_tween).end = Some(child_size);
266            self.restart_animation(app);
267            self.get_mut(app).state = RenderAnimatedSizeState::Unstable;
268        } else {
269            self.get_mut(app).state = RenderAnimatedSizeState::Stable;
270            let controller = self.get(app).controller;
271            if !controller.is_animating(app) {
272                controller.forward(app, None);
273            }
274        }
275    }
276
277    fn layout_unstable(self: RenderHandle<Self>, app: &mut App) {
278        let child_size = self.child(app).expect("child").size(app);
279        let size_tween = self.get(app).size_tween;
280        if app.get(size_tween).end != Some(child_size) {
281            app.get_mut(size_tween).begin = Some(child_size);
282            app.get_mut(size_tween).end = Some(child_size);
283            self.restart_animation(app);
284        } else {
285            let controller = self.get(app).controller;
286            controller.stop(app, true);
287            self.get_mut(app).state = RenderAnimatedSizeState::Stable;
288        }
289    }
290}
291
292impl RenderObjectWithChildMixin for RenderAnimatedSize {
293    type ChildType = AnyRenderBox;
294
295    fn child_data(self: RenderHandle<Self>, app: &App) -> &RenderObjectWithChildData<AnyRenderBox> {
296        &self.get(app).child
297    }
298
299    fn child_data_mut(
300        self: RenderHandle<Self>,
301        app: &mut App,
302    ) -> &mut RenderObjectWithChildData<AnyRenderBox> {
303        &mut self.get_mut(app).child
304    }
305}
306
307impl RenderShiftedBox for RenderAnimatedSize {}
308
309impl RenderAligningShiftedBox for RenderAnimatedSize {
310    fn aligning_data(self: RenderHandle<Self>, app: &App) -> &RenderAligningShiftedBoxData {
311        &self.get(app).aligning
312    }
313
314    fn aligning_data_mut(
315        self: RenderHandle<Self>,
316        app: &mut App,
317    ) -> &mut RenderAligningShiftedBoxData {
318        &mut self.get_mut(app).aligning
319    }
320}
321
322impl RenderObject for RenderAnimatedSize {
323    crate::render_object_accessors!();
324
325    fn did_attach(self: RenderHandle<Self>, app: &mut App, owner: Handle<PipelineOwner>) {
326        if let Some(child) = self.child(app) {
327            child.as_object().attach(app, owner);
328        }
329        match self.get(app).state {
330            RenderAnimatedSizeState::Start | RenderAnimatedSizeState::Stable => {}
331            RenderAnimatedSizeState::Changed | RenderAnimatedSizeState::Unstable => {
332                RenderBox::mark_needs_layout(self, app);
333            }
334        }
335        let controller = self.get(app).controller;
336        controller.add_status_listener(
337            app,
338            AnimationStatusListener::handle_method(self.handle(), animation_status_listener),
339        );
340    }
341
342    fn did_detach(self: RenderHandle<Self>, app: &mut App) {
343        let controller = self.get(app).controller;
344        controller.stop(app, true);
345        controller.remove_status_listener(
346            app,
347            &AnimationStatusListener::handle_method(self.handle(), animation_status_listener),
348        );
349        if let Some(child) = self.child(app) {
350            child.as_object().detach(app);
351        }
352    }
353
354    fn perform_layout(self: RenderHandle<Self>, app: &mut App) {
355        let controller = self.get(app).controller;
356        let value = controller.value(app);
357        self.get_mut(app).last_value = Some(value);
358        self.get_mut(app).has_visual_overflow = false;
359        let constraints = self.constraints(app);
360        if self.child(app).is_none() || constraints.is_tight() {
361            controller.stop(app, true);
362            let smallest = constraints.smallest();
363            let size_tween = self.get(app).size_tween;
364            app.get_mut(size_tween).begin = Some(smallest);
365            app.get_mut(size_tween).end = Some(smallest);
366            self.set_size(app, smallest);
367            self.get_mut(app).current_size = smallest;
368            self.get_mut(app).state = RenderAnimatedSizeState::Start;
369            if let Some(child) = self.child(app) {
370                child.layout(app, constraints, false);
371            }
372            return;
373        }
374
375        let child = self.child(app).expect("child");
376        child.layout(app, constraints, true);
377
378        match self.get(app).state {
379            RenderAnimatedSizeState::Start => self.layout_start(app),
380            RenderAnimatedSizeState::Stable => self.layout_stable(app),
381            RenderAnimatedSizeState::Changed => self.layout_changed(app),
382            RenderAnimatedSizeState::Unstable => self.layout_unstable(app),
383        }
384
385        let animated = self.animated_size(app).expect("animated size");
386        let size = constraints.constrain(animated);
387        self.set_size(app, size);
388        self.get_mut(app).current_size = size;
389        self.align_child(app);
390
391        let end = app.get(self.get(app).size_tween).end.expect("end");
392        if size.width() < end.width() || size.height() < end.height() {
393            self.get_mut(app).has_visual_overflow = true;
394        }
395    }
396
397    fn paint(
398        self: RenderHandle<Self>,
399        app: &mut App,
400        context: &mut PaintingContext,
401        offset: Offset,
402    ) {
403        if self.child(app).is_some()
404            && self.get(app).has_visual_overflow
405            && self.get(app).clip_behavior != Clip::None
406        {
407            let clip_rect = Offset::ZERO & self.size(app);
408            let clip_behavior = self.get(app).clip_behavior;
409            let old = self.get(app).clip_rect_layer.layer();
410            let layer = context.push_clip_rect(
411                app,
412                self.as_object().needs_compositing(app),
413                offset,
414                clip_rect,
415                |app, context, offset| RenderShiftedBox::paint(self, app, context, offset),
416                clip_behavior,
417                old,
418            );
419            LayerHandle::set_layer(app, |app| &mut self.get_mut(app).clip_rect_layer, layer);
420        } else {
421            LayerHandle::set_layer(app, |app| &mut self.get_mut(app).clip_rect_layer, None);
422            RenderShiftedBox::paint(self, app, context, offset);
423        }
424    }
425
426    fn visit_children(
427        self: RenderHandle<Self>,
428        app: &App,
429        visitor: &mut dyn FnMut(AnyRenderObject),
430    ) {
431        if let Some(child) = self.child(app) {
432            visitor(child.as_object());
433        }
434    }
435
436    fn dispose(self: RenderHandle<Self>, app: &mut App) {
437        LayerHandle::set_layer(app, |app| &mut self.get_mut(app).clip_rect_layer, None);
438        let controller = self.get(app).controller;
439        let animation = self.get(app).animation;
440        controller.dispose(app);
441        animation.dispose(app);
442        RenderObjectBase::dispose(self, app);
443    }
444}
445
446impl RenderBox for RenderAnimatedSize {
447    crate::render_box_accessors!();
448
449    fn hit_test_children(
450        self: RenderHandle<Self>,
451        app: &mut App,
452        result: &mut BoxHitTestResult<'_>,
453        position: Offset,
454    ) -> bool {
455        RenderShiftedBox::hit_test_children(self, app, result, position)
456    }
457
458    fn compute_min_intrinsic_width(self: RenderHandle<Self>, app: &mut App, height: f64) -> f64 {
459        RenderShiftedBox::compute_min_intrinsic_width(self, app, height)
460    }
461
462    fn compute_max_intrinsic_width(self: RenderHandle<Self>, app: &mut App, height: f64) -> f64 {
463        RenderShiftedBox::compute_max_intrinsic_width(self, app, height)
464    }
465
466    fn compute_min_intrinsic_height(self: RenderHandle<Self>, app: &mut App, width: f64) -> f64 {
467        RenderShiftedBox::compute_min_intrinsic_height(self, app, width)
468    }
469
470    fn compute_max_intrinsic_height(self: RenderHandle<Self>, app: &mut App, width: f64) -> f64 {
471        RenderShiftedBox::compute_max_intrinsic_height(self, app, width)
472    }
473
474    fn compute_distance_to_actual_baseline(
475        self: RenderHandle<Self>,
476        app: &mut App,
477        baseline: TextBaseline,
478    ) -> Option<f64> {
479        RenderShiftedBox::compute_distance_to_actual_baseline(self, app, baseline)
480    }
481
482    fn compute_dry_layout(
483        self: RenderHandle<Self>,
484        app: &mut App,
485        constraints: BoxConstraints,
486    ) -> Size {
487        let Some(child) = self.child(app) else {
488            return constraints.smallest();
489        };
490        if constraints.is_tight() {
491            return constraints.smallest();
492        }
493        let child_size = child.get_dry_layout(app, constraints);
494        match self.get(app).state {
495            RenderAnimatedSizeState::Start => return constraints.constrain(child_size),
496            RenderAnimatedSizeState::Stable => {
497                if app.get(self.get(app).size_tween).end != Some(child_size) {
498                    return constraints.constrain(self.get(app).current_size);
499                }
500                let controller = self.get(app).controller;
501                if controller.value(app) == app.get(controller).upper_bound {
502                    return constraints.constrain(child_size);
503                }
504            }
505            RenderAnimatedSizeState::Unstable | RenderAnimatedSizeState::Changed => {
506                if app.get(self.get(app).size_tween).end != Some(child_size) {
507                    return constraints.constrain(child_size);
508                }
509            }
510        }
511        constraints.constrain(self.animated_size(app).expect("animated size"))
512    }
513
514    fn compute_dry_baseline(
515        self: RenderHandle<Self>,
516        app: &mut App,
517        constraints: BoxConstraints,
518        baseline: TextBaseline,
519    ) -> Option<f64> {
520        let child = self.child(app)?;
521        let result = child.get_dry_baseline(app, constraints, baseline)?;
522        let child_size = child.get_dry_layout(app, constraints);
523        let my_size = self.get_dry_layout(app, constraints);
524        let offset = self
525            .resolved_alignment(app)
526            .along_offset(my_size - child_size);
527        Some(result + offset.dy())
528    }
529}
530
531impl std::fmt::Debug for RenderAnimatedSize {
532    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
533        f.debug_struct("RenderAnimatedSize").finish_non_exhaustive()
534    }
535}
536
537fn on_controller_tick(this: Handle<RenderAnimatedSize>, app: &mut App) {
538    let this = RenderHandle::from_handle(this);
539    let value = this.get(app).controller.value(app);
540    if this.get(app).last_value != Some(value) {
541        RenderBox::mark_needs_layout(this, app);
542    }
543}
544
545fn animation_status_listener(
546    this: Handle<RenderAnimatedSize>,
547    app: &mut App,
548    status: AnimationStatus,
549) {
550    let this = RenderHandle::from_handle(this);
551    if status.is_completed()
552        && let Some(on_end) = this.get(app).on_end.clone()
553    {
554        on_end.call(app);
555    }
556}