Skip to main content

blitz_shell/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2
3//! Event loop, windowing and system integration.
4//!
5//! ## Feature flags
6//!  - `default`: Enables the features listed below.
7//!  - `accessibility`: Enables [`accesskit`] accessibility support.
8//!  - `hot-reload`: Enables hot-reloading of Dioxus RSX.
9//!  - `tracing`: Enables tracing support.
10
11mod application;
12mod convert_events;
13mod event;
14mod net;
15mod window;
16
17#[cfg(feature = "accessibility")]
18mod accessibility;
19
20pub use crate::application::BlitzApplication;
21pub use crate::event::{BlitzShellEvent, BlitzShellProxy};
22pub use crate::window::{View, WindowConfig};
23
24#[cfg(feature = "data-uri")]
25pub use crate::net::DataUriNetProvider;
26
27#[cfg(all(
28    feature = "file_dialog",
29    any(
30        target_os = "windows",
31        target_os = "macos",
32        target_os = "linux",
33        target_os = "dragonfly",
34        target_os = "freebsd",
35        target_os = "netbsd",
36        target_os = "openbsd"
37    )
38))]
39use blitz_traits::shell::FileDialogFilter;
40use blitz_traits::shell::ShellProvider;
41use std::sync::Arc;
42use winit::cursor::{Cursor, CursorIcon};
43use winit::dpi::{LogicalPosition, LogicalSize};
44pub use winit::event_loop::{ControlFlow, EventLoop, EventLoopProxy};
45pub use winit::window::Window;
46use winit::window::{ImeCapabilities, ImeEnableRequest, ImeRequest, ImeRequestData};
47
48#[derive(Default)]
49pub struct Config {
50    pub stylesheets: Vec<String>,
51    pub base_url: Option<String>,
52}
53
54/// Build an event loop for the application
55pub fn create_default_event_loop() -> EventLoop {
56    let mut ev_builder = EventLoop::builder();
57    #[cfg(target_os = "android")]
58    {
59        use winit::platform::android::EventLoopBuilderExtAndroid;
60        ev_builder.with_android_app(current_android_app());
61    }
62
63    let event_loop = ev_builder.build().unwrap();
64    event_loop.set_control_flow(ControlFlow::Wait);
65
66    event_loop
67}
68
69#[cfg(target_os = "android")]
70static ANDROID_APP: std::sync::OnceLock<android_activity::AndroidApp> = std::sync::OnceLock::new();
71
72#[cfg(target_os = "android")]
73#[cfg_attr(docsrs, doc(cfg(target_os = "android")))]
74/// Set the current [`AndroidApp`](android_activity::AndroidApp).
75pub fn set_android_app(app: android_activity::AndroidApp) {
76    ANDROID_APP.set(app).unwrap()
77}
78
79#[cfg(target_os = "android")]
80#[cfg_attr(docsrs, doc(cfg(target_os = "android")))]
81/// Get the current [`AndroidApp`](android_activity::AndroidApp).
82/// This will panic if the android activity has not been setup with [`set_android_app`].
83pub fn current_android_app() -> android_activity::AndroidApp {
84    ANDROID_APP.get().unwrap().clone()
85}
86
87pub struct BlitzShellProvider {
88    window: Arc<dyn Window>,
89}
90impl BlitzShellProvider {
91    pub fn new(window: Arc<dyn Window>) -> Self {
92        Self { window }
93    }
94}
95
96impl ShellProvider for BlitzShellProvider {
97    fn request_redraw(&self) {
98        self.window.request_redraw();
99    }
100    fn set_cursor(&self, icon: CursorIcon) {
101        self.window.set_cursor(Cursor::Icon(icon));
102    }
103    fn set_window_title(&self, title: String) {
104        self.window.set_title(&title);
105    }
106    fn set_ime_enabled(&self, is_enabled: bool) {
107        if is_enabled {
108            let _ = self.window.request_ime_update(ImeRequest::Enable(
109                ImeEnableRequest::new(ImeCapabilities::new(), ImeRequestData::default()).unwrap(),
110            ));
111        } else {
112            let _ = self.window.request_ime_update(ImeRequest::Disable);
113        }
114    }
115    fn set_ime_cursor_area(&self, x: f32, y: f32, width: f32, height: f32) {
116        let _ = self.window.request_ime_update(ImeRequest::Update(
117            ImeRequestData::default().with_cursor_area(
118                LogicalPosition::new(x, y).into(),
119                LogicalSize::new(width, height).into(),
120            ),
121        ));
122    }
123
124    #[cfg(all(
125        feature = "clipboard",
126        any(
127            target_os = "windows",
128            target_os = "macos",
129            target_os = "linux",
130            target_os = "dragonfly",
131            target_os = "freebsd",
132            target_os = "netbsd",
133            target_os = "openbsd"
134        )
135    ))]
136    fn get_clipboard_text(&self) -> Result<String, blitz_traits::shell::ClipboardError> {
137        let mut cb = arboard::Clipboard::new().unwrap();
138        cb.get_text()
139            .map_err(|_| blitz_traits::shell::ClipboardError)
140    }
141
142    #[cfg(all(
143        feature = "clipboard",
144        any(
145            target_os = "windows",
146            target_os = "macos",
147            target_os = "linux",
148            target_os = "dragonfly",
149            target_os = "freebsd",
150            target_os = "netbsd",
151            target_os = "openbsd"
152        )
153    ))]
154    fn set_clipboard_text(&self, text: String) -> Result<(), blitz_traits::shell::ClipboardError> {
155        let mut cb = arboard::Clipboard::new().unwrap();
156        cb.set_text(text.to_owned())
157            .map_err(|_| blitz_traits::shell::ClipboardError)
158    }
159
160    #[cfg(all(
161        feature = "file_dialog",
162        any(
163            target_os = "windows",
164            target_os = "macos",
165            target_os = "linux",
166            target_os = "dragonfly",
167            target_os = "freebsd",
168            target_os = "netbsd",
169            target_os = "openbsd"
170        )
171    ))]
172    fn open_file_dialog(
173        &self,
174        multiple: bool,
175        filter: Option<FileDialogFilter>,
176    ) -> Vec<std::path::PathBuf> {
177        let mut dialog = rfd::FileDialog::new();
178        if let Some(FileDialogFilter { name, extensions }) = filter {
179            dialog = dialog.add_filter(&name, &extensions);
180        }
181        let files = if multiple {
182            dialog.pick_files()
183        } else {
184            dialog.pick_file().map(|file| vec![file])
185        };
186        files.unwrap_or_default()
187    }
188}