pix_win_loop/
lib.rs

1#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))]
2#![warn(missing_docs)]
3
4pub use pixels;
5pub use pixels::Pixels;
6pub use win_loop::anyhow::{Error, Result};
7pub use win_loop::winit::{
8    self,
9    dpi::PhysicalSize,
10    keyboard::{KeyCode, NamedKey},
11    window::WindowBuilder,
12};
13pub use win_loop::{Context, Duration, Input, InputState};
14
15use pixels::SurfaceTexture;
16use win_loop::winit::{
17    event::{Event, WindowEvent},
18    event_loop::EventLoop,
19    window::WindowId,
20};
21
22/// Application trait.
23pub trait App {
24    /// Application update.
25    /// Rate of updates can be set using [`Context`].
26    fn update(&mut self, ctx: &mut Context) -> Result<()>;
27
28    /// Application render.
29    /// Will be called once every frame.
30    fn render(&mut self, pix: &mut Pixels, blending_factor: f64) -> Result<()>;
31
32    /// Custom event handler if needed.
33    #[inline]
34    fn handle(&mut self, _event: &Event<()>) -> Result<()> {
35        Ok(())
36    }
37}
38
39/// Start the application. Not available on web targets (use [`start_async()`]).
40#[cfg(not(target_arch = "wasm32"))]
41#[inline]
42pub fn start(
43    window_builder: WindowBuilder,
44    app: impl App + 'static,
45    pixel_buffer_size: PhysicalSize<u32>,
46    target_frame_time: Duration,
47    max_frame_time: Duration,
48) -> Result<()> {
49    use pollster::FutureExt;
50
51    start_async(
52        window_builder,
53        app,
54        pixel_buffer_size,
55        target_frame_time,
56        max_frame_time,
57    )
58    .block_on()
59}
60
61/// Start the application asynchronously.
62#[inline]
63pub async fn start_async(
64    #[allow(unused_mut)] mut window_builder: WindowBuilder,
65    app: impl App + 'static,
66    pixel_buffer_size: PhysicalSize<u32>,
67    target_frame_time: Duration,
68    max_frame_time: Duration,
69) -> Result<()> {
70    let event_loop = EventLoop::new()?;
71
72    #[cfg(target_arch = "wasm32")]
73    {
74        use winit::platform::web::WindowBuilderExtWebSys;
75
76        window_builder = window_builder.with_append(true);
77    }
78
79    let window = window_builder.build(&event_loop)?;
80    let window_size = window.inner_size();
81
82    let surface_texture = SurfaceTexture::new(window_size.width, window_size.height, &window);
83    let pixels = Pixels::new_async(
84        pixel_buffer_size.width,
85        pixel_buffer_size.height,
86        surface_texture,
87    )
88    .await?;
89
90    let app = WinLoopApp {
91        app,
92        win_id: window.id(),
93        resize_order: None,
94    };
95
96    win_loop::start(
97        event_loop,
98        window,
99        app,
100        pixels,
101        target_frame_time,
102        max_frame_time,
103    )
104}
105
106struct WinLoopApp<A: App> {
107    app: A,
108    win_id: WindowId,
109    resize_order: Option<PhysicalSize<u32>>,
110}
111
112impl<A> win_loop::App for WinLoopApp<A>
113where
114    A: App,
115{
116    type RenderContext = Pixels;
117
118    #[inline]
119    fn update(&mut self, ctx: &mut Context) -> Result<()> {
120        self.app.update(ctx)
121    }
122
123    #[inline]
124    fn render(&mut self, ctx: &mut Self::RenderContext, blending_factor: f64) -> Result<()> {
125        if let Some(new_size) = self.resize_order {
126            ctx.resize_surface(new_size.width, new_size.height)?;
127
128            self.resize_order = None;
129        }
130
131        self.app.render(ctx, blending_factor)
132    }
133
134    #[inline]
135    fn handle(&mut self, event: &Event<()>) -> Result<()> {
136        match event {
137            Event::WindowEvent {
138                window_id,
139                event: WindowEvent::Resized(new_size),
140            } if *window_id == self.win_id => {
141                self.resize_order = Some(*new_size);
142            }
143            _ => {}
144        }
145
146        self.app.handle(event)
147    }
148}