mittens_engine/engine/
user_input.rs1use std::collections::HashSet;
7
8use winit::event::{ElementState, MouseButton, MouseScrollDelta, WindowEvent};
9use winit::keyboard::{Key, NamedKey};
10
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub enum TextInputFrameEvent {
13 InsertText(String),
14 Backspace,
15 DeleteForward,
16 MoveCaretLeft,
17 MoveCaretRight,
18}
19
20#[derive(Default, Debug, Clone)]
28pub struct InputState {
29 pub keys_down: HashSet<Key>,
30 pub keys_pressed: HashSet<Key>,
31 pub keys_released: HashSet<Key>,
32
33 pub mouse_down: HashSet<MouseButton>,
34 pub mouse_pressed: HashSet<MouseButton>,
35 pub mouse_released: HashSet<MouseButton>,
36
37 pub cursor_pos: Option<(f32, f32)>,
39
40 prev_cursor_pos: Option<(f32, f32)>,
42
43 mouse_movement: (f32, f32),
45
46 mouse_dragging: bool,
48 mouse_drag_delta: (f32, f32),
49
50 pub wheel_delta: (f32, f32),
52
53 text_input_events: Vec<TextInputFrameEvent>,
54}
55
56impl InputState {
57 pub fn start_frame(&mut self) {
63 self.mouse_movement = match (self.cursor_pos, self.prev_cursor_pos) {
65 (Some((cx, cy)), Some((px, py))) => (cx - px, cy - py),
66 _ => (0.0, 0.0),
67 };
68 self.prev_cursor_pos = self.cursor_pos;
69
70 let any_button_down = !self.mouse_down.is_empty();
72 let moved = self.mouse_movement.0 != 0.0 || self.mouse_movement.1 != 0.0;
73 self.mouse_dragging = any_button_down && moved;
74 self.mouse_drag_delta = if self.mouse_dragging {
75 self.mouse_movement
76 } else {
77 (0.0, 0.0)
78 };
79 }
80
81 pub fn end_frame(&mut self) {
83 self.keys_pressed.clear();
84 self.keys_released.clear();
85 self.mouse_pressed.clear();
86 self.mouse_released.clear();
87 self.wheel_delta = (0.0, 0.0);
88 self.text_input_events.clear();
89 }
90
91 #[inline]
92 pub fn key_down(&self, key: &Key) -> bool {
93 self.keys_down.contains(key)
94 }
95
96 #[inline]
97 pub fn key_pressed(&self, key: &Key) -> bool {
98 self.keys_pressed.contains(key)
99 }
100
101 #[inline]
102 pub fn key_released(&self, key: &Key) -> bool {
103 self.keys_released.contains(key)
104 }
105
106 #[inline]
109 pub fn mouse_movement(&self) -> (f32, f32) {
110 self.mouse_movement
111 }
112
113 #[inline]
115 pub fn mouse_dragging(&self) -> bool {
116 self.mouse_dragging
117 }
118
119 #[inline]
121 pub fn mouse_drag_delta(&self) -> (f32, f32) {
122 self.mouse_drag_delta
123 }
124
125 #[inline]
128 pub fn mouse_dragging_button(&self, button: MouseButton) -> bool {
129 self.mouse_down.contains(&button)
130 && (self.mouse_movement.0 != 0.0 || self.mouse_movement.1 != 0.0)
131 }
132
133 #[inline]
135 pub fn mouse_drag_delta_button(&self, button: MouseButton) -> (f32, f32) {
136 if self.mouse_dragging_button(button) {
137 self.mouse_movement
138 } else {
139 (0.0, 0.0)
140 }
141 }
142
143 #[inline]
144 pub fn text_input_events(&self) -> &[TextInputFrameEvent] {
145 &self.text_input_events
146 }
147}
148
149#[derive(Default, Debug, Clone)]
151pub struct UserInput {
152 state: InputState,
153}
154
155impl UserInput {
156 pub fn new() -> Self {
157 Self::default()
158 }
159
160 pub fn state(&self) -> &InputState {
161 &self.state
162 }
163
164 pub fn state_mut(&mut self) -> &mut InputState {
165 &mut self.state
166 }
167
168 pub fn start_frame(&mut self) {
169 self.state.start_frame();
170 }
171
172 pub fn end_frame(&mut self) {
173 self.state.end_frame();
174 }
175
176 pub fn handle_window_event(&mut self, event: &WindowEvent) -> bool {
180 match event {
181 WindowEvent::KeyboardInput { event, .. } => {
182 fn normalize_key(key: &Key) -> Key {
183 match key {
184 Key::Character(s) => {
187 if s.len() == 1 {
188 let c = s.chars().next().unwrap_or('\0');
189 if c.is_ascii_alphabetic() {
190 return Key::Character(
191 c.to_ascii_lowercase().to_string().into(),
192 );
193 }
194 }
195 Key::Character(s.clone())
196 }
197 _ => key.clone(),
198 }
199 }
200
201 let key = normalize_key(&event.logical_key);
202 match event.state {
203 ElementState::Pressed => {
204 let was_down = self.state.keys_down.contains(&key);
205 self.state.keys_down.insert(key.clone());
206 if !was_down {
207 self.state.keys_pressed.insert(key);
208 }
209 match &event.logical_key {
210 Key::Named(NamedKey::Backspace) => {
211 self.state
212 .text_input_events
213 .push(TextInputFrameEvent::Backspace);
214 }
215 Key::Named(NamedKey::Delete) => {
216 self.state
217 .text_input_events
218 .push(TextInputFrameEvent::DeleteForward);
219 }
220 Key::Named(NamedKey::ArrowLeft) => {
221 self.state
222 .text_input_events
223 .push(TextInputFrameEvent::MoveCaretLeft);
224 }
225 Key::Named(NamedKey::ArrowRight) => {
226 self.state
227 .text_input_events
228 .push(TextInputFrameEvent::MoveCaretRight);
229 }
230 _ => {}
231 }
232 if let Some(text) = event.text.as_ref() {
233 let filtered: String =
234 text.chars().filter(|c| !c.is_control()).collect();
235 if !filtered.is_empty() {
236 self.state
237 .text_input_events
238 .push(TextInputFrameEvent::InsertText(filtered));
239 }
240 }
241 }
242 ElementState::Released => {
243 self.state.keys_down.remove(&key);
244 self.state.keys_released.insert(key);
245 }
246 }
247 true
248 }
249
250 WindowEvent::MouseInput { state, button, .. } => {
251 match state {
252 ElementState::Pressed => {
253 let was_down = self.state.mouse_down.contains(button);
254 self.state.mouse_down.insert(*button);
255 if !was_down {
256 self.state.mouse_pressed.insert(*button);
257 }
258 }
259 ElementState::Released => {
260 self.state.mouse_down.remove(button);
261 self.state.mouse_released.insert(*button);
262 }
263 }
264 true
265 }
266
267 WindowEvent::CursorMoved { position, .. } => {
268 self.state.cursor_pos = Some((position.x as f32, position.y as f32));
269 true
270 }
271
272 WindowEvent::MouseWheel { delta, .. } => {
273 let (dx, dy) = match delta {
274 MouseScrollDelta::LineDelta(x, y) => (*x, *y),
275 MouseScrollDelta::PixelDelta(pos) => (pos.x as f32, pos.y as f32),
276 };
277 self.state.wheel_delta.0 += dx;
278 self.state.wheel_delta.1 += dy;
279 true
280 }
281
282 _ => false,
283 }
284 }
285}