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}