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: Option<CursorIcon>) {
102 match icon {
103 Some(icon) => {
104 self.window.set_cursor_visible(true);
105 self.window.set_cursor(Cursor::Icon(icon));
106 }
107 None => {
108 self.window.set_cursor(Cursor::Icon(CursorIcon::Default));
109 self.window.set_cursor_visible(false)
110 }
111 }
112 }
113 fn set_window_title(&self, title: String) {
114 self.window.set_title(&title);
115 }
116 fn set_ime_enabled(&self, is_enabled: bool) {
117 if is_enabled {
118 let _ = self.window.request_ime_update(ImeRequest::Enable(
119 ImeEnableRequest::new(ImeCapabilities::new(), ImeRequestData::default()).unwrap(),
120 ));
121 } else {
122 let _ = self.window.request_ime_update(ImeRequest::Disable);
123 }
124 }
125 fn set_ime_cursor_area(&self, x: f32, y: f32, width: f32, height: f32) {
126 let _ = self.window.request_ime_update(ImeRequest::Update(
127 ImeRequestData::default().with_cursor_area(
128 LogicalPosition::new(x, y).into(),
129 LogicalSize::new(width, height).into(),
130 ),
131 ));
132 }
133
134 fn request_window_close(&self) {
135 self.proxy.send_event(BlitzShellEvent::CloseWindow {
136 window_id: self.window.id(),
137 });
138 }
139 fn set_window_minimized(&self, minimized: bool) {
140 self.window.set_minimized(minimized);
141 }
142 fn set_window_maximized(&self, maximized: bool) {
143 self.window.set_maximized(maximized);
144 }
145 fn is_window_maximized(&self) -> bool {
146 self.window.is_maximized()
147 }
148 fn set_window_decorations(&self, decorations: bool) {
149 self.window.set_decorations(decorations);
150 }
151 fn drag_window(&self) {
152 let _ = self.window.drag_window();
153 }
154
155 #[cfg(all(
156 feature = "clipboard",
157 any(
158 target_os = "windows",
159 target_os = "macos",
160 target_os = "linux",
161 target_os = "dragonfly",
162 target_os = "freebsd",
163 target_os = "netbsd",
164 target_os = "openbsd"
165 )
166 ))]
167 fn get_clipboard_text(&self) -> Result<String, blitz_traits::shell::ClipboardError> {
168 let mut cb = arboard::Clipboard::new().unwrap();
169 cb.get_text()
170 .map_err(|_| blitz_traits::shell::ClipboardError)
171 }
172
173 #[cfg(all(
174 feature = "clipboard",
175 any(
176 target_os = "windows",
177 target_os = "macos",
178 target_os = "linux",
179 target_os = "dragonfly",
180 target_os = "freebsd",
181 target_os = "netbsd",
182 target_os = "openbsd"
183 )
184 ))]
185 fn set_clipboard_text(&self, text: String) -> Result<(), blitz_traits::shell::ClipboardError> {
186 let mut cb = arboard::Clipboard::new().unwrap();
187 cb.set_text(text.to_owned())
188 .map_err(|_| blitz_traits::shell::ClipboardError)
189 }
190
191 #[cfg(all(
192 feature = "file-dialog",
193 any(
194 target_os = "windows",
195 target_os = "macos",
196 target_os = "linux",
197 target_os = "dragonfly",
198 target_os = "freebsd",
199 target_os = "netbsd",
200 target_os = "openbsd"
201 )
202 ))]
203 fn open_file_dialog(
204 &self,
205 multiple: bool,
206 filter: Option<FileDialogFilter>,
207 ) -> Vec<std::path::PathBuf> {
208 let mut dialog = rfd::FileDialog::new();
209 if let Some(FileDialogFilter { name, extensions }) = filter {
210 dialog = dialog.add_filter(&name, &extensions);
211 }
212 let files = if multiple {
213 dialog.pick_files()
214 } else {
215 dialog.pick_file().map(|file| vec![file])
216 };
217 files.unwrap_or_default()
218 }
219}