pebble/graphics/
window.rs1use std::sync::{Arc, Mutex};
2
3use winit::{
4 application::ApplicationHandler,
5 event::{DeviceEvent, DeviceId, WindowEvent},
6 event_loop::{ActiveEventLoop, ControlFlow, EventLoop},
7 window::{Fullscreen, Window as OsWindow, WindowId},
8};
9use winit_input_helper::WinitInputHelper;
10
11use crate::{
12 ecs::plugin::Plugin,
13 graphics::types::{CursorGrabMode, CursorIcon, KeyCode, MouseButton},
14};
15
16pub struct WindowConfig {
18 pub title: String,
19 pub width: u32,
20 pub height: u32,
21}
22
23impl Default for WindowConfig {
24 fn default() -> Self {
25 Self {
26 title: "Pebble".to_string(),
27 width: 1280,
28 height: 720,
29 }
30 }
31}
32
33#[derive(Clone)]
36pub struct Window(Arc<OsWindow>);
37
38impl Window {
39 fn new(handle: Arc<OsWindow>) -> Self {
40 Self(handle)
41 }
42
43 pub(crate) fn raw(&self) -> Arc<OsWindow> {
44 self.0.clone()
45 }
46
47 pub fn set_title(&self, title: &str) {
48 self.0.set_title(title);
49 }
50
51 pub fn inner_size(&self) -> (u32, u32) {
52 let size = self.0.inner_size();
53 (size.width, size.height)
54 }
55
56 pub fn set_inner_size(&self, width: u32, height: u32) {
57 let _ = self
58 .0
59 .request_inner_size(winit::dpi::PhysicalSize::new(width, height));
60 }
61
62 pub fn set_resizable(&self, resizable: bool) {
63 self.0.set_resizable(resizable);
64 }
65
66 pub fn set_visible(&self, visible: bool) {
67 self.0.set_visible(visible);
68 }
69
70 pub fn set_minimized(&self, minimized: bool) {
71 self.0.set_minimized(minimized);
72 }
73
74 pub fn set_maximized(&self, maximized: bool) {
75 self.0.set_maximized(maximized);
76 }
77
78 pub fn set_decorations(&self, decorations: bool) {
79 self.0.set_decorations(decorations);
80 }
81
82 pub fn focus(&self) {
83 self.0.focus_window();
84 }
85
86 pub fn set_fullscreen(&self, fullscreen: bool) {
87 self.0
88 .set_fullscreen(fullscreen.then_some(Fullscreen::Borderless(None)));
89 }
90
91 pub fn is_fullscreen(&self) -> bool {
92 self.0.fullscreen().is_some()
93 }
94
95 pub fn set_cursor_icon(&self, icon: CursorIcon) {
96 self.0.set_cursor(winit::window::CursorIcon::from(icon));
97 }
98
99 pub fn set_cursor_visible(&self, visible: bool) {
100 self.0.set_cursor_visible(visible);
101 }
102
103 pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> bool {
104 self.0.set_cursor_grab(mode.into()).is_ok()
105 }
106
107 pub fn request_redraw(&self) {
108 self.0.request_redraw();
109 }
110}
111
112struct InputState {
113 helper: WinitInputHelper,
114}
115
116#[derive(Clone)]
121pub struct Input(Arc<Mutex<InputState>>);
122
123impl Input {
124 fn new() -> Self {
125 Self(Arc::new(Mutex::new(InputState {
126 helper: WinitInputHelper::new(),
127 })))
128 }
129
130 fn step(&self) {
131 self.0.lock().unwrap().helper.step();
132 }
133
134 fn process_window_event(&self, event: &WindowEvent) {
135 self.0.lock().unwrap().helper.process_window_event(event);
136 }
137
138 fn process_device_event(&self, event: &DeviceEvent) {
139 self.0.lock().unwrap().helper.process_device_event(event);
140 }
141
142 fn end_step(&self) {
143 self.0.lock().unwrap().helper.end_step();
144 }
145
146 pub fn key_pressed(&self, key: KeyCode) -> bool {
147 self.0.lock().unwrap().helper.key_pressed(key.into())
148 }
149
150 pub fn key_released(&self, key: KeyCode) -> bool {
151 self.0.lock().unwrap().helper.key_released(key.into())
152 }
153
154 pub fn key_held(&self, key: KeyCode) -> bool {
155 self.0.lock().unwrap().helper.key_held(key.into())
156 }
157
158 pub fn mouse_pressed(&self, button: MouseButton) -> bool {
159 self.0.lock().unwrap().helper.mouse_pressed(button.into())
160 }
161
162 pub fn mouse_released(&self, button: MouseButton) -> bool {
163 self.0.lock().unwrap().helper.mouse_released(button.into())
164 }
165
166 pub fn mouse_held(&self, button: MouseButton) -> bool {
167 self.0.lock().unwrap().helper.mouse_held(button.into())
168 }
169
170 pub fn cursor(&self) -> Option<(f32, f32)> {
172 self.0.lock().unwrap().helper.cursor()
173 }
174
175 pub fn cursor_diff(&self) -> (f32, f32) {
177 self.0.lock().unwrap().helper.cursor_diff()
178 }
179
180 pub fn mouse_diff(&self) -> (f32, f32) {
183 self.0.lock().unwrap().helper.mouse_diff()
184 }
185
186 pub fn scroll_diff(&self) -> (f32, f32) {
187 self.0.lock().unwrap().helper.scroll_diff()
188 }
189
190 pub fn close_requested(&self) -> bool {
193 self.0.lock().unwrap().helper.close_requested()
194 }
195
196 pub fn resolution(&self) -> Option<(u32, u32)> {
198 self.0.lock().unwrap().helper.resolution()
199 }
200}
201
202pub struct WindowPlugin {
206 config: WindowConfig,
207}
208
209impl WindowPlugin {
210 pub fn new(config: WindowConfig) -> Self {
211 Self { config }
212 }
213}
214
215impl Default for WindowPlugin {
216 fn default() -> Self {
217 Self::new(WindowConfig::default())
218 }
219}
220
221struct WinitApp {
238 config: Option<WindowConfig>,
239 app: crate::app::App,
240 input: Input,
241 window: Option<Arc<OsWindow>>,
242 pending_window_events: Vec<WindowEvent>,
243 pending_device_events: Vec<DeviceEvent>,
244}
245
246impl ApplicationHandler for WinitApp {
247 fn resumed(&mut self, event_loop: &ActiveEventLoop) {
248 let Some(config) = self.config.take() else {
249 return;
250 };
251
252 #[allow(unused_mut)]
253 let mut attrs = OsWindow::default_attributes()
254 .with_title(config.title)
255 .with_inner_size(winit::dpi::PhysicalSize::new(config.width, config.height));
256
257 #[cfg(target_arch = "wasm32")]
261 {
262 use winit::platform::web::WindowAttributesExtWebSys;
263 attrs = attrs.with_append(true);
264 }
265
266 let os_window = Arc::new(event_loop.create_window(attrs).unwrap());
267 self.window = Some(os_window.clone());
268 let window = Window::new(os_window);
269
270 self.app = std::mem::take(&mut self.app)
271 .insert_resource(window)
272 .insert_resource(self.input.clone());
273
274 self.window.as_ref().unwrap().request_redraw();
277 }
278
279 fn window_event(&mut self, event_loop: &ActiveEventLoop, _window_id: WindowId, event: WindowEvent) {
280 match event {
281 WindowEvent::CloseRequested => {
282 event_loop.exit();
283 }
284 WindowEvent::RedrawRequested => {
285 self.input.step();
286 for pending in self.pending_window_events.drain(..) {
287 self.input.process_window_event(&pending);
288 }
289 for pending in self.pending_device_events.drain(..) {
290 self.input.process_device_event(&pending);
291 }
292 self.input.end_step();
293
294 self.app.update();
295 if self.app.should_exit() {
296 event_loop.exit();
297 return;
298 }
299
300 self.window.as_ref().unwrap().request_redraw();
303 }
304 other => self.pending_window_events.push(other),
305 }
306 }
307
308 fn device_event(&mut self, _event_loop: &ActiveEventLoop, _device_id: DeviceId, event: DeviceEvent) {
309 self.pending_device_events.push(event);
310 }
311}
312
313impl Plugin for WindowPlugin {
314 fn build(self, app: crate::app::App) -> crate::app::App {
315 let event_loop = EventLoop::new().unwrap();
316 event_loop.set_control_flow(ControlFlow::Wait);
320
321 app.set_runner(move |app| {
322 let handler = WinitApp {
323 config: Some(self.config),
324 app,
325 input: Input::new(),
326 window: None,
327 pending_window_events: Vec::new(),
328 pending_device_events: Vec::new(),
329 };
330
331 #[cfg(not(target_arch = "wasm32"))]
337 {
338 let mut handler = handler;
339 event_loop.run_app(&mut handler).unwrap();
340 }
341
342 #[cfg(target_arch = "wasm32")]
343 {
344 use winit::platform::web::EventLoopExtWebSys;
345 event_loop.spawn_app(handler);
346 }
347 })
348 }
349}