freya_hooks/
use_node.rs

1use std::sync::Arc;
2
3use dioxus_core::{
4    prelude::spawn,
5    use_hook,
6    AttributeValue,
7};
8use dioxus_signals::{
9    ReadOnlySignal,
10    Readable,
11    Signal,
12    Writable,
13};
14use freya_core::custom_attributes::{
15    CustomAttributeValues,
16    NodeReference,
17    NodeReferenceLayout,
18};
19use tokio::sync::watch::channel;
20
21/// Subscribe to a Node layout changes.
22pub fn use_node() -> (AttributeValue, NodeReferenceLayout) {
23    use_node_from_signal(Signal::default)
24}
25
26pub fn use_node_from_signal(
27    init: impl FnOnce() -> Signal<NodeReferenceLayout>,
28) -> (AttributeValue, NodeReferenceLayout) {
29    let (tx, signal) = use_hook(|| {
30        let (tx, mut rx) = channel::<NodeReferenceLayout>(NodeReferenceLayout::default());
31        let mut signal = init();
32        spawn(async move {
33            while rx.changed().await.is_ok() {
34                if *signal.peek() != *rx.borrow() {
35                    signal.set(rx.borrow().clone());
36                }
37            }
38        });
39
40        (Arc::new(tx), signal)
41    });
42
43    (
44        AttributeValue::any_value(CustomAttributeValues::Reference(NodeReference(tx))),
45        signal.read_unchecked().clone(),
46    )
47}
48
49/// Get a signal to read the latest layout from a Node.
50pub fn use_node_signal() -> (AttributeValue, ReadOnlySignal<NodeReferenceLayout>) {
51    let (tx, signal) = use_hook(|| {
52        let (tx, mut rx) = channel::<NodeReferenceLayout>(NodeReferenceLayout::default());
53        let mut signal = Signal::new(NodeReferenceLayout::default());
54
55        spawn(async move {
56            while rx.changed().await.is_ok() {
57                if *signal.peek() != *rx.borrow() {
58                    signal.set(rx.borrow().clone());
59                }
60            }
61        });
62
63        (Arc::new(tx), signal)
64    });
65
66    (
67        AttributeValue::any_value(CustomAttributeValues::Reference(NodeReference(tx))),
68        signal.into(),
69    )
70}
71
72pub fn use_node_signal_with_prev() -> (
73    AttributeValue,
74    ReadOnlySignal<Option<NodeReferenceLayout>>,
75    ReadOnlySignal<Option<NodeReferenceLayout>>,
76) {
77    let (tx, curr_signal, prev_signal) = use_hook(|| {
78        let (tx, mut rx) = channel::<NodeReferenceLayout>(NodeReferenceLayout::default());
79        let mut curr_signal = Signal::new(None);
80        let mut prev_signal = Signal::new(None);
81
82        spawn(async move {
83            while rx.changed().await.is_ok() {
84                if *curr_signal.peek() != Some(rx.borrow().clone()) {
85                    prev_signal.set(curr_signal());
86                    curr_signal.set(Some(rx.borrow().clone()));
87                }
88            }
89        });
90
91        (Arc::new(tx), curr_signal, prev_signal)
92    });
93
94    (
95        AttributeValue::any_value(CustomAttributeValues::Reference(NodeReference(tx))),
96        curr_signal.into(),
97        prev_signal.into(),
98    )
99}
100
101pub fn use_node_with_reference() -> (NodeReference, ReadOnlySignal<NodeReferenceLayout>) {
102    let (tx, signal) = use_hook(|| {
103        let (tx, mut rx) = channel::<NodeReferenceLayout>(NodeReferenceLayout::default());
104        let mut signal = Signal::new(NodeReferenceLayout::default());
105
106        spawn(async move {
107            while rx.changed().await.is_ok() {
108                if *signal.peek() != *rx.borrow() {
109                    signal.set(rx.borrow().clone());
110                }
111            }
112        });
113
114        (Arc::new(tx), signal)
115    });
116
117    (NodeReference(tx), signal.into())
118}
119
120#[cfg(test)]
121mod test {
122    use freya::prelude::*;
123    use freya_testing::prelude::*;
124
125    use crate::use_node;
126
127    #[tokio::test]
128    pub async fn track_size() {
129        fn use_node_app() -> Element {
130            let (reference, size) = use_node();
131
132            rsx!(
133                rect {
134                    reference: reference,
135                    width: "50%",
136                    height: "25%",
137                    label {
138                        "{size.area.width()}"
139                    }
140                }
141            )
142        }
143
144        let mut utils = launch_test_with_config(
145            use_node_app,
146            TestingConfig::<()> {
147                size: (500.0, 800.0).into(),
148                ..TestingConfig::default()
149            },
150        );
151
152        utils.wait_for_update().await;
153        let root = utils.root().get(0);
154        assert_eq!(
155            root.get(0).get(0).text().unwrap().parse::<f32>(),
156            Ok(500.0 * 0.5)
157        );
158
159        utils.resize((300.0, 800.0).into());
160        utils.wait_for_update().await;
161
162        let root = utils.root().get(0);
163        assert_eq!(
164            root.get(0).get(0).text().unwrap().parse::<f32>(),
165            Ok(300.0 * 0.5)
166        );
167    }
168}