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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
use std::sync::Arc;

use dioxus_core::use_hook;
use dioxus_hooks::{
    use_context,
    use_context_provider,
    use_signal,
};
use dioxus_signals::{
    CopyValue,
    Readable,
    ReadableRef,
    Signal,
    Writable,
};
use tokio::sync::Notify;

/// Created using [use_popup].
pub struct UsePopup<Data: 'static, Answer: 'static> {
    open: Signal<bool>,
    data: Signal<Option<Data>>,
    answer: Signal<Option<Answer>>,
    waker: CopyValue<Arc<Notify>>,
}

impl<Data, Answer> Copy for UsePopup<Data, Answer> {}

impl<Data, Answer> Clone for UsePopup<Data, Answer> {
    fn clone(&self) -> Self {
        *self
    }
}

impl<Data, Answer> UsePopup<Data, Answer> {
    /// Check if the popup needs to be open.
    pub fn is_open(&self) -> bool {
        *self.open.read()
    }

    /// Read the last answer.
    pub fn read(&self) -> ReadableRef<Signal<Option<Answer>>> {
        self.answer.read_unchecked()
    }

    /// Mark the popup as open and await for its response.
    pub async fn open(
        &mut self,
        data: impl Into<Option<Data>>,
    ) -> ReadableRef<Signal<Option<Answer>>> {
        self.open.set(true);
        self.data.set(data.into());
        let waker = self.waker.read().clone();
        waker.notified().await;
        self.open.set(false);
        self.answer.read_unchecked()
    }
}

/// Create a popups context which can later be answered using [use_popup_answer].
pub fn use_popup<Data, Answer>() -> UsePopup<Data, Answer> {
    let data = use_signal(|| None);
    let answer = use_signal(|| None);
    let waker = use_hook(|| CopyValue::new(Arc::new(Notify::new())));

    use_context_provider(move || UsePopupAnswer {
        data,
        answer,
        waker,
    });

    use_hook(move || UsePopup {
        open: Signal::new(false),
        data,
        answer,
        waker,
    })
}

pub struct UsePopupAnswer<Data: 'static, Answer: 'static> {
    data: Signal<Option<Data>>,
    answer: Signal<Option<Answer>>,
    waker: CopyValue<Arc<Notify>>,
}

impl<Data, Answer> Clone for UsePopupAnswer<Data, Answer> {
    fn clone(&self) -> Self {
        *self
    }
}

impl<Data, Answer> Copy for UsePopupAnswer<Data, Answer> {}

impl<Data, Answer> UsePopupAnswer<Data, Answer> {
    /// Answer the popup.
    pub fn answer(&mut self, data: impl Into<Option<Answer>>) {
        self.answer.set(data.into());
        self.waker.read().notify_waiters();
    }

    /// Read the data provided to this popup, if any.
    pub fn data(&self) -> ReadableRef<Signal<Option<Data>>> {
        self.data.read_unchecked()
    }
}

/// Answer a popup created with [use_popup].
pub fn use_popup_answer<Data, Answer>() -> UsePopupAnswer<Data, Answer> {
    use_context::<UsePopupAnswer<Data, Answer>>()
}

#[cfg(test)]
mod test {
    use dioxus::prelude::component;
    use freya::prelude::*;
    use freya_testing::prelude::*;

    #[tokio::test]
    pub async fn popup() {
        fn popup_app() -> Element {
            let mut my_popup = use_popup::<(), String>();

            let onpress = move |_| async move {
                let _name = my_popup.open(()).await;
            };

            rsx!(
                Button {
                    onpress,
                    label {
                        "{my_popup.read():?}"
                    }
                }
                if my_popup.is_open() {
                    AskNamePopup {}
                }
            )
        }

        #[component]
        fn AskNamePopup() -> Element {
            let mut popup_answer = use_popup_answer::<(), String>();

            rsx!(
                Button {
                    onpress: move |_| {
                        popup_answer.answer("Marc".to_string())
                    },
                    label {
                        "Answer 'Marc'"
                    }
                }
            )
        }

        let mut utils = launch_test(popup_app);
        let root = utils.root();
        let label = root.get(0).get(0);

        assert_eq!(label.get(0).text(), Some("None"));

        // Open popup
        utils.click_cursor((15.0, 15.0)).await;
        utils.wait_for_update().await;

        // Answer
        utils.click_cursor((15.0, 40.0)).await;
        utils.wait_for_update().await;

        assert_eq!(label.get(0).text(), Some("Some(\"Marc\")"));
    }
}