freya_hooks/
use_platform.rs

1use std::sync::Arc;
2
3use dioxus_core::{
4    prelude::{
5        consume_context,
6        provide_root_context,
7        try_consume_context,
8        use_hook,
9    },
10    ScopeId,
11};
12use dioxus_signals::{
13    Readable,
14    Signal,
15};
16use freya_core::{
17    accessibility::AccessibilityFocusStrategy,
18    event_loop_messages::EventLoopMessage,
19    platform::{
20        CursorIcon,
21        EventLoopProxy,
22        Fullscreen,
23        Window,
24    },
25};
26use tokio::sync::{
27    broadcast,
28    mpsc::UnboundedSender,
29};
30use torin::prelude::Area;
31
32#[derive(Clone, Copy, PartialEq)]
33pub struct UsePlatform {
34    ticker: Signal<Arc<broadcast::Receiver<()>>>,
35    event_loop_proxy: Signal<Option<EventLoopProxy<EventLoopMessage>>>,
36    platform_emitter: Signal<Option<UnboundedSender<EventLoopMessage>>>,
37}
38
39#[derive(PartialEq, Eq, Debug)]
40pub enum UsePlatformError {
41    EventLoopProxyFailed,
42    PlatformEmitterFailed,
43}
44
45impl UsePlatform {
46    pub fn current() -> Self {
47        match try_consume_context() {
48            Some(p) => p,
49            None => provide_root_context(UsePlatform {
50                event_loop_proxy: Signal::new_in_scope(
51                    try_consume_context::<EventLoopProxy<EventLoopMessage>>(),
52                    ScopeId::ROOT,
53                ),
54                platform_emitter: Signal::new_in_scope(
55                    try_consume_context::<UnboundedSender<EventLoopMessage>>(),
56                    ScopeId::ROOT,
57                ),
58                ticker: Signal::new_in_scope(
59                    consume_context::<Arc<broadcast::Receiver<()>>>(),
60                    ScopeId::ROOT,
61                ),
62            }),
63        }
64    }
65
66    pub fn send(&self, event: EventLoopMessage) -> Result<(), UsePlatformError> {
67        if let Some(event_loop_proxy) = &*self.event_loop_proxy.peek() {
68            event_loop_proxy
69                .send_event(event)
70                .map_err(|_| UsePlatformError::EventLoopProxyFailed)?;
71        } else if let Some(platform_emitter) = &*self.platform_emitter.peek() {
72            platform_emitter
73                .send(event)
74                .map_err(|_| UsePlatformError::PlatformEmitterFailed)?;
75        }
76        Ok(())
77    }
78
79    pub fn set_cursor(&self, cursor_icon: CursorIcon) {
80        self.send(EventLoopMessage::SetCursorIcon(cursor_icon)).ok();
81    }
82
83    pub fn set_title(&self, title: impl Into<String>) {
84        let title = title.into();
85        self.with_window(move |window| {
86            window.set_title(&title);
87        });
88    }
89
90    pub fn with_window(&self, cb: impl FnOnce(&Window) + 'static + Send + Sync) {
91        self.send(EventLoopMessage::WithWindow(Box::new(cb))).ok();
92    }
93
94    pub fn drag_window(&self) {
95        self.with_window(|window| {
96            window.drag_window().ok();
97        });
98    }
99
100    pub fn set_maximize_window(&self, maximize: bool) {
101        self.with_window(move |window| {
102            window.set_maximized(maximize);
103        });
104    }
105
106    pub fn toggle_maximize_window(&self) {
107        self.with_window(|window| {
108            window.set_maximized(!window.is_maximized());
109        });
110    }
111
112    pub fn set_minimize_window(&self, minimize: bool) {
113        self.with_window(move |window| {
114            window.set_minimized(minimize);
115        });
116    }
117
118    pub fn toggle_minimize_window(&self) {
119        self.with_window(|window| {
120            window.set_minimized(window.is_minimized().map(|v| !v).unwrap_or_default());
121        });
122    }
123
124    pub fn toggle_fullscreen_window(&self) {
125        self.with_window(|window| match window.fullscreen() {
126            Some(_) => window.set_fullscreen(None),
127            None => window.set_fullscreen(Some(Fullscreen::Borderless(None))),
128        });
129    }
130
131    pub fn set_fullscreen_window(&self, fullscreen: bool) {
132        self.with_window(move |window| {
133            if fullscreen {
134                window.set_fullscreen(Some(Fullscreen::Borderless(None)))
135            } else {
136                window.set_fullscreen(None)
137            }
138        });
139    }
140
141    pub fn invalidate_drawing_area(&self, area: Area) {
142        self.send(EventLoopMessage::InvalidateArea(area)).ok();
143    }
144
145    pub fn request_animation_frame(&self) {
146        self.send(EventLoopMessage::RequestRerender).ok();
147    }
148
149    pub fn focus(&self, strategy: AccessibilityFocusStrategy) {
150        self.send(EventLoopMessage::FocusAccessibilityNode(strategy))
151            .ok();
152    }
153
154    pub fn new_ticker(&self) -> Ticker {
155        Ticker {
156            inner: self.ticker.peek().resubscribe(),
157        }
158    }
159
160    /// Closes the whole app.
161    pub fn exit(&self) {
162        self.send(EventLoopMessage::ExitApp).ok();
163    }
164}
165
166/// Get access to information and features of the platform.
167pub fn use_platform() -> UsePlatform {
168    use_hook(UsePlatform::current)
169}
170
171pub struct Ticker {
172    inner: broadcast::Receiver<()>,
173}
174
175impl Ticker {
176    pub async fn tick(&mut self) {
177        self.inner.recv().await.ok();
178    }
179}