Skip to main content

floem/views/
dyn_container.rs

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