1use std::path::PathBuf;
25use std::sync::Arc;
26use tokio::sync::{RwLock, oneshot};
27
28pub use egui_mcp_protocol::{FrameStats, LogEntry, MouseButton, PerfReport, Request, Response};
29
30mod log_layer;
31mod server;
32
33pub use log_layer::{DEFAULT_MAX_MESSAGE_LENGTH, LogBuffer, McpLogLayer, level_to_priority};
34pub use server::IpcServer;
35
36pub use egui;
38
39#[derive(Debug, Clone)]
41pub enum PendingInput {
42 Click { x: f32, y: f32, button: MouseButton },
44 DoubleClick { x: f32, y: f32, button: MouseButton },
46 MoveMouse { x: f32, y: f32 },
48 Keyboard { key: String },
50 Scroll {
52 x: f32,
53 y: f32,
54 delta_x: f32,
55 delta_y: f32,
56 },
57 Drag {
59 start_x: f32,
60 start_y: f32,
61 end_x: f32,
62 end_y: f32,
63 button: MouseButton,
64 },
65}
66
67#[derive(Debug, Clone)]
69pub struct Highlight {
70 pub rect: egui::Rect,
72 pub color: egui::Color32,
74 pub expires_at: Option<std::time::Instant>,
76}
77
78#[derive(Clone)]
80pub struct McpClient {
81 state: Arc<RwLock<ClientState>>,
82}
83
84struct ClientState {
85 socket_path: PathBuf,
86 screenshot_sender: Option<oneshot::Sender<Vec<u8>>>,
88 pending_inputs: Vec<PendingInput>,
90 highlights: Vec<Highlight>,
92 log_buffer: Option<LogBuffer>,
94 frame_times: std::collections::VecDeque<std::time::Duration>,
96 max_frame_samples: usize,
98 perf_recording: Option<PerfRecording>,
100 last_frame_instant: Option<std::time::Instant>,
102}
103
104struct PerfRecording {
106 start_time: std::time::Instant,
108 frame_times: Vec<std::time::Duration>,
110 duration_ms: u64,
112}
113
114impl McpClient {
115 pub fn new() -> Self {
117 Self::with_socket_path(egui_mcp_protocol::default_socket_path())
118 }
119
120 pub fn with_socket_path(socket_path: PathBuf) -> Self {
122 Self {
123 state: Arc::new(RwLock::new(ClientState {
124 socket_path,
125 screenshot_sender: None,
126 pending_inputs: Vec::new(),
127 highlights: Vec::new(),
128 log_buffer: None,
129 frame_times: std::collections::VecDeque::with_capacity(120),
130 max_frame_samples: 120, perf_recording: None,
132 last_frame_instant: None,
133 })),
134 }
135 }
136
137 pub async fn with_log_buffer(self, buffer: LogBuffer) -> Self {
139 self.state.write().await.log_buffer = Some(buffer);
140 self
141 }
142
143 pub fn with_log_buffer_sync(self, buffer: LogBuffer) -> Self {
145 if let Ok(mut state) = self.state.try_write() {
147 state.log_buffer = Some(buffer);
148 }
149 self
150 }
151
152 pub async fn socket_path(&self) -> PathBuf {
154 self.state.read().await.socket_path.clone()
155 }
156
157 pub async fn request_screenshot(&self) -> oneshot::Receiver<Vec<u8>> {
162 let (tx, rx) = oneshot::channel();
163 self.state.write().await.screenshot_sender = Some(tx);
164 rx
165 }
166
167 pub async fn take_screenshot_request(&self) -> bool {
170 self.state.read().await.screenshot_sender.is_some()
171 }
172
173 pub async fn set_screenshot(&self, data: Vec<u8>) {
176 let sender = self.state.write().await.screenshot_sender.take();
177 if let Some(tx) = sender {
178 let _ = tx.send(data);
180 }
181 }
182
183 pub async fn queue_input(&self, input: PendingInput) {
187 self.state.write().await.pending_inputs.push(input);
188 }
189
190 pub async fn take_pending_inputs(&self) -> Vec<PendingInput> {
192 std::mem::take(&mut self.state.write().await.pending_inputs)
193 }
194
195 pub async fn add_highlight(&self, highlight: Highlight) {
199 self.state.write().await.highlights.push(highlight);
200 }
201
202 pub async fn clear_highlights(&self) {
204 self.state.write().await.highlights.clear();
205 }
206
207 pub async fn get_highlights(&self) -> Vec<Highlight> {
209 let mut state = self.state.write().await;
210 let now = std::time::Instant::now();
211 state
213 .highlights
214 .retain(|h| h.expires_at.is_none() || h.expires_at.unwrap() > now);
215 state.highlights.clone()
216 }
217
218 pub async fn get_logs(&self, min_level: Option<&str>, limit: Option<usize>) -> Vec<LogEntry> {
222 let state = self.state.read().await;
223 if let Some(ref buffer) = state.log_buffer {
224 let buf = buffer.lock();
225 let min_priority = min_level.map(level_to_priority).unwrap_or(0);
226
227 let filtered: Vec<LogEntry> = buf
228 .iter()
229 .filter(|entry| level_to_priority(&entry.level) >= min_priority)
230 .cloned()
231 .collect();
232
233 match limit {
234 Some(n) => filtered.into_iter().rev().take(n).rev().collect(),
235 None => filtered,
236 }
237 } else {
238 Vec::new()
239 }
240 }
241
242 pub async fn clear_logs(&self) {
244 let state = self.state.read().await;
245 if let Some(ref buffer) = state.log_buffer {
246 buffer.lock().clear();
247 }
248 }
249
250 pub async fn record_frame_auto(&self) {
256 let mut state = self.state.write().await;
257 let now = std::time::Instant::now();
258
259 if let Some(last) = state.last_frame_instant {
260 let frame_time = now.duration_since(last);
261 let max_samples = state.max_frame_samples;
262
263 state.frame_times.push_back(frame_time);
265 while state.frame_times.len() > max_samples {
266 state.frame_times.pop_front();
267 }
268
269 if let Some(ref mut recording) = state.perf_recording {
271 recording.frame_times.push(frame_time);
272 }
273 }
274
275 state.last_frame_instant = Some(now);
276 }
277
278 pub async fn record_frame(&self, frame_time: std::time::Duration) {
281 let mut state = self.state.write().await;
282 let max_samples = state.max_frame_samples;
283
284 state.frame_times.push_back(frame_time);
286 while state.frame_times.len() > max_samples {
287 state.frame_times.pop_front();
288 }
289
290 if let Some(ref mut recording) = state.perf_recording {
292 recording.frame_times.push(frame_time);
293
294 if recording.duration_ms > 0 {
296 let elapsed = recording.start_time.elapsed().as_millis() as u64;
297 if elapsed >= recording.duration_ms {
298 }
300 }
301 }
302 }
303
304 pub async fn get_frame_stats(&self) -> FrameStats {
306 let state = self.state.read().await;
307
308 if state.frame_times.is_empty() {
309 return FrameStats {
310 fps: 0.0,
311 frame_time_ms: 0.0,
312 frame_time_min_ms: 0.0,
313 frame_time_max_ms: 0.0,
314 sample_count: 0,
315 };
316 }
317
318 let times: Vec<f32> = state
319 .frame_times
320 .iter()
321 .map(|d| d.as_secs_f32() * 1000.0)
322 .collect();
323
324 let sum: f32 = times.iter().sum();
325 let avg = sum / times.len() as f32;
326 let min = times.iter().cloned().fold(f32::INFINITY, f32::min);
327 let max = times.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
328
329 FrameStats {
330 fps: if avg > 0.0 { 1000.0 / avg } else { 0.0 },
331 frame_time_ms: avg,
332 frame_time_min_ms: min,
333 frame_time_max_ms: max,
334 sample_count: times.len(),
335 }
336 }
337
338 pub async fn start_perf_recording(&self, duration_ms: u64) {
340 let mut state = self.state.write().await;
341 state.perf_recording = Some(PerfRecording {
342 start_time: std::time::Instant::now(),
343 frame_times: Vec::new(),
344 duration_ms,
345 });
346 }
347
348 pub async fn get_perf_report(&self) -> Option<PerfReport> {
350 let mut state = self.state.write().await;
351 let recording = state.perf_recording.take()?;
352
353 if recording.frame_times.is_empty() {
354 return None;
355 }
356
357 let duration_ms = recording.start_time.elapsed().as_millis() as u64;
358 let total_frames = recording.frame_times.len();
359
360 let mut times_ms: Vec<f32> = recording
361 .frame_times
362 .iter()
363 .map(|d| d.as_secs_f32() * 1000.0)
364 .collect();
365
366 let sum: f32 = times_ms.iter().sum();
367 let avg_frame_time = sum / total_frames as f32;
368 let avg_fps = if avg_frame_time > 0.0 {
369 1000.0 / avg_frame_time
370 } else {
371 0.0
372 };
373 let min_frame_time = times_ms.iter().cloned().fold(f32::INFINITY, f32::min);
374 let max_frame_time = times_ms.iter().cloned().fold(f32::NEG_INFINITY, f32::max);
375
376 times_ms.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
378 let p95_idx = (total_frames as f32 * 0.95) as usize;
379 let p99_idx = (total_frames as f32 * 0.99) as usize;
380 let p95_frame_time = times_ms
381 .get(p95_idx.min(total_frames - 1))
382 .copied()
383 .unwrap_or(0.0);
384 let p99_frame_time = times_ms
385 .get(p99_idx.min(total_frames - 1))
386 .copied()
387 .unwrap_or(0.0);
388
389 Some(PerfReport {
390 duration_ms,
391 total_frames,
392 avg_fps,
393 avg_frame_time_ms: avg_frame_time,
394 min_frame_time_ms: min_frame_time,
395 max_frame_time_ms: max_frame_time,
396 p95_frame_time_ms: p95_frame_time,
397 p99_frame_time_ms: p99_frame_time,
398 })
399 }
400
401 pub fn start_server(&self) -> tokio::task::JoinHandle<()> {
403 let client = self.clone();
404 tokio::spawn(async move {
405 if let Err(e) = IpcServer::run(client).await {
406 tracing::error!("IPC server error: {}", e);
407 }
408 })
409 }
410}
411
412impl Default for McpClient {
413 fn default() -> Self {
414 Self::new()
415 }
416}
417
418fn convert_mouse_button(button: &MouseButton) -> egui::PointerButton {
424 match button {
425 MouseButton::Left => egui::PointerButton::Primary,
426 MouseButton::Right => egui::PointerButton::Secondary,
427 MouseButton::Middle => egui::PointerButton::Middle,
428 }
429}
430
431fn parse_special_key(key: &str) -> Option<egui::Key> {
433 match key.to_lowercase().as_str() {
434 "enter" | "return" => Some(egui::Key::Enter),
436 "tab" => Some(egui::Key::Tab),
437 "backspace" => Some(egui::Key::Backspace),
438 "delete" => Some(egui::Key::Delete),
439 "escape" | "esc" => Some(egui::Key::Escape),
440 "space" => Some(egui::Key::Space),
441 "arrowup" | "up" => Some(egui::Key::ArrowUp),
442 "arrowdown" | "down" => Some(egui::Key::ArrowDown),
443 "arrowleft" | "left" => Some(egui::Key::ArrowLeft),
444 "arrowright" | "right" => Some(egui::Key::ArrowRight),
445 "home" => Some(egui::Key::Home),
446 "end" => Some(egui::Key::End),
447 "pageup" => Some(egui::Key::PageUp),
448 "pagedown" => Some(egui::Key::PageDown),
449 "insert" => Some(egui::Key::Insert),
450 "copy" => Some(egui::Key::Copy),
451 "cut" => Some(egui::Key::Cut),
452 "paste" => Some(egui::Key::Paste),
453
454 "f1" => Some(egui::Key::F1),
456 "f2" => Some(egui::Key::F2),
457 "f3" => Some(egui::Key::F3),
458 "f4" => Some(egui::Key::F4),
459 "f5" => Some(egui::Key::F5),
460 "f6" => Some(egui::Key::F6),
461 "f7" => Some(egui::Key::F7),
462 "f8" => Some(egui::Key::F8),
463 "f9" => Some(egui::Key::F9),
464 "f10" => Some(egui::Key::F10),
465 "f11" => Some(egui::Key::F11),
466 "f12" => Some(egui::Key::F12),
467 "f13" => Some(egui::Key::F13),
468 "f14" => Some(egui::Key::F14),
469 "f15" => Some(egui::Key::F15),
470 "f16" => Some(egui::Key::F16),
471 "f17" => Some(egui::Key::F17),
472 "f18" => Some(egui::Key::F18),
473 "f19" => Some(egui::Key::F19),
474 "f20" => Some(egui::Key::F20),
475 "f21" => Some(egui::Key::F21),
476 "f22" => Some(egui::Key::F22),
477 "f23" => Some(egui::Key::F23),
478 "f24" => Some(egui::Key::F24),
479 "f25" => Some(egui::Key::F25),
480 "f26" => Some(egui::Key::F26),
481 "f27" => Some(egui::Key::F27),
482 "f28" => Some(egui::Key::F28),
483 "f29" => Some(egui::Key::F29),
484 "f30" => Some(egui::Key::F30),
485 "f31" => Some(egui::Key::F31),
486 "f32" => Some(egui::Key::F32),
487 "f33" => Some(egui::Key::F33),
488 "f34" => Some(egui::Key::F34),
489 "f35" => Some(egui::Key::F35),
490
491 "colon" | ":" => Some(egui::Key::Colon),
493 "comma" | "," => Some(egui::Key::Comma),
494 "backslash" | "\\" => Some(egui::Key::Backslash),
495 "slash" | "/" => Some(egui::Key::Slash),
496 "pipe" | "|" => Some(egui::Key::Pipe),
497 "questionmark" | "?" => Some(egui::Key::Questionmark),
498 "exclamationmark" | "!" => Some(egui::Key::Exclamationmark),
499 "openbracket" | "[" => Some(egui::Key::OpenBracket),
500 "closebracket" | "]" => Some(egui::Key::CloseBracket),
501 "opencurlybracket" | "{" => Some(egui::Key::OpenCurlyBracket),
502 "closecurlybracket" | "}" => Some(egui::Key::CloseCurlyBracket),
503 "backtick" | "grave" | "`" => Some(egui::Key::Backtick),
504 "minus" | "-" => Some(egui::Key::Minus),
505 "period" | "." => Some(egui::Key::Period),
506 "plus" | "+" => Some(egui::Key::Plus),
507 "equals" | "=" => Some(egui::Key::Equals),
508 "semicolon" | ";" => Some(egui::Key::Semicolon),
509 "quote" | "'" => Some(egui::Key::Quote),
510
511 "num0" | "0" => Some(egui::Key::Num0),
513 "num1" | "1" => Some(egui::Key::Num1),
514 "num2" | "2" => Some(egui::Key::Num2),
515 "num3" | "3" => Some(egui::Key::Num3),
516 "num4" | "4" => Some(egui::Key::Num4),
517 "num5" | "5" => Some(egui::Key::Num5),
518 "num6" | "6" => Some(egui::Key::Num6),
519 "num7" | "7" => Some(egui::Key::Num7),
520 "num8" | "8" => Some(egui::Key::Num8),
521 "num9" | "9" => Some(egui::Key::Num9),
522
523 "a" => Some(egui::Key::A),
525 "b" => Some(egui::Key::B),
526 "c" => Some(egui::Key::C),
527 "d" => Some(egui::Key::D),
528 "e" => Some(egui::Key::E),
529 "f" => Some(egui::Key::F),
530 "g" => Some(egui::Key::G),
531 "h" => Some(egui::Key::H),
532 "i" => Some(egui::Key::I),
533 "j" => Some(egui::Key::J),
534 "k" => Some(egui::Key::K),
535 "l" => Some(egui::Key::L),
536 "m" => Some(egui::Key::M),
537 "n" => Some(egui::Key::N),
538 "o" => Some(egui::Key::O),
539 "p" => Some(egui::Key::P),
540 "q" => Some(egui::Key::Q),
541 "r" => Some(egui::Key::R),
542 "s" => Some(egui::Key::S),
543 "t" => Some(egui::Key::T),
544 "u" => Some(egui::Key::U),
545 "v" => Some(egui::Key::V),
546 "w" => Some(egui::Key::W),
547 "x" => Some(egui::Key::X),
548 "y" => Some(egui::Key::Y),
549 "z" => Some(egui::Key::Z),
550
551 "browserback" => Some(egui::Key::BrowserBack),
553
554 _ => None,
555 }
556}
557
558pub fn inject_inputs(
574 ctx: &egui::Context,
575 raw_input: &mut egui::RawInput,
576 inputs: Vec<PendingInput>,
577) {
578 if inputs.is_empty() {
579 return;
580 }
581
582 ctx.request_repaint();
584
585 for input in inputs {
586 match input {
587 PendingInput::MoveMouse { x, y } => {
588 tracing::debug!("Injecting mouse move to ({}, {})", x, y);
589 raw_input
590 .events
591 .push(egui::Event::PointerMoved(egui::pos2(x, y)));
592 }
593 PendingInput::Click { x, y, button } => {
594 tracing::debug!("Injecting click at ({}, {})", x, y);
595 let egui_button = convert_mouse_button(&button);
596 let pos = egui::pos2(x, y);
597
598 raw_input.events.push(egui::Event::PointerMoved(pos));
599 raw_input.events.push(egui::Event::PointerButton {
600 pos,
601 button: egui_button,
602 pressed: true,
603 modifiers: egui::Modifiers::NONE,
604 });
605 raw_input.events.push(egui::Event::PointerButton {
606 pos,
607 button: egui_button,
608 pressed: false,
609 modifiers: egui::Modifiers::NONE,
610 });
611 }
612 PendingInput::DoubleClick { x, y, button } => {
613 tracing::debug!("Injecting double click at ({}, {})", x, y);
614 let egui_button = convert_mouse_button(&button);
615 let pos = egui::pos2(x, y);
616
617 raw_input.events.push(egui::Event::PointerMoved(pos));
618 raw_input.events.push(egui::Event::PointerButton {
620 pos,
621 button: egui_button,
622 pressed: true,
623 modifiers: egui::Modifiers::NONE,
624 });
625 raw_input.events.push(egui::Event::PointerButton {
626 pos,
627 button: egui_button,
628 pressed: false,
629 modifiers: egui::Modifiers::NONE,
630 });
631 raw_input.events.push(egui::Event::PointerButton {
633 pos,
634 button: egui_button,
635 pressed: true,
636 modifiers: egui::Modifiers::NONE,
637 });
638 raw_input.events.push(egui::Event::PointerButton {
639 pos,
640 button: egui_button,
641 pressed: false,
642 modifiers: egui::Modifiers::NONE,
643 });
644 }
645 PendingInput::Drag {
646 start_x,
647 start_y,
648 end_x,
649 end_y,
650 button,
651 } => {
652 tracing::debug!(
653 "Injecting drag from ({}, {}) to ({}, {})",
654 start_x,
655 start_y,
656 end_x,
657 end_y
658 );
659 let egui_button = convert_mouse_button(&button);
660 let start_pos = egui::pos2(start_x, start_y);
661 let end_pos = egui::pos2(end_x, end_y);
662
663 raw_input.events.push(egui::Event::PointerMoved(start_pos));
664 raw_input.events.push(egui::Event::PointerButton {
665 pos: start_pos,
666 button: egui_button,
667 pressed: true,
668 modifiers: egui::Modifiers::NONE,
669 });
670 raw_input.events.push(egui::Event::PointerMoved(end_pos));
671 raw_input.events.push(egui::Event::PointerButton {
672 pos: end_pos,
673 button: egui_button,
674 pressed: false,
675 modifiers: egui::Modifiers::NONE,
676 });
677 }
678 PendingInput::Keyboard { key } => {
679 tracing::debug!("Injecting keyboard input: {}", key);
680 if let Some(egui_key) = parse_special_key(&key) {
681 raw_input.events.push(egui::Event::Key {
683 key: egui_key,
684 physical_key: Some(egui_key),
685 pressed: true,
686 repeat: false,
687 modifiers: egui::Modifiers::NONE,
688 });
689 raw_input.events.push(egui::Event::Key {
690 key: egui_key,
691 physical_key: Some(egui_key),
692 pressed: false,
693 repeat: false,
694 modifiers: egui::Modifiers::NONE,
695 });
696 } else {
697 raw_input.events.push(egui::Event::Text(key));
699 }
700 }
701 PendingInput::Scroll {
702 x,
703 y,
704 delta_x,
705 delta_y,
706 } => {
707 tracing::debug!(
708 "Injecting scroll at ({}, {}) delta ({}, {})",
709 x,
710 y,
711 delta_x,
712 delta_y
713 );
714 raw_input
715 .events
716 .push(egui::Event::PointerMoved(egui::pos2(x, y)));
717 raw_input.events.push(egui::Event::MouseWheel {
718 unit: egui::MouseWheelUnit::Point,
719 delta: egui::vec2(delta_x, delta_y),
720 modifiers: egui::Modifiers::NONE,
721 });
722 }
723 }
724 }
725}
726
727pub fn draw_highlights(ctx: &egui::Context, highlights: &[Highlight]) {
750 if highlights.is_empty() {
751 return;
752 }
753
754 ctx.request_repaint();
756
757 let painter = ctx.debug_painter();
759
760 for highlight in highlights {
761 painter.rect_stroke(
763 highlight.rect,
764 0.0, egui::Stroke::new(3.0, highlight.color),
766 egui::StrokeKind::Outside,
767 );
768
769 let fill_color = egui::Color32::from_rgba_unmultiplied(
771 highlight.color.r(),
772 highlight.color.g(),
773 highlight.color.b(),
774 highlight.color.a() / 4, );
776 painter.rect_filled(highlight.rect, 0.0, fill_color);
777 }
778}
779
780#[cfg(test)]
781mod tests {
782 use super::*;
783
784 #[test]
785 fn test_parse_special_key_command_keys() {
786 assert_eq!(parse_special_key("Enter"), Some(egui::Key::Enter));
788 assert_eq!(parse_special_key("return"), Some(egui::Key::Enter));
789 assert_eq!(parse_special_key("Tab"), Some(egui::Key::Tab));
790 assert_eq!(parse_special_key("Backspace"), Some(egui::Key::Backspace));
791 assert_eq!(parse_special_key("Delete"), Some(egui::Key::Delete));
792 assert_eq!(parse_special_key("Escape"), Some(egui::Key::Escape));
793 assert_eq!(parse_special_key("esc"), Some(egui::Key::Escape));
794 assert_eq!(parse_special_key("Space"), Some(egui::Key::Space));
795 assert_eq!(parse_special_key("Insert"), Some(egui::Key::Insert));
796 }
797
798 #[test]
799 fn test_parse_special_key_arrow_keys() {
800 assert_eq!(parse_special_key("ArrowUp"), Some(egui::Key::ArrowUp));
801 assert_eq!(parse_special_key("up"), Some(egui::Key::ArrowUp));
802 assert_eq!(parse_special_key("ArrowDown"), Some(egui::Key::ArrowDown));
803 assert_eq!(parse_special_key("down"), Some(egui::Key::ArrowDown));
804 assert_eq!(parse_special_key("ArrowLeft"), Some(egui::Key::ArrowLeft));
805 assert_eq!(parse_special_key("left"), Some(egui::Key::ArrowLeft));
806 assert_eq!(parse_special_key("ArrowRight"), Some(egui::Key::ArrowRight));
807 assert_eq!(parse_special_key("right"), Some(egui::Key::ArrowRight));
808 }
809
810 #[test]
811 fn test_parse_special_key_navigation_keys() {
812 assert_eq!(parse_special_key("Home"), Some(egui::Key::Home));
813 assert_eq!(parse_special_key("End"), Some(egui::Key::End));
814 assert_eq!(parse_special_key("PageUp"), Some(egui::Key::PageUp));
815 assert_eq!(parse_special_key("PageDown"), Some(egui::Key::PageDown));
816 }
817
818 #[test]
819 fn test_parse_special_key_clipboard_keys() {
820 assert_eq!(parse_special_key("Copy"), Some(egui::Key::Copy));
821 assert_eq!(parse_special_key("Cut"), Some(egui::Key::Cut));
822 assert_eq!(parse_special_key("Paste"), Some(egui::Key::Paste));
823 }
824
825 #[test]
826 fn test_parse_special_key_function_keys() {
827 assert_eq!(parse_special_key("F1"), Some(egui::Key::F1));
829 assert_eq!(parse_special_key("f12"), Some(egui::Key::F12));
830
831 assert_eq!(parse_special_key("F13"), Some(egui::Key::F13));
833 assert_eq!(parse_special_key("F20"), Some(egui::Key::F20));
834 assert_eq!(parse_special_key("F35"), Some(egui::Key::F35));
835 }
836
837 #[test]
838 fn test_parse_special_key_punctuation_by_name() {
839 assert_eq!(parse_special_key("colon"), Some(egui::Key::Colon));
840 assert_eq!(parse_special_key("comma"), Some(egui::Key::Comma));
841 assert_eq!(parse_special_key("backslash"), Some(egui::Key::Backslash));
842 assert_eq!(parse_special_key("slash"), Some(egui::Key::Slash));
843 assert_eq!(parse_special_key("pipe"), Some(egui::Key::Pipe));
844 assert_eq!(
845 parse_special_key("questionmark"),
846 Some(egui::Key::Questionmark)
847 );
848 assert_eq!(
849 parse_special_key("exclamationmark"),
850 Some(egui::Key::Exclamationmark)
851 );
852 assert_eq!(
853 parse_special_key("openbracket"),
854 Some(egui::Key::OpenBracket)
855 );
856 assert_eq!(
857 parse_special_key("closebracket"),
858 Some(egui::Key::CloseBracket)
859 );
860 assert_eq!(
861 parse_special_key("opencurlybracket"),
862 Some(egui::Key::OpenCurlyBracket)
863 );
864 assert_eq!(
865 parse_special_key("closecurlybracket"),
866 Some(egui::Key::CloseCurlyBracket)
867 );
868 assert_eq!(parse_special_key("backtick"), Some(egui::Key::Backtick));
869 assert_eq!(parse_special_key("grave"), Some(egui::Key::Backtick));
870 assert_eq!(parse_special_key("minus"), Some(egui::Key::Minus));
871 assert_eq!(parse_special_key("period"), Some(egui::Key::Period));
872 assert_eq!(parse_special_key("plus"), Some(egui::Key::Plus));
873 assert_eq!(parse_special_key("equals"), Some(egui::Key::Equals));
874 assert_eq!(parse_special_key("semicolon"), Some(egui::Key::Semicolon));
875 assert_eq!(parse_special_key("quote"), Some(egui::Key::Quote));
876 }
877
878 #[test]
879 fn test_parse_special_key_punctuation_by_symbol() {
880 assert_eq!(parse_special_key(":"), Some(egui::Key::Colon));
881 assert_eq!(parse_special_key(","), Some(egui::Key::Comma));
882 assert_eq!(parse_special_key("\\"), Some(egui::Key::Backslash));
883 assert_eq!(parse_special_key("/"), Some(egui::Key::Slash));
884 assert_eq!(parse_special_key("|"), Some(egui::Key::Pipe));
885 assert_eq!(parse_special_key("?"), Some(egui::Key::Questionmark));
886 assert_eq!(parse_special_key("!"), Some(egui::Key::Exclamationmark));
887 assert_eq!(parse_special_key("["), Some(egui::Key::OpenBracket));
888 assert_eq!(parse_special_key("]"), Some(egui::Key::CloseBracket));
889 assert_eq!(parse_special_key("{"), Some(egui::Key::OpenCurlyBracket));
890 assert_eq!(parse_special_key("}"), Some(egui::Key::CloseCurlyBracket));
891 assert_eq!(parse_special_key("`"), Some(egui::Key::Backtick));
892 assert_eq!(parse_special_key("-"), Some(egui::Key::Minus));
893 assert_eq!(parse_special_key("."), Some(egui::Key::Period));
894 assert_eq!(parse_special_key("+"), Some(egui::Key::Plus));
895 assert_eq!(parse_special_key("="), Some(egui::Key::Equals));
896 assert_eq!(parse_special_key(";"), Some(egui::Key::Semicolon));
897 assert_eq!(parse_special_key("'"), Some(egui::Key::Quote));
898 }
899
900 #[test]
901 fn test_parse_special_key_digit_keys() {
902 assert_eq!(parse_special_key("0"), Some(egui::Key::Num0));
903 assert_eq!(parse_special_key("1"), Some(egui::Key::Num1));
904 assert_eq!(parse_special_key("9"), Some(egui::Key::Num9));
905 assert_eq!(parse_special_key("num0"), Some(egui::Key::Num0));
906 assert_eq!(parse_special_key("num5"), Some(egui::Key::Num5));
907 }
908
909 #[test]
910 fn test_parse_special_key_letter_keys() {
911 assert_eq!(parse_special_key("a"), Some(egui::Key::A));
912 assert_eq!(parse_special_key("A"), Some(egui::Key::A));
913 assert_eq!(parse_special_key("z"), Some(egui::Key::Z));
914 assert_eq!(parse_special_key("Z"), Some(egui::Key::Z));
915 assert_eq!(parse_special_key("m"), Some(egui::Key::M));
916 }
917
918 #[test]
919 fn test_parse_special_key_browser_keys() {
920 assert_eq!(
921 parse_special_key("BrowserBack"),
922 Some(egui::Key::BrowserBack)
923 );
924 assert_eq!(
925 parse_special_key("browserback"),
926 Some(egui::Key::BrowserBack)
927 );
928 }
929
930 #[test]
931 fn test_parse_special_key_unknown() {
932 assert_eq!(parse_special_key("unknown"), None);
934 assert_eq!(parse_special_key("ctrl"), None);
935 assert_eq!(parse_special_key("shift"), None);
936 assert_eq!(parse_special_key("alt"), None);
937 }
938
939 #[test]
940 fn test_parse_special_key_case_insensitive() {
941 assert_eq!(parse_special_key("ENTER"), Some(egui::Key::Enter));
943 assert_eq!(parse_special_key("Enter"), Some(egui::Key::Enter));
944 assert_eq!(parse_special_key("enter"), Some(egui::Key::Enter));
945 assert_eq!(parse_special_key("eNtEr"), Some(egui::Key::Enter));
946 }
947}