vertigo/computed/
computed_box.rs

1use std::hash::Hash;
2use std::rc::Rc;
3
4use crate::{
5    computed::{graph_id::GraphId, GraphValue},
6    render::{render_list, render_value, render_value_option},
7    struct_mut::ValueMut,
8    DomNode, DropResource, Value,
9};
10
11use super::context::Context;
12
13/// A reactive value that is read-only and computed by dependency graph.
14///
15/// ## Computed directly from Value
16///
17/// ```rust
18/// use vertigo::{Value, transaction};
19///
20/// let value = Value::new(5);
21///
22/// let comp = value.to_computed();
23///
24/// transaction(|context| {
25///     assert_eq!(comp.get(context), 5);
26/// });
27///
28/// // Can't do that
29/// // comp.set(10);
30/// ```
31///
32/// ## Computed from Value by provided function
33///
34/// ```rust
35/// use vertigo::{Computed, Value, transaction};
36///
37/// let value = Value::new(2);
38///
39/// let comp_2 = {
40///     let v = value.clone();
41///     Computed::from(move |context| v.get(context) * 2)
42/// };
43///
44/// transaction(|context| {
45///     assert_eq!(comp_2.get(context), 4);
46/// });
47///
48/// value.set(6);
49///
50/// transaction(|context| {
51///     assert_eq!(comp_2.get(context), 12);
52/// });
53///
54/// ```
55pub struct Computed<T: Clone> {
56    inner: Rc<GraphValue<T>>,
57}
58
59impl<T: Clone + 'static> Clone for Computed<T> {
60    fn clone(&self) -> Self {
61        Computed {
62            inner: self.inner.clone(),
63        }
64    }
65}
66
67impl<T: Clone + 'static> PartialEq for Computed<T> {
68    fn eq(&self, other: &Self) -> bool {
69        self.id() == other.id()
70    }
71}
72
73impl<T: Clone + 'static> Computed<T> {
74    /// Creates new [`Computed<T>`] which state is determined by provided generator function.
75    pub fn from<F: Fn(&Context) -> T + 'static>(get_value: F) -> Computed<T> {
76        Computed {
77            inner: GraphValue::new(true, move |context| get_value(context)),
78        }
79    }
80
81    /// Get current value, it will be computed on-the-fly if the previous one ran out of date.
82    pub fn get(&self, context: &Context) -> T {
83        self.inner.get_value(context)
84    }
85
86    /// Reactively convert [`Computed<T>`] to [`Computed<K>`] with provided transformation function applied.
87    pub fn map<K: Clone + 'static, F: 'static + Fn(T) -> K>(&self, fun: F) -> Computed<K> {
88        Computed::from({
89            let myself = self.clone();
90            move |context| fun(myself.get(context))
91        })
92    }
93
94    pub fn id(&self) -> GraphId {
95        self.inner.id()
96    }
97
98    /// Do something every time the value inside [Computed] is triggered.
99    ///
100    /// Note that the `callback` is fired every time the value in [Computed] is computed, even if the outcome value is not changed.
101    pub fn subscribe_all<R: 'static, F: Fn(T) -> R + 'static>(self, callback: F) -> DropResource {
102        let resource_box = ValueMut::new(None);
103
104        let graph_value = GraphValue::new(false, move |context| {
105            let value = self.get(context);
106
107            let resource = callback(value);
108            resource_box.change(move |inner| {
109                *inner = Some(resource);
110            });
111        });
112
113        let context = Context::new();
114        graph_value.get_value(&context);
115        let _ = context;
116
117        DropResource::from_struct(graph_value)
118    }
119}
120
121impl<T: Clone + PartialEq + 'static> Computed<T> {
122    /// Do something every time the value inside [Computed] is changed.
123    ///
124    /// Note that the `callback` is fired only if the value *really* changes. This means that even if computation takes place with different source value,
125    /// but the resulting value is the same as old one, the `callback` is not fired.
126    pub fn subscribe<R: 'static, F: Fn(T) -> R + 'static>(self, callback: F) -> DropResource {
127        let prev_value = ValueMut::new(None);
128
129        let resource_box = ValueMut::new(None);
130
131        let graph_value = GraphValue::new(false, move |context| {
132            let value = self.get(context);
133
134            let should_update = prev_value.set_if_changed(Some(value.clone()));
135
136            if should_update {
137                let resource = callback(value);
138                resource_box.change(move |inner| {
139                    *inner = Some(resource);
140                });
141            }
142        });
143
144        let context = Context::new();
145        graph_value.get_value(&context);
146        let _ = context;
147
148        DropResource::from_struct(graph_value)
149    }
150
151    /// Render value inside this [Computed]. See [Value::render_value()] for examples.
152    pub fn render_value(&self, render: impl Fn(T) -> DomNode + 'static) -> DomNode {
153        render_value(self.clone(), render)
154    }
155
156    /// Render optional value inside this [Computed]. See [Value::render_value_option()] for examples.
157    pub fn render_value_option(&self, render: impl Fn(T) -> Option<DomNode> + 'static) -> DomNode {
158        render_value_option(self.clone(), render)
159    }
160}
161
162impl<T: Clone + PartialEq + 'static, L: IntoIterator<Item = T> + Clone + 'static> Computed<L> {
163    /// Render iterable value inside this [Computed]. See [Value::render_list()] for examples.
164    pub fn render_list<K: Eq + Hash>(
165        &self,
166        get_key: impl Fn(&T) -> K + 'static,
167        render: impl Fn(&T) -> DomNode + 'static,
168    ) -> DomNode {
169        let list = self.map(|inner| inner.into_iter().collect::<Vec<_>>());
170        render_list(list, get_key, render)
171    }
172}
173
174impl<T: Clone + 'static> From<Value<T>> for Computed<T> {
175    fn from(val: Value<T>) -> Self {
176        val.to_computed()
177    }
178}
179
180impl<T: Clone + 'static> From<T> for Computed<T> {
181    fn from(value: T) -> Self {
182        Value::new(value).to_computed()
183    }
184}
185
186impl<T: Clone + 'static> From<&T> for Computed<T> {
187    fn from(value: &T) -> Self {
188        Value::new(value.clone()).to_computed()
189    }
190}
191
192impl From<&str> for Computed<String> {
193    fn from(value: &str) -> Self {
194        Value::new(value.to_string()).to_computed()
195    }
196}