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
use dioxus::prelude::*;
use freya_elements::elements as dioxus_elements;
use freya_hooks::use_platform;
pub use winit::window::CursorIcon;

/// Properties for the [`CursorArea`] component.
#[derive(Props, Clone, PartialEq)]
pub struct CursorAreaProps {
    /// Cursor icon that will be used when hovering this area.
    icon: CursorIcon,
    /// Inner children for the CursorArea.
    children: Element,
}

/// Change the cursor icon when it's hovering over this component.
///
/// # Example
///
/// ```no_run
/// # use freya::prelude::*;
/// # use winit::window::CursorIcon;
/// fn app() -> Element {
///     rsx!(
///         CursorArea {
///             icon: CursorIcon::Progress,
///             label {
///                 height: "100%",
///                 width: "100%",
///                 "Loading"
///             }
///         }
///     )
/// }
/// ```
#[allow(non_snake_case)]
pub fn CursorArea(CursorAreaProps { children, icon }: CursorAreaProps) -> Element {
    let platform = use_platform();
    let mut is_hovering = use_signal(|| false);

    let onmouseover = move |_| {
        *is_hovering.write() = true;
        platform.set_cursor(icon);
    };

    let onmouseleave = move |_| {
        *is_hovering.write() = false;
        platform.set_cursor(CursorIcon::default());
    };

    use_drop(move || {
        if *is_hovering.peek() {
            platform.set_cursor(CursorIcon::default());
        }
    });

    rsx!(
        rect {
            onmouseover,
            onmouseleave,
            {children}
        }
    )
}

#[cfg(test)]
mod test {
    use freya::prelude::*;
    use freya_testing::prelude::*;
    use winit::{
        event::MouseButton,
        window::CursorIcon,
    };

    #[tokio::test]
    pub async fn cursor_area() {
        fn cursor_area_app() -> Element {
            rsx!(
                CursorArea {
                    icon: CursorIcon::Progress,
                    rect {
                        height: "50%",
                        width: "100%",
                    }
                }
                CursorArea {
                    icon: CursorIcon::Pointer,
                    rect {
                        height: "50%",
                        width: "100%",
                    }
                }
            )
        }

        let mut utils = launch_test(cursor_area_app);

        // Initial cursor
        assert_eq!(utils.cursor_icon(), CursorIcon::default());

        utils.push_event(PlatformEvent::Mouse {
            name: EventName::MouseOver,
            cursor: (100., 100.).into(),
            button: Some(MouseButton::Left),
        });

        utils.wait_for_update().await;

        // Cursor after hovering the first half
        assert_eq!(utils.cursor_icon(), CursorIcon::Progress);

        utils.push_event(PlatformEvent::Mouse {
            name: EventName::MouseOver,
            cursor: (100., 300.).into(),
            button: Some(MouseButton::Left),
        });

        utils.wait_for_update().await;

        // Cursor after hovering the second half
        assert_eq!(utils.cursor_icon(), CursorIcon::Pointer);

        utils.push_event(PlatformEvent::Mouse {
            name: EventName::MouseOver,
            cursor: (-1., -1.).into(),
            button: Some(MouseButton::Left),
        });

        utils.wait_for_update().await;

        // Cursor after leaving the window
        assert_eq!(utils.cursor_icon(), CursorIcon::default());
    }
}