1use super::{Application, WindowHandle, profiling::GpuResourceSummary};
9use incular_config::Constraints;
10use incular_core::time::Instant;
11use incular_core::{
12 Code, InputEvent, KeyState, KeyboardEvent, KeyboardKey, Location, Modifiers, NamedKey, Offset,
13 PointerPhase, Rect,
14};
15use incular_platform::WindowId;
16use incular_semantics::SemanticActionKind;
17use std::collections::{HashMap, HashSet};
18use std::sync::atomic::{AtomicBool, Ordering};
19use std::sync::{Arc, Mutex, mpsc};
20use std::time::Duration;
21
22const SIMULATION_REPLY_TIMEOUT: Duration = Duration::from_secs(30);
23const MAX_SIMULATION_REQUESTS_PER_TURN: usize = 64;
24
25#[derive(Clone, Debug, PartialEq, Eq)]
31pub struct Screenshot {
32 width: u32,
33 height: u32,
34 rgba8: Vec<u8>,
35}
36
37impl Screenshot {
38 pub fn from_rgba8(width: u32, height: u32, rgba8: Vec<u8>) -> Result<Self, SimulationError> {
40 let expected = usize::try_from(width)
41 .ok()
42 .and_then(|width| {
43 usize::try_from(height)
44 .ok()
45 .and_then(|height| width.checked_mul(height))
46 })
47 .and_then(|pixels| pixels.checked_mul(4))
48 .ok_or_else(|| {
49 SimulationError::InvalidInput("screenshot dimensions overflow".into())
50 })?;
51 if expected == 0 || rgba8.len() != expected {
52 return Err(SimulationError::InvalidInput(format!(
53 "screenshot pixel length {} does not match {}x{} RGBA8",
54 rgba8.len(),
55 width,
56 height
57 )));
58 }
59 Ok(Self {
60 width,
61 height,
62 rgba8,
63 })
64 }
65
66 #[must_use]
67 pub const fn width(&self) -> u32 {
68 self.width
69 }
70
71 #[must_use]
72 pub const fn height(&self) -> u32 {
73 self.height
74 }
75
76 #[must_use]
78 pub fn pixels(&self) -> &[u8] {
79 &self.rgba8
80 }
81
82 #[must_use]
84 pub fn into_pixels(self) -> Vec<u8> {
85 self.rgba8
86 }
87}
88
89#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
91pub enum SimulationError {
92 #[error("the Incular simulation bridge is disconnected")]
95 Disconnected,
96 #[error("the Incular simulation request timed out")]
98 Timeout,
99 #[error("simulation window {0} was not found")]
101 WindowNotFound(WindowId),
102 #[error("simulation window {0} was closed")]
104 WindowClosed(WindowId),
105 #[error("no semantic element labeled {0:?} was found")]
107 ElementNotFound(String),
108 #[error("invalid simulation input: {0}")]
110 InvalidInput(String),
111 #[error("window capture unavailable: {0}")]
113 CaptureUnavailable(String),
114 #[error("simulation frame failed: {0}")]
116 FrameFailed(String),
117}
118
119type Reply<T> = mpsc::SyncSender<Result<T, SimulationError>>;
120
121pub(crate) enum SimulationRequest {
122 Input {
123 window_id: WindowId,
124 event: InputEvent,
125 reply: Reply<()>,
126 },
127 ClickAt {
128 window_id: WindowId,
129 position: Offset,
130 reply: Reply<()>,
131 },
132 ClickLabel {
133 window_id: WindowId,
134 label: String,
135 reply: Reply<()>,
136 },
137 WaitForFrame {
138 window_id: WindowId,
139 reply: Reply<()>,
140 },
141 Capture {
142 window_id: WindowId,
143 reply: Reply<Screenshot>,
144 },
145 GpuResources {
146 window_id: WindowId,
147 reply: Reply<GpuResourceSummary>,
148 },
149}
150
151pub(crate) struct SimulationBridge {
152 sender: mpsc::Sender<SimulationRequest>,
153 wake: Mutex<Option<Arc<dyn super::RuntimeWake>>>,
154 accepting: AtomicBool,
155}
156
157impl SimulationBridge {
158 pub(crate) fn new(sender: mpsc::Sender<SimulationRequest>) -> Self {
159 Self {
160 sender,
161 wake: Mutex::new(None),
162 accepting: AtomicBool::new(true),
163 }
164 }
165
166 fn enqueue(&self, request: SimulationRequest) -> Result<(), SimulationError> {
167 if !self.accepting.load(Ordering::Acquire) {
168 return Err(SimulationError::Disconnected);
169 }
170 self.sender
171 .send(request)
172 .map_err(|_| SimulationError::Disconnected)?;
173 if let Some(wake) = self.wake.lock().expect("simulation wake mutex").as_ref() {
174 wake.wake();
175 }
176 Ok(())
177 }
178
179 pub(crate) fn set_wake(&self, wake: Arc<dyn super::RuntimeWake>) {
180 *self.wake.lock().expect("simulation wake mutex") = Some(wake);
181 }
182
183 pub(crate) fn stop(&self) {
184 self.accepting.store(false, Ordering::Release);
185 }
186}
187
188#[derive(Default)]
189struct KeyboardState {
190 modifiers: Modifiers,
191 pressed: HashSet<Code>,
192}
193
194#[derive(Default)]
201pub(crate) struct SimulationWaiters {
202 frame: HashMap<WindowId, Vec<mpsc::SyncSender<Result<(), SimulationError>>>>,
203 capture: HashMap<WindowId, Vec<mpsc::SyncSender<Result<Screenshot, SimulationError>>>>,
204 gpu: HashMap<WindowId, Vec<mpsc::SyncSender<Result<GpuResourceSummary, SimulationError>>>>,
205}
206
207impl SimulationWaiters {
208 fn settle<T: Clone>(
209 waiters: &mut HashMap<WindowId, Vec<mpsc::SyncSender<Result<T, SimulationError>>>>,
210 window_id: WindowId,
211 result: Result<T, SimulationError>,
212 ) {
213 if let Some(pending) = waiters.remove(&window_id) {
214 for reply in pending {
215 let _ = reply.send(result.clone());
216 }
217 }
218 }
219
220 #[must_use]
222 pub(crate) fn frame_pending(&self, window_id: WindowId) -> bool {
223 self.frame
224 .get(&window_id)
225 .is_some_and(|waiters| !waiters.is_empty())
226 }
227
228 #[must_use]
230 pub(crate) fn capture_pending(&self, window_id: WindowId) -> bool {
231 self.capture
232 .get(&window_id)
233 .is_some_and(|waiters| !waiters.is_empty())
234 }
235
236 pub(crate) fn insert_frame(
238 &mut self,
239 window_id: WindowId,
240 reply: mpsc::SyncSender<Result<(), SimulationError>>,
241 ) {
242 self.frame.entry(window_id).or_default().push(reply);
243 }
244
245 pub(crate) fn insert_capture(
247 &mut self,
248 window_id: WindowId,
249 reply: mpsc::SyncSender<Result<Screenshot, SimulationError>>,
250 ) {
251 self.capture.entry(window_id).or_default().push(reply);
252 }
253
254 pub(crate) fn insert_gpu(
256 &mut self,
257 window_id: WindowId,
258 reply: mpsc::SyncSender<Result<GpuResourceSummary, SimulationError>>,
259 ) {
260 self.gpu.entry(window_id).or_default().push(reply);
261 }
262
263 pub(crate) fn complete_frame(
267 &mut self,
268 window_id: WindowId,
269 presented: bool,
270 capture: Option<Result<Screenshot, String>>,
271 ) {
272 if !presented {
273 return;
274 }
275 Self::settle(&mut self.frame, window_id, Ok(()));
276 let result = capture
277 .map(|result| result.map_err(SimulationError::CaptureUnavailable))
278 .unwrap_or_else(|| {
279 Err(SimulationError::CaptureUnavailable(
280 "the renderer did not return a capture".into(),
281 ))
282 });
283 Self::settle(&mut self.capture, window_id, result);
284 }
285
286 pub(crate) fn fail_frame(&mut self, window_id: WindowId, message: impl Into<String>) {
288 let error = SimulationError::FrameFailed(message.into());
289 Self::settle(&mut self.frame, window_id, Err(error.clone()));
290 let capture_error = SimulationError::CaptureUnavailable(error.to_string());
291 Self::settle(&mut self.capture, window_id, Err(capture_error));
292 }
293
294 pub(crate) fn fail_window(&mut self, window_id: WindowId) {
297 let error = SimulationError::WindowClosed(window_id);
298 Self::settle(&mut self.frame, window_id, Err(error.clone()));
299 Self::settle(&mut self.capture, window_id, Err(error.clone()));
300 Self::settle(&mut self.gpu, window_id, Err(error));
304 }
305
306 pub(crate) fn complete_gpu(&mut self, window_id: WindowId, summary: GpuResourceSummary) {
309 Self::settle(&mut self.gpu, window_id, Ok(summary));
310 }
311
312 pub(crate) fn gpu_windows(&self) -> Vec<WindowId> {
315 self.gpu.keys().copied().collect()
316 }
317
318 pub(crate) fn take_gpu_waiters(
321 &mut self,
322 window_id: WindowId,
323 ) -> Vec<Reply<GpuResourceSummary>> {
324 self.gpu.remove(&window_id).unwrap_or_default()
325 }
326}
327
328#[derive(Clone)]
334pub struct Simulation {
335 bridge: Arc<SimulationBridge>,
336 window_id: WindowId,
337 keyboard: Arc<Mutex<KeyboardState>>,
338}
339
340impl Simulation {
341 pub(crate) fn new(bridge: Arc<SimulationBridge>, window_id: WindowId) -> Self {
342 Self {
343 bridge,
344 window_id,
345 keyboard: Arc::new(Mutex::new(KeyboardState::default())),
346 }
347 }
348
349 #[must_use]
351 pub fn window(&self, handle: &WindowHandle) -> Self {
352 self.for_window(handle.id())
353 }
354
355 #[must_use]
357 pub fn for_window(&self, window_id: WindowId) -> Self {
358 Self {
359 bridge: self.bridge.clone(),
360 window_id,
361 keyboard: self.keyboard.clone(),
362 }
363 }
364
365 #[must_use]
366 pub const fn window_id(&self) -> WindowId {
367 self.window_id
368 }
369
370 #[must_use]
372 pub fn mouse(&self) -> SimulationMouse {
373 SimulationMouse {
374 simulation: self.clone(),
375 }
376 }
377
378 #[must_use]
380 pub fn keyboard(&self) -> SimulationKeyboard {
381 SimulationKeyboard {
382 simulation: self.clone(),
383 }
384 }
385
386 pub fn click(&self, label: impl Into<String>) -> Result<(), SimulationError> {
389 self.mouse().click(label)
390 }
391
392 pub fn click_at(&self, position: Offset) -> Result<(), SimulationError> {
393 self.mouse().click_at(position)
394 }
395
396 pub fn move_mouse_to(&self, position: Offset) -> Result<(), SimulationError> {
397 self.mouse().move_to(position)
398 }
399
400 pub fn scroll(&self, delta: Offset) -> Result<(), SimulationError> {
401 self.mouse().scroll(delta)
402 }
403
404 pub fn key_down(&self, code: Code) -> Result<(), SimulationError> {
405 self.keyboard().key_down(code)
406 }
407
408 pub fn key_up(&self, code: Code) -> Result<(), SimulationError> {
409 self.keyboard().key_up(code)
410 }
411
412 pub fn press(&self, code: Code) -> Result<(), SimulationError> {
413 self.keyboard().press(code)
414 }
415
416 pub fn press_key(&self, key: KeyboardKey, code: Code) -> Result<(), SimulationError> {
417 self.keyboard().press_key(key, code)
418 }
419
420 pub fn type_text(&self, text: impl Into<String>) -> Result<(), SimulationError> {
421 self.keyboard().type_text(text)
422 }
423
424 pub fn wait_for_frame(&self) -> Result<(), SimulationError> {
426 self.request(|reply| SimulationRequest::WaitForFrame {
427 window_id: self.window_id,
428 reply,
429 })
430 }
431
432 pub fn capture(&self) -> Result<Screenshot, SimulationError> {
434 self.request(|reply| SimulationRequest::Capture {
435 window_id: self.window_id,
436 reply,
437 })
438 }
439
440 pub fn query_gpu_resources(&self) -> Result<GpuResourceSummary, SimulationError> {
444 self.request(|reply| SimulationRequest::GpuResources {
445 window_id: self.window_id,
446 reply,
447 })
448 }
449
450 pub fn release_all_keys(&self) -> Result<(), SimulationError> {
452 self.keyboard().release_all()
453 }
454
455 fn request<T>(
456 &self,
457 build: impl FnOnce(Reply<T>) -> SimulationRequest,
458 ) -> Result<T, SimulationError> {
459 let (sender, receiver) = mpsc::sync_channel(1);
460 self.bridge.enqueue(build(sender))?;
461 match receiver.recv_timeout(SIMULATION_REPLY_TIMEOUT) {
462 Ok(result) => result,
463 Err(mpsc::RecvTimeoutError::Timeout) => Err(SimulationError::Timeout),
464 Err(mpsc::RecvTimeoutError::Disconnected) => Err(SimulationError::Disconnected),
465 }
466 }
467
468 fn input(&self, event: InputEvent) -> Result<(), SimulationError> {
469 self.request(|reply| SimulationRequest::Input {
470 window_id: self.window_id,
471 event,
472 reply,
473 })
474 }
475}
476
477#[derive(Clone)]
481pub struct SimulationMouse {
482 simulation: Simulation,
483}
484
485impl SimulationMouse {
486 pub fn move_to(&self, position: Offset) -> Result<(), SimulationError> {
487 self.simulation.input(InputEvent::Pointer {
488 phase: PointerPhase::Move,
489 position,
490 })
491 }
492
493 pub fn down(&self, position: Offset) -> Result<(), SimulationError> {
494 self.simulation.input(InputEvent::Pointer {
495 phase: PointerPhase::Down,
496 position,
497 })
498 }
499
500 pub fn up(&self, position: Offset) -> Result<(), SimulationError> {
501 self.simulation.input(InputEvent::Pointer {
502 phase: PointerPhase::Up,
503 position,
504 })
505 }
506
507 pub fn click(&self, label: impl Into<String>) -> Result<(), SimulationError> {
508 self.simulation
509 .request(|reply| SimulationRequest::ClickLabel {
510 window_id: self.simulation.window_id,
511 label: label.into(),
512 reply,
513 })
514 }
515
516 pub fn click_at(&self, position: Offset) -> Result<(), SimulationError> {
517 self.simulation.request(|reply| SimulationRequest::ClickAt {
518 window_id: self.simulation.window_id,
519 position,
520 reply,
521 })
522 }
523
524 pub fn scroll(&self, delta: Offset) -> Result<(), SimulationError> {
525 self.simulation.input(InputEvent::Scroll { delta })
526 }
527}
528
529#[derive(Clone)]
534pub struct SimulationKeyboard {
535 simulation: Simulation,
536}
537
538impl SimulationKeyboard {
539 pub fn key_down(&self, code: Code) -> Result<(), SimulationError> {
540 self.send_key(KeyState::Down, None, code)
541 }
542
543 pub fn key_up(&self, code: Code) -> Result<(), SimulationError> {
544 self.send_key(KeyState::Up, None, code)
545 }
546
547 pub fn press(&self, code: Code) -> Result<(), SimulationError> {
548 self.key_down(code)?;
549 self.key_up(code)
550 }
551
552 pub fn press_key(&self, key: KeyboardKey, code: Code) -> Result<(), SimulationError> {
553 self.send_key(KeyState::Down, Some(key.clone()), code)?;
554 self.send_key(KeyState::Up, Some(key), code)
555 }
556
557 pub fn type_text(&self, text: impl Into<String>) -> Result<(), SimulationError> {
558 let text = text.into();
559 if text.chars().any(char::is_control) {
560 return Err(SimulationError::InvalidInput(
561 "type_text accepts printable committed text; use press for commands".into(),
562 ));
563 }
564 self.simulation.input(InputEvent::Text(text))
565 }
566
567 pub fn release_all(&self) -> Result<(), SimulationError> {
568 let pressed = self
569 .simulation
570 .keyboard
571 .lock()
572 .expect("simulation keyboard mutex")
573 .pressed
574 .iter()
575 .copied()
576 .collect::<Vec<_>>();
577 for code in pressed {
578 self.key_up(code)?;
579 }
580 Ok(())
581 }
582
583 fn send_key(
584 &self,
585 state: KeyState,
586 explicit_key: Option<KeyboardKey>,
587 code: Code,
588 ) -> Result<(), SimulationError> {
589 let (key, modifiers, location, repeat, was_pressed) = {
590 let mut keyboard = self
591 .simulation
592 .keyboard
593 .lock()
594 .expect("simulation keyboard mutex");
595 let was_pressed = keyboard.pressed.contains(&code);
596 if state.is_down() {
597 if let Some(modifier) = modifier_for_code(code) {
598 keyboard.modifiers.insert(modifier);
599 }
600 keyboard.pressed.insert(code);
601 }
602 let values = (
603 explicit_key.unwrap_or_else(|| logical_key(code)),
604 keyboard.modifiers,
605 location_for_code(code),
606 state.is_down() && was_pressed,
607 was_pressed,
608 );
609 if state.is_up() {
610 keyboard.pressed.remove(&code);
613 if let Some(modifier) = modifier_for_code(code) {
614 keyboard.modifiers.remove(modifier);
615 }
616 }
617 values
618 };
619 let event = KeyboardEvent {
620 state,
621 key,
622 code,
623 location,
624 modifiers,
625 repeat,
626 is_composing: false,
627 };
628 let result = self.simulation.input(InputEvent::Key(event));
629 if result.is_err() {
630 let mut keyboard = self
632 .simulation
633 .keyboard
634 .lock()
635 .expect("simulation keyboard mutex");
636 if state.is_down() {
637 if !was_pressed {
638 keyboard.pressed.remove(&code);
639 }
640 } else {
641 keyboard.pressed.insert(code);
642 }
643 keyboard.modifiers = modifiers_for_pressed(&keyboard.pressed);
644 }
645 result
646 }
647}
648
649fn modifier_for_code(code: Code) -> Option<Modifiers> {
650 match code {
651 Code::AltLeft | Code::AltRight => Some(Modifiers::ALT),
652 Code::ControlLeft | Code::ControlRight => Some(Modifiers::CONTROL),
653 Code::MetaLeft | Code::MetaRight => Some(Modifiers::META),
654 Code::ShiftLeft | Code::ShiftRight => Some(Modifiers::SHIFT),
655 _ => None,
656 }
657}
658
659fn modifiers_for_pressed(pressed: &HashSet<Code>) -> Modifiers {
660 pressed
661 .iter()
662 .fold(Modifiers::default(), |mut modifiers, code| {
663 if let Some(modifier) = modifier_for_code(*code) {
664 modifiers.insert(modifier);
665 }
666 modifiers
667 })
668}
669
670fn location_for_code(code: Code) -> Location {
671 match code {
672 Code::AltLeft | Code::ControlLeft | Code::MetaLeft | Code::ShiftLeft => Location::Left,
673 Code::AltRight | Code::ControlRight | Code::MetaRight | Code::ShiftRight => Location::Right,
674 Code::NumpadEnter => Location::Numpad,
675 _ => Location::Standard,
676 }
677}
678
679fn logical_key(code: Code) -> KeyboardKey {
680 let character = match code {
681 Code::Backquote => Some("`"),
682 Code::Backslash => Some("\\"),
683 Code::BracketLeft => Some("["),
684 Code::BracketRight => Some("]"),
685 Code::Comma => Some(","),
686 Code::Digit0 => Some("0"),
687 Code::Digit1 => Some("1"),
688 Code::Digit2 => Some("2"),
689 Code::Digit3 => Some("3"),
690 Code::Digit4 => Some("4"),
691 Code::Digit5 => Some("5"),
692 Code::Digit6 => Some("6"),
693 Code::Digit7 => Some("7"),
694 Code::Digit8 => Some("8"),
695 Code::Digit9 => Some("9"),
696 Code::Equal => Some("="),
697 Code::KeyA => Some("a"),
698 Code::KeyB => Some("b"),
699 Code::KeyC => Some("c"),
700 Code::KeyD => Some("d"),
701 Code::KeyE => Some("e"),
702 Code::KeyF => Some("f"),
703 Code::KeyG => Some("g"),
704 Code::KeyH => Some("h"),
705 Code::KeyI => Some("i"),
706 Code::KeyJ => Some("j"),
707 Code::KeyK => Some("k"),
708 Code::KeyL => Some("l"),
709 Code::KeyM => Some("m"),
710 Code::KeyN => Some("n"),
711 Code::KeyO => Some("o"),
712 Code::KeyP => Some("p"),
713 Code::KeyQ => Some("q"),
714 Code::KeyR => Some("r"),
715 Code::KeyS => Some("s"),
716 Code::KeyT => Some("t"),
717 Code::KeyU => Some("u"),
718 Code::KeyV => Some("v"),
719 Code::KeyW => Some("w"),
720 Code::KeyX => Some("x"),
721 Code::KeyY => Some("y"),
722 Code::KeyZ => Some("z"),
723 Code::Minus => Some("-"),
724 Code::Period => Some("."),
725 Code::Quote => Some("'"),
726 Code::Semicolon => Some(";"),
727 Code::Slash => Some("/"),
728 Code::Space => Some(" "),
729 Code::Numpad0 => Some("0"),
730 Code::Numpad1 => Some("1"),
731 Code::Numpad2 => Some("2"),
732 Code::Numpad3 => Some("3"),
733 Code::Numpad4 => Some("4"),
734 Code::Numpad5 => Some("5"),
735 Code::Numpad6 => Some("6"),
736 Code::Numpad7 => Some("7"),
737 Code::Numpad8 => Some("8"),
738 Code::Numpad9 => Some("9"),
739 Code::NumpadAdd => Some("+"),
740 Code::NumpadComma => Some(","),
741 Code::NumpadDecimal => Some("."),
742 Code::NumpadDivide => Some("/"),
743 Code::NumpadEqual => Some("="),
744 Code::NumpadMultiply | Code::NumpadStar => Some("*"),
745 Code::NumpadSubtract => Some("-"),
746 _ => None,
747 };
748 if let Some(character) = character {
749 return KeyboardKey::Character(character.into());
750 }
751 let named = match code {
752 Code::AltLeft | Code::AltRight => NamedKey::Alt,
753 Code::CapsLock => NamedKey::CapsLock,
754 Code::ControlLeft | Code::ControlRight => NamedKey::Control,
755 Code::Enter | Code::NumpadEnter => NamedKey::Enter,
756 Code::Escape => NamedKey::Escape,
757 Code::MetaLeft | Code::MetaRight => NamedKey::Meta,
758 Code::NumLock => NamedKey::NumLock,
759 Code::ScrollLock => NamedKey::ScrollLock,
760 Code::ShiftLeft | Code::ShiftRight => NamedKey::Shift,
761 Code::Tab => NamedKey::Tab,
762 Code::Backspace => NamedKey::Backspace,
763 Code::Delete => NamedKey::Delete,
764 Code::Insert => NamedKey::Insert,
765 Code::PageDown => NamedKey::PageDown,
766 Code::PageUp => NamedKey::PageUp,
767 Code::ArrowDown => NamedKey::ArrowDown,
768 Code::ArrowLeft => NamedKey::ArrowLeft,
769 Code::ArrowRight => NamedKey::ArrowRight,
770 Code::ArrowUp => NamedKey::ArrowUp,
771 Code::End => NamedKey::End,
772 Code::Home => NamedKey::Home,
773 _ => NamedKey::Unidentified,
774 };
775 KeyboardKey::Named(named)
776}
777
778fn valid_offset(offset: Offset) -> Result<(), SimulationError> {
779 if offset.x.is_finite() && offset.y.is_finite() {
780 Ok(())
781 } else {
782 Err(SimulationError::InvalidInput(
783 "pointer and scroll offsets must be finite".into(),
784 ))
785 }
786}
787
788impl Application {
789 pub fn process_simulation_requests(&mut self) {
792 for _ in 0..MAX_SIMULATION_REQUESTS_PER_TURN {
793 let request = match self.simulation_receiver.try_recv() {
794 Ok(request) => request,
795 Err(mpsc::TryRecvError::Empty | mpsc::TryRecvError::Disconnected) => break,
796 };
797 self.handle_simulation_request(request);
798 }
799 }
800
801 fn handle_simulation_request(&mut self, request: SimulationRequest) {
802 match request {
803 SimulationRequest::Input {
804 window_id,
805 event,
806 reply,
807 } => {
808 let result = self
809 .ensure_simulation_layout(window_id)
810 .and_then(|_| self.dispatch_simulation_input(window_id, event));
811 let _ = reply.send(result);
812 }
813 SimulationRequest::ClickAt {
814 window_id,
815 position,
816 reply,
817 } => {
818 let _ = reply.send(self.dispatch_simulation_click(window_id, position));
819 }
820 SimulationRequest::ClickLabel {
821 window_id,
822 label,
823 reply,
824 } => {
825 let trigger = format!("click({label:?})");
826 let result = self
827 .simulation_label_bounds(window_id, &label)
828 .and_then(|bounds| {
829 self.dispatch_simulation_click_prepared(
830 window_id,
831 bounds.center(),
832 &trigger,
833 )
834 });
835 let _ = reply.send(result);
836 }
837 SimulationRequest::WaitForFrame { window_id, reply } => {
838 if self.contains_window(window_id) {
839 let _ =
840 self.with_window_mut(window_id, |record| record.runtime.request_frame());
841 self.simulation_waiters.insert_frame(window_id, reply);
842 } else {
843 let _ = reply.send(Err(SimulationError::WindowNotFound(window_id)));
844 }
845 }
846 SimulationRequest::Capture { window_id, reply } => {
847 if self.contains_window(window_id) {
848 let _ =
849 self.with_window_mut(window_id, |record| record.runtime.request_frame());
850 self.simulation_waiters.insert_capture(window_id, reply);
851 } else {
852 let _ = reply.send(Err(SimulationError::WindowNotFound(window_id)));
853 }
854 }
855 SimulationRequest::GpuResources { window_id, reply } => {
856 if self.contains_window(window_id) {
860 self.simulation_waiters.insert_gpu(window_id, reply);
861 } else {
862 let _ = reply.send(Err(SimulationError::WindowNotFound(window_id)));
863 }
864 }
865 }
866 }
867
868 fn dispatch_simulation_input(
869 &mut self,
870 window_id: WindowId,
871 event: InputEvent,
872 ) -> Result<(), SimulationError> {
873 self.dispatch_simulation_input_with_trigger(window_id, event, None)
874 }
875
876 fn dispatch_simulation_input_with_trigger(
877 &mut self,
878 window_id: WindowId,
879 event: InputEvent,
880 trigger: Option<&str>,
881 ) -> Result<(), SimulationError> {
882 if let InputEvent::Pointer { position, .. } | InputEvent::PointerWithId { position, .. } =
883 &event
884 {
885 valid_offset(*position)?;
886 }
887 if let InputEvent::Scroll { delta } = &event {
888 valid_offset(*delta)?;
889 }
890 if !self.contains_window(window_id) {
891 return Err(SimulationError::WindowNotFound(window_id));
892 }
893 let applied = self.with_window_mut(window_id, |record| {
894 record.input_events = record.input_events.wrapping_add(1);
895 let _ = match trigger {
896 Some(trigger) => record.runtime.handle_input_with_trigger(event, trigger),
897 None => {
898 record.runtime.tree().set_diagnostic_input_trigger(
899 Some("simulation::"),
900 &event,
901 super::diagnostic_input_trigger,
902 );
903 record.runtime.handle_input_timed(event)
904 }
905 };
906 });
907 applied
908 .map(|_| ())
909 .ok_or(SimulationError::WindowNotFound(window_id))
910 }
911
912 fn dispatch_simulation_click(
913 &mut self,
914 window_id: WindowId,
915 position: Offset,
916 ) -> Result<(), SimulationError> {
917 valid_offset(position)?;
918 self.ensure_simulation_layout(window_id)?;
919 self.dispatch_simulation_click_prepared(
920 window_id,
921 position,
922 &format!("click_at({:.1}, {:.1})", position.x, position.y),
923 )
924 }
925
926 fn dispatch_simulation_click_prepared(
927 &mut self,
928 window_id: WindowId,
929 position: Offset,
930 trigger: &str,
931 ) -> Result<(), SimulationError> {
932 self.dispatch_simulation_input_with_trigger(
933 window_id,
934 InputEvent::Pointer {
935 phase: PointerPhase::Move,
936 position,
937 },
938 Some(trigger),
939 )?;
940 self.dispatch_simulation_input_with_trigger(
941 window_id,
942 InputEvent::Pointer {
943 phase: PointerPhase::Down,
944 position,
945 },
946 Some(trigger),
947 )?;
948 self.dispatch_simulation_input_with_trigger(
949 window_id,
950 InputEvent::Pointer {
951 phase: PointerPhase::Up,
952 position,
953 },
954 Some(trigger),
955 )
956 }
957
958 fn ensure_simulation_layout(&mut self, window_id: WindowId) -> Result<(), SimulationError> {
959 let diagnostics = self
960 .window_diagnostics(window_id)
961 .ok_or(SimulationError::WindowNotFound(window_id))?;
962 if diagnostics.physical_size.is_zero() {
963 return Err(SimulationError::FrameFailed(
964 "the target window has a zero-sized surface".into(),
965 ));
966 }
967 self.run_window_frame_at(
968 window_id,
969 Constraints::tight(diagnostics.logical_size),
970 Instant::now(),
971 )
972 .map_err(|error| SimulationError::FrameFailed(format!("{error:?}")))?;
973 let _ = self.with_window_mut(window_id, |record| record.runtime.request_frame());
976 Ok(())
977 }
978
979 fn simulation_label_bounds(
980 &mut self,
981 window_id: WindowId,
982 label: &str,
983 ) -> Result<Rect, SimulationError> {
984 self.set_semantics_enabled(window_id, true);
987 self.ensure_simulation_layout(window_id)?;
988 let viewport = self
989 .window_diagnostics(window_id)
990 .map(|diagnostics| Rect::from_origin_size(Offset::ZERO, diagnostics.logical_size))
991 .ok_or(SimulationError::WindowNotFound(window_id))?;
992 let result = self.registry.borrow().get(window_id).and_then(|record| {
993 let mut fallback = None;
994 record
995 .runtime
996 .tree()
997 .semantics()
998 .iter()
999 .find_map(|(_, node)| {
1000 if node.label.as_deref() != Some(label) || !node.state.enabled {
1001 return None;
1002 }
1003 let visible_bounds = node.bounds.intersection(viewport)?;
1009 if visible_bounds.size.width <= 0.0 || visible_bounds.size.height <= 0.0 {
1010 return None;
1011 }
1012 if node.actions.contains(&SemanticActionKind::Activate) {
1013 return Some(visible_bounds);
1014 }
1015 fallback = Some(visible_bounds);
1016 None
1017 })
1018 .or(fallback)
1019 });
1020 result.ok_or_else(|| SimulationError::ElementNotFound(label.to_owned()))
1021 }
1022
1023 pub fn simulation_capture_pending(&self, window_id: WindowId) -> bool {
1024 self.simulation_waiters.capture_pending(window_id)
1025 }
1026
1027 pub fn take_gpu_resource_queries(&mut self) -> Vec<WindowId> {
1036 let mut live = Vec::new();
1037 for window_id in self.simulation_waiters.gpu_windows() {
1038 if self.contains_window(window_id) {
1039 live.push(window_id);
1040 } else {
1041 for reply in self.simulation_waiters.take_gpu_waiters(window_id) {
1042 let _ = reply.send(Err(SimulationError::WindowNotFound(window_id)));
1043 }
1044 }
1045 }
1046 live
1047 }
1048
1049 pub fn complete_gpu_resource_query(
1052 &mut self,
1053 window_id: WindowId,
1054 summary: GpuResourceSummary,
1055 ) {
1056 self.simulation_waiters.complete_gpu(window_id, summary);
1057 }
1058
1059 #[must_use]
1061 pub fn simulation_frame_pending(&self, window_id: WindowId) -> bool {
1062 self.simulation_waiters.frame_pending(window_id)
1063 }
1064
1065 pub fn complete_simulation_frame(
1069 &mut self,
1070 window_id: WindowId,
1071 presented: bool,
1072 capture: Option<Result<Screenshot, String>>,
1073 ) {
1074 self.simulation_waiters
1075 .complete_frame(window_id, presented, capture);
1076 }
1077
1078 pub fn fail_simulation_frame(&mut self, window_id: WindowId, message: impl Into<String>) {
1080 self.simulation_waiters.fail_frame(window_id, message);
1081 }
1082
1083 pub(crate) fn fail_simulation_window(&mut self, window_id: WindowId) {
1084 self.simulation_waiters.fail_window(window_id);
1085 }
1086
1087 pub(crate) fn stop_simulation(&self) {
1088 self.simulation_bridge.stop();
1089 }
1090}
1091
1092trait RectCenter {
1093 fn center(self) -> Offset;
1094}
1095
1096impl RectCenter for Rect {
1097 fn center(self) -> Offset {
1098 Offset::new(
1099 self.origin.x + self.size.width * 0.5,
1100 self.origin.y + self.size.height * 0.5,
1101 )
1102 }
1103}
1104
1105