freya_hooks/
use_popup.rs

1use std::sync::Arc;
2
3use dioxus_core::use_hook;
4use dioxus_hooks::{
5    use_context,
6    use_context_provider,
7    use_signal,
8};
9use dioxus_signals::{
10    CopyValue,
11    Readable,
12    ReadableRef,
13    Signal,
14    Writable,
15};
16use tokio::sync::Notify;
17
18/// Created using [use_popup].
19pub struct UsePopup<Data: 'static, Answer: 'static> {
20    open: Signal<bool>,
21    data: Signal<Option<Data>>,
22    answer: Signal<Option<Answer>>,
23    waker: CopyValue<Arc<Notify>>,
24}
25
26impl<Data, Answer> Copy for UsePopup<Data, Answer> {}
27
28impl<Data, Answer> Clone for UsePopup<Data, Answer> {
29    fn clone(&self) -> Self {
30        *self
31    }
32}
33
34impl<Data, Answer> UsePopup<Data, Answer> {
35    /// Check if the popup needs to be open.
36    pub fn is_open(&self) -> bool {
37        *self.open.read()
38    }
39
40    /// Read the last answer.
41    pub fn read(&self) -> ReadableRef<Signal<Option<Answer>>> {
42        self.answer.read_unchecked()
43    }
44
45    /// Mark the popup as open and await for its response.
46    pub async fn open(
47        &mut self,
48        data: impl Into<Option<Data>>,
49    ) -> ReadableRef<Signal<Option<Answer>>> {
50        self.open.set(true);
51        self.data.set(data.into());
52        let waker = self.waker.read().clone();
53        waker.notified().await;
54        self.open.set(false);
55        self.answer.read_unchecked()
56    }
57}
58
59/// Create a popups context which can later be answered using [use_popup_answer].
60pub fn use_popup<Data, Answer>() -> UsePopup<Data, Answer> {
61    let data = use_signal(|| None);
62    let answer = use_signal(|| None);
63    let waker = use_hook(|| CopyValue::new(Arc::new(Notify::new())));
64
65    use_context_provider(move || UsePopupAnswer {
66        data,
67        answer,
68        waker,
69    });
70
71    use_hook(move || UsePopup {
72        open: Signal::new(false),
73        data,
74        answer,
75        waker,
76    })
77}
78
79pub struct UsePopupAnswer<Data: 'static, Answer: 'static> {
80    data: Signal<Option<Data>>,
81    answer: Signal<Option<Answer>>,
82    waker: CopyValue<Arc<Notify>>,
83}
84
85impl<Data, Answer> Clone for UsePopupAnswer<Data, Answer> {
86    fn clone(&self) -> Self {
87        *self
88    }
89}
90
91impl<Data, Answer> Copy for UsePopupAnswer<Data, Answer> {}
92
93impl<Data, Answer> UsePopupAnswer<Data, Answer> {
94    /// Answer the popup.
95    pub fn answer(&mut self, data: impl Into<Option<Answer>>) {
96        self.answer.set(data.into());
97        self.waker.read().notify_waiters();
98    }
99
100    /// Read the data provided to this popup, if any.
101    pub fn data(&self) -> ReadableRef<Signal<Option<Data>>> {
102        self.data.read_unchecked()
103    }
104}
105
106/// Answer a popup created with [use_popup].
107pub fn use_popup_answer<Data, Answer>() -> UsePopupAnswer<Data, Answer> {
108    use_context::<UsePopupAnswer<Data, Answer>>()
109}
110
111#[cfg(test)]
112mod test {
113    use dioxus::prelude::component;
114    use freya::prelude::*;
115    use freya_testing::prelude::*;
116
117    #[tokio::test]
118    pub async fn popup() {
119        fn popup_app() -> Element {
120            let mut my_popup = use_popup::<(), String>();
121
122            let onpress = move |_| async move {
123                let _name = my_popup.open(()).await;
124            };
125
126            rsx!(
127                Button {
128                    onpress,
129                    label {
130                        "{my_popup.read():?}"
131                    }
132                }
133                if my_popup.is_open() {
134                    AskNamePopup {}
135                }
136            )
137        }
138
139        #[component]
140        fn AskNamePopup() -> Element {
141            let mut popup_answer = use_popup_answer::<(), String>();
142
143            rsx!(
144                Button {
145                    onpress: move |_| {
146                        popup_answer.answer("Marc".to_string())
147                    },
148                    label {
149                        "Answer 'Marc'"
150                    }
151                }
152            )
153        }
154
155        let mut utils = launch_test(popup_app);
156        let root = utils.root();
157        let label = root.get(0).get(0);
158
159        assert_eq!(label.get(0).text(), Some("None"));
160
161        // Open popup
162        utils.click_cursor((15.0, 15.0)).await;
163        utils.wait_for_update().await;
164
165        // Answer
166        utils.click_cursor((15.0, 40.0)).await;
167        utils.wait_for_update().await;
168
169        assert_eq!(label.get(0).text(), Some("Some(\"Marc\")"));
170    }
171}