pebble/graphics/
window.rs1use std::sync::{Arc, Mutex};
2
3use winit::{
4 event::{Event, WindowEvent},
5 event_loop::{ControlFlow, EventLoop},
6 window::{Fullscreen, Window as OsWindow, WindowBuilder},
7};
8use winit_input_helper::WinitInputHelper;
9
10use crate::{
11 ecs::plugin::Plugin,
12 graphics::types::{CursorGrabMode, CursorIcon, KeyCode, MouseButton},
13};
14
15pub struct WindowConfig {
17 pub title: String,
18 pub width: u32,
19 pub height: u32,
20}
21
22impl Default for WindowConfig {
23 fn default() -> Self {
24 Self {
25 title: "Pebble".to_string(),
26 width: 1280,
27 height: 720,
28 }
29 }
30}
31
32#[derive(Clone)]
35pub struct Window(Arc<OsWindow>);
36
37impl Window {
38 fn new(handle: Arc<OsWindow>) -> Self {
39 Self(handle)
40 }
41
42 pub(crate) fn raw(&self) -> Arc<OsWindow> {
43 self.0.clone()
44 }
45
46 pub fn set_title(&self, title: &str) {
47 self.0.set_title(title);
48 }
49
50 pub fn inner_size(&self) -> (u32, u32) {
51 let size = self.0.inner_size();
52 (size.width, size.height)
53 }
54
55 pub fn set_inner_size(&self, width: u32, height: u32) {
56 let _ = self.0.request_inner_size(winit::dpi::PhysicalSize::new(width, height));
57 }
58
59 pub fn set_resizable(&self, resizable: bool) {
60 self.0.set_resizable(resizable);
61 }
62
63 pub fn set_visible(&self, visible: bool) {
64 self.0.set_visible(visible);
65 }
66
67 pub fn set_minimized(&self, minimized: bool) {
68 self.0.set_minimized(minimized);
69 }
70
71 pub fn set_maximized(&self, maximized: bool) {
72 self.0.set_maximized(maximized);
73 }
74
75 pub fn set_decorations(&self, decorations: bool) {
76 self.0.set_decorations(decorations);
77 }
78
79 pub fn focus(&self) {
80 self.0.focus_window();
81 }
82
83 pub fn set_fullscreen(&self, fullscreen: bool) {
84 self.0.set_fullscreen(fullscreen.then_some(Fullscreen::Borderless(None)));
85 }
86
87 pub fn is_fullscreen(&self) -> bool {
88 self.0.fullscreen().is_some()
89 }
90
91 pub fn set_cursor_icon(&self, icon: CursorIcon) {
92 self.0.set_cursor_icon(icon.into());
93 }
94
95 pub fn set_cursor_visible(&self, visible: bool) {
96 self.0.set_cursor_visible(visible);
97 }
98
99 pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> bool {
100 self.0.set_cursor_grab(mode.into()).is_ok()
101 }
102
103 pub fn request_redraw(&self) {
104 self.0.request_redraw();
105 }
106}
107
108struct InputState {
109 helper: WinitInputHelper,
110}
111
112#[derive(Clone)]
117pub struct Input(Arc<Mutex<InputState>>);
118
119impl Input {
120 fn new() -> Self {
121 Self(Arc::new(Mutex::new(InputState {
122 helper: WinitInputHelper::new(),
123 })))
124 }
125
126 fn update(&self, event: &Event<()>) -> bool {
127 self.0.lock().unwrap().helper.update(event)
128 }
129
130 pub fn key_pressed(&self, key: KeyCode) -> bool {
131 self.0.lock().unwrap().helper.key_pressed(key.into())
132 }
133
134 pub fn key_released(&self, key: KeyCode) -> bool {
135 self.0.lock().unwrap().helper.key_released(key.into())
136 }
137
138 pub fn key_held(&self, key: KeyCode) -> bool {
139 self.0.lock().unwrap().helper.key_held(key.into())
140 }
141
142 pub fn mouse_pressed(&self, button: MouseButton) -> bool {
143 self.0.lock().unwrap().helper.mouse_pressed(button.into())
144 }
145
146 pub fn mouse_released(&self, button: MouseButton) -> bool {
147 self.0.lock().unwrap().helper.mouse_released(button.into())
148 }
149
150 pub fn mouse_held(&self, button: MouseButton) -> bool {
151 self.0.lock().unwrap().helper.mouse_held(button.into())
152 }
153
154 pub fn cursor(&self) -> Option<(f32, f32)> {
156 self.0.lock().unwrap().helper.cursor()
157 }
158
159 pub fn cursor_diff(&self) -> (f32, f32) {
161 self.0.lock().unwrap().helper.cursor_diff()
162 }
163
164 pub fn mouse_diff(&self) -> (f32, f32) {
167 self.0.lock().unwrap().helper.mouse_diff()
168 }
169
170 pub fn scroll_diff(&self) -> (f32, f32) {
171 self.0.lock().unwrap().helper.scroll_diff()
172 }
173
174 pub fn close_requested(&self) -> bool {
177 self.0.lock().unwrap().helper.close_requested()
178 }
179
180 pub fn resolution(&self) -> Option<(u32, u32)> {
182 self.0.lock().unwrap().helper.resolution()
183 }
184}
185
186pub struct WindowPlugin {
190 config: WindowConfig,
191}
192
193impl WindowPlugin {
194 pub fn new(config: WindowConfig) -> Self {
195 Self { config }
196 }
197}
198
199impl Default for WindowPlugin {
200 fn default() -> Self {
201 Self::new(WindowConfig::default())
202 }
203}
204
205impl Plugin for WindowPlugin {
206 fn build(self, app: crate::app::App) -> crate::app::App {
207 let event_loop = EventLoop::new().unwrap();
208 event_loop.set_control_flow(ControlFlow::Poll);
209
210 #[allow(unused_mut)]
211 let mut builder = WindowBuilder::new()
212 .with_title(self.config.title)
213 .with_inner_size(winit::dpi::PhysicalSize::new(self.config.width, self.config.height));
214
215 #[cfg(target_arch = "wasm32")]
219 {
220 use winit::platform::web::WindowBuilderExtWebSys;
221 builder = builder.with_append(true);
222 }
223
224 let os_window = Arc::new(builder.build(&event_loop).unwrap());
225
226 let window = Window::new(os_window);
227 let input = Input::new();
228
229 app.insert_resource(window)
230 .insert_resource(input.clone())
231 .set_runner(move |mut app| {
232 let handler = move |event, elwt: &winit::event_loop::EventLoopWindowTarget<()>| {
233 let stepped = input.update(&event);
234
235 if let Event::WindowEvent {
236 event: WindowEvent::CloseRequested,
237 ..
238 } = &event
239 {
240 elwt.exit();
241 return;
242 }
243
244 if stepped {
245 app.update();
246 if app.should_exit() {
247 elwt.exit();
248 }
249 }
250 };
251
252 #[cfg(not(target_arch = "wasm32"))]
258 event_loop.run(handler).unwrap();
259
260 #[cfg(target_arch = "wasm32")]
261 {
262 use winit::platform::web::EventLoopExtWebSys;
263 event_loop.spawn(handler);
264 }
265 })
266 }
267}