1#![doc = include_str!("../README.md")]
2#![allow(
3 clippy::collapsible_if,
4 clippy::collapsible_else_if,
5 clippy::unused_unit,
6 clippy::identity_op,
7 clippy::missing_safety_doc
8)]
9
10pub mod conf;
11mod event;
12pub mod fs;
13pub mod graphics;
14pub mod native;
15use std::collections::HashMap;
16use std::ops::{Index, IndexMut};
17
18#[cfg(feature = "log-impl")]
19pub mod log;
20
21pub use event::*;
22
23pub use graphics::*;
24
25mod default_icon;
26
27pub use native::gl;
28
29#[derive(Clone)]
30pub(crate) struct ResourceManager<T> {
31 id: usize,
32 resources: HashMap<usize, T>,
33}
34
35impl<T> Default for ResourceManager<T> {
36 fn default() -> Self {
37 Self {
38 id: 0,
39 resources: HashMap::new(),
40 }
41 }
42}
43
44impl<T> ResourceManager<T> {
45 pub fn add(&mut self, resource: T) -> usize {
46 self.resources.insert(self.id, resource);
47 self.id += 1;
48 self.id - 1
49 }
50
51 pub fn remove(&mut self, id: usize) -> T {
52 self.resources.remove(&id).unwrap()
54 }
55}
56
57impl<T> Index<usize> for ResourceManager<T> {
58 type Output = T;
59 fn index(&self, index: usize) -> &Self::Output {
60 &self.resources[&index]
61 }
62}
63
64impl<T> IndexMut<usize> for ResourceManager<T> {
65 fn index_mut(&mut self, index: usize) -> &mut Self::Output {
66 self.resources.get_mut(&index).unwrap()
67 }
68}
69
70pub mod date {
71 #[cfg(not(target_arch = "wasm32"))]
72 pub fn now() -> f64 {
73 use std::time::SystemTime;
74
75 let time = SystemTime::now()
76 .duration_since(SystemTime::UNIX_EPOCH)
77 .unwrap_or_else(|e| panic!("{}", e));
78 time.as_secs_f64()
79 }
80
81 #[cfg(target_arch = "wasm32")]
82 pub fn now() -> f64 {
83 use crate::native;
84
85 unsafe { native::wasm::now() }
86 }
87}
88
89pub type Context = dyn RenderingBackend;
90
91use std::sync::{Mutex, OnceLock};
92
93static NATIVE_DISPLAY: OnceLock<Mutex<native::NativeDisplayData>> = OnceLock::new();
94
95fn set_display(display: native::NativeDisplayData) {
96 NATIVE_DISPLAY
97 .set(Mutex::new(display))
98 .unwrap_or_else(|_| panic!("NATIVE_DISPLAY already set"));
99}
100fn set_or_replace_display(display: native::NativeDisplayData) {
103 if let Some(m) = NATIVE_DISPLAY.get() {
104 *m.lock().unwrap() = display;
106 } else {
107 set_display(display);
109 }
110}
111fn native_display() -> &'static Mutex<native::NativeDisplayData> {
112 NATIVE_DISPLAY
113 .get()
114 .expect("Backend has not initialized NATIVE_DISPLAY yet.") }
116
117pub mod window {
120 use super::*;
121
122 pub fn new_rendering_backend() -> Box<dyn RenderingBackend> {
132 #[cfg(target_vendor = "apple")]
133 {
134 if window::apple_gfx_api() == conf::AppleGfxApi::Metal {
135 Box::new(MetalContext::new())
136 } else {
137 Box::new(GlContext::new())
138 }
139 }
140 #[cfg(not(target_vendor = "apple"))]
141 Box::new(GlContext::new())
142 }
143
144 pub fn screen_size() -> (f32, f32) {
147 let d = native_display().lock().unwrap();
148 (d.screen_width as f32, d.screen_height as f32)
149 }
150
151 pub fn dpi_scale() -> f32 {
154 let d = native_display().lock().unwrap();
155 d.dpi_scale
156 }
157
158 pub fn high_dpi() -> bool {
161 let d = native_display().lock().unwrap();
162 d.high_dpi
163 }
164
165 pub fn blocking_event_loop() -> bool {
166 let d = native_display().lock().unwrap();
167 d.blocking_event_loop
168 }
169
170 pub fn order_quit() {
178 let mut d = native_display().lock().unwrap();
179 d.quit_ordered = true;
180 }
181
182 pub fn quit() {
184 order_quit()
185 }
186
187 pub fn request_quit() {
193 let mut d = native_display().lock().unwrap();
194 d.quit_requested = true;
195 }
196
197 pub fn cancel_quit() {
203 let mut d = native_display().lock().unwrap();
204 d.quit_requested = false;
205 }
206 pub fn set_cursor_grab(grab: bool) {
213 let d = native_display().lock().unwrap();
214 #[cfg(target_os = "android")]
215 {
216 (d.native_requests)(native::Request::SetCursorGrab(grab));
217 }
218
219 #[cfg(not(target_os = "android"))]
220 {
221 d.native_requests
222 .send(native::Request::SetCursorGrab(grab))
223 .unwrap();
224 }
225 }
226
227 pub fn schedule_update() {
233 #[cfg(all(target_os = "android", not(target_arch = "wasm32")))]
234 {
235 let d = native_display().lock().unwrap();
236 (d.native_requests)(native::Request::ScheduleUpdate);
237 }
238
239 #[cfg(not(any(target_arch = "wasm32", target_os = "android")))]
240 {
241 let d = native_display().lock().unwrap();
242 d.native_requests
243 .send(native::Request::ScheduleUpdate)
244 .unwrap();
245 }
246
247 #[cfg(target_arch = "wasm32")]
248 unsafe {
249 native::wasm::sapp_schedule_update();
250 }
251 }
252
253 pub fn show_mouse(shown: bool) {
255 let d = native_display().lock().unwrap();
256 #[cfg(target_os = "android")]
257 {
258 (d.native_requests)(native::Request::ShowMouse(shown));
259 }
260
261 #[cfg(not(target_os = "android"))]
262 {
263 d.native_requests
264 .send(native::Request::ShowMouse(shown))
265 .unwrap();
266 }
267 }
268
269 pub fn set_mouse_cursor(cursor_icon: CursorIcon) {
271 let d = native_display().lock().unwrap();
272 #[cfg(target_os = "android")]
273 {
274 (d.native_requests)(native::Request::SetMouseCursor(cursor_icon));
275 }
276
277 #[cfg(not(target_os = "android"))]
278 {
279 d.native_requests
280 .send(native::Request::SetMouseCursor(cursor_icon))
281 .unwrap();
282 }
283 }
284
285 pub fn set_window_size(new_width: u32, new_height: u32) {
287 let d = native_display().lock().unwrap();
288 #[cfg(target_os = "android")]
289 {
290 (d.native_requests)(native::Request::SetWindowSize {
291 new_width,
292 new_height,
293 });
294 }
295
296 #[cfg(not(target_os = "android"))]
297 {
298 d.native_requests
299 .send(native::Request::SetWindowSize {
300 new_width,
301 new_height,
302 })
303 .unwrap();
304 }
305 }
306
307 pub fn set_window_position(new_x: u32, new_y: u32) {
308 let d = native_display().lock().unwrap();
309 #[cfg(target_os = "android")]
310 {
311 (d.native_requests)(native::Request::SetWindowPosition { new_x, new_y });
312 }
313
314 #[cfg(not(target_os = "android"))]
315 {
316 d.native_requests
317 .send(native::Request::SetWindowPosition { new_x, new_y })
318 .unwrap();
319 }
320 }
321
322 #[cfg(any(target_os = "windows", target_os = "linux"))]
325 pub fn get_window_position() -> (u32, u32) {
326 let d = native_display().lock().unwrap();
327 d.screen_position
328 }
329
330 pub fn set_fullscreen(fullscreen: bool) {
331 let d = native_display().lock().unwrap();
332 #[cfg(target_os = "android")]
333 {
334 (d.native_requests)(native::Request::SetFullscreen(fullscreen));
335 }
336
337 #[cfg(not(target_os = "android"))]
338 {
339 d.native_requests
340 .send(native::Request::SetFullscreen(fullscreen))
341 .unwrap();
342 }
343 }
344
345 pub fn clipboard_get() -> Option<String> {
347 let mut d = native_display().lock().unwrap();
348 d.clipboard.get()
349 }
350
351 pub fn clipboard_set(data: &str) {
353 let mut d = native_display().lock().unwrap();
354 d.clipboard.set(data)
355 }
356 pub fn dropped_file_count() -> usize {
357 let d = native_display().lock().unwrap();
358 d.dropped_files.bytes.len()
359 }
360 pub fn dropped_file_bytes(index: usize) -> Option<Vec<u8>> {
361 let d = native_display().lock().unwrap();
362 d.dropped_files.bytes.get(index).cloned()
363 }
364 pub fn dropped_file_path(index: usize) -> Option<std::path::PathBuf> {
365 let d = native_display().lock().unwrap();
366 d.dropped_files.paths.get(index).cloned()
367 }
368
369 pub fn show_keyboard(show: bool) {
372 let d = native_display().lock().unwrap();
373 #[cfg(target_os = "android")]
374 {
375 (d.native_requests)(native::Request::ShowKeyboard(show));
376 }
377
378 #[cfg(not(target_os = "android"))]
379 {
380 d.native_requests
381 .send(native::Request::ShowKeyboard(show))
382 .unwrap();
383 }
384 }
385
386 pub fn set_ime_position(x: i32, y: i32) {
391 let d = native_display().lock().unwrap();
392 #[cfg(target_os = "android")]
393 {
394 let _ = (x, y); }
396
397 #[cfg(not(target_os = "android"))]
398 {
399 d.native_requests
400 .send(native::Request::SetImePosition { x, y })
401 .unwrap();
402 }
403 }
404
405 pub fn set_ime_enabled(enabled: bool) {
413 let d = native_display().lock().unwrap();
414 #[cfg(target_os = "android")]
415 {
416 let _ = enabled; }
418
419 #[cfg(not(target_os = "android"))]
420 {
421 d.native_requests
422 .send(native::Request::SetImeEnabled(enabled))
423 .unwrap();
424 }
425 }
426
427 #[cfg(target_vendor = "apple")]
428 pub fn apple_gfx_api() -> crate::conf::AppleGfxApi {
429 let d = native_display().lock().unwrap();
430 d.gfx_api
431 }
432 #[cfg(target_vendor = "apple")]
433 pub fn apple_view() -> crate::native::apple::frameworks::ObjcId {
434 let d = native_display().lock().unwrap();
435 d.view
436 }
437 #[cfg(target_os = "ios")]
438 pub fn apple_view_ctrl() -> crate::native::apple::frameworks::ObjcId {
439 let d = native_display().lock().unwrap();
440 d.view_ctrl
441 }
442
443 #[cfg(target_os = "windows")]
448 pub fn windows_hwnd() -> *mut std::ffi::c_void {
449 crate::native_display().lock().unwrap().hwnd
450 }
451}
452
453#[derive(Debug, Copy, Clone, PartialEq, Hash, Eq)]
454pub enum CursorIcon {
455 Default,
456 Help,
457 Pointer,
458 Wait,
459 Crosshair,
460 Text,
461 Move,
462 NotAllowed,
463 EWResize,
464 NSResize,
465 NESWResize,
466 NWSEResize,
467}
468
469pub fn start<F>(conf: conf::Conf, f: F)
471where
472 F: 'static + FnOnce() -> Box<dyn EventHandler>,
473{
474 #[cfg(target_os = "linux")]
475 {
476 let mut f = Some(f);
477 let f = &mut f;
478 match conf.platform.linux_backend {
479 conf::LinuxBackend::X11Only => {
480 native::linux_x11::run(&conf, f).expect("X11 backend failed")
481 }
482 conf::LinuxBackend::WaylandOnly => {
483 native::linux_wayland::run(&conf, f).expect("Wayland backend failed")
484 }
485 conf::LinuxBackend::X11WithWaylandFallback => {
486 if let Err(err) = native::linux_x11::run(&conf, f) {
487 eprintln!("{err:?}");
488 eprintln!("Failed to initialize through X11! Trying wayland instead");
489 native::linux_wayland::run(&conf, f);
490 }
491 }
492 conf::LinuxBackend::WaylandWithX11Fallback => {
493 if native::linux_wayland::run(&conf, f).is_none() {
494 eprintln!("Failed to initialize through wayland! Trying X11 instead");
495 native::linux_x11::run(&conf, f).unwrap()
496 }
497 }
498 }
499 }
500
501 #[cfg(target_os = "android")]
502 unsafe {
503 native::android::run(conf, f);
504 }
505
506 #[cfg(target_arch = "wasm32")]
507 {
508 native::wasm::run(&conf, f);
509 }
510
511 #[cfg(target_os = "windows")]
512 {
513 native::windows::run(&conf, f);
514 }
515
516 #[cfg(target_os = "macos")]
517 unsafe {
518 native::macos::run(conf, f);
519 }
520
521 #[cfg(target_os = "ios")]
522 unsafe {
523 native::ios::run(conf, f);
524 }
525}