pixels_graphics_lib/integration/
pixels_winit.rs

1use crate::prelude::*;
2use crate::GraphicsError::LoadingWindowPref;
3use crate::{GraphicsError, MouseData, Options, System};
4use buffer_graphics_lib::Graphics;
5use pixels::PixelsBuilder;
6use pixels::{Pixels, SurfaceTexture};
7use simple_game_utils::prelude::Timing;
8use winit::dpi::LogicalSize;
9use winit::dpi::PhysicalSize;
10use winit::event::{Event, MouseButton, WindowEvent};
11use winit::event_loop::EventLoop;
12use winit::window::CursorGrabMode;
13use winit::window::WindowBuilder;
14use winit_input_helper::WinitInputHelper;
15
16fn create_window(
17    size: (u32, u32),
18    title: &str,
19    scale: WindowScaling,
20    event_loop: &EventLoop<()>,
21) -> Result<Window, GraphicsError> {
22    let window = WindowBuilder::new()
23        .with_visible(false)
24        .with_title(title)
25        .build(event_loop)
26        .map_err(|err| GraphicsError::WindowInit(format!("{err:?}")))?;
27    let factor = match scale {
28        WindowScaling::Native => window.scale_factor(),
29        WindowScaling::Double => window.scale_factor() + 2.0,
30        WindowScaling::Quad => window.scale_factor() + 4.0,
31    };
32
33    let px_size: PhysicalSize<u32> = LogicalSize::new(size.0, size.1).to_physical(factor);
34
35    window.set_min_inner_size(Some(px_size));
36    let _ = window.request_inner_size(px_size);
37    window.set_visible(true);
38
39    Ok(window)
40}
41
42/// Creates the window and pixels wrapper
43///
44/// The inner size mentioned in the arguments refers to the size of the area available to draw in, it doesn't include the window frame, etc
45///
46/// This uses logical pixels, where on a low DPI screen each library pixel is one display pixel but on higher DPI screens (and if
47/// `scale` != `None`) then a library pixel will be represented by multiple display pixels
48///
49/// # Arguments
50///
51/// * `canvas_size` - Inner width and height of window in logical pixels
52/// * `options` - Scaling, UPS, etc options
53/// * `title` - Title for window
54/// * `event_loop` - Provided by `EventLoop::new()`, this allows the window to receive events from the OS
55///
56/// # Example
57///
58/// This creates a 160x160 window:
59///
60/// `let (mut window, graphics) = setup(160, 160, "Example", true, &event_loop)?;`
61///
62/// # Returns
63///
64/// A result with a pair of Window and PixelsWrapper
65///
66/// # Errors
67///
68/// * `WindowInit` - If the window can not be created
69fn setup(
70    canvas_size: (u32, u32),
71    options: &Options,
72    title: &str,
73    event_loop: &EventLoop<()>,
74) -> Result<(Window, Pixels), GraphicsError> {
75    let win = create_window(canvas_size, title, options.scaling, event_loop)?;
76    let surface = SurfaceTexture::new(win.inner_size().width, win.inner_size().height, &win);
77    let pixels = PixelsBuilder::new(canvas_size.0, canvas_size.1, surface)
78        .enable_vsync(options.vsync)
79        .build()
80        .map_err(GraphicsError::PixelsInit)?;
81    Ok((win, pixels))
82}
83
84/// Create and run a loop using Pixels and Winit
85///
86/// If you want to use [Scene][scenes::Scene]s consider [run_scenes][scenes::run_scenes]
87///
88/// # Arguments
89/// * `width` - Width of the whole window canvas in pixels
90/// * `height` - Height of the whole window canvas in pixels
91/// * `title` - Window title
92/// * `system` - Your program
93/// * `options` - [Options] controls how fast the program can update, [UiElement] styling, etc
94///
95/// # Returns
96///
97/// Returns when the program is finished executing either due to it quitting or a fatal error occurring
98pub fn run(
99    width: usize,
100    height: usize,
101    title: &str,
102    mut system: Box<dyn System>,
103    options: Options,
104) -> Result<(), GraphicsError> {
105    let event_loop = EventLoop::new().expect("Failed to setup event loop");
106    let mut input = WinitInputHelper::new();
107    let (mut window, mut pixels) =
108        setup((width as u32, height as u32), &options, title, &event_loop)?;
109
110    if options.confine_cursor {
111        #[cfg(target_os = "macos")]
112        let _ = window.set_cursor_grab(CursorGrabMode::Locked);
113        #[cfg(not(target_os = "macos"))]
114        let _ = window.set_cursor_grab(CursorGrabMode::Confined);
115    }
116
117    if options.hide_cursor {
118        window.set_cursor_visible(false);
119    }
120
121    #[cfg(feature = "window_prefs")]
122    if let Some(mut prefs) = system.window_prefs() {
123        prefs.load().map_err(|e| LoadingWindowPref(e.to_string()))?;
124        prefs.restore(&mut window);
125    }
126
127    let mut timing = Timing::new(options.ups);
128    let mut mouse = MouseData::default();
129
130    event_loop
131        .run(move |event, target| {
132            timing.update();
133            match &event {
134                Event::LoopExiting => {
135                    system.on_window_closed();
136                    #[cfg(feature = "window_prefs")]
137                    if let Some(mut prefs) = system.window_prefs() {
138                        prefs.store(&window);
139                        //can't return from here so just print out error
140                        let _ = prefs
141                            .save()
142                            .map_err(|err| eprintln!("Unable to save prefs: {err:?}"));
143                    }
144                }
145                Event::WindowEvent { event, .. } => match event {
146                    WindowEvent::Occluded(hidden) => system.on_visibility_changed(!hidden),
147                    WindowEvent::Focused(focused) => system.on_focus_changed(*focused),
148                    WindowEvent::RedrawRequested => {
149                        let mut graphics = Graphics::new_u8_rgba(pixels.frame_mut(), width, height)
150                            .expect("Creating graphics wrapper");
151                        system.render(&mut graphics);
152                        timing.renders += 1;
153                        if pixels
154                            .render()
155                            .map_err(|e| eprintln!("pixels.render() failed: {e:?}"))
156                            .is_err()
157                        {
158                            system.on_window_closed();
159                            target.exit();
160                            return;
161                        }
162                    }
163                    _ => {}
164                },
165                _ => {}
166            }
167
168            timing.accumulated_time += timing.delta;
169            while timing.accumulated_time >= timing.fixed_time_step {
170                system.update(&timing, &window);
171                timing.accumulated_time -= timing.fixed_time_step;
172                timing.updates += 1;
173            }
174
175            if input.update(&event) {
176                if input.close_requested() || input.destroyed() {
177                    system.on_window_closed();
178                    target.exit();
179                    return;
180                }
181
182                if let Some(size) = input.window_resized() {
183                    pixels
184                        .resize_surface(size.width, size.height)
185                        .expect("Unable to resize buffer");
186                }
187
188                if let Some(mc) = input.cursor() {
189                    let (x, y) = pixels
190                        .window_pos_to_pixel(mc)
191                        .unwrap_or_else(|pos| pixels.clamp_pixel_pos(pos));
192                    mouse.xy = coord!(x, y);
193                    system.on_mouse_move(&mouse);
194                }
195
196                let mut held_buttons = vec![];
197                for button in system.keys_used() {
198                    if input.key_held(*button) {
199                        held_buttons.push(*button);
200                    }
201                }
202                if !held_buttons.is_empty() {
203                    system.on_key_down(held_buttons);
204                }
205
206                let mut released_buttons = vec![];
207                for button in system.keys_used() {
208                    if input.key_released(*button) {
209                        released_buttons.push(*button);
210                    }
211                }
212                if !released_buttons.is_empty() {
213                    system.on_key_up(released_buttons);
214                }
215
216                if input.mouse_pressed(MouseButton::Left) {
217                    mouse.add_down(mouse.xy, MouseButton::Left);
218                    system.on_mouse_down(&mouse, MouseButton::Left);
219                }
220                if input.mouse_pressed(MouseButton::Right) {
221                    mouse.add_down(mouse.xy, MouseButton::Right);
222                    system.on_mouse_down(&mouse, MouseButton::Right);
223                }
224                if input.mouse_pressed(MouseButton::Middle) {
225                    mouse.add_down(mouse.xy, MouseButton::Middle);
226                    system.on_mouse_down(&mouse, MouseButton::Middle);
227                }
228
229                if input.mouse_released(MouseButton::Left) {
230                    mouse.add_up(MouseButton::Left);
231                    system.on_mouse_up(&mouse, MouseButton::Left);
232                }
233                if input.mouse_released(MouseButton::Right) {
234                    mouse.add_up(MouseButton::Right);
235                    system.on_mouse_up(&mouse, MouseButton::Right);
236                }
237                if input.mouse_released(MouseButton::Middle) {
238                    mouse.add_up(MouseButton::Middle);
239                    system.on_mouse_up(&mouse, MouseButton::Middle);
240                }
241
242                let scroll = input.scroll_diff();
243                if scroll.0 != 0.0 || scroll.1 != 0.0 {
244                    system.on_scroll(&mouse, scroll.0.trunc() as isize, scroll.1.trunc() as isize);
245                }
246
247                window.request_redraw();
248            }
249
250            if system.should_exit() {
251                target.exit();
252            }
253
254            timing.update_fps();
255
256            timing.last = timing.now;
257        })
258        .expect("Error when executing event loop");
259
260    Ok(())
261}