1use astrelis_core::geometry::{LogicalPosition, LogicalSize, PhysicalPosition};
2use astrelis_core::profiling::profile_function;
3pub use winit::event::{ElementState, MouseButton, MouseScrollDelta, WindowEvent as WinitEvent};
4pub use winit::keyboard::*;
5
6use std::collections::VecDeque;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
10pub enum TouchPhase {
11 Started,
13 Moved,
15 Ended,
17 Cancelled,
19}
20
21impl From<winit::event::TouchPhase> for TouchPhase {
22 fn from(phase: winit::event::TouchPhase) -> Self {
23 match phase {
24 winit::event::TouchPhase::Started => TouchPhase::Started,
25 winit::event::TouchPhase::Moved => TouchPhase::Moved,
26 winit::event::TouchPhase::Ended => TouchPhase::Ended,
27 winit::event::TouchPhase::Cancelled => TouchPhase::Cancelled,
28 }
29 }
30}
31
32#[derive(Debug, Clone)]
34pub struct TouchEvent {
35 pub device_id: u64,
37 pub id: u64,
39 pub phase: TouchPhase,
41 pub position: LogicalPosition<f64>,
43 pub force: Option<f32>,
45}
46
47#[derive(Debug, Clone)]
49pub struct PinchGesture {
50 pub delta: f64,
52 pub phase: TouchPhase,
54}
55
56#[derive(Debug, Clone)]
58pub struct RotationGesture {
59 pub delta: f64,
61 pub phase: TouchPhase,
63}
64
65#[derive(Debug, Clone)]
67pub struct PanGesture {
68 pub delta: LogicalPosition<f64>,
70 pub phase: TouchPhase,
72}
73
74pub struct EventQueue {
76 pending: VecDeque<Event>,
78
79 priority: VecDeque<Event>,
81
82 latest_mouse_pos: Option<LogicalPosition<f64>>,
84 latest_scale_factor: Option<f64>,
85
86 stats: EventStats,
88}
89
90impl EventQueue {
91 pub fn new() -> Self {
92 Self {
93 pending: VecDeque::with_capacity(64),
94 priority: VecDeque::with_capacity(8),
95 latest_mouse_pos: None,
96 latest_scale_factor: None,
97 stats: EventStats::default(),
98 }
99 }
100
101 pub fn push(&mut self, event: Event) {
103 self.stats.events_received += 1;
104
105 match event {
106 Event::CloseRequested
108 | Event::WindowResized(_)
109 | Event::Focused(_)
110 | Event::ThemeChanged(_) => {
111 self.priority.push_back(event);
112 }
113
114 Event::MouseMoved(pos) => {
116 self.latest_mouse_pos = Some(pos);
117 }
118 Event::ScaleFactorChanged(scale) => {
119 self.latest_scale_factor = Some(scale);
120 }
121
122 _ => {
124 self.pending.push_back(event);
125 }
126 }
127 }
128
129 pub fn drain(&mut self) -> EventBatch {
131 let mut events = Vec::with_capacity(self.priority.len() + self.pending.len() + 2);
132
133 events.extend(self.priority.drain(..));
135
136 if let Some(pos) = self.latest_mouse_pos.take() {
138 events.push(Event::MouseMoved(pos));
139 }
140 if let Some(scale) = self.latest_scale_factor.take() {
141 events.push(Event::ScaleFactorChanged(scale));
142 }
143
144 events.extend(self.pending.drain(..));
146
147 self.stats.events_processed += events.len();
148 self.stats.events_dropped = self.stats.events_received - self.stats.events_processed;
149
150 EventBatch { events }
151 }
152
153 pub fn stats(&self) -> &EventStats {
154 &self.stats
155 }
156
157 pub fn reset_stats(&mut self) {
158 self.stats = EventStats::default();
159 }
160}
161
162impl Default for EventQueue {
163 fn default() -> Self {
164 Self::new()
165 }
166}
167
168pub struct EventBatch {
169 events: Vec<Event>,
170}
171
172impl EventBatch {
173 pub fn iter(&self) -> impl Iterator<Item = &Event> {
174 self.events.iter()
175 }
176
177 pub fn len(&self) -> usize {
178 self.events.len()
179 }
180
181 pub fn is_empty(&self) -> bool {
182 self.events.is_empty()
183 }
184
185 pub fn dispatch<H>(&mut self, mut handler: H)
186 where
187 H: FnMut(&Event) -> HandleStatus,
188 {
189 profile_function!();
190 self.events.retain(|event| {
191 let status = handler(event);
192 !status.is_consumed()
193 });
194 }
195}
196
197#[derive(Default, Debug, Clone)]
198pub struct EventStats {
199 pub events_received: usize,
200 pub events_processed: usize,
201 pub events_dropped: usize,
202}
203
204#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
206pub enum SystemTheme {
207 Light,
209 Dark,
211}
212
213#[derive(Debug, Clone)]
214pub enum Event {
215 WindowMoved(PhysicalPosition<i32>),
217 WindowResized(LogicalSize<u32>),
219 ScaleFactorChanged(f64),
221 Focused(bool),
223 CloseRequested,
225 ThemeChanged(SystemTheme),
227 MouseButtonDown(MouseButton),
229 MouseButtonUp(MouseButton),
231 MouseScrolled(MouseScrollDelta),
233 MouseMoved(LogicalPosition<f64>),
235 MouseEntered,
237 MouseLeft,
239 KeyInput(KeyEvent),
241 Touch(TouchEvent),
243 PinchGesture(PinchGesture),
245 RotationGesture(RotationGesture),
247 PanGesture(PanGesture),
249}
250
251#[derive(Debug, Clone)]
252pub struct KeyEvent {
253 pub physical_key: PhysicalKey,
254 pub logical_key: Key,
255 pub text: Option<SmolStr>,
256 pub location: KeyLocation,
257 pub state: ElementState,
258 pub repeat: bool,
259 pub is_synthetic: bool,
260}
261
262bitflags::bitflags! {
263 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
264 pub struct HandleStatus: u8 {
265 const HANDLED = 0b00000001;
266 const CONSUMED = 0b00000010;
267 }
268}
269
270impl HandleStatus {
271 pub const fn is_consumed(&self) -> bool {
272 self.contains(Self::CONSUMED)
273 }
274
275 pub const fn is_handled(&self) -> bool {
276 self.contains(Self::HANDLED)
277 }
278
279 pub const fn consumed() -> Self {
280 Self::from_bits_truncate(Self::HANDLED.bits() | Self::CONSUMED.bits())
281 }
282
283 pub const fn handled() -> Self {
284 Self::from_bits_truncate(Self::HANDLED.bits())
285 }
286
287 pub const fn ignored() -> Self {
288 Self::empty()
289 }
290}
291
292impl Event {
293 pub(crate) fn from_winit(event: winit::event::WindowEvent, scale_factor: f64) -> Option<Self> {
294 match event {
295 WinitEvent::Moved(pos) => Some(Event::WindowMoved(pos.into())),
296 WinitEvent::Resized(size) => Some(Event::WindowResized(LogicalSize::new(
297 (size.width as f64 / scale_factor) as u32,
298 (size.height as f64 / scale_factor) as u32,
299 ))),
300 WinitEvent::ScaleFactorChanged {
301 scale_factor,
302 inner_size_writer: _,
303 } => Some(Event::ScaleFactorChanged(scale_factor)),
304 WinitEvent::Focused(focus) => Some(Event::Focused(focus)),
305 WinitEvent::CloseRequested => Some(Event::CloseRequested),
306 WinitEvent::MouseInput {
307 device_id: _,
308 state,
309 button,
310 } => match state {
311 ElementState::Pressed => Some(Event::MouseButtonDown(button)),
312 ElementState::Released => Some(Event::MouseButtonUp(button)),
313 },
314 WinitEvent::MouseWheel {
315 device_id: _,
316 delta,
317 phase: _,
318 } => Some(Event::MouseScrolled(delta)),
319 WinitEvent::CursorMoved {
320 device_id: _,
321 position,
322 } => Some(Event::MouseMoved(LogicalPosition::new(
323 position.x / scale_factor,
324 position.y / scale_factor,
325 ))),
326 WinitEvent::CursorEntered { device_id: _ } => Some(Event::MouseEntered),
327 WinitEvent::CursorLeft { device_id: _ } => Some(Event::MouseLeft),
328 WinitEvent::KeyboardInput {
329 device_id: _,
330 event,
331 is_synthetic,
332 } => Some(Event::KeyInput(KeyEvent {
333 physical_key: event.physical_key,
334 logical_key: event.logical_key,
335 location: event.location,
336 repeat: event.repeat,
337 text: event.text,
338 state: event.state,
339
340 is_synthetic,
341 })),
342 WinitEvent::Touch(touch) => {
343 let force = match touch.force {
344 Some(winit::event::Force::Normalized(f)) => Some(f as f32),
345 Some(winit::event::Force::Calibrated {
346 force,
347 max_possible_force,
348 ..
349 }) => Some((force / max_possible_force) as f32),
350 None => None,
351 };
352 let device_id = {
354 use std::hash::{Hash, Hasher};
355 let mut hasher = std::collections::hash_map::DefaultHasher::new();
356 touch.device_id.hash(&mut hasher);
357 hasher.finish()
358 };
359 Some(Event::Touch(TouchEvent {
360 device_id,
361 id: touch.id,
362 phase: touch.phase.into(),
363 position: LogicalPosition::new(
364 touch.location.x / scale_factor,
365 touch.location.y / scale_factor,
366 ),
367 force,
368 }))
369 }
370 WinitEvent::PinchGesture { delta, phase, .. } => {
371 Some(Event::PinchGesture(PinchGesture {
372 delta,
373 phase: phase.into(),
374 }))
375 }
376 WinitEvent::RotationGesture { delta, phase, .. } => {
377 Some(Event::RotationGesture(RotationGesture {
378 delta: delta as f64,
379 phase: phase.into(),
380 }))
381 }
382 WinitEvent::PanGesture { delta, phase, .. } => Some(Event::PanGesture(PanGesture {
383 delta: LogicalPosition::new(
384 delta.x as f64 / scale_factor,
385 delta.y as f64 / scale_factor,
386 ),
387 phase: phase.into(),
388 })),
389 WinitEvent::ThemeChanged(theme) => Some(Event::ThemeChanged(match theme {
390 winit::window::Theme::Light => SystemTheme::Light,
391 winit::window::Theme::Dark => SystemTheme::Dark,
392 })),
393 WinitEvent::TouchpadPressure { .. } => None,
395 WinitEvent::DoubleTapGesture { .. } => None,
397 unknown => {
398 tracing::warn!("unhandled window event: {:?}", unknown);
399 None
400 }
401 }
402 }
403}