Skip to main content

macroquad_ply/
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    previous_cursor_grabbed: bool,
203
204    input_events: Vec<Vec<MiniquadInputEvent>>,
205
206    gl: QuadGl,
207    camera_matrix: Option<Mat4>,
208
209    ui_context: UiContext,
210    coroutines_context: experimental::coroutines::CoroutinesContext,
211    fonts_storage: text::FontsStorage,
212
213    pc_assets_folder: Option<String>,
214
215    start_time: f64,
216    last_frame_time: f64,
217    frame_time: f64,
218
219    #[cfg(one_screenshot)]
220    counter: usize,
221
222    camera_stack: Vec<camera::CameraState>,
223    texture_batcher: texture::Batcher,
224    unwind: bool,
225    recovery_future: Option<Pin<Box<dyn Future<Output = ()>>>>,
226
227    quad_context: Box<dyn miniquad::RenderingBackend>,
228
229    default_filter_mode: crate::quad_gl::FilterMode,
230    textures: crate::texture::TexturesContext,
231
232    update_on: conf::UpdateTrigger,
233
234    dropped_files: Vec<DroppedFile>,
235}
236
237#[derive(Clone)]
238enum MiniquadInputEvent {
239    MouseMotion {
240        x: f32,
241        y: f32,
242    },
243    MouseWheel {
244        x: f32,
245        y: f32,
246    },
247    MouseButtonDown {
248        x: f32,
249        y: f32,
250        btn: MouseButton,
251    },
252    MouseButtonUp {
253        x: f32,
254        y: f32,
255        btn: MouseButton,
256    },
257    Char {
258        character: char,
259        modifiers: KeyMods,
260        repeat: bool,
261    },
262    KeyDown {
263        keycode: KeyCode,
264        modifiers: KeyMods,
265        repeat: bool,
266    },
267    KeyUp {
268        keycode: KeyCode,
269        modifiers: KeyMods,
270    },
271    Touch {
272        phase: TouchPhase,
273        id: u64,
274        x: f32,
275        y: f32,
276    },
277    WindowMinimized,
278    WindowRestored,
279}
280
281impl MiniquadInputEvent {
282    fn repeat<T: miniquad::EventHandler>(&self, t: &mut T) {
283        use crate::MiniquadInputEvent::*;
284        match self {
285            MouseMotion { x, y } => t.mouse_motion_event(*x, *y),
286            MouseWheel { x, y } => t.mouse_wheel_event(*x, *y),
287            MouseButtonDown { x, y, btn } => t.mouse_button_down_event(*btn, *x, *y),
288            MouseButtonUp { x, y, btn } => t.mouse_button_up_event(*btn, *x, *y),
289            Char {
290                character,
291                modifiers,
292                repeat,
293            } => t.char_event(*character, *modifiers, *repeat),
294            KeyDown {
295                keycode,
296                modifiers,
297                repeat,
298            } => t.key_down_event(*keycode, *modifiers, *repeat),
299            KeyUp { keycode, modifiers } => t.key_up_event(*keycode, *modifiers),
300            Touch { phase, id, x, y } => t.touch_event(*phase, *id, *x, *y),
301            WindowMinimized => t.window_minimized_event(),
302            WindowRestored => t.window_restored_event(),
303        }
304    }
305}
306
307impl Context {
308    const DEFAULT_BG_COLOR: Color = BLACK;
309
310    fn new(
311        update_on: conf::UpdateTrigger,
312        default_filter_mode: crate::FilterMode,
313        draw_call_vertex_capacity: usize,
314        draw_call_index_capacity: usize,
315    ) -> Context {
316        let mut ctx: Box<dyn miniquad::RenderingBackend> =
317            miniquad::window::new_rendering_backend();
318        let (screen_width, screen_height) = miniquad::window::screen_size();
319
320        Context {
321            screen_width,
322            screen_height,
323
324            simulate_mouse_with_touch: true,
325
326            keys_down: HashSet::new(),
327            keys_pressed: HashSet::new(),
328            keys_released: HashSet::new(),
329            chars_pressed_queue: Vec::new(),
330            chars_pressed_ui_queue: Vec::new(),
331            mouse_down: HashSet::new(),
332            mouse_pressed: HashSet::new(),
333            mouse_released: HashSet::new(),
334            touches: HashMap::new(),
335            mouse_position: vec2(0., 0.),
336            last_mouse_position: None,
337            mouse_wheel: vec2(0., 0.),
338
339            prevent_quit_event: false,
340            quit_requested: false,
341
342            cursor_grabbed: false,
343            previous_cursor_grabbed: false,
344
345            input_events: Vec::new(),
346
347            camera_matrix: None,
348            gl: QuadGl::new(
349                &mut *ctx,
350                draw_call_vertex_capacity,
351                draw_call_index_capacity,
352            ),
353
354            ui_context: UiContext::new(&mut *ctx, screen_width, screen_height),
355            fonts_storage: text::FontsStorage::new(&mut *ctx),
356            texture_batcher: texture::Batcher::new(&mut *ctx),
357            camera_stack: vec![],
358
359            audio_context: audio::AudioContext::new(),
360            coroutines_context: experimental::coroutines::CoroutinesContext::new(),
361
362            pc_assets_folder: None,
363
364            start_time: miniquad::date::now(),
365            last_frame_time: miniquad::date::now(),
366            frame_time: 1. / 60.,
367
368            #[cfg(one_screenshot)]
369            counter: 0,
370            unwind: false,
371            recovery_future: None,
372
373            quad_context: ctx,
374
375            default_filter_mode,
376            textures: crate::texture::TexturesContext::new(),
377            update_on,
378
379            dropped_files: Vec::new(),
380        }
381    }
382
383    /// Returns the handle for this texture.
384    pub fn raw_miniquad_id(&self, handle: &TextureHandle) -> miniquad::TextureId {
385        match handle {
386            TextureHandle::Unmanaged(texture) => *texture,
387            TextureHandle::Managed(texture) => self
388                .textures
389                .texture(texture.0)
390                .unwrap_or(self.gl.white_texture),
391            TextureHandle::ManagedWeak(texture) => self
392                .textures
393                .texture(*texture)
394                .unwrap_or(self.gl.white_texture),
395        }
396    }
397
398    /// Returns the files which have been dropped onto the window.
399    pub fn dropped_files(&mut self) -> Vec<DroppedFile> {
400        std::mem::take(&mut self.dropped_files)
401    }
402
403    fn begin_frame(&mut self) {
404        telemetry::begin_gpu_query("GPU");
405
406        self.ui_context.process_input();
407
408        let color = Self::DEFAULT_BG_COLOR;
409
410        get_quad_context().clear(Some((color.r, color.g, color.b, color.a)), None, None);
411        self.gl.reset();
412    }
413
414    fn end_frame(&mut self) {
415        crate::experimental::scene::update();
416
417        self.perform_render_passes();
418
419        self.ui_context.draw(get_quad_context(), &mut self.gl);
420        let screen_mat = self.pixel_perfect_projection_matrix();
421        self.gl.draw(get_quad_context(), screen_mat);
422
423        get_quad_context().commit_frame();
424
425        #[cfg(one_screenshot)]
426        {
427            get_context().counter += 1;
428            if get_context().counter == 3 {
429                crate::prelude::get_screen_data().export_png("screenshot.png");
430                panic!("screenshot successfully saved to `screenshot.png`");
431            }
432        }
433
434        telemetry::end_gpu_query();
435
436        self.mouse_wheel = Vec2::new(0., 0.);
437        self.keys_pressed.clear();
438        self.keys_released.clear();
439        self.mouse_pressed.clear();
440        self.mouse_released.clear();
441        self.last_mouse_position = Some(crate::prelude::mouse_position_local());
442
443        self.quit_requested = false;
444
445        self.textures.garbage_collect(get_quad_context());
446
447        // remove all touches that were Ended or Cancelled
448        self.touches.retain(|_, touch| {
449            touch.phase != input::TouchPhase::Ended && touch.phase != input::TouchPhase::Cancelled
450        });
451
452        // change all Started or Moved touches to Stationary
453        for touch in self.touches.values_mut() {
454            if touch.phase == input::TouchPhase::Started || touch.phase == input::TouchPhase::Moved
455            {
456                touch.phase = input::TouchPhase::Stationary;
457            }
458        }
459
460        self.dropped_files.clear();
461    }
462
463    pub(crate) fn pixel_perfect_projection_matrix(&self) -> glam::Mat4 {
464        let (width, height) = miniquad::window::screen_size();
465
466        let dpi = miniquad::window::dpi_scale();
467
468        glam::Mat4::orthographic_rh_gl(0., width / dpi, height / dpi, 0., -1., 1.)
469    }
470
471    pub(crate) fn projection_matrix(&self) -> glam::Mat4 {
472        if let Some(matrix) = self.camera_matrix {
473            matrix
474        } else {
475            self.pixel_perfect_projection_matrix()
476        }
477    }
478
479    pub(crate) fn perform_render_passes(&mut self) {
480        let matrix = self.projection_matrix();
481
482        self.gl.draw(get_quad_context(), matrix);
483    }
484}
485
486#[no_mangle]
487static mut CONTEXT: Option<Context> = None;
488
489// This is required for #[macroquad::test]
490//
491// unfortunately #[cfg(test)] do not work with integration tests
492// so this module should be publicly available
493#[doc(hidden)]
494pub mod test {
495    pub static mut MUTEX: Option<std::sync::Mutex<()>> = None;
496    pub static ONCE: std::sync::Once = std::sync::Once::new();
497}
498
499fn get_context() -> &'static mut Context {
500    thread_assert::same_thread();
501
502    unsafe { CONTEXT.as_mut().unwrap_or_else(|| panic!()) }
503}
504
505fn get_quad_context() -> &'static mut dyn miniquad::RenderingBackend {
506    thread_assert::same_thread();
507
508    unsafe {
509        assert!(CONTEXT.is_some());
510    }
511
512    unsafe { &mut *CONTEXT.as_mut().unwrap().quad_context }
513}
514
515struct Stage {
516    main_future: Pin<Box<dyn Future<Output = ()>>>,
517}
518
519impl EventHandler for Stage {
520    fn resize_event(&mut self, width: f32, height: f32) {
521        let _z = telemetry::ZoneGuard::new("Event::resize_event");
522        get_context().screen_width = width;
523        get_context().screen_height = height;
524
525        if miniquad::window::blocking_event_loop() {
526            miniquad::window::schedule_update();
527        }
528    }
529
530    fn raw_mouse_motion(&mut self, x: f32, y: f32) {
531        let context = get_context();
532
533        if context.cursor_grabbed {
534            context.mouse_position += Vec2::new(x, y);
535
536            let event = MiniquadInputEvent::MouseMotion {
537                x: context.mouse_position.x,
538                y: context.mouse_position.y,
539            };
540            context
541                .input_events
542                .iter_mut()
543                .for_each(|arr| arr.push(event.clone()));
544        }
545    }
546
547    fn mouse_motion_event(&mut self, x: f32, y: f32) {
548        let context = get_context();
549
550        if !context.cursor_grabbed {
551            context.mouse_position = Vec2::new(x, y);
552
553            context
554                .input_events
555                .iter_mut()
556                .for_each(|arr| arr.push(MiniquadInputEvent::MouseMotion { x, y }));
557        }
558
559        if context.update_on.mouse_motion {
560            miniquad::window::schedule_update();
561        }
562    }
563
564    fn mouse_wheel_event(&mut self, x: f32, y: f32) {
565        let context = get_context();
566
567        context.mouse_wheel.x = x;
568        context.mouse_wheel.y = y;
569
570        context
571            .input_events
572            .iter_mut()
573            .for_each(|arr| arr.push(MiniquadInputEvent::MouseWheel { x, y }));
574
575        if context.update_on.mouse_wheel {
576            miniquad::window::schedule_update();
577        }
578    }
579
580    fn mouse_button_down_event(&mut self, btn: MouseButton, x: f32, y: f32) {
581        let context = get_context();
582
583        context.mouse_down.insert(btn);
584        context.mouse_pressed.insert(btn);
585
586        context
587            .input_events
588            .iter_mut()
589            .for_each(|arr| arr.push(MiniquadInputEvent::MouseButtonDown { x, y, btn }));
590
591        if !context.cursor_grabbed {
592            context.mouse_position = Vec2::new(x, y);
593        }
594
595        if context.update_on.mouse_down {
596            miniquad::window::schedule_update();
597        }
598    }
599
600    fn mouse_button_up_event(&mut self, btn: MouseButton, x: f32, y: f32) {
601        let context = get_context();
602
603        context.mouse_down.remove(&btn);
604        context.mouse_released.insert(btn);
605
606        context
607            .input_events
608            .iter_mut()
609            .for_each(|arr| arr.push(MiniquadInputEvent::MouseButtonUp { x, y, btn }));
610
611        if !context.cursor_grabbed {
612            context.mouse_position = Vec2::new(x, y);
613        }
614        if context.update_on.mouse_up {
615            miniquad::window::schedule_update();
616        }
617    }
618
619    fn touch_event(&mut self, phase: TouchPhase, id: u64, x: f32, y: f32) {
620        let context = get_context();
621
622        context.touches.insert(
623            id,
624            input::Touch {
625                id,
626                phase: phase.into(),
627                position: Vec2::new(x, y),
628            },
629        );
630
631        if context.simulate_mouse_with_touch {
632            if phase == TouchPhase::Started {
633                self.mouse_button_down_event(MouseButton::Left, x, y);
634            }
635
636            if phase == TouchPhase::Ended {
637                self.mouse_button_up_event(MouseButton::Left, x, y);
638            }
639
640            if phase == TouchPhase::Moved {
641                self.mouse_motion_event(x, y);
642            }
643        } else if context.update_on.touch {
644            miniquad::window::schedule_update();
645        };
646
647        context
648            .input_events
649            .iter_mut()
650            .for_each(|arr| arr.push(MiniquadInputEvent::Touch { phase, id, x, y }));
651    }
652
653    fn char_event(&mut self, character: char, modifiers: KeyMods, repeat: bool) {
654        let context = get_context();
655
656        context.chars_pressed_queue.push(character);
657        context.chars_pressed_ui_queue.push(character);
658
659        context.input_events.iter_mut().for_each(|arr| {
660            arr.push(MiniquadInputEvent::Char {
661                character,
662                modifiers,
663                repeat,
664            });
665        });
666    }
667
668    fn key_down_event(&mut self, keycode: KeyCode, modifiers: KeyMods, repeat: bool) {
669        let context = get_context();
670        context.keys_down.insert(keycode);
671        if repeat == false {
672            context.keys_pressed.insert(keycode);
673        }
674
675        context.input_events.iter_mut().for_each(|arr| {
676            arr.push(MiniquadInputEvent::KeyDown {
677                keycode,
678                modifiers,
679                repeat,
680            });
681        });
682        if context
683            .update_on
684            .specific_key
685            .as_ref()
686            .map_or(context.update_on.key_down, |keys| keys.contains(&keycode))
687        {
688            miniquad::window::schedule_update();
689        }
690    }
691
692    fn key_up_event(&mut self, keycode: KeyCode, modifiers: KeyMods) {
693        let context = get_context();
694        context.keys_down.remove(&keycode);
695        context.keys_released.insert(keycode);
696
697        context
698            .input_events
699            .iter_mut()
700            .for_each(|arr| arr.push(MiniquadInputEvent::KeyUp { keycode, modifiers }));
701
702        if miniquad::window::blocking_event_loop() {
703            //miniquad::window::schedule_update();
704        }
705    }
706
707    fn update(&mut self) {
708        let _z = telemetry::ZoneGuard::new("Event::update");
709
710        // Keep the cursor grabbed by calling every frame.
711        // But only ungrab once to preserve implicit mouse grabs.
712        let context = get_context();
713        if context.cursor_grabbed || context.cursor_grabbed != context.previous_cursor_grabbed {
714            miniquad::window::set_cursor_grab(context.cursor_grabbed);
715        }
716        context.previous_cursor_grabbed = context.cursor_grabbed;
717
718        #[cfg(not(target_arch = "wasm32"))]
719        {
720            // TODO: consider making it a part of miniquad?
721            std::thread::yield_now();
722        }
723    }
724
725    fn files_dropped_event(&mut self) {
726        let context = get_context();
727        for i in 0..miniquad::window::dropped_file_count() {
728            context.dropped_files.push(DroppedFile {
729                path: miniquad::window::dropped_file_path(i),
730                bytes: miniquad::window::dropped_file_bytes(i),
731            });
732        }
733    }
734
735    fn draw(&mut self) {
736        {
737            let _z = telemetry::ZoneGuard::new("Event::draw");
738
739            use std::panic;
740
741            {
742                let _z = telemetry::ZoneGuard::new("Event::draw begin_frame");
743                get_context().begin_frame();
744            }
745
746            fn maybe_unwind(unwind: bool, f: impl FnOnce() + Sized + panic::UnwindSafe) -> bool {
747                if unwind {
748                    panic::catch_unwind(f).is_ok()
749                } else {
750                    f();
751                    true
752                }
753            }
754
755            let result = maybe_unwind(
756                get_context().unwind,
757                AssertUnwindSafe(|| {
758                    let _z = telemetry::ZoneGuard::new("Event::draw user code");
759
760                    if exec::resume(&mut self.main_future).is_some() {
761                        self.main_future = Box::pin(async move {});
762                        miniquad::window::quit();
763                        return;
764                    }
765                    get_context().coroutines_context.update();
766                }),
767            );
768
769            if result == false {
770                if let Some(recovery_future) = get_context().recovery_future.take() {
771                    self.main_future = recovery_future;
772                }
773            }
774
775            {
776                let _z = telemetry::ZoneGuard::new("Event::draw end_frame");
777                get_context().end_frame();
778            }
779            get_context().frame_time = date::now() - get_context().last_frame_time;
780            get_context().last_frame_time = date::now();
781
782            #[cfg(any(target_arch = "wasm32", target_os = "linux"))]
783            {
784                let _z = telemetry::ZoneGuard::new("glFinish/glFLush");
785
786                unsafe {
787                    miniquad::gl::glFlush();
788                    miniquad::gl::glFinish();
789                }
790            }
791        }
792
793        telemetry::reset();
794    }
795
796    fn window_restored_event(&mut self) {
797        let context = get_context();
798
799        #[cfg(target_os = "android")]
800        context.audio_context.resume();
801        #[cfg(target_os = "android")]
802        if miniquad::window::blocking_event_loop() {
803            miniquad::window::schedule_update();
804        }
805
806        context
807            .input_events
808            .iter_mut()
809            .for_each(|arr| arr.push(MiniquadInputEvent::WindowRestored));
810    }
811
812    fn window_minimized_event(&mut self) {
813        let context = get_context();
814
815        #[cfg(target_os = "android")]
816        context.audio_context.pause();
817
818        // Clear held down keys and button and announce them as released
819        context.mouse_released.extend(context.mouse_down.drain());
820        context.keys_released.extend(context.keys_down.drain());
821
822        // Announce all touches as released
823        for (_, touch) in context.touches.iter_mut() {
824            touch.phase = input::TouchPhase::Ended;
825        }
826
827        context
828            .input_events
829            .iter_mut()
830            .for_each(|arr| arr.push(MiniquadInputEvent::WindowMinimized));
831    }
832
833    fn quit_requested_event(&mut self) {
834        let context = get_context();
835        if context.prevent_quit_event {
836            miniquad::window::cancel_quit();
837            context.quit_requested = true;
838        }
839    }
840}
841
842pub mod conf {
843    #[derive(Default, Debug)]
844    pub struct UpdateTrigger {
845        pub key_down: bool,
846        pub mouse_down: bool,
847        pub mouse_up: bool,
848        pub mouse_motion: bool,
849        pub mouse_wheel: bool,
850        pub specific_key: Option<Vec<crate::KeyCode>>,
851        pub touch: bool,
852    }
853
854    #[derive(Debug)]
855    pub struct Conf {
856        pub miniquad_conf: miniquad::conf::Conf,
857        /// With miniquad_conf.platform.blocking_event_loop = true,
858        /// next_frame().await will never finish and will wait forever with
859        /// zero CPU usage.
860        /// update_on will tell macroquad when to proceed with the event loop.
861        pub update_on: Option<UpdateTrigger>,
862        pub default_filter_mode: crate::FilterMode,
863        /// Macroquad performs automatic and static batching for each
864        /// draw_* call. For each draw call, it pre-allocate a huge cpu/gpu
865        /// buffer to add vertices to. When it exceeds the buffer, it allocates the
866        /// new one, marking the new draw call.
867        ///
868        /// Some examples when altering those values migh be convinient:
869        /// - for huge 3d models that do not fit into a single draw call, increasing
870        ///     the buffer size might be easier than splitting the model.
871        /// - when each draw_* call got its own material,
872        ///     buffer size might be reduced to save some memory
873        pub draw_call_vertex_capacity: usize,
874        pub draw_call_index_capacity: usize,
875    }
876
877    impl Default for Conf {
878        fn default() -> Self {
879            Self {
880                miniquad_conf: miniquad::conf::Conf::default(),
881                update_on: Some(UpdateTrigger::default()),
882                default_filter_mode: crate::FilterMode::Linear,
883                draw_call_vertex_capacity: 10000,
884                draw_call_index_capacity: 5000,
885            }
886        }
887    }
888}
889
890impl From<miniquad::conf::Conf> for conf::Conf {
891    fn from(conf: miniquad::conf::Conf) -> conf::Conf {
892        conf::Conf {
893            miniquad_conf: conf,
894            update_on: None,
895            default_filter_mode: crate::FilterMode::Linear,
896            draw_call_vertex_capacity: 10000,
897            draw_call_index_capacity: 5000,
898        }
899    }
900}
901
902/// Not meant to be used directly, only from the macro.
903#[doc(hidden)]
904pub struct Window {}
905
906impl Window {
907    pub fn new(label: &str, future: impl Future<Output = ()> + 'static) {
908        Window::from_config(
909            conf::Conf {
910                miniquad_conf: miniquad::conf::Conf {
911                    window_title: label.to_string(),
912                    ..Default::default()
913                },
914                ..Default::default()
915            },
916            future,
917        );
918    }
919
920    pub fn from_config(config: impl Into<conf::Conf>, future: impl Future<Output = ()> + 'static) {
921        let conf::Conf {
922            miniquad_conf,
923            update_on,
924            default_filter_mode,
925            draw_call_vertex_capacity,
926            draw_call_index_capacity,
927        } = config.into();
928        miniquad::start(miniquad_conf, move || {
929            thread_assert::set_thread_id();
930            let context = Context::new(
931                update_on.unwrap_or_default(),
932                default_filter_mode,
933                draw_call_vertex_capacity,
934                draw_call_index_capacity,
935            );
936            unsafe { CONTEXT = Some(context) };
937
938            Box::new(Stage {
939                main_future: Box::pin(async {
940                    future.await;
941                    unsafe {
942                        if let Some(ctx) = CONTEXT.as_mut() {
943                            ctx.gl.reset();
944                        }
945                    }
946                }),
947            })
948        });
949    }
950}
951
952/// Information about a dropped file.
953pub struct DroppedFile {
954    pub path: Option<std::path::PathBuf>,
955    pub bytes: Option<Vec<u8>>,
956}