macroquad/
lib.rs

1//!
2//! `macroquad` is a simple and easy to use game library for Rust programming language.
3//!
4//! `macroquad` attempts to avoid any rust-specific programming concepts like lifetimes/borrowing, making it very friendly for rust beginners.
5//!
6//! ## Supported platforms
7//!
8//! * PC: Windows/Linux/MacOS
9//! * HTML5
10//! * Android
11//! * IOS
12//!
13//! ## Features
14//!
15//! * Same code for all supported platforms, no platform dependent defines required
16//! * Efficient 2D rendering with automatic geometry batching
17//! * Minimal amount of dependencies: build after `cargo clean` takes only 16s on x230(~6years old laptop)
18//! * Immediate mode UI library included
19//! * Single command deploy for both WASM and Android [build instructions](https://github.com/not-fl3/miniquad/#building-examples)
20//! # Example
21//! ```no_run
22//! use macroquad::prelude::*;
23//!
24//! #[macroquad::main("BasicShapes")]
25//! async fn main() {
26//!     loop {
27//!         clear_background(RED);
28//!
29//!         draw_line(40.0, 40.0, 100.0, 200.0, 15.0, BLUE);
30//!         draw_rectangle(screen_width() / 2.0 - 60.0, 100.0, 120.0, 60.0, GREEN);
31//!         draw_circle(screen_width() - 30.0, screen_height() - 30.0, 15.0, YELLOW);
32//!         draw_text("HELLO", 20.0, 20.0, 20.0, DARKGRAY);
33//!
34//!         next_frame().await
35//!     }
36//! }
37//!```
38
39use miniquad::*;
40
41use std::collections::{HashMap, HashSet};
42use std::future::Future;
43use std::panic::AssertUnwindSafe;
44use std::pin::Pin;
45
46mod exec;
47mod quad_gl;
48mod tobytes;
49
50pub mod audio;
51pub mod camera;
52pub mod color;
53pub mod file;
54pub mod input;
55pub mod material;
56pub mod math;
57pub mod models;
58pub mod shapes;
59pub mod text;
60pub mod texture;
61pub mod time;
62pub mod ui;
63pub mod window;
64
65pub mod experimental;
66
67pub mod prelude;
68
69pub mod telemetry;
70
71mod error;
72
73pub use error::Error;
74
75/// Macroquad entry point.
76///
77/// ```skip
78/// #[main("Window name")]
79/// async fn main() {
80/// }
81/// ```
82///
83/// ```skip
84/// fn window_conf() -> Conf {
85///     Conf {
86///         window_title: "Window name".to_owned(),
87///         fullscreen: true,
88///         ..Default::default()
89///     }
90/// }
91/// #[macroquad::main(window_conf)]
92/// async fn main() {
93/// }
94/// ```
95///
96/// ## Error handling
97///
98/// `async fn main()` can have the same signature as a normal `main` in Rust.
99/// The most typical use cases are:
100/// * `async fn main() {}`
101/// * `async fn main() -> Result<(), Error> {}` (note that `Error` should implement `Debug`)
102///
103/// When a lot of third party crates are involved and very different errors may happens, `anyhow` crate may help:
104/// * `async fn main() -> anyhow::Result<()> {}`
105///
106/// For better control over game errors custom error type may be introduced:
107/// ```skip
108/// #[derive(Debug)]
109/// enum GameError {
110///     FileError(macroquad::FileError),
111///     SomeThirdPartyCrateError(somecrate::Error)
112/// }
113/// impl From<macroquad::file::FileError> for GameError {
114///     fn from(error: macroquad::file::FileError) -> GameError {
115///         GameError::FileError(error)
116///     }
117/// }
118/// impl From<somecrate::Error> for GameError {
119///     fn from(error: somecrate::Error) -> GameError {
120///         GameError::SomeThirdPartyCrateError(error)
121///     }
122/// }
123/// ```
124pub use macroquad_macro::main;
125
126/// #[macroquad::test] fn test() {}
127///
128/// Very similar to macroquad::main
129/// Right now it will still spawn a window, just like ::main, therefore
130/// is not really useful for anything than developping macroquad itself
131#[doc(hidden)]
132pub use macroquad_macro::test;
133
134/// Cross platform random generator.
135pub mod rand {
136    pub use quad_rand::*;
137}
138
139#[cfg(not(feature = "log-rs"))]
140/// Logging macros, available with miniquad "log-impl" feature.
141pub mod logging {
142    pub use miniquad::{debug, error, info, trace, warn};
143}
144#[cfg(feature = "log-rs")]
145// Use logging facade
146pub use ::log as logging;
147pub use miniquad;
148
149use crate::{
150    color::{colors::*, Color},
151    quad_gl::QuadGl,
152    texture::TextureHandle,
153    ui::ui_context::UiContext,
154};
155
156use glam::{vec2, Mat4, Vec2};
157
158pub(crate) mod thread_assert {
159    static mut THREAD_ID: Option<std::thread::ThreadId> = None;
160
161    pub fn set_thread_id() {
162        unsafe {
163            THREAD_ID = Some(std::thread::current().id());
164        }
165    }
166
167    pub fn same_thread() {
168        unsafe {
169            thread_local! {
170                static CURRENT_THREAD_ID: std::thread::ThreadId = std::thread::current().id();
171            }
172            assert!(THREAD_ID.is_some());
173            assert!(THREAD_ID.unwrap() == CURRENT_THREAD_ID.with(|id| *id));
174        }
175    }
176}
177struct Context {
178    audio_context: audio::AudioContext,
179
180    screen_width: f32,
181    screen_height: f32,
182
183    simulate_mouse_with_touch: bool,
184
185    keys_down: HashSet<KeyCode>,
186    keys_pressed: HashSet<KeyCode>,
187    keys_released: HashSet<KeyCode>,
188    mouse_down: HashSet<MouseButton>,
189    mouse_pressed: HashSet<MouseButton>,
190    mouse_released: HashSet<MouseButton>,
191    touches: HashMap<u64, input::Touch>,
192    chars_pressed_queue: Vec<char>,
193    chars_pressed_ui_queue: Vec<char>,
194    mouse_position: Vec2,
195    last_mouse_position: Option<Vec2>,
196    mouse_wheel: Vec2,
197
198    prevent_quit_event: bool,
199    quit_requested: bool,
200
201    cursor_grabbed: bool,
202
203    input_events: Vec<Vec<MiniquadInputEvent>>,
204
205    gl: QuadGl,
206    camera_matrix: Option<Mat4>,
207
208    ui_context: UiContext,
209    coroutines_context: experimental::coroutines::CoroutinesContext,
210    fonts_storage: text::FontsStorage,
211
212    pc_assets_folder: Option<String>,
213
214    start_time: f64,
215    last_frame_time: f64,
216    frame_time: f64,
217
218    #[cfg(one_screenshot)]
219    counter: usize,
220
221    camera_stack: Vec<camera::CameraState>,
222    texture_batcher: texture::Batcher,
223    unwind: bool,
224    recovery_future: Option<Pin<Box<dyn Future<Output = ()>>>>,
225
226    quad_context: Box<dyn miniquad::RenderingBackend>,
227
228    default_filter_mode: crate::quad_gl::FilterMode,
229    textures: crate::texture::TexturesContext,
230
231    update_on: conf::UpdateTrigger,
232
233    dropped_files: Vec<DroppedFile>,
234}
235
236#[derive(Clone)]
237enum MiniquadInputEvent {
238    MouseMotion {
239        x: f32,
240        y: f32,
241    },
242    MouseWheel {
243        x: f32,
244        y: f32,
245    },
246    MouseButtonDown {
247        x: f32,
248        y: f32,
249        btn: MouseButton,
250    },
251    MouseButtonUp {
252        x: f32,
253        y: f32,
254        btn: MouseButton,
255    },
256    Char {
257        character: char,
258        modifiers: KeyMods,
259        repeat: bool,
260    },
261    KeyDown {
262        keycode: KeyCode,
263        modifiers: KeyMods,
264        repeat: bool,
265    },
266    KeyUp {
267        keycode: KeyCode,
268        modifiers: KeyMods,
269    },
270    Touch {
271        phase: TouchPhase,
272        id: u64,
273        x: f32,
274        y: f32,
275    },
276}
277
278impl MiniquadInputEvent {
279    fn repeat<T: miniquad::EventHandler>(&self, t: &mut T) {
280        use crate::MiniquadInputEvent::*;
281        match self {
282            MouseMotion { x, y } => t.mouse_motion_event(*x, *y),
283            MouseWheel { x, y } => t.mouse_wheel_event(*x, *y),
284            MouseButtonDown { x, y, btn } => t.mouse_button_down_event(*btn, *x, *y),
285            MouseButtonUp { x, y, btn } => t.mouse_button_up_event(*btn, *x, *y),
286            Char {
287                character,
288                modifiers,
289                repeat,
290            } => t.char_event(*character, *modifiers, *repeat),
291            KeyDown {
292                keycode,
293                modifiers,
294                repeat,
295            } => t.key_down_event(*keycode, *modifiers, *repeat),
296            KeyUp { keycode, modifiers } => t.key_up_event(*keycode, *modifiers),
297            Touch { phase, id, x, y } => t.touch_event(*phase, *id, *x, *y),
298        }
299    }
300}
301
302impl Context {
303    const DEFAULT_BG_COLOR: Color = BLACK;
304
305    fn new(
306        update_on: conf::UpdateTrigger,
307        default_filter_mode: crate::FilterMode,
308        draw_call_vertex_capacity: usize,
309        draw_call_index_capacity: usize,
310    ) -> Context {
311        let mut ctx: Box<dyn miniquad::RenderingBackend> =
312            miniquad::window::new_rendering_backend();
313        let (screen_width, screen_height) = miniquad::window::screen_size();
314
315        Context {
316            screen_width,
317            screen_height,
318
319            simulate_mouse_with_touch: true,
320
321            keys_down: HashSet::new(),
322            keys_pressed: HashSet::new(),
323            keys_released: HashSet::new(),
324            chars_pressed_queue: Vec::new(),
325            chars_pressed_ui_queue: Vec::new(),
326            mouse_down: HashSet::new(),
327            mouse_pressed: HashSet::new(),
328            mouse_released: HashSet::new(),
329            touches: HashMap::new(),
330            mouse_position: vec2(0., 0.),
331            last_mouse_position: None,
332            mouse_wheel: vec2(0., 0.),
333
334            prevent_quit_event: false,
335            quit_requested: false,
336
337            cursor_grabbed: false,
338
339            input_events: Vec::new(),
340
341            camera_matrix: None,
342            gl: QuadGl::new(
343                &mut *ctx,
344                draw_call_vertex_capacity,
345                draw_call_index_capacity,
346            ),
347
348            ui_context: UiContext::new(&mut *ctx, screen_width, screen_height),
349            fonts_storage: text::FontsStorage::new(&mut *ctx),
350            texture_batcher: texture::Batcher::new(&mut *ctx),
351            camera_stack: vec![],
352
353            audio_context: audio::AudioContext::new(),
354            coroutines_context: experimental::coroutines::CoroutinesContext::new(),
355
356            pc_assets_folder: None,
357
358            start_time: miniquad::date::now(),
359            last_frame_time: miniquad::date::now(),
360            frame_time: 1. / 60.,
361
362            #[cfg(one_screenshot)]
363            counter: 0,
364            unwind: false,
365            recovery_future: None,
366
367            quad_context: ctx,
368
369            default_filter_mode,
370            textures: crate::texture::TexturesContext::new(),
371            update_on,
372
373            dropped_files: Vec::new(),
374        }
375    }
376
377    /// Returns the handle for this texture.
378    pub fn raw_miniquad_id(&self, handle: &TextureHandle) -> miniquad::TextureId {
379        match handle {
380            TextureHandle::Unmanaged(texture) => *texture,
381            TextureHandle::Managed(texture) => self
382                .textures
383                .texture(texture.0)
384                .unwrap_or(self.gl.white_texture),
385            TextureHandle::ManagedWeak(texture) => self
386                .textures
387                .texture(*texture)
388                .unwrap_or(self.gl.white_texture),
389        }
390    }
391
392    /// Returns the files which have been dropped onto the window.
393    pub fn dropped_files(&mut self) -> Vec<DroppedFile> {
394        std::mem::take(&mut self.dropped_files)
395    }
396
397    fn begin_frame(&mut self) {
398        telemetry::begin_gpu_query("GPU");
399
400        self.ui_context.process_input();
401
402        let color = Self::DEFAULT_BG_COLOR;
403
404        get_quad_context().clear(Some((color.r, color.g, color.b, color.a)), None, None);
405        self.gl.reset();
406    }
407
408    fn end_frame(&mut self) {
409        crate::experimental::scene::update();
410
411        self.perform_render_passes();
412
413        self.ui_context.draw(get_quad_context(), &mut self.gl);
414        let screen_mat = self.pixel_perfect_projection_matrix();
415        self.gl.draw(get_quad_context(), screen_mat);
416
417        get_quad_context().commit_frame();
418
419        #[cfg(one_screenshot)]
420        {
421            get_context().counter += 1;
422            if get_context().counter == 3 {
423                crate::prelude::get_screen_data().export_png("screenshot.png");
424                panic!("screenshot successfully saved to `screenshot.png`");
425            }
426        }
427
428        telemetry::end_gpu_query();
429
430        self.mouse_wheel = Vec2::new(0., 0.);
431        self.keys_pressed.clear();
432        self.keys_released.clear();
433        self.mouse_pressed.clear();
434        self.mouse_released.clear();
435        self.last_mouse_position = Some(crate::prelude::mouse_position_local());
436
437        self.quit_requested = false;
438
439        self.textures.garbage_collect(get_quad_context());
440
441        // remove all touches that were Ended or Cancelled
442        self.touches.retain(|_, touch| {
443            touch.phase != input::TouchPhase::Ended && touch.phase != input::TouchPhase::Cancelled
444        });
445
446        // change all Started or Moved touches to Stationary
447        for touch in self.touches.values_mut() {
448            if touch.phase == input::TouchPhase::Started || touch.phase == input::TouchPhase::Moved
449            {
450                touch.phase = input::TouchPhase::Stationary;
451            }
452        }
453
454        self.dropped_files.clear();
455    }
456
457    pub(crate) fn pixel_perfect_projection_matrix(&self) -> glam::Mat4 {
458        let (width, height) = miniquad::window::screen_size();
459
460        let dpi = miniquad::window::dpi_scale();
461
462        glam::Mat4::orthographic_rh_gl(0., width / dpi, height / dpi, 0., -1., 1.)
463    }
464
465    pub(crate) fn projection_matrix(&self) -> glam::Mat4 {
466        if let Some(matrix) = self.camera_matrix {
467            matrix
468        } else {
469            self.pixel_perfect_projection_matrix()
470        }
471    }
472
473    pub(crate) fn perform_render_passes(&mut self) {
474        let matrix = self.projection_matrix();
475
476        self.gl.draw(get_quad_context(), matrix);
477    }
478}
479
480#[no_mangle]
481static mut CONTEXT: Option<Context> = None;
482
483// This is required for #[macroquad::test]
484//
485// unfortunately #[cfg(test)] do not work with integration tests
486// so this module should be publicly available
487#[doc(hidden)]
488pub mod test {
489    pub static mut MUTEX: Option<std::sync::Mutex<()>> = None;
490    pub static ONCE: std::sync::Once = std::sync::Once::new();
491}
492
493fn get_context() -> &'static mut Context {
494    thread_assert::same_thread();
495
496    unsafe { CONTEXT.as_mut().unwrap_or_else(|| panic!()) }
497}
498
499fn get_quad_context() -> &'static mut dyn miniquad::RenderingBackend {
500    thread_assert::same_thread();
501
502    unsafe {
503        assert!(CONTEXT.is_some());
504    }
505
506    unsafe { &mut *CONTEXT.as_mut().unwrap().quad_context }
507}
508
509struct Stage {
510    main_future: Pin<Box<dyn Future<Output = ()>>>,
511}
512
513impl EventHandler for Stage {
514    fn resize_event(&mut self, width: f32, height: f32) {
515        let _z = telemetry::ZoneGuard::new("Event::resize_event");
516        get_context().screen_width = width;
517        get_context().screen_height = height;
518
519        if miniquad::window::blocking_event_loop() {
520            miniquad::window::schedule_update();
521        }
522    }
523
524    fn raw_mouse_motion(&mut self, x: f32, y: f32) {
525        let context = get_context();
526
527        if context.cursor_grabbed {
528            context.mouse_position += Vec2::new(x, y);
529
530            let event = MiniquadInputEvent::MouseMotion {
531                x: context.mouse_position.x,
532                y: context.mouse_position.y,
533            };
534            context
535                .input_events
536                .iter_mut()
537                .for_each(|arr| arr.push(event.clone()));
538        }
539    }
540
541    fn mouse_motion_event(&mut self, x: f32, y: f32) {
542        let context = get_context();
543
544        if !context.cursor_grabbed {
545            context.mouse_position = Vec2::new(x, y);
546
547            context
548                .input_events
549                .iter_mut()
550                .for_each(|arr| arr.push(MiniquadInputEvent::MouseMotion { x, y }));
551        }
552
553        if context.update_on.mouse_motion {
554            miniquad::window::schedule_update();
555        }
556    }
557
558    fn mouse_wheel_event(&mut self, x: f32, y: f32) {
559        let context = get_context();
560
561        context.mouse_wheel.x = x;
562        context.mouse_wheel.y = y;
563
564        context
565            .input_events
566            .iter_mut()
567            .for_each(|arr| arr.push(MiniquadInputEvent::MouseWheel { x, y }));
568
569        if context.update_on.mouse_wheel {
570            miniquad::window::schedule_update();
571        }
572    }
573
574    fn mouse_button_down_event(&mut self, btn: MouseButton, x: f32, y: f32) {
575        let context = get_context();
576
577        context.mouse_down.insert(btn);
578        context.mouse_pressed.insert(btn);
579
580        context
581            .input_events
582            .iter_mut()
583            .for_each(|arr| arr.push(MiniquadInputEvent::MouseButtonDown { x, y, btn }));
584
585        if !context.cursor_grabbed {
586            context.mouse_position = Vec2::new(x, y);
587        }
588
589        if context.update_on.mouse_down {
590            miniquad::window::schedule_update();
591        }
592    }
593
594    fn mouse_button_up_event(&mut self, btn: MouseButton, x: f32, y: f32) {
595        let context = get_context();
596
597        context.mouse_down.remove(&btn);
598        context.mouse_released.insert(btn);
599
600        context
601            .input_events
602            .iter_mut()
603            .for_each(|arr| arr.push(MiniquadInputEvent::MouseButtonUp { x, y, btn }));
604
605        if !context.cursor_grabbed {
606            context.mouse_position = Vec2::new(x, y);
607        }
608        if context.update_on.mouse_up {
609            miniquad::window::schedule_update();
610        }
611    }
612
613    fn touch_event(&mut self, phase: TouchPhase, id: u64, x: f32, y: f32) {
614        let context = get_context();
615
616        context.touches.insert(
617            id,
618            input::Touch {
619                id,
620                phase: phase.into(),
621                position: Vec2::new(x, y),
622            },
623        );
624
625        if context.simulate_mouse_with_touch {
626            if phase == TouchPhase::Started {
627                self.mouse_button_down_event(MouseButton::Left, x, y);
628            }
629
630            if phase == TouchPhase::Ended {
631                self.mouse_button_up_event(MouseButton::Left, x, y);
632            }
633
634            if phase == TouchPhase::Moved {
635                self.mouse_motion_event(x, y);
636            }
637        } else if context.update_on.touch {
638            miniquad::window::schedule_update();
639        };
640
641        context
642            .input_events
643            .iter_mut()
644            .for_each(|arr| arr.push(MiniquadInputEvent::Touch { phase, id, x, y }));
645    }
646
647    fn char_event(&mut self, character: char, modifiers: KeyMods, repeat: bool) {
648        let context = get_context();
649
650        context.chars_pressed_queue.push(character);
651        context.chars_pressed_ui_queue.push(character);
652
653        context.input_events.iter_mut().for_each(|arr| {
654            arr.push(MiniquadInputEvent::Char {
655                character,
656                modifiers,
657                repeat,
658            });
659        });
660    }
661
662    fn key_down_event(&mut self, keycode: KeyCode, modifiers: KeyMods, repeat: bool) {
663        let context = get_context();
664        context.keys_down.insert(keycode);
665        if repeat == false {
666            context.keys_pressed.insert(keycode);
667        }
668
669        context.input_events.iter_mut().for_each(|arr| {
670            arr.push(MiniquadInputEvent::KeyDown {
671                keycode,
672                modifiers,
673                repeat,
674            });
675        });
676        if context
677            .update_on
678            .specific_key
679            .as_ref()
680            .map_or(context.update_on.key_down, |keys| keys.contains(&keycode))
681        {
682            miniquad::window::schedule_update();
683        }
684    }
685
686    fn key_up_event(&mut self, keycode: KeyCode, modifiers: KeyMods) {
687        let context = get_context();
688        context.keys_down.remove(&keycode);
689        context.keys_released.insert(keycode);
690
691        context
692            .input_events
693            .iter_mut()
694            .for_each(|arr| arr.push(MiniquadInputEvent::KeyUp { keycode, modifiers }));
695
696        if miniquad::window::blocking_event_loop() {
697            //miniquad::window::schedule_update();
698        }
699    }
700
701    fn update(&mut self) {
702        let _z = telemetry::ZoneGuard::new("Event::update");
703
704        // Unless called every frame, cursor will not remain grabbed
705        miniquad::window::set_cursor_grab(get_context().cursor_grabbed);
706
707        #[cfg(not(target_arch = "wasm32"))]
708        {
709            // TODO: consider making it a part of miniquad?
710            std::thread::yield_now();
711        }
712    }
713
714    fn files_dropped_event(&mut self) {
715        let context = get_context();
716        for i in 0..miniquad::window::dropped_file_count() {
717            context.dropped_files.push(DroppedFile {
718                path: miniquad::window::dropped_file_path(i),
719                bytes: miniquad::window::dropped_file_bytes(i),
720            });
721        }
722    }
723
724    fn draw(&mut self) {
725        {
726            let _z = telemetry::ZoneGuard::new("Event::draw");
727
728            use std::panic;
729
730            {
731                let _z = telemetry::ZoneGuard::new("Event::draw begin_frame");
732                get_context().begin_frame();
733            }
734
735            fn maybe_unwind(unwind: bool, f: impl FnOnce() + Sized + panic::UnwindSafe) -> bool {
736                if unwind {
737                    panic::catch_unwind(f).is_ok()
738                } else {
739                    f();
740                    true
741                }
742            }
743
744            let result = maybe_unwind(
745                get_context().unwind,
746                AssertUnwindSafe(|| {
747                    let _z = telemetry::ZoneGuard::new("Event::draw user code");
748
749                    if exec::resume(&mut self.main_future).is_some() {
750                        self.main_future = Box::pin(async move {});
751                        miniquad::window::quit();
752                        return;
753                    }
754                    get_context().coroutines_context.update();
755                }),
756            );
757
758            if result == false {
759                if let Some(recovery_future) = get_context().recovery_future.take() {
760                    self.main_future = recovery_future;
761                }
762            }
763
764            {
765                let _z = telemetry::ZoneGuard::new("Event::draw end_frame");
766                get_context().end_frame();
767            }
768            get_context().frame_time = date::now() - get_context().last_frame_time;
769            get_context().last_frame_time = date::now();
770
771            #[cfg(any(target_arch = "wasm32", target_os = "linux"))]
772            {
773                let _z = telemetry::ZoneGuard::new("glFinish/glFLush");
774
775                unsafe {
776                    miniquad::gl::glFlush();
777                    miniquad::gl::glFinish();
778                }
779            }
780        }
781
782        telemetry::reset();
783    }
784
785    fn window_restored_event(&mut self) {
786        #[cfg(target_os = "android")]
787        get_context().audio_context.resume();
788        #[cfg(target_os = "android")]
789        if miniquad::window::blocking_event_loop() {
790            miniquad::window::schedule_update();
791        }
792    }
793
794    fn window_minimized_event(&mut self) {
795        #[cfg(target_os = "android")]
796        get_context().audio_context.pause();
797    }
798
799    fn quit_requested_event(&mut self) {
800        let context = get_context();
801        if context.prevent_quit_event {
802            miniquad::window::cancel_quit();
803            context.quit_requested = true;
804        }
805    }
806}
807
808pub mod conf {
809    #[derive(Default, Debug)]
810    pub struct UpdateTrigger {
811        pub key_down: bool,
812        pub mouse_down: bool,
813        pub mouse_up: bool,
814        pub mouse_motion: bool,
815        pub mouse_wheel: bool,
816        pub specific_key: Option<Vec<crate::KeyCode>>,
817        pub touch: bool,
818    }
819
820    #[derive(Debug)]
821    pub struct Conf {
822        pub miniquad_conf: miniquad::conf::Conf,
823        /// With miniquad_conf.platform.blocking_event_loop = true,
824        /// next_frame().await will never finish and will wait forever with
825        /// zero CPU usage.
826        /// update_on will tell macroquad when to proceed with the event loop.
827        pub update_on: Option<UpdateTrigger>,
828        pub default_filter_mode: crate::FilterMode,
829        /// Macroquad performs automatic and static batching for each
830        /// draw_* call. For each draw call, it pre-allocate a huge cpu/gpu
831        /// buffer to add vertices to. When it exceeds the buffer, it allocates the
832        /// new one, marking the new draw call.
833        ///
834        /// Some examples when altering those values migh be convinient:
835        /// - for huge 3d models that do not fit into a single draw call, increasing
836        ///     the buffer size might be easier than splitting the model.
837        /// - when each draw_* call got its own material,
838        ///     buffer size might be reduced to save some memory
839        pub draw_call_vertex_capacity: usize,
840        pub draw_call_index_capacity: usize,
841    }
842
843    impl Default for Conf {
844        fn default() -> Self {
845            Self {
846                miniquad_conf: miniquad::conf::Conf::default(),
847                update_on: Some(UpdateTrigger::default()),
848                default_filter_mode: crate::FilterMode::Linear,
849                draw_call_vertex_capacity: 10000,
850                draw_call_index_capacity: 5000,
851            }
852        }
853    }
854}
855
856impl From<miniquad::conf::Conf> for conf::Conf {
857    fn from(conf: miniquad::conf::Conf) -> conf::Conf {
858        conf::Conf {
859            miniquad_conf: conf,
860            update_on: None,
861            default_filter_mode: crate::FilterMode::Linear,
862            draw_call_vertex_capacity: 10000,
863            draw_call_index_capacity: 5000,
864        }
865    }
866}
867
868/// Not meant to be used directly, only from the macro.
869#[doc(hidden)]
870pub struct Window {}
871
872impl Window {
873    pub fn new(label: &str, future: impl Future<Output = ()> + 'static) {
874        Window::from_config(
875            conf::Conf {
876                miniquad_conf: miniquad::conf::Conf {
877                    window_title: label.to_string(),
878                    ..Default::default()
879                },
880                ..Default::default()
881            },
882            future,
883        );
884    }
885
886    pub fn from_config(config: impl Into<conf::Conf>, future: impl Future<Output = ()> + 'static) {
887        let conf::Conf {
888            miniquad_conf,
889            update_on,
890            default_filter_mode,
891            draw_call_vertex_capacity,
892            draw_call_index_capacity,
893        } = config.into();
894        miniquad::start(miniquad_conf, move || {
895            thread_assert::set_thread_id();
896            let context = Context::new(
897                update_on.unwrap_or_default(),
898                default_filter_mode,
899                draw_call_vertex_capacity,
900                draw_call_index_capacity,
901            );
902            unsafe { CONTEXT = Some(context) };
903
904            Box::new(Stage {
905                main_future: Box::pin(async {
906                    future.await;
907                    unsafe {
908                        if let Some(ctx) = CONTEXT.as_mut() {
909                            ctx.gl.reset();
910                        }
911                    }
912                }),
913            })
914        });
915    }
916}
917
918/// Information about a dropped file.
919pub struct DroppedFile {
920    pub path: Option<std::path::PathBuf>,
921    pub bytes: Option<Vec<u8>>,
922}