1#![cfg_attr(docsrs, feature(doc_cfg))]
2
3mod 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
54pub 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")))]
74pub 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")))]
81pub fn current_android_app() -> android_activity::AndroidApp {
84 ANDROID_APP.get().unwrap().clone()
85}
86
87pub struct BlitzShellProvider {
88 window: Arc<dyn Window>,
89 proxy: BlitzShellProxy,
90}
91impl BlitzShellProvider {
92 pub fn new(window: Arc<dyn Window>, proxy: BlitzShellProxy) -> Self {
93 Self { window, proxy }
94 }
95}
96
97impl ShellProvider for BlitzShellProvider {
98 fn request_redraw(&self) {
99 self.window.request_redraw();
100 }
101 fn set_cursor(&self, icon: CursorIcon) {
102 self.window.set_cursor(Cursor::Icon(icon));
103 }
104 fn set_window_title(&self, title: String) {
105 self.window.set_title(&title);
106 }
107 fn set_ime_enabled(&self, is_enabled: bool) {
108 if is_enabled {
109 let _ = self.window.request_ime_update(ImeRequest::Enable(
110 ImeEnableRequest::new(ImeCapabilities::new(), ImeRequestData::default()).unwrap(),
111 ));
112 } else {
113 let _ = self.window.request_ime_update(ImeRequest::Disable);
114 }
115 }
116 fn set_ime_cursor_area(&self, x: f32, y: f32, width: f32, height: f32) {
117 let _ = self.window.request_ime_update(ImeRequest::Update(
118 ImeRequestData::default().with_cursor_area(
119 LogicalPosition::new(x, y).into(),
120 LogicalSize::new(width, height).into(),
121 ),
122 ));
123 }
124
125 fn request_window_close(&self) {
126 self.proxy.send_event(BlitzShellEvent::CloseWindow {
127 window_id: self.window.id(),
128 });
129 }
130 fn set_window_minimized(&self, minimized: bool) {
131 self.window.set_minimized(minimized);
132 }
133 fn set_window_maximized(&self, maximized: bool) {
134 self.window.set_maximized(maximized);
135 }
136 fn is_window_maximized(&self) -> bool {
137 self.window.is_maximized()
138 }
139 fn set_window_decorations(&self, decorations: bool) {
140 self.window.set_decorations(decorations);
141 }
142 fn drag_window(&self) {
143 let _ = self.window.drag_window();
144 }
145
146 #[cfg(all(
147 feature = "clipboard",
148 any(
149 target_os = "windows",
150 target_os = "macos",
151 target_os = "linux",
152 target_os = "dragonfly",
153 target_os = "freebsd",
154 target_os = "netbsd",
155 target_os = "openbsd"
156 )
157 ))]
158 fn get_clipboard_text(&self) -> Result<String, blitz_traits::shell::ClipboardError> {
159 let mut cb = arboard::Clipboard::new().unwrap();
160 cb.get_text()
161 .map_err(|_| blitz_traits::shell::ClipboardError)
162 }
163
164 #[cfg(all(
165 feature = "clipboard",
166 any(
167 target_os = "windows",
168 target_os = "macos",
169 target_os = "linux",
170 target_os = "dragonfly",
171 target_os = "freebsd",
172 target_os = "netbsd",
173 target_os = "openbsd"
174 )
175 ))]
176 fn set_clipboard_text(&self, text: String) -> Result<(), blitz_traits::shell::ClipboardError> {
177 let mut cb = arboard::Clipboard::new().unwrap();
178 cb.set_text(text.to_owned())
179 .map_err(|_| blitz_traits::shell::ClipboardError)
180 }
181
182 #[cfg(all(
183 feature = "file_dialog",
184 any(
185 target_os = "windows",
186 target_os = "macos",
187 target_os = "linux",
188 target_os = "dragonfly",
189 target_os = "freebsd",
190 target_os = "netbsd",
191 target_os = "openbsd"
192 )
193 ))]
194 fn open_file_dialog(
195 &self,
196 multiple: bool,
197 filter: Option<FileDialogFilter>,
198 ) -> Vec<std::path::PathBuf> {
199 let mut dialog = rfd::FileDialog::new();
200 if let Some(FileDialogFilter { name, extensions }) = filter {
201 dialog = dialog.add_filter(&name, &extensions);
202 }
203 let files = if multiple {
204 dialog.pick_files()
205 } else {
206 dialog.pick_file().map(|file| vec![file])
207 };
208 files.unwrap_or_default()
209 }
210}