1use crate::core::{Position, Rect, Size};
7use serde::{Deserialize, Serialize};
8use winit::event::{ElementState, MouseButton as WinitButton, WindowEvent};
9use winit::keyboard::{Key, NamedKey};
10
11#[derive(Debug, Clone, PartialEq)]
15pub enum Event {
16 Key(KeyEvent),
18 Mouse(MouseEvent),
20 TextInput(String),
22 Resize(Size),
24 FocusGained,
26 FocusLost,
28 CloseRequested,
30 Tick,
32 FileDrop(Vec<String>),
34 FileHover(Vec<String>),
36 FileHoverCancelled,
38 DragDrop(DragDropEvent),
40 AgentAction {
42 agent_id: String,
43 action: String,
44 params: serde_json::Value,
45 },
46 ImePreedit {
48 text: String,
50 cursor: Option<(usize, usize)>,
52 },
53 ImeCommit(String),
55}
56
57#[derive(Debug, Clone, PartialEq, Eq, Hash)]
59pub struct KeyEvent {
60 pub code: KeyCode,
61 pub modifiers: KeyModifiers,
62 pub kind: KeyEventKind,
63}
64
65impl KeyEvent {
66 pub fn new(code: KeyCode, modifiers: KeyModifiers) -> Self {
67 Self {
68 code,
69 modifiers,
70 kind: KeyEventKind::Press,
71 }
72 }
73
74 pub fn is_ctrl(&self, code: KeyCode) -> bool {
75 self.code == code && self.modifiers.contains(KeyModifiers::CONTROL)
76 }
77
78 pub fn is_key(&self, code: KeyCode) -> bool {
79 self.code == code && self.modifiers.is_empty()
80 }
81}
82
83#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
85pub enum KeyEventKind {
86 Press,
87 Release,
88 Repeat,
89}
90
91#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
93pub enum KeyCode {
94 Char(char),
95 F(u8),
96 Backspace,
97 Enter,
98 Tab,
99 BackTab,
100 Esc,
101 Left,
102 Right,
103 Up,
104 Down,
105 Home,
106 End,
107 PageUp,
108 PageDown,
109 Insert,
110 Delete,
111 Null,
112}
113
114#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
116pub struct KeyModifiers(u8);
117
118impl KeyModifiers {
119 pub const NONE: Self = Self(0);
120 pub const SHIFT: Self = Self(1 << 0);
121 pub const CONTROL: Self = Self(1 << 1);
122 pub const ALT: Self = Self(1 << 2);
123 pub const SUPER: Self = Self(1 << 3);
124
125 pub const fn empty() -> Self {
126 Self(0)
127 }
128
129 pub const fn contains(self, other: Self) -> bool {
130 self.0 & other.0 == other.0
131 }
132
133 pub const fn is_empty(self) -> bool {
134 self.0 == 0
135 }
136
137 pub const fn union(self, other: Self) -> Self {
138 Self(self.0 | other.0)
139 }
140}
141
142impl std::ops::BitOr for KeyModifiers {
143 type Output = Self;
144 fn bitor(self, rhs: Self) -> Self::Output {
145 Self(self.0 | rhs.0)
146 }
147}
148
149impl std::ops::BitOrAssign for KeyModifiers {
150 fn bitor_assign(&mut self, rhs: Self) {
151 self.0 |= rhs.0;
152 }
153}
154
155#[derive(Debug, Clone, PartialEq)]
157pub struct MouseEvent {
158 pub kind: MouseEventKind,
159 pub position: Position,
160 pub modifiers: KeyModifiers,
161}
162
163impl MouseEvent {
164 pub fn is_click(&self) -> bool {
165 matches!(self.kind, MouseEventKind::Click(_))
166 }
167
168 pub fn is_drag(&self) -> bool {
169 matches!(self.kind, MouseEventKind::Drag(_))
170 }
171
172 pub fn is_scroll(&self) -> bool {
173 matches!(self.kind, MouseEventKind::Scroll { .. })
174 }
175
176 pub fn clicked_in(&self, area: Rect) -> bool {
178 self.is_click() && area.contains(self.position)
179 }
180}
181
182#[derive(Debug, Clone, Copy, PartialEq)]
184pub enum MouseEventKind {
185 Click(MouseButton),
186 Release(MouseButton),
187 Drag(MouseButton),
188 Move,
189 Scroll { delta_x: f32, delta_y: f32 },
190}
191
192#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
194pub enum MouseButton {
195 Left,
196 Right,
197 Middle,
198}
199
200#[derive(Debug, Clone, PartialEq)]
202pub struct DragDropEvent {
203 pub kind: DragDropKind,
205 pub position: Position,
207}
208
209#[derive(Debug, Clone, PartialEq)]
211pub enum DragDropKind {
212 DragStart {
214 source_id: String,
215 payload: DragPayload,
216 },
217 DragOver { target_id: String },
219 DragLeave { target_id: String },
221 Drop {
223 source_id: String,
224 target_id: String,
225 payload: DragPayload,
226 },
227 DragCancel,
229}
230
231#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
233pub enum DragPayload {
234 Text(String),
236 Index(usize),
238 Path(Vec<String>),
240 Json(serde_json::Value),
242}
243
244#[derive(Debug, Default)]
249pub struct HitMap {
250 entries: Vec<HitEntry>,
251}
252
253#[derive(Debug)]
254struct HitEntry {
255 agent_id: String,
256 bounds: Rect,
257 z_order: u32,
258}
259
260impl HitMap {
261 pub fn new() -> Self {
262 Self::default()
263 }
264
265 pub fn clear(&mut self) {
267 self.entries.clear();
268 }
269
270 pub fn register(&mut self, agent_id: impl Into<String>, bounds: Rect, z_order: u32) {
272 self.entries.push(HitEntry {
273 agent_id: agent_id.into(),
274 bounds,
275 z_order,
276 });
277 }
278
279 pub fn hit_test(&self, pos: Position) -> Option<&str> {
281 self.entries
282 .iter()
283 .filter(|e| e.bounds.contains(pos))
284 .max_by_key(|e| e.z_order)
285 .map(|e| e.agent_id.as_str())
286 }
287}
288
289pub fn convert_window_event(event: &WindowEvent) -> Vec<Event> {
293 let mut out = Vec::new();
294
295 match event {
296 WindowEvent::KeyboardInput { event: key_ev, .. } => {
297 if let Some(code) = convert_key(&key_ev.logical_key) {
298 let kind = match key_ev.state {
299 ElementState::Pressed => KeyEventKind::Press,
300 ElementState::Released => KeyEventKind::Release,
301 };
302 let mods = KeyModifiers::empty(); out.push(Event::Key(KeyEvent {
304 code,
305 modifiers: mods,
306 kind,
307 }));
308 }
309 if key_ev.state == ElementState::Pressed {
311 if let Key::Character(ch) = &key_ev.logical_key {
312 out.push(Event::TextInput(ch.to_string()));
313 }
314 }
315 }
316
317 WindowEvent::CursorMoved { position, .. } => {
318 out.push(Event::Mouse(MouseEvent {
319 kind: MouseEventKind::Move,
320 position: Position::new(position.x as f32, position.y as f32),
321 modifiers: KeyModifiers::empty(),
322 }));
323 }
324
325 WindowEvent::MouseInput { state, button, .. } => {
326 let btn = match button {
327 WinitButton::Left => MouseButton::Left,
328 WinitButton::Right => MouseButton::Right,
329 WinitButton::Middle => MouseButton::Middle,
330 _ => MouseButton::Left,
331 };
332 let kind = match state {
333 ElementState::Pressed => MouseEventKind::Click(btn),
334 ElementState::Released => MouseEventKind::Release(btn),
335 };
336 out.push(Event::Mouse(MouseEvent {
337 kind,
338 position: Position::ZERO, modifiers: KeyModifiers::empty(),
340 }));
341 }
342
343 WindowEvent::MouseWheel { delta, .. } => {
344 let (dx, dy) = match delta {
345 winit::event::MouseScrollDelta::LineDelta(x, y) => (*x, *y),
346 winit::event::MouseScrollDelta::PixelDelta(p) => (p.x as f32, p.y as f32),
347 };
348 out.push(Event::Mouse(MouseEvent {
349 kind: MouseEventKind::Scroll {
350 delta_x: dx,
351 delta_y: dy,
352 },
353 position: Position::ZERO,
354 modifiers: KeyModifiers::empty(),
355 }));
356 }
357
358 WindowEvent::Resized(size) => {
359 out.push(Event::Resize(Size::new(
360 size.width as f32,
361 size.height as f32,
362 )));
363 }
364
365 WindowEvent::Focused(focused) => {
366 out.push(if *focused {
367 Event::FocusGained
368 } else {
369 Event::FocusLost
370 });
371 }
372
373 WindowEvent::CloseRequested => {
374 out.push(Event::CloseRequested);
375 }
376
377 WindowEvent::DroppedFile(path) => {
378 out.push(Event::FileDrop(vec![path.display().to_string()]));
379 }
380
381 WindowEvent::HoveredFile(path) => {
382 out.push(Event::FileHover(vec![path.display().to_string()]));
383 }
384
385 WindowEvent::HoveredFileCancelled => {
386 out.push(Event::FileHoverCancelled);
387 }
388
389 WindowEvent::Ime(ime) => match ime {
390 winit::event::Ime::Preedit(text, cursor) => {
391 out.push(Event::ImePreedit {
392 text: text.clone(),
393 cursor: *cursor,
394 });
395 }
396 winit::event::Ime::Commit(text) => {
397 out.push(Event::ImeCommit(text.clone()));
398 }
399 _ => {}
400 },
401
402 _ => {}
403 }
404
405 out
406}
407
408fn convert_key(key: &Key) -> Option<KeyCode> {
410 match key {
411 Key::Named(named) => match named {
412 NamedKey::Enter => Some(KeyCode::Enter),
413 NamedKey::Tab => Some(KeyCode::Tab),
414 NamedKey::Backspace => Some(KeyCode::Backspace),
415 NamedKey::Escape => Some(KeyCode::Esc),
416 NamedKey::ArrowLeft => Some(KeyCode::Left),
417 NamedKey::ArrowRight => Some(KeyCode::Right),
418 NamedKey::ArrowUp => Some(KeyCode::Up),
419 NamedKey::ArrowDown => Some(KeyCode::Down),
420 NamedKey::Home => Some(KeyCode::Home),
421 NamedKey::End => Some(KeyCode::End),
422 NamedKey::PageUp => Some(KeyCode::PageUp),
423 NamedKey::PageDown => Some(KeyCode::PageDown),
424 NamedKey::Insert => Some(KeyCode::Insert),
425 NamedKey::Delete => Some(KeyCode::Delete),
426 NamedKey::F1 => Some(KeyCode::F(1)),
427 NamedKey::F2 => Some(KeyCode::F(2)),
428 NamedKey::F3 => Some(KeyCode::F(3)),
429 NamedKey::F4 => Some(KeyCode::F(4)),
430 NamedKey::F5 => Some(KeyCode::F(5)),
431 NamedKey::F6 => Some(KeyCode::F(6)),
432 NamedKey::F7 => Some(KeyCode::F(7)),
433 NamedKey::F8 => Some(KeyCode::F(8)),
434 NamedKey::F9 => Some(KeyCode::F(9)),
435 NamedKey::F10 => Some(KeyCode::F(10)),
436 NamedKey::F11 => Some(KeyCode::F(11)),
437 NamedKey::F12 => Some(KeyCode::F(12)),
438 _ => None,
439 },
440 Key::Character(ch) => {
441 let c = ch.chars().next()?;
442 Some(KeyCode::Char(c))
443 }
444 _ => None,
445 }
446}
447
448#[cfg(test)]
449mod tests {
450 use super::*;
451
452 #[test]
455 fn key_modifiers_empty() {
456 let m = KeyModifiers::empty();
457 assert!(m.is_empty());
458 assert!(!m.contains(KeyModifiers::SHIFT));
459 }
460
461 #[test]
462 fn key_modifiers_contains() {
463 let m = KeyModifiers::SHIFT | KeyModifiers::CONTROL;
464 assert!(m.contains(KeyModifiers::SHIFT));
465 assert!(m.contains(KeyModifiers::CONTROL));
466 assert!(!m.contains(KeyModifiers::ALT));
467 }
468
469 #[test]
470 fn key_modifiers_union() {
471 let a = KeyModifiers::SHIFT;
472 let b = KeyModifiers::ALT;
473 let u = a.union(b);
474 assert!(u.contains(KeyModifiers::SHIFT));
475 assert!(u.contains(KeyModifiers::ALT));
476 }
477
478 #[test]
479 fn key_modifiers_bitor_assign() {
480 let mut m = KeyModifiers::NONE;
481 m |= KeyModifiers::SUPER;
482 assert!(m.contains(KeyModifiers::SUPER));
483 }
484
485 #[test]
488 fn key_event_new() {
489 let ke = KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE);
490 assert_eq!(ke.code, KeyCode::Enter);
491 assert!(ke.modifiers.is_empty());
492 assert_eq!(ke.kind, KeyEventKind::Press);
493 }
494
495 #[test]
496 fn key_event_is_ctrl() {
497 let ke = KeyEvent::new(KeyCode::Char('s'), KeyModifiers::CONTROL);
498 assert!(ke.is_ctrl(KeyCode::Char('s')));
499 assert!(!ke.is_ctrl(KeyCode::Char('c')));
500 }
501
502 #[test]
503 fn key_event_is_key() {
504 let ke = KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE);
505 assert!(ke.is_key(KeyCode::Esc));
506
507 let with_mod = KeyEvent::new(KeyCode::Esc, KeyModifiers::SHIFT);
508 assert!(!with_mod.is_key(KeyCode::Esc));
509 }
510
511 #[test]
514 fn mouse_event_is_click() {
515 let me = MouseEvent {
516 kind: MouseEventKind::Click(MouseButton::Left),
517 position: Position::ZERO,
518 modifiers: KeyModifiers::NONE,
519 };
520 assert!(me.is_click());
521 assert!(!me.is_drag());
522 assert!(!me.is_scroll());
523 }
524
525 #[test]
526 fn mouse_event_clicked_in() {
527 let area = Rect::new(10.0, 10.0, 100.0, 100.0);
528 let inside = MouseEvent {
529 kind: MouseEventKind::Click(MouseButton::Left),
530 position: Position::new(50.0, 50.0),
531 modifiers: KeyModifiers::NONE,
532 };
533 let outside = MouseEvent {
534 kind: MouseEventKind::Click(MouseButton::Left),
535 position: Position::new(5.0, 5.0),
536 modifiers: KeyModifiers::NONE,
537 };
538 let not_click = MouseEvent {
539 kind: MouseEventKind::Move,
540 position: Position::new(50.0, 50.0),
541 modifiers: KeyModifiers::NONE,
542 };
543 assert!(inside.clicked_in(area));
544 assert!(!outside.clicked_in(area));
545 assert!(!not_click.clicked_in(area));
546 }
547
548 #[test]
551 fn hit_map_empty() {
552 let hm = HitMap::new();
553 assert!(hm.hit_test(Position::ZERO).is_none());
554 }
555
556 #[test]
557 fn hit_map_register_and_hit() {
558 let mut hm = HitMap::new();
559 hm.register("btn-1", Rect::new(0.0, 0.0, 50.0, 50.0), 0);
560 assert_eq!(hm.hit_test(Position::new(25.0, 25.0)), Some("btn-1"));
561 assert!(hm.hit_test(Position::new(60.0, 60.0)).is_none());
562 }
563
564 #[test]
565 fn hit_map_z_order() {
566 let mut hm = HitMap::new();
567 hm.register("back", Rect::new(0.0, 0.0, 100.0, 100.0), 0);
568 hm.register("front", Rect::new(0.0, 0.0, 100.0, 100.0), 10);
569 assert_eq!(hm.hit_test(Position::new(50.0, 50.0)), Some("front"));
570 }
571
572 #[test]
573 fn hit_map_clear() {
574 let mut hm = HitMap::new();
575 hm.register("widget", Rect::new(0.0, 0.0, 50.0, 50.0), 0);
576 assert!(hm.hit_test(Position::new(25.0, 25.0)).is_some());
577 hm.clear();
578 assert!(hm.hit_test(Position::new(25.0, 25.0)).is_none());
579 }
580
581 #[test]
584 fn drag_payload_serialize_roundtrip() {
585 let payloads = vec![
586 DragPayload::Text("hello".into()),
587 DragPayload::Index(42),
588 DragPayload::Path(vec!["a".into(), "b".into()]),
589 DragPayload::Json(serde_json::json!({"key": "value"})),
590 ];
591 for p in payloads {
592 let json = serde_json::to_string(&p).unwrap();
593 let p2: DragPayload = serde_json::from_str(&json).unwrap();
594 assert_eq!(p, p2);
595 }
596 }
597
598 #[test]
601 fn keycode_serialize_roundtrip() {
602 let codes = vec![
603 KeyCode::Char('a'),
604 KeyCode::F(5),
605 KeyCode::Enter,
606 KeyCode::Esc,
607 KeyCode::Null,
608 ];
609 for code in codes {
610 let json = serde_json::to_string(&code).unwrap();
611 let code2: KeyCode = serde_json::from_str(&json).unwrap();
612 assert_eq!(code, code2);
613 }
614 }
615}