swamp_window_runner/
lib.rs

1/*
2 * Copyright (c) Peter Bjorklund. All rights reserved. https://github.com/swamp/swamp
3 * Licensed under the MIT License. See LICENSE in the project root for license information.
4 */
5mod convert;
6
7use crate::convert::{
8    from_element_state, from_mouse_scroll_delta, from_touch_phase, try_from_key_code,
9    try_from_mouse_button,
10};
11use int_math::{UVec2, Vec2};
12use std::sync::{Arc, Mutex};
13use swamp_app::prelude::Resource;
14use swamp_app::prelude::{App, AppReturnValue, ApplicationExit};
15use swamp_basic_input::prelude::*;
16use swamp_screen::WindowMessage;
17use swamp_wgpu_window::{annoying_async_device_creation, WgpuWindow};
18use swamp_window::AppHandler;
19use tracing::{debug, error};
20use winit::dpi;
21use winit::keyboard::PhysicalKey;
22
23pub struct WindowState {
24    pub app: Arc<Mutex<App>>,
25    pub wgpu_window: Option<WgpuWindow>,
26    requested_surface_size: UVec2,
27    minimal_surface_size: UVec2,
28    physical_surface_size: dpi::PhysicalSize<u32>,
29}
30
31#[derive(Debug, Resource)]
32pub struct WindowHandle {
33    pub window: Arc<winit::window::Window>,
34}
35
36impl AppHandler for WindowState {
37    fn min_size(&self) -> (u16, u16) {
38        (self.minimal_surface_size.x, self.minimal_surface_size.y)
39    }
40
41    fn start_size(&self) -> (u16, u16) {
42        (self.requested_surface_size.x, self.requested_surface_size.y)
43    }
44
45    fn cursor_should_be_visible(&self) -> bool {
46        true
47    }
48
49    fn redraw(&mut self) -> bool {
50        let mut app = self.app.lock().unwrap();
51        app.update();
52        !app.has_resource::<ApplicationExit>()
53    }
54
55    fn got_focus(&mut self) {}
56
57    fn lost_focus(&mut self) {}
58
59    fn window_created(&mut self, window: Arc<winit::window::Window>) {
60        debug!("received callback for: window created");
61        let app = Arc::clone(&self.app);
62        future_runner::run_future(async move {
63            let async_device_info = annoying_async_device_creation(window)
64                .await
65                .expect("couldn't get device info");
66            app.lock().unwrap().insert_resource(async_device_info);
67        });
68        self.app
69            .lock()
70            .unwrap()
71            .send(WindowMessage::WindowCreated());
72    }
73
74    fn resized(&mut self, size: dpi::PhysicalSize<u32>) {
75        self.physical_surface_size = size;
76        self.app
77            .lock()
78            .unwrap()
79            .send(WindowMessage::Resized(UVec2::new(
80                size.width as u16,
81                size.height as u16,
82            )));
83    }
84
85    fn keyboard_input(
86        &mut self,
87        element_state: winit::event::ElementState,
88        physical_key: winit::keyboard::PhysicalKey,
89    ) {
90        if let PhysicalKey::Code(key_code) = physical_key {
91            if let Ok(converted_key) = try_from_key_code(key_code) {
92                self.app.lock().unwrap().send(InputMessage::KeyboardInput(
93                    from_element_state(element_state),
94                    converted_key,
95                ));
96            }
97        }
98    }
99
100    fn cursor_entered(&mut self) {}
101
102    fn cursor_left(&mut self) {}
103
104    /// opinionated, we want origin (0,0) in the lower left corner of the window
105    fn cursor_moved(&mut self, physical_position: dpi::PhysicalPosition<u32>) {
106        if physical_position.x >= self.physical_surface_size.width {
107            return;
108        }
109        if physical_position.y >= self.physical_surface_size.height {
110            return;
111        }
112
113        if self.physical_surface_size.height == 0 {
114            return;
115        }
116
117        if self
118            .physical_surface_size
119            .height
120            .checked_sub(physical_position.y)
121            .is_none()
122        {
123            error!(
124                "problem! {} {:?}",
125                self.physical_surface_size.height, physical_position.y
126            );
127        }
128
129        self.app
130            .lock()
131            .unwrap()
132            .send(WindowMessage::CursorMoved(UVec2::new(
133                physical_position.x as u16,
134                ((self.physical_surface_size.height - 1) - physical_position.y) as u16,
135            )));
136    }
137
138    fn mouse_input(
139        &mut self,
140        element_state: winit::event::ElementState,
141        button: winit::event::MouseButton,
142    ) {
143        if let Ok(converted_button) = try_from_mouse_button(button) {
144            self.app.lock().unwrap().send(InputMessage::MouseInput(
145                from_element_state(element_state),
146                converted_button,
147            ));
148        }
149    }
150
151    fn mouse_wheel(
152        &mut self,
153        delta: winit::event::MouseScrollDelta,
154        touch_phase: winit::event::TouchPhase,
155    ) {
156        self.app.lock().unwrap().send(InputMessage::MouseWheel(
157            from_mouse_scroll_delta(delta),
158            from_touch_phase(touch_phase),
159        ));
160    }
161
162    fn pinch_gesture(&mut self, delta: f64, touch_phase: winit::event::TouchPhase) {
163        let virtual_wheel_y = (delta * 50.0) as i16;
164        let mouse_delta = MouseScrollDelta::LineDelta(Vec2::new(0, virtual_wheel_y));
165        self.app.lock().unwrap().send(InputMessage::MouseWheel(
166            mouse_delta,
167            from_touch_phase(touch_phase),
168        ));
169    }
170
171    fn mouse_motion(&mut self, _delta: (f64, f64)) {}
172
173    fn touch(&mut self, _touch: winit::event::Touch) {}
174
175    fn scale_factor_changed(
176        &mut self,
177        _scale_factor: f64,
178        _inner_size_writer: winit::event::InnerSizeWriter,
179    ) {
180    }
181}
182
183pub fn runner(mut app: App) -> AppReturnValue {
184    console_error_panic_hook::set_once();
185    debug!("window-runner started!");
186
187    let requested_surface_size: UVec2;
188    let minimal_surface_size: UVec2;
189    let title: String;
190
191    {
192        let window_settings = app.resource::<swamp_screen::Window>();
193
194        title = window_settings.title.clone();
195        requested_surface_size = window_settings.requested_surface_size;
196        minimal_surface_size = window_settings.minimal_surface_size;
197
198        app.create_message_type::<WindowMessage>();
199        app.create_message_type::<InputMessage>();
200    }
201
202    #[allow(clippy::arc_with_non_send_sync)]
203    let arc_app = Arc::new(Mutex::new(app));
204
205    let mut state = WindowState {
206        app: arc_app,
207        wgpu_window: None,
208        requested_surface_size,
209        minimal_surface_size,
210        physical_surface_size: dpi::PhysicalSize::new(
211            requested_surface_size.x as u32,
212            requested_surface_size.y as u32,
213        ),
214    };
215
216    swamp_window::WindowRunner::run_app(&mut state, title.as_str()).expect("run_app failed");
217
218    debug!("we returned, that is not guaranteed for all platforms");
219
220    AppReturnValue::Value(0)
221}