vizia_core/binding/
binding.rs

1use vizia_reactive::{Scope, SignalGet, UpdaterEffect};
2
3use crate::{binding::BindingHandler, context::SIGNAL_REBUILDS, prelude::*};
4
5/// A binding view that observes a reactive [`vizia_reactive`] signal and rebuilds its
6/// contents whenever the signal value changes.
7///
8/// `Binding` subscribes to
9/// the signal's reactive graph and is notified immediately when the signal is mutated,
10/// even from inside a model's event handler.
11///
12/// # Example
13/// ```ignore
14/// pub struct AppData {
15///     pub count: Signal<i32>,
16/// }
17///
18/// impl Model for AppData {
19///     fn event(&mut self, _cx: &mut EventContext, event: &mut Event) {
20///         event.map(|e, _| match e {
21///             AppEvent::Increment => self.count.update(|c| *c += 1),
22///         });
23///     }
24/// }
25///
26/// // In the view tree:
27/// Binding::new(cx, app_data.count, |cx| {
28///     Label::new(cx, app_data.count.get(cx).to_string());
29/// });
30/// ```
31pub struct Binding<T: 'static + Clone> {
32    entity: Entity,
33    content: Option<Box<dyn Fn(&mut Context)>>,
34    /// Owns the reactive scope; dropping/disposing it cleans up the `UpdaterEffect`.
35    scope: Scope,
36    marker: std::marker::PhantomData<T>,
37}
38
39impl<T: 'static + Clone> Binding<T> {
40    /// Creates a new `Binding`.
41    ///
42    /// * `signal` — any value implementing [`SignalGet<T>`], typically a [`Signal<T>`].
43    /// * `builder` — closure called immediately and on every subsequent signal change to
44    ///   (re)build child views.
45    #[allow(clippy::new_ret_no_self)]
46    pub fn new<S, F>(cx: &mut Context, signal: S, builder: F)
47    where
48        S: SignalGet<T> + 'static,
49        F: 'static + Fn(&mut Context),
50    {
51        let entity = cx.entity_manager.create();
52        cx.tree.add(entity, cx.current()).expect("Failed to add to tree");
53        cx.tree.set_ignored(entity, true);
54
55        let scope = Scope::new();
56
57        // Create an UpdaterEffect under the scope.
58        // `compute` reads the signal (subscribing to it); `on_change` queues a rebuild.
59        scope.enter(|| {
60            UpdaterEffect::new(
61                move || signal.get(),
62                move |_new_value| {
63                    SIGNAL_REBUILDS.with_borrow_mut(|set| {
64                        set.insert(entity);
65                    });
66                },
67            )
68        });
69
70        let binding = Self {
71            entity,
72            content: Some(Box::new(builder)),
73            scope,
74            marker: std::marker::PhantomData,
75        };
76
77        // Build initial content.
78        if let Some(b) = &binding.content {
79            cx.with_current(entity, |cx| {
80                (b)(cx);
81            });
82        }
83
84        cx.bindings.insert(entity, Box::new(binding));
85
86        let _: Handle<Self> =
87            Handle { current: entity, entity, p: Default::default(), cx }.ignore();
88    }
89}
90
91impl<T: 'static + Clone> BindingHandler for Binding<T> {
92    fn update(&mut self, cx: &mut Context) {
93        cx.remove_children(cx.current());
94
95        if let Some(b) = &self.content {
96            cx.with_current(self.entity, |cx| {
97                (b)(cx);
98            });
99        }
100    }
101
102    fn remove(&self, _cx: &mut Context) {
103        // Disposing the scope cleans up the UpdaterEffect and unsubscribes from the signal.
104        self.scope.dispose();
105    }
106
107    fn debug(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
108        f.write_str("Binding")
109    }
110}