floem/views/
dyn_container.rs

1use std::any::Any;
2
3use floem_reactive::{as_child_of_current_scope, create_updater, Scope};
4
5use crate::{
6    animate::RepeatMode,
7    context::UpdateCx,
8    view::{AnyView, View},
9    IntoView, ViewId,
10};
11
12#[macro_export]
13macro_rules! dyn_view {
14    ($signal:ident, $new:ident => $body:expr) => {
15        dyn_container(
16            move || {
17                use floem::reactive::SignalGet;
18                $signal.get()
19            },
20            move |$new| $body,
21        )
22    };
23
24    ($signal:ident => $body:expr) => {
25        dyn_container(
26            move || {
27                use floem::reactive::SignalGet;
28                $signal.get()
29            },
30            move |$signal| $body,
31        )
32    };
33}
34
35type ChildFn<T> = dyn Fn(T) -> (AnyView, Scope);
36
37/// A container for a dynamically updating View. See [`dyn_container`]
38pub struct DynamicContainer<T: 'static> {
39    id: ViewId,
40    child_id: ViewId,
41    child_scope: Scope,
42    child_fn: Box<ChildFn<T>>,
43    next_val_state: Option<(T, ViewId, Scope)>,
44    num_started_animations: u16,
45}
46
47/// A container for a dynamically updating View
48///
49/// ## Example
50/// ```
51/// use floem::{
52///     reactive::{create_rw_signal, SignalGet, SignalUpdate},
53///     views::{dyn_container, label, toggle_button, v_stack, Decorators},
54///     IntoView, View,
55/// };
56///
57/// #[derive(Clone)]
58/// enum ViewSwitcher {
59///     One,
60///     Two,
61/// }
62///
63/// fn app_view() -> impl View {
64///     let view = create_rw_signal(ViewSwitcher::One);
65///     v_stack((
66///         toggle_button(|| true)
67///             .on_toggle(move |is_on| {
68///                 if is_on {
69///                     view.update(|val| *val = ViewSwitcher::One);
70///                 } else {
71///                     view.update(|val| *val = ViewSwitcher::Two);
72///                 }
73///             })
74///             .style(|s| s.margin_bottom(20)),
75///         dyn_container(
76///             move || view.get(),
77///             move |value| match value {
78///                 ViewSwitcher::One => label(|| "One").into_any(),
79///                 ViewSwitcher::Two => v_stack((label(|| "Stacked"), label(|| "Two"))).into_any(),
80///             },
81///         ),
82///     ))
83///     .style(|s| {
84///         s.width_full()
85///             .height_full()
86///             .items_center()
87///             .justify_center()
88///             .row_gap(10)
89///     })
90/// }
91/// ```
92pub fn dyn_container<CF: Fn(T) -> IV + 'static, T: 'static, IV: IntoView>(
93    update_view: impl Fn() -> T + 'static,
94    child_fn: CF,
95) -> DynamicContainer<T> {
96    let id = ViewId::new();
97
98    let initial = create_updater(update_view, move |new_state| {
99        id.update_state(DynMessage::Val(Box::new(new_state)));
100    });
101
102    let child_fn = Box::new(as_child_of_current_scope(move |e| child_fn(e).into_any()));
103    let (child, child_scope) = child_fn(initial);
104    let child_id = child.id();
105    id.set_children([child]);
106    DynamicContainer {
107        id,
108        child_scope,
109        child_id,
110        child_fn,
111        next_val_state: None,
112        num_started_animations: 0,
113    }
114}
115enum DynMessage {
116    Val(Box<dyn Any>),
117    CompletedAnimation,
118}
119
120impl<T: 'static> View for DynamicContainer<T> {
121    fn id(&self) -> ViewId {
122        self.id
123    }
124
125    fn debug_name(&self) -> std::borrow::Cow<'static, str> {
126        "Dynamic Container".into()
127    }
128
129    fn update(&mut self, cx: &mut UpdateCx, state: Box<dyn Any>) {
130        if let Ok(message) = state.downcast::<DynMessage>() {
131            match *message {
132                DynMessage::Val(val) => {
133                    if let Ok(val) = val.downcast::<T>() {
134                        self.new_val(cx, *val);
135                    }
136                }
137                DynMessage::CompletedAnimation => {
138                    self.num_started_animations = self.num_started_animations.saturating_sub(1);
139                    if self.num_started_animations == 0 {
140                        let next_val_state = self
141                            .next_val_state
142                            .take()
143                            .expect("when waiting for animations the next value will be stored and all message effects should have been dropped by dropping the child id if another value was sent before the animations finished");
144                        self.swap_val(cx, next_val_state);
145                    }
146                }
147            }
148        }
149    }
150}
151impl<T> DynamicContainer<T> {
152    fn new_val(&mut self, cx: &mut UpdateCx, val: T) {
153        let id = self.id;
154
155        let old_child_scope = self.child_scope;
156        let old_child_id = self.child_id;
157
158        if self.num_started_animations > 0 {
159            // another update was sent before the animations finished processing
160            let next_state = self
161                .next_val_state
162                .take()
163                .expect("valid when waiting for animations");
164            // force swap
165            self.swap_val(cx, next_state);
166            self.num_started_animations = 0;
167        }
168
169        self.num_started_animations =
170            animations_recursive_on_remove(id, old_child_id, old_child_scope);
171
172        let next_state = (val, old_child_id, old_child_scope);
173        if self.num_started_animations == 0 {
174            // after recursively checking, no animations were found that needed to be started
175            self.swap_val(cx, next_state);
176        } else {
177            self.next_val_state = Some(next_state);
178        }
179    }
180
181    fn swap_val(&mut self, cx: &mut UpdateCx, next_val_state: (T, ViewId, Scope)) {
182        let (val, old_child_id, old_child_scope) = next_val_state;
183        let (new_child, new_child_scope) = (self.child_fn)(val);
184        self.child_id = new_child.id();
185        self.id.set_children([new_child]);
186        self.child_scope = new_child_scope;
187        cx.app_state_mut().remove_view(old_child_id);
188        old_child_scope.dispose();
189        animations_recursive_on_create(self.child_id);
190        self.id.request_all();
191    }
192}
193
194fn animations_recursive_on_remove(id: ViewId, child_id: ViewId, child_scope: Scope) -> u16 {
195    let mut wait_for = 0;
196    let state = child_id.state();
197    let mut state = state.borrow_mut();
198    let animations = &mut state.animations.stack;
199    let mut request_style = false;
200    for anim in animations {
201        if anim.run_on_remove && !matches!(anim.repeat_mode, RepeatMode::LoopForever) {
202            anim.reverse_mut();
203            request_style = true;
204            wait_for += 1;
205            let trigger = anim.on_visual_complete;
206            child_scope.create_updater(
207                move || trigger.track(),
208                move |_| {
209                    id.update_state(DynMessage::CompletedAnimation);
210                },
211            );
212        }
213    }
214    drop(state);
215    if request_style {
216        child_id.request_style();
217    }
218
219    child_id
220        .children()
221        .into_iter()
222        .fold(wait_for, |acc, child_id| {
223            acc + animations_recursive_on_remove(id, child_id, child_scope)
224        })
225}
226fn animations_recursive_on_create(child_id: ViewId) {
227    let state = child_id.state();
228    let mut state = state.borrow_mut();
229    let animations = &mut state.animations.stack;
230    let mut request_style = false;
231    for anim in animations {
232        if anim.run_on_create && !matches!(anim.repeat_mode, RepeatMode::LoopForever) {
233            anim.start_mut();
234            request_style = true;
235        }
236    }
237    drop(state);
238    if request_style {
239        child_id.request_style();
240    }
241
242    child_id
243        .children()
244        .into_iter()
245        .for_each(animations_recursive_on_create);
246}