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