1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
use super::{super::operators::MapExt, CellValue, Gettable, Watchable};
use crate::cell::{Cell, CellImmutable};
pub trait WithLatestFromExt<T>: Watchable<T> {
/// When source emits, pair with the latest value from another cell.
///
/// Unlike `join()` which emits when either source changes, this only
/// emits when the primary source changes, using the latest value from
/// the secondary source.
///
/// # Example
///
/// ```
/// use hyphae::{Cell, Mutable, Gettable, WithLatestFromExt};
///
/// let clicks = Cell::new(0u32);
/// let mouse_pos = Cell::new((0, 0));
///
/// // Only emit on clicks, include current mouse position
/// let click_positions = clicks.with_latest_from(&mouse_pos);
///
/// mouse_pos.set((10, 20)); // No emission
/// mouse_pos.set((30, 40)); // No emission
/// clicks.set(1); // Emits (1, (30, 40))
/// ```
#[track_caller]
fn with_latest_from<U, W2>(&self, other: &W2) -> Cell<(T, U), CellImmutable>
where
T: CellValue,
U: CellValue,
W2: Gettable<U> + Clone + Send + Sync + 'static,
Self: Clone + Send + Sync + 'static,
{
// When self emits, pair with latest from other
let other = other.clone();
self.map(move |t| (t.clone(), other.get()))
}
}
impl<T, W: Watchable<T>> WithLatestFromExt<T> for W {}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Mutable, Signal};
#[test]
fn test_with_latest_from() {
let source = Cell::new(0);
let other = Cell::new("a".to_string());
let combined = source.with_latest_from(&other);
let (tx, rx) = std::sync::mpsc::channel::<(i32, String)>();
let _guard = combined.subscribe(move |signal| {
if let Signal::Value(v) = signal {
let _ = tx.send((**v).clone());
}
});
// Initial combined value
assert_eq!(rx.recv().ok(), Some((0, "a".to_string())));
// Other changes - no emission
other.set("b".to_string());
other.set("c".to_string());
assert!(rx.try_recv().is_err());
// Source changes - emits with latest other
source.set(1);
assert_eq!(rx.recv().ok(), Some((1, "c".to_string())));
// Other changes again - no emission
other.set("d".to_string());
assert!(rx.try_recv().is_err());
// Source changes - emits with latest other
source.set(2);
assert_eq!(rx.recv().ok(), Some((2, "d".to_string())));
}
}