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}