Skip to main content

vizia_core/binding/
binding.rs

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