use std::collections::HashMap; use ecs::extension::Collector as ExtensionCollector; use ecs::sole::Single; use ecs::Sole; use crate::event::{PreUpdate as PreUpdateEvent, Start as StartEvent}; use crate::vector::Vec2; use crate::window::Window; mod reexports { pub use crate::window::{Key, KeyState}; } pub use reexports::*; #[derive(Debug, Clone, Default, Sole)] pub struct Keys { map: HashMap, } impl Keys { #[must_use] pub fn new() -> Self { Self::default() } #[must_use] pub fn get_key_state(&self, key: Key) -> KeyState { self.map.get(&key).copied().unwrap_or(KeyState::Released) } pub fn set_key_state(&mut self, key: Key, key_state: KeyState) { if matches!(key_state, KeyState::Released) { self.map.remove(&key); return; } if matches!(key_state, KeyState::Repeat) { return; } self.map.insert(key, key_state); } #[must_use] pub fn is_anything_pressed(&self) -> bool { !self.map.is_empty() } } #[derive(Debug, Default, Clone, Sole)] pub struct Cursor { pub position: Vec2, pub has_moved: bool, } #[derive(Debug, Clone, Sole)] pub struct CursorFlags { /// This flag is set in two situations: /// A: The window has just started /// B: The window has gained focus again after losing focus. /// /// This flag only lasts a single tick then it is cleared (at the beginning of the /// next tick). pub is_first_move: CursorFlag, } impl Default for CursorFlags { fn default() -> Self { Self { is_first_move: CursorFlag { flag: true, ..Default::default() }, } } } #[derive(Debug, Default, Clone)] pub struct CursorFlag { pub flag: bool, pub pending_clear: bool, } impl CursorFlag { pub fn clear(&mut self) { self.flag = false; self.pending_clear = false; } } /// Input extension. #[derive(Debug, Default)] pub struct Extension {} impl ecs::extension::Extension for Extension { fn collect(self, mut collector: ExtensionCollector<'_>) { collector.add_system(StartEvent, initialize); collector.add_system(PreUpdateEvent, maybe_clear_cursor_is_first_move); collector.add_sole(Keys::default()).ok(); collector.add_sole(Cursor::default()).ok(); collector.add_sole(CursorFlags::default()).ok(); } } fn initialize( keys: Single, cursor: Single, cursor_flags: Single, window: Single, ) { let keys_weak_ref = keys.to_weak_ref(); window.set_key_callback(move |key, _scancode, key_state, _modifiers| { let keys_weak_ref = keys_weak_ref.clone(); let keys_ref = keys_weak_ref.access().expect("No world"); let mut keys = keys_ref.to_single(); keys.set_key_state(key, key_state); }); let cursor_weak_ref = cursor.to_weak_ref(); window.set_cursor_pos_callback(move |cursor_position| { let cursor_weak_ref = cursor_weak_ref.clone(); let cursor_ref = cursor_weak_ref.access().expect("No world"); let mut cursor = cursor_ref.to_single(); cursor.position = Vec2 { x: cursor_position.x, y: cursor_position.y, }; cursor.has_moved = true; }); let cursor_flags_weak_ref = cursor_flags.to_weak_ref(); window.set_focus_callback(move |is_focused| { #[cfg(feature = "debug")] tracing::trace!("Window is focused: {is_focused}"); let cursor_flags_ref = cursor_flags_weak_ref.access().expect("No world"); cursor_flags_ref.to_single().is_first_move.flag = is_focused; }); } fn maybe_clear_cursor_is_first_move( cursor: Single, mut cursor_flags: Single, ) { if cursor_flags.is_first_move.pending_clear { #[cfg(feature = "debug")] tracing::trace!("Clearing is_first_move"); // This flag was set for the whole previous tick so it can be cleared now cursor_flags.is_first_move.clear(); return; } if cursor.has_moved && cursor_flags.is_first_move.flag { #[cfg(feature = "debug")] tracing::trace!("Setting flag to clear is_first_move next tick"); // Make this system clear is_first_move the next time it runs cursor_flags.is_first_move.pending_clear = true; } }