1use crate::application_activations::{ActivationRouteBridge, ApplicationActivationService};
2use crate::application_shell::{
3 ApplicationShellService, NativeApplicationShellCompletion, NativeApplicationShellEvent,
4 NativeApplicationShellRequest,
5};
6use crate::application_types::{
7 ApplicationSchedulerCounters, LastWindowPolicy, RestorableWindowFactory,
8 RestorableWindowMetadata, WindowError, WindowRestorationId,
9};
10use crate::context::BuildContext;
11use crate::file_dialogs::{
12 FileDialogBridge, FileDialogCompletionStatus, NativeFileDialogCompletion,
13 NativeFileDialogRequest, QueuedFileDialogRequest, explicitly_unsupported_request,
14 result_matches_request,
15};
16use crate::frame::{FrameStats, Runtime};
17use crate::global_shortcuts::{
18 GlobalShortcutBridge, GlobalShortcutCompletionStatus, GlobalShortcutService,
19 NativeGlobalShortcutCompletion, NativeGlobalShortcutOperation, NativeGlobalShortcutRequest,
20 PendingGlobalShortcutRequest, QueuedGlobalShortcutRequest, drain_queued,
21};
22use crate::profiling::{
23 AccessibilitySnapshot, BudgetStatistics, FrameHistory, FrameRecord, FrameWork, GpuSample,
24 PerformanceHub, PerformanceProfiler, PerformanceSnapshot, ProfilerMode, RenderFrameMetrics,
25 TextCacheSnapshot, WidgetWorkSnapshot, WindowPerformance,
26};
27use crate::request_registry::{CompletionRejection, RequestPhase, RequestRegistry};
28use crate::restoration::{self, RestorationConfig, RestorationDiagnostics};
29use crate::scheduler_counters;
30use crate::simulation::{self, Simulation};
31#[cfg(not(target_arch = "wasm32"))]
32use crate::tasks::TokioHandle;
33use crate::tasks::{self, RuntimeWake, Task, TaskFailure, TaskHandle};
34use crate::transient_presentation::{
35 ResolvedTransientPresentation, TransientPresentationResolution,
36};
37use crate::window_commands::{
38 DisplayCatalog, NativeOperationCompletionStatus, NativeWindowCommand, QueuedNativeRequest,
39 QueuedWindowCommand, WindowCommandBridge, WindowHandle,
40};
41use crate::window_state::{
42 ApplicationDiagnostics, ContentSizeCoordinator, WindowDiagnostics, WindowManager, WindowRecord,
43 WindowRegistry,
44};
45use incular_accessibility::{
46 AccessKitProjection, AccessibilityDiagnostics, MobileAccessibilityProjection,
47 MobileAccessibilityUpdate, NativeAccessibilityUpdate, SemanticActionRequest,
48};
49use incular_config::{Constraints, RuntimeEnvironment};
50use incular_core::RestorationKey;
51use incular_core::time::Instant;
52use incular_platform::{
53 ApplicationActivation, Clipboard, DisplayId, DisplaySnapshot, ExternalDragEvent,
54 ExternalDragResponse, FileDialogError, FileDialogOutcome, FileDialogRequest,
55 FileDialogRequestId, LaunchActivation, NativeOperationCompletion, NativeRequestId,
56 PlatformCapabilities, PlatformEvent, PlatformLifecycle, PlatformOperationError,
57 PlatformOperationErrorKind, PlatformOperationResult, SingleInstancePolicy, TextInputCommand,
58 WindowCommand, WindowEvent, WindowEventKind, WindowId, WindowLifecycle, WindowOperation,
59 WindowOptions,
60};
61use incular_rendering::DisplayList;
62use incular_widgets::internal::{Key, MouseCursor, PlatformMenuBinding, TreeError};
63use incular_widgets::{RouteInformation, RouteInformationProvider, Widget};
64use std::{
65 cell::RefCell,
66 collections::{HashMap, HashSet, VecDeque},
67 rc::Rc,
68 sync::{Arc, RwLock, atomic::Ordering, mpsc},
69};
70use tokio::sync::oneshot;
71
72struct PendingNativeRequest {
73 window_id: WindowId,
74 sender: oneshot::Sender<PlatformOperationResult>,
75}
76
77struct PendingFileDialogRequest {
78 window_id: WindowId,
79 request: FileDialogRequest,
80 sender: Option<oneshot::Sender<Result<FileDialogOutcome, FileDialogError>>>,
81 cancelled: Arc<std::sync::atomic::AtomicBool>,
82}
83
84pub struct Application {
88 pub(crate) scheduler: Rc<RefCell<tasks::TaskScheduler>>,
89 pub(crate) registry: Rc<RefCell<WindowRegistry>>,
90 pub(crate) manager: WindowManager,
91 pub(crate) command_receiver: mpsc::Receiver<QueuedWindowCommand>,
92 pub(crate) request_cancellation_receiver: mpsc::Receiver<NativeRequestId>,
93 pending_native_requests: RequestRegistry<NativeRequestId, PendingNativeRequest>,
94 file_dialog_receiver: mpsc::Receiver<QueuedFileDialogRequest>,
95 file_dialog_cancellation_receiver: mpsc::Receiver<FileDialogRequestId>,
96 file_dialog_bridge: Arc<FileDialogBridge>,
97 pending_file_dialog_requests: RequestRegistry<FileDialogRequestId, PendingFileDialogRequest>,
98 active_file_dialog_requests: HashMap<WindowId, FileDialogRequestId>,
99 queued_file_dialog_requests: HashMap<WindowId, VecDeque<FileDialogRequestId>>,
100 native_file_dialog_requests: VecDeque<NativeFileDialogRequest>,
101 global_shortcut_receiver: mpsc::Receiver<QueuedGlobalShortcutRequest>,
102 global_shortcut_bridge: Arc<GlobalShortcutBridge>,
103 pending_global_shortcut_requests: RequestRegistry<
104 crate::global_shortcuts::GlobalShortcutRequestId,
105 PendingGlobalShortcutRequest,
106 >,
107 native_global_shortcut_requests: VecDeque<NativeGlobalShortcutRequest>,
108 platform_capabilities: Arc<RwLock<PlatformCapabilities>>,
109 application_shell: ApplicationShellService,
110 activations: ApplicationActivationService,
111 launch_activation: LaunchActivation,
112 single_instance_policy: Option<SingleInstancePolicy>,
113 display_catalog: Arc<RwLock<DisplayCatalog>>,
114 pub(crate) simulation_receiver: mpsc::Receiver<simulation::SimulationRequest>,
115 pub(crate) simulation_bridge: Arc<simulation::SimulationBridge>,
116 pub(crate) simulation_waiters: simulation::SimulationWaiters,
117 pub(crate) native_commands: Rc<RefCell<VecDeque<NativeWindowCommand>>>,
118 pub(crate) primary_window: WindowId,
119 pub(crate) last_window_policy: LastWindowPolicy,
120 pub(crate) should_exit: bool,
121 pub(crate) close_request: Option<Box<dyn FnMut(WindowId) -> bool>>,
122 pub(crate) restoration: Option<restoration::RestorationManager>,
123 pub(crate) restoration_window_factories:
124 HashMap<String, (WindowOptions, RestorableWindowFactory)>,
125 pub(crate) profiler: PerformanceProfiler,
126 pub(crate) hub: PerformanceHub,
127 pub(crate) scheduler_counters: ApplicationSchedulerCounters,
128}
129impl Drop for Application {
130 fn drop(&mut self) {
131 self.application_shell.stop();
132 }
133}
134impl Application {
135 pub fn new(
142 build: impl FnMut(&mut BuildContext) -> Widget + 'static,
143 ) -> Result<Self, TreeError> {
144 Self::new_with_options(WindowOptions::default(), build).map_err(|error| match error {
145 WindowError::Tree(error) => error,
146 WindowError::Options(_)
147 | WindowError::Restoration(_)
148 | WindowError::ApplicationStopped => {
149 unreachable!("default application options are valid while constructing")
150 }
151 })
152 }
153
154 pub fn new_with_options(
156 options: WindowOptions,
157 build: impl FnMut(&mut BuildContext) -> Widget + 'static,
158 ) -> Result<Self, WindowError> {
159 Self::new_inner(options, None, None, build)
160 }
161
162 pub fn new_with_restoration(
167 options: WindowOptions,
168 restoration: RestorationConfig,
169 build: impl FnMut(&mut BuildContext) -> Widget + 'static,
170 ) -> Result<Self, WindowError> {
171 Self::new_inner(options, Some(restoration), None, build)
172 }
173
174 pub fn new_restorable(
179 restoration_id: WindowRestorationId,
180 kind: impl Into<String>,
181 options: WindowOptions,
182 restoration: RestorationConfig,
183 build: impl FnMut(&mut BuildContext) -> Widget + 'static,
184 ) -> Result<Self, WindowError> {
185 Self::new_inner(
186 options,
187 Some(restoration),
188 Some(RestorableWindowMetadata::new(restoration_id, kind)?),
189 build,
190 )
191 }
192
193 fn new_inner(
194 options: WindowOptions,
195 restoration_config: Option<RestorationConfig>,
196 primary_restoration: Option<RestorableWindowMetadata>,
197 build: impl FnMut(&mut BuildContext) -> Widget + 'static,
198 ) -> Result<Self, WindowError> {
199 Self::with_primary(tasks::TaskScheduler::new(), restoration_config, |manager| {
200 manager
201 .open_window_with_inner(options, build, primary_restoration)
202 .map(|handle| handle.id())
203 })
204 }
205
206 pub fn from_runtime(
211 mut runtime: Runtime,
212 on_action: impl FnMut(incular_widgets::internal::ActionId) + 'static,
213 ) -> Self {
214 runtime.legacy_action_handler = Some(Box::new(on_action));
215 Self::with_primary(runtime.scheduler.clone(), None, |manager| {
216 Ok(manager.adopt_runtime(runtime))
217 })
218 .expect("adopting a mounted runtime with default window options is infallible")
219 }
220
221 fn with_primary(
222 scheduler: Rc<RefCell<tasks::TaskScheduler>>,
223 restoration_config: Option<RestorationConfig>,
224 create: impl FnOnce(&WindowManager) -> Result<WindowId, WindowError>,
225 ) -> Result<Self, WindowError> {
226 let restoration = restoration_config.map(restoration::RestorationManager::load);
227 if let Some(restoration) = &restoration {
228 restoration.attach_scheduler(&scheduler);
229 }
230 let registry = Rc::new(RefCell::new(WindowRegistry::default()));
231 let native_commands = Rc::new(RefCell::new(VecDeque::new()));
232 let (sender, command_receiver) = mpsc::channel();
233 let (cancellation_sender, request_cancellation_receiver) = mpsc::channel();
234 let (file_dialog_sender, file_dialog_receiver) = mpsc::channel();
235 let (file_dialog_cancellation_sender, file_dialog_cancellation_receiver) = mpsc::channel();
236 let file_dialog_bridge = Arc::new(FileDialogBridge::new(
237 file_dialog_sender,
238 file_dialog_cancellation_sender,
239 ));
240 let (global_shortcut_sender, global_shortcut_receiver) = mpsc::channel();
241 let global_shortcut_bridge = Arc::new(GlobalShortcutBridge::new(global_shortcut_sender));
242 let (simulation_sender, simulation_receiver) = mpsc::channel();
243 let simulation_bridge = Arc::new(simulation::SimulationBridge::new(simulation_sender));
244 let platform_capabilities = Arc::new(RwLock::new(PlatformCapabilities::default()));
245 let application_shell = ApplicationShellService::new(platform_capabilities.clone());
246 let activations = ApplicationActivationService::default();
247 let display_catalog = Arc::new(RwLock::new(DisplayCatalog::default()));
248 let application_lifecycle = Rc::new(std::cell::Cell::new(
249 crate::application_types::ApplicationLifecycle::Starting,
250 ));
251 let bridge = Arc::new(WindowCommandBridge::new(sender, cancellation_sender));
252 let manager = WindowManager {
253 registry: Rc::downgrade(®istry),
254 scheduler: scheduler.clone(),
255 bridge,
256 file_dialog_bridge: file_dialog_bridge.clone(),
257 global_shortcut_bridge: global_shortcut_bridge.clone(),
258 application_shell: application_shell.clone(),
259 native_commands: native_commands.clone(),
260 restoration: restoration.clone(),
261 application_capabilities: platform_capabilities.clone(),
262 displays: display_catalog.clone(),
263 application_lifecycle,
264 };
265 let primary_window = create(&manager)?;
266 Ok(Self {
267 scheduler,
268 registry,
269 manager,
270 command_receiver,
271 request_cancellation_receiver,
272 pending_native_requests: RequestRegistry::new(),
273 file_dialog_receiver,
274 file_dialog_cancellation_receiver,
275 file_dialog_bridge,
276 pending_file_dialog_requests: RequestRegistry::new(),
277 active_file_dialog_requests: HashMap::new(),
278 queued_file_dialog_requests: HashMap::new(),
279 native_file_dialog_requests: VecDeque::new(),
280 global_shortcut_receiver,
281 global_shortcut_bridge,
282 pending_global_shortcut_requests: RequestRegistry::new(),
283 native_global_shortcut_requests: VecDeque::new(),
284 platform_capabilities,
285 application_shell,
286 activations,
287 launch_activation: LaunchActivation::current_process(),
288 single_instance_policy: None,
289 display_catalog,
290 simulation_receiver,
291 simulation_bridge,
292 simulation_waiters: simulation::SimulationWaiters::default(),
293 native_commands,
294 primary_window,
295 last_window_policy: LastWindowPolicy::ExitOnLastWindow,
296 should_exit: false,
297 close_request: None,
298 restoration,
299 restoration_window_factories: HashMap::new(),
300 profiler: PerformanceProfiler::new(ProfilerMode::Normal),
301 hub: PerformanceHub::new(),
302 scheduler_counters: ApplicationSchedulerCounters::default(),
303 })
304 }
305
306 pub fn open_window(
309 &mut self,
310 options: WindowOptions,
311 root: Widget,
312 ) -> Result<WindowHandle, WindowError> {
313 self.manager.open_window(options, root)
314 }
315
316 pub fn open_window_with(
319 &mut self,
320 options: WindowOptions,
321 build: impl FnMut(&mut BuildContext) -> Widget + 'static,
322 ) -> Result<WindowHandle, WindowError> {
323 self.manager.open_window_with(options, build)
324 }
325
326 pub fn open_restorable_window_with(
331 &mut self,
332 restoration_id: WindowRestorationId,
333 kind: impl Into<String>,
334 options: WindowOptions,
335 build: impl FnMut(&mut BuildContext) -> Widget + 'static,
336 ) -> Result<WindowHandle, WindowError> {
337 self.manager
338 .open_restorable_window_with(restoration_id, kind, options, build)
339 }
340
341 pub fn register_restorable_window_factory(
344 &mut self,
345 kind: impl Into<String>,
346 options: WindowOptions,
347 factory: impl Fn(&mut BuildContext) -> Widget + 'static,
348 ) -> Result<(), WindowError> {
349 let kind = kind.into();
350 if kind.trim().is_empty() || kind.chars().any(char::is_control) {
351 return Err(WindowError::Restoration(
352 "window factory kind must be a non-empty printable stable identifier".to_owned(),
353 ));
354 }
355 let factory: RestorableWindowFactory = Rc::new(move |context: &mut BuildContext| {
356 incular_core::hot_reload::call_with(&mut &factory, context)
357 });
358 self.restoration_window_factories
359 .insert(kind, (options, factory));
360 Ok(())
361 }
362
363 pub fn restore_restorable_windows(&mut self) -> Result<usize, WindowError> {
370 let Some(restoration) = self.restoration.clone() else {
371 return Ok(0);
372 };
373 let mut active = self
374 .registry
375 .borrow()
376 .slots
377 .iter()
378 .filter_map(|slot| slot.record.as_ref())
379 .filter_map(|record| record.restoration.as_ref())
380 .map(|metadata| metadata.id.as_key().as_str().to_owned())
381 .collect::<HashSet<_>>();
382 let descriptors = restoration.windows();
383 let mut restored = 0;
384 for descriptor in descriptors {
385 if !active.insert(descriptor.restoration_id.clone()) {
386 continue;
387 }
388 let restoration_id = match WindowRestorationId::new(descriptor.restoration_id) {
389 Ok(id) => id,
390 Err(_) => {
391 restoration.note_skipped_window();
392 continue;
393 }
394 };
395 let Some((options, factory)) = self
396 .restoration_window_factories
397 .get(&descriptor.kind)
398 .cloned()
399 else {
400 restoration.note_skipped_window();
401 continue;
402 };
403 let kind = descriptor.kind;
404 let factory = factory.clone();
405 self.manager
406 .open_restorable_window_with(restoration_id, kind, options, move |cx| {
407 factory(cx)
408 })?;
409 restored += 1;
410 }
411 Ok(restored)
412 }
413
414 #[must_use]
415 pub const fn primary_window(&self) -> WindowId {
416 self.primary_window
417 }
418
419 #[must_use]
420 pub fn active_window_ids(&self) -> Vec<WindowId> {
421 self.registry.borrow().ids()
422 }
423
424 #[must_use]
426 pub fn window_handle(&self, window_id: WindowId) -> Option<WindowHandle> {
427 self.manager.handle(window_id)
428 }
429
430 #[doc(hidden)]
434 #[must_use]
435 pub fn window_mouse_cursor(&self, window_id: WindowId) -> Option<MouseCursor> {
436 self.registry
437 .borrow()
438 .get(window_id)
439 .map(|record| record.runtime.effective_mouse_cursor())
440 }
441
442 #[doc(hidden)]
445 #[must_use]
446 pub fn platform_menu_bindings(&self, window_id: WindowId) -> Vec<PlatformMenuBinding> {
447 self.registry
448 .borrow()
449 .get(window_id)
450 .map_or_else(Vec::new, |record| record.runtime.platform_menu_bindings())
451 }
452
453 #[must_use]
456 pub fn platform_capabilities(&self) -> PlatformCapabilities {
457 *self
458 .platform_capabilities
459 .read()
460 .expect("application capability snapshot lock")
461 }
462
463 #[must_use]
467 pub fn activations(&self) -> ApplicationActivationService {
468 self.activations.clone()
469 }
470
471 #[must_use]
473 pub fn global_shortcuts(&self) -> GlobalShortcutService {
474 GlobalShortcutService::new(
475 self.global_shortcut_bridge.clone(),
476 self.platform_capabilities.clone(),
477 )
478 }
479
480 #[must_use]
484 pub fn application_shell(&self) -> ApplicationShellService {
485 self.application_shell.clone()
486 }
487
488 #[doc(hidden)]
489 pub fn take_native_application_shell_requests(&self) -> Vec<NativeApplicationShellRequest> {
490 self.application_shell.take_native_requests()
491 }
492
493 #[doc(hidden)]
494 pub fn complete_application_shell_request(&self, completion: NativeApplicationShellCompletion) {
495 self.application_shell.complete(completion);
496 }
497
498 #[doc(hidden)]
499 pub fn handle_application_shell_event(&self, event: NativeApplicationShellEvent) -> bool {
500 self.application_shell.handle_native_event(event)
501 }
502
503 pub fn set_launch_activation(&mut self, activation: LaunchActivation) {
507 self.launch_activation = activation;
508 }
509
510 #[doc(hidden)]
511 #[must_use]
512 pub fn launch_activation(&self) -> LaunchActivation {
513 self.launch_activation.clone()
514 }
515
516 pub fn set_single_instance_policy(&mut self, policy: Option<SingleInstancePolicy>) {
519 self.single_instance_policy = policy;
520 }
521
522 #[must_use]
523 pub fn single_instance_policy(&self) -> Option<&SingleInstancePolicy> {
524 self.single_instance_policy.as_ref()
525 }
526
527 #[must_use]
533 pub fn bridge_activation_routes(
534 &self,
535 provider: Rc<dyn RouteInformationProvider>,
536 map: impl Fn(&str) -> Option<RouteInformation> + 'static,
537 ) -> ActivationRouteBridge {
538 ActivationRouteBridge::new(&self.activations, provider, map)
539 }
540
541 #[doc(hidden)]
545 pub fn handle_application_activation(&mut self, activation: ApplicationActivation) {
546 self.activations.publish(activation);
547 }
548
549 #[doc(hidden)]
560 pub fn publish_displays(
561 &mut self,
562 displays: impl IntoIterator<Item = DisplaySnapshot>,
563 primary: Option<DisplayId>,
564 ) {
565 self.display_catalog
566 .write()
567 .expect("display catalog lock")
568 .replace(displays, primary);
569 }
570
571 #[must_use]
572 pub fn displays(&self) -> Vec<DisplaySnapshot> {
573 self.display_catalog
574 .read()
575 .expect("display catalog lock")
576 .all()
577 }
578
579 #[must_use]
580 pub fn display(&self, id: DisplayId) -> Option<DisplaySnapshot> {
581 self.display_catalog
582 .read()
583 .expect("display catalog lock")
584 .get(id)
585 }
586
587 #[must_use]
588 pub fn primary_display(&self) -> Option<DisplaySnapshot> {
589 self.display_catalog
590 .read()
591 .expect("display catalog lock")
592 .primary()
593 }
594
595 #[must_use]
596 pub fn current_display(&self, window_id: WindowId) -> Option<DisplaySnapshot> {
597 let display_id = self
598 .window_handle(window_id)?
599 .observed_state()
600 .current_display?;
601 self.display(display_id)
602 }
603
604 pub fn set_platform_capabilities(&mut self, capabilities: PlatformCapabilities) {
608 *self
609 .platform_capabilities
610 .write()
611 .expect("application capability snapshot lock") = capabilities;
612 for slot in &self.registry.borrow().slots {
613 if let Some(record) = slot.record.as_ref() {
614 *record
615 .capabilities
616 .write()
617 .expect("window capability snapshot lock") = capabilities;
618 }
619 }
620 }
621
622 #[must_use]
624 pub fn window_capabilities(&self, window_id: WindowId) -> Option<PlatformCapabilities> {
625 let registry = self.registry.borrow();
626 let record = registry.get(window_id)?;
627 Some(
628 *record
629 .capabilities
630 .read()
631 .expect("window capability snapshot lock"),
632 )
633 }
634
635 pub fn set_window_capabilities(
638 &mut self,
639 window_id: WindowId,
640 capabilities: PlatformCapabilities,
641 ) -> bool {
642 let registry = self.registry.borrow();
643 let Some(record) = registry.get(window_id) else {
644 return false;
645 };
646 *record
647 .capabilities
648 .write()
649 .expect("window capability snapshot lock") = capabilities;
650 true
651 }
652
653 pub fn set_transient_presentations(
657 &mut self,
658 window_id: WindowId,
659 presentations: Vec<TransientPresentationResolution>,
660 ) -> bool {
661 self.with_window_mut(window_id, |record| {
662 let native = presentations
663 .iter()
664 .filter_map(|presentation| {
665 (presentation.resolved == ResolvedTransientPresentation::Native)
666 .then_some(presentation.id)
667 })
668 .collect::<Vec<_>>();
669 record.runtime.set_native_transient_presentations(native);
670 *record
671 .transient_presentations
672 .write()
673 .expect("transient presentation snapshot lock") = presentations;
674 })
675 .is_some()
676 }
677
678 pub fn set_native_transient_bounds(
681 &mut self,
682 window_id: WindowId,
683 bounds: Option<incular_core::Rect>,
684 ) -> bool {
685 self.with_window_mut(window_id, |record| {
686 record.runtime.set_native_transient_bounds(bounds)
687 })
688 .unwrap_or(false)
689 }
690
691 #[cfg(feature = "devtools")]
695 #[must_use]
696 pub fn devtools_windows(&self) -> Vec<incular_devtools_protocol::WindowSummary> {
697 self.active_window_ids()
698 .into_iter()
699 .filter_map(|id| {
700 self.window_diagnostics(id)
701 .map(|window| incular_devtools_protocol::WindowSummary {
702 id: incular_devtools_protocol::DevWindowId::new(
703 u64::from(id.index()) + 1,
704 u64::from(id.generation()),
705 ),
706 title: window.title,
707 logical_size: [window.logical_size.width, window.logical_size.height],
708 scale_factor: window.scale_factor,
709 })
710 })
711 .collect()
712 }
713
714 #[cfg(feature = "devtools")]
718 pub fn devtools_widget_tree(
719 &self,
720 window: incular_devtools_protocol::DevWindowId,
721 ) -> Result<Vec<incular_devtools_protocol::TreeDelta>, incular_devtools_protocol::ErrorCode>
722 {
723 let id = WindowId::from_parts(
724 window.index().saturating_sub(1) as u32,
725 window.generation() as u32,
726 );
727 let registry = self.registry.borrow();
728 let record = registry
729 .get(id)
730 .ok_or(incular_devtools_protocol::ErrorCode::StaleId)?;
731 let root = record
732 .runtime
733 .tree()
734 .root()
735 .ok_or(incular_devtools_protocol::ErrorCode::UnknownId)?;
736 let (mut nodes, truncated) = record.runtime.tree().devtools_snapshot(root, false);
737 let root = nodes
738 .first()
739 .cloned()
740 .ok_or(incular_devtools_protocol::ErrorCode::UnknownId)?;
741 nodes.remove(0);
742 Ok(vec![incular_devtools_protocol::TreeDelta::Snapshot {
743 window,
744 root: Box::new(root),
745 nodes,
746 truncated,
747 }])
748 }
749
750 #[cfg(feature = "devtools")]
753 pub fn devtools_node_details(
754 &self,
755 id: incular_devtools_protocol::DevWidgetId,
756 ) -> Result<incular_devtools_protocol::NodeDetails, incular_devtools_protocol::ErrorCode> {
757 for window in self.active_window_ids() {
758 let registry = self.registry.borrow();
759 let Some(record) = registry.get(window) else {
760 continue;
761 };
762 let tree = record.runtime.tree();
763 if let Some(element) = tree.devtools_resolve_id(id) {
764 let mut details = tree
765 .devtools_node_details(
766 element,
767 id,
768 incular_devtools_protocol::DevWindowId::new(
769 u64::from(window.index()) + 1,
770 u64::from(window.generation()),
771 ),
772 )
773 .ok_or(incular_devtools_protocol::ErrorCode::StaleId)?;
774 let root = record.runtime.devtools_reactive_root();
775 details.consumed_signals = crate::devtools_registry::with_all(|signals| {
776 signals
777 .iter()
778 .filter(|(_, registration)| {
779 (registration.subscribers)().into_iter().any(
780 |(subscriber_root, subscriber)| {
781 subscriber_root == root && subscriber == element
782 },
783 )
784 })
785 .map(|(signal_id, _)| {
786 incular_devtools_protocol::DevSignalId::new(*signal_id, 1)
787 })
788 .collect()
789 });
790 return Ok(details);
791 }
792 }
793 Err(incular_devtools_protocol::ErrorCode::StaleId)
794 }
795
796 #[cfg(feature = "devtools")]
799 pub fn devtools_edit_property(
800 &mut self,
801 id: incular_devtools_protocol::DevWidgetId,
802 name: &str,
803 value: &incular_devtools_protocol::DebugValue,
804 ) -> bool {
805 let mut registry = self.registry.borrow_mut();
806 for slot in &mut registry.slots {
807 let Some(record) = slot.record.as_mut() else {
808 continue;
809 };
810 if record.runtime.devtools_edit_property(id, name, value) {
811 return true;
812 }
813 }
814 false
815 }
816
817 #[cfg(feature = "devtools")]
820 pub fn devtools_revert_property(
821 &mut self,
822 id: incular_devtools_protocol::DevWidgetId,
823 name: &str,
824 original: &incular_devtools_protocol::DebugValue,
825 ) -> bool {
826 let mut registry = self.registry.borrow_mut();
827 for slot in &mut registry.slots {
828 let Some(record) = slot.record.as_mut() else {
829 continue;
830 };
831 if record.runtime.devtools_revert_property(id, name, original) {
832 return true;
833 }
834 }
835 false
836 }
837
838 #[cfg(feature = "devtools")]
840 pub fn devtools_node_bounds(
841 &self,
842 id: incular_devtools_protocol::DevWidgetId,
843 ) -> Option<[f32; 4]> {
844 for window in self.active_window_ids() {
845 let registry = self.registry.borrow();
846 let Some(record) = registry.get(window) else {
847 continue;
848 };
849 let tree = record.runtime.tree();
850 if let Some(element) = tree.devtools_resolve_id(id) {
851 return tree.element_bounds(element).map(|bounds| {
852 [
853 bounds.origin.x,
854 bounds.origin.y,
855 bounds.size.width,
856 bounds.size.height,
857 ]
858 });
859 }
860 }
861 None
862 }
863
864 #[cfg(feature = "devtools")]
866 pub fn devtools_node_overlay_geometry(
867 &self,
868 id: incular_devtools_protocol::DevWidgetId,
869 ) -> Option<incular_widgets::devtools::DevOverlayGeometry> {
870 for window in self.active_window_ids() {
871 let registry = self.registry.borrow();
872 let Some(record) = registry.get(window) else {
873 continue;
874 };
875 let tree = record.runtime.tree();
876 if let Some(element) = tree.devtools_resolve_id(id) {
877 return tree.devtools_overlay_geometry(element);
878 }
879 }
880 None
881 }
882
883 #[cfg(feature = "devtools")]
887 pub fn devtools_window_layout_bounds(&self, window: WindowId, limit: usize) -> Vec<[f32; 4]> {
888 self.registry
889 .borrow()
890 .get(window)
891 .map(|record| record.runtime.tree().devtools_layout_bounds(limit))
892 .unwrap_or_default()
893 }
894
895 #[cfg(feature = "devtools")]
896 pub fn devtools_window_semantics_bounds(
897 &self,
898 window: WindowId,
899 limit: usize,
900 ) -> Vec<[f32; 4]> {
901 self.registry
902 .borrow()
903 .get(window)
904 .map(|record| record.runtime.tree().devtools_semantics_bounds(limit))
905 .unwrap_or_default()
906 }
907
908 #[cfg(feature = "devtools")]
909 pub fn devtools_window_hit_regions(&self, window: WindowId, limit: usize) -> Vec<[f32; 4]> {
910 self.registry
911 .borrow()
912 .get(window)
913 .map(|record| record.runtime.tree().devtools_hit_regions(limit))
914 .unwrap_or_default()
915 }
916
917 #[cfg(feature = "devtools")]
918 pub fn devtools_window_scroll_viewports(
919 &self,
920 window: WindowId,
921 limit: usize,
922 ) -> Vec<[f32; 4]> {
923 self.registry
924 .borrow()
925 .get(window)
926 .map(|record| record.runtime.tree().devtools_scroll_viewports(limit))
927 .unwrap_or_default()
928 }
929
930 #[cfg(feature = "devtools")]
931 pub fn devtools_window_layer_bounds(&self, window: WindowId, limit: usize) -> Vec<[f32; 4]> {
932 self.registry
933 .borrow()
934 .get(window)
935 .map(|record| record.runtime.tree().devtools_layer_bounds(limit))
936 .unwrap_or_default()
937 }
938
939 #[cfg(feature = "devtools")]
941 pub fn devtools_window_phase_nodes(
942 &self,
943 window: WindowId,
944 limit: usize,
945 ) -> Vec<incular_widgets::devtools::DevPhaseNode> {
946 self.registry
947 .borrow()
948 .get(window)
949 .map(|record| record.runtime.tree().devtools_phase_nodes(limit))
950 .unwrap_or_default()
951 }
952
953 #[cfg(feature = "devtools")]
957 pub fn devtools_begin_deep_trace(&mut self, window: WindowId, max_events: usize) -> bool {
958 self.with_window_mut(window, |record| {
959 record.runtime.tree_mut().begin_deep_trace(max_events);
960 })
961 .is_some()
962 }
963
964 #[cfg(feature = "devtools")]
966 pub fn devtools_take_deep_trace(
967 &mut self,
968 window: WindowId,
969 frame: u64,
970 ) -> Option<incular_devtools_protocol::DeepFrameTrace> {
971 let (events, dropped_events) = self
972 .with_window_mut(window, |record| record.runtime.tree_mut().take_deep_trace())
973 .flatten()?;
974 Some(incular_devtools_protocol::DeepFrameTrace {
975 window: incular_devtools_protocol::DevWindowId::new(
976 u64::from(window.index()) + 1,
977 u64::from(window.generation()),
978 ),
979 frame,
980 truncated: dropped_events > 0,
981 dropped_events,
982 events,
983 })
984 }
985
986 #[cfg(feature = "devtools")]
989 pub fn devtools_subtree_layout_bounds(
990 &self,
991 id: incular_devtools_protocol::DevWidgetId,
992 limit: usize,
993 ) -> Option<(WindowId, Vec<[f32; 4]>)> {
994 for window in self.active_window_ids() {
995 let registry = self.registry.borrow();
996 let Some(record) = registry.get(window) else {
997 continue;
998 };
999 let tree = record.runtime.tree();
1000 if let Some(element) = tree.devtools_resolve_id(id) {
1001 return Some((window, tree.devtools_subtree_layout_bounds(element, limit)));
1002 }
1003 }
1004 None
1005 }
1006
1007 #[cfg(feature = "devtools")]
1010 pub fn devtools_hit_test(
1011 &self,
1012 window: WindowId,
1013 point: incular_core::Offset,
1014 ) -> Option<(incular_devtools_protocol::DevWidgetId, [f32; 4])> {
1015 let registry = self.registry.borrow();
1016 let record = registry.get(window)?;
1017 let tree = record.runtime.tree();
1018 let element = tree.devtools_deepest_at(point)?;
1019 let id = tree.devtools_id_for_element(element)?;
1020 let bounds = tree.element_bounds(element)?;
1021 Some((
1022 id,
1023 [
1024 bounds.origin.x,
1025 bounds.origin.y,
1026 bounds.size.width,
1027 bounds.size.height,
1028 ],
1029 ))
1030 }
1031
1032 #[cfg(feature = "devtools")]
1036 #[must_use]
1037 pub fn devtools_resource_counts(&self) -> incular_devtools_protocol::ResourceCounts {
1038 let mut counts = incular_devtools_protocol::ResourceCounts::default();
1039 for window in self.active_window_ids() {
1040 let registry = self.registry.borrow();
1041 let Some(record) = registry.get(window) else {
1042 continue;
1043 };
1044 let tree = record.runtime.tree();
1045 counts.elements += tree.element_count();
1046 counts.render_objects += tree.render_object_count();
1047 counts.layers += usize::try_from(tree.compositor_diagnostics().layers).unwrap_or(0);
1048 counts.semantics_nodes += tree.semantics().len();
1049 counts.tasks_active += record.runtime.runtime_diagnostics().active_tracked_tasks;
1050 }
1051 crate::devtools_registry::with_all(|signals| counts.signals = signals.len());
1052 counts
1053 }
1054
1055 #[cfg(feature = "devtools")]
1058 #[must_use]
1059 pub fn devtools_signals(&self) -> Vec<incular_devtools_protocol::SignalSummary> {
1060 crate::devtools_registry::with_all(|signals| {
1061 signals
1062 .iter()
1063 .map(|(id, registration)| {
1064 let (_, last_write) = (registration.last_write)();
1065 let writes = (registration.write_count)();
1066 incular_devtools_protocol::SignalSummary {
1067 id: incular_devtools_protocol::DevSignalId::new(*id, 1),
1068 name: registration.name.clone(),
1069 type_name: registration.type_name.to_owned(),
1070 generation: writes,
1071 write_count: writes,
1072 subscriber_count: (registration.subscriber_count)(),
1073 last_write_summary: last_write,
1074 editable: registration.editable_kind.is_some(),
1075 }
1076 })
1077 .collect()
1078 })
1079 }
1080
1081 #[cfg(feature = "devtools")]
1086 #[must_use]
1087 pub fn devtools_signal_subscribers(
1088 &self,
1089 signal: incular_devtools_protocol::DevSignalId,
1090 ) -> Vec<incular_devtools_protocol::SignalSubscriber> {
1091 let entries = crate::devtools_registry::with_all(|signals| {
1092 signals
1093 .iter()
1094 .find(|(id, _)| *id == signal.index())
1095 .and_then(|(_, registration)| {
1096 (signal.generation() == 1).then(|| (registration.subscribers)())
1097 })
1098 .unwrap_or_default()
1099 });
1100 if entries.is_empty() {
1101 return Vec::new();
1102 }
1103
1104 let windows = self.active_window_ids();
1105 let registry = self.registry.borrow();
1106 windows
1107 .into_iter()
1108 .flat_map(|window| {
1109 let Some(record) = registry.get(window) else {
1110 return Vec::new();
1111 };
1112 let root = record.runtime.devtools_reactive_root();
1113 let tree = record.runtime.tree();
1114 entries
1115 .iter()
1116 .filter(|(entry_root, _)| *entry_root == root)
1117 .filter_map(|(_, element)| {
1118 tree.devtools_id_for_element(*element).map(|dev_id| {
1119 incular_devtools_protocol::SignalSubscriber {
1120 signal,
1121 element: dev_id,
1122 path: tree.devtools_element_path(*element),
1123 }
1124 })
1125 })
1126 .collect::<Vec<_>>()
1127 })
1128 .collect()
1129 }
1130
1131 #[cfg(feature = "devtools")]
1135 pub fn devtools_edit_signal(
1136 &self,
1137 signal: incular_devtools_protocol::DevSignalId,
1138 value: &incular_devtools_protocol::EditableValue,
1139 ) -> bool {
1140 if signal.generation() != 1 {
1141 return false;
1142 }
1143 crate::devtools_registry::with_all(|signals| {
1144 signals
1145 .iter()
1146 .find(|(id, registration)| {
1147 *id == signal.index() && registration.editable_kind.is_some()
1148 })
1149 .is_some_and(|(_, registration)| (registration.apply_edit)(value))
1150 })
1151 }
1152
1153 #[must_use]
1154 pub fn contains_window(&self, window_id: WindowId) -> bool {
1155 self.registry.borrow().contains(window_id)
1156 }
1157
1158 #[must_use]
1163 pub fn transient_surfaces(
1164 &self,
1165 window_id: WindowId,
1166 ) -> Vec<incular_widgets::TransientSurfaceSnapshot> {
1167 self.registry
1168 .borrow()
1169 .get(window_id)
1170 .map_or_else(Vec::new, |record| record.runtime.transient_surfaces())
1171 }
1172
1173 pub fn set_last_window_policy(&mut self, policy: LastWindowPolicy) {
1174 self.last_window_policy = policy;
1175 }
1176
1177 #[must_use]
1178 pub const fn last_window_policy(&self) -> LastWindowPolicy {
1179 self.last_window_policy
1180 }
1181
1182 pub fn on_close_request(&mut self, callback: impl FnMut(WindowId) -> bool + 'static) {
1186 self.close_request = Some(Box::new(callback));
1187 }
1188
1189 pub fn set_wake_handler(&mut self, wake: Arc<dyn RuntimeWake>) {
1190 self.scheduler.borrow_mut().set_wake(wake.clone());
1191 self.manager.bridge.set_wake(wake.clone());
1192 self.file_dialog_bridge.set_wake(wake.clone());
1193 self.global_shortcut_bridge.set_wake(wake.clone());
1194 self.application_shell.set_wake(wake.clone());
1195 self.simulation_bridge.set_wake(wake);
1196 }
1197
1198 #[must_use]
1202 pub fn simulation(&self) -> Simulation {
1203 Simulation::new(self.simulation_bridge.clone(), self.primary_window)
1204 }
1205
1206 pub(crate) fn with_window_mut<R>(
1207 &mut self,
1208 window_id: WindowId,
1209 callback: impl FnOnce(&mut WindowRecord) -> R,
1210 ) -> Option<R> {
1211 let mut record = self.registry.borrow_mut().take(window_id)?;
1212 let result = callback(&mut record);
1213 self.registry.borrow_mut().restore(window_id, record);
1217 Some(result)
1218 }
1219 pub fn spawn<F, T>(&self, future: F) -> Task<T>
1222 where
1223 F: std::future::Future<Output = T> + Send + 'static,
1224 T: Send + 'static,
1225 {
1226 tasks::TaskScheduler::spawner(&self.scheduler).spawn(future)
1227 }
1228
1229 pub fn spawn_blocking<T>(
1230 &self,
1231 work: impl FnOnce() -> T + Send + 'static,
1232 complete: impl FnOnce(Result<T, TaskFailure>, &mut Runtime) + 'static,
1233 ) -> TaskHandle
1234 where
1235 T: Send + 'static,
1236 {
1237 self.scheduler.borrow_mut().spawn_blocking(work, complete)
1238 }
1239
1240 pub fn dispatch(&self, callback: impl FnOnce(&mut Runtime) + Send + 'static) {
1243 self.scheduler
1244 .borrow()
1245 .dispatcher_for(Some(self.primary_window))
1246 .dispatch(callback);
1247 }
1248
1249 pub fn spawn_into<F, T>(
1250 &self,
1251 future: F,
1252 complete: impl FnOnce(Result<T, TaskFailure>, &mut Runtime) + 'static,
1253 ) -> TaskHandle
1254 where
1255 F: std::future::Future<Output = T> + Send + 'static,
1256 T: Send + 'static,
1257 {
1258 tasks::TaskScheduler::spawner(&self.scheduler).spawn_into(future, complete)
1259 }
1260
1261 #[must_use]
1262 #[cfg(not(target_arch = "wasm32"))]
1263 pub fn tokio_handle(&self) -> TokioHandle {
1264 self.scheduler.borrow().tokio_handle()
1265 }
1266
1267 pub fn process_runtime_work_at(&mut self, _now: Instant) {
1270 let work = self.scheduler.borrow_mut().take_turn();
1271 for item in work {
1272 let requested_window = match &item {
1273 tasks::UiWork::Dispatch { window_id, .. } => *window_id,
1274 tasks::UiWork::Task(pending) => pending.control.window_id(),
1275 };
1276 let window_id = requested_window.or_else(|| {
1281 if self.contains_window(self.primary_window) {
1282 Some(self.primary_window)
1283 } else {
1284 self.active_window_ids().into_iter().next()
1285 }
1286 });
1287 let Some(window_id) = window_id else {
1288 self.discard_stale_ui_work(item);
1289 continue;
1290 };
1291 let record = self.registry.borrow_mut().take(window_id);
1292 if let Some(mut record) = record {
1293 record.runtime.process_ui_work(&self.scheduler, item);
1294 self.registry.borrow_mut().restore(window_id, record);
1295 } else {
1296 self.discard_stale_ui_work(item);
1297 }
1298 }
1299 self.drain_window_commands();
1300 self.drain_native_request_cancellations();
1301 self.drain_file_dialog_requests();
1302 self.drain_file_dialog_cancellations();
1303 self.drain_global_shortcut_requests();
1304 self.process_simulation_requests();
1305 }
1306
1307 pub fn process_runtime_work(&mut self) {
1308 self.process_runtime_work_at(Instant::now());
1309 }
1310
1311 fn discard_stale_ui_work(&mut self, work: tasks::UiWork) {
1312 if let tasks::UiWork::Task(pending) = work {
1313 self.scheduler.borrow().detach_scope(&pending.control);
1314 (pending.discard)();
1315 self.scheduler
1316 .borrow_mut()
1317 .record_discard(pending.blocking, true);
1318 }
1319 }
1320
1321 #[must_use]
1326 pub fn handle_external_drag_event(
1327 &mut self,
1328 window_id: WindowId,
1329 event: ExternalDragEvent,
1330 ) -> ExternalDragResponse {
1331 self.with_window_mut(window_id, |record| {
1332 record.input_events = record.input_events.wrapping_add(1);
1333 record.runtime.handle_external_drag(event)
1334 })
1335 .unwrap_or_default()
1336 }
1337
1338 pub fn handle_window_event(&mut self, event: WindowEvent) {
1340 let window_id = event.window_id;
1341 match event.kind {
1342 WindowEventKind::Platform(PlatformEvent::CloseRequested) => {
1343 self.request_close(window_id);
1344 }
1345 WindowEventKind::Platform(PlatformEvent::Metrics(metrics)) => {
1346 let _ = self.with_window_mut(window_id, |record| {
1347 if record.metrics != metrics {
1348 record.metrics = metrics;
1349 record.surface_generation = record
1350 .surface_generation
1351 .checked_add(1)
1352 .expect("runtime surface generation exhausted");
1353 record.runtime.update_window_metrics(metrics);
1354 }
1355 });
1356 self.manager.sync_restorable_windows(true);
1357 }
1358 WindowEventKind::Platform(PlatformEvent::Environment(environment)) => {
1359 let _ = self.with_window_mut(window_id, |record| {
1360 record.runtime.set_environment(environment);
1361 });
1362 }
1363 WindowEventKind::Platform(PlatformEvent::Input(input)) => {
1364 let _ = self.with_window_mut(window_id, |record| {
1365 record.input_events = record.input_events.wrapping_add(1);
1366 let _ = record.runtime.handle_input(input);
1367 });
1368 }
1369 WindowEventKind::Platform(PlatformEvent::TextInputAction(action)) => {
1370 let _ = self.with_window_mut(window_id, |record| {
1371 record.input_events = record.input_events.wrapping_add(1);
1372 let _ = record.runtime.handle_text_input_action(action);
1373 });
1374 }
1375 WindowEventKind::Platform(PlatformEvent::Lifecycle(lifecycle)) => {
1376 self.handle_application_lifecycle(lifecycle);
1377 }
1378 WindowEventKind::Platform(PlatformEvent::ExternalDrag(event)) => {
1379 let _ = self.handle_external_drag_event(window_id, event);
1380 }
1381 WindowEventKind::Lifecycle(lifecycle) => {
1382 let _ = self.with_window_mut(window_id, |record| {
1383 record.lifecycle = lifecycle;
1384 match lifecycle {
1385 WindowLifecycle::Focused => {
1386 record.native_focused = true;
1387 let mut environment = record.runtime.environment();
1388 environment.window_focused = true;
1389 record.runtime.set_environment(environment);
1390 }
1391 WindowLifecycle::Unfocused => {
1392 record.native_focused = false;
1393 record.runtime.dismiss_transients(
1394 incular_widgets::TransientDismissReason::ParentDeactivated,
1395 );
1396 let mut environment = record.runtime.environment();
1397 environment.window_focused = false;
1398 record.runtime.set_environment(environment);
1399 }
1400 WindowLifecycle::Visible
1401 | WindowLifecycle::Hidden
1402 | WindowLifecycle::Creating
1403 | WindowLifecycle::Closing
1404 | WindowLifecycle::Closed => {}
1405 }
1406 });
1407 }
1408 WindowEventKind::RedrawRequested => {}
1409 WindowEventKind::StateChanged(state) => {
1410 let _ = self.with_window_mut(window_id, |record| {
1411 *record
1412 .observed_state
1413 .write()
1414 .expect("window observed-state snapshot lock") = state;
1415 });
1416 }
1417 }
1418 }
1419
1420 pub fn handle_application_lifecycle(&mut self, lifecycle: PlatformLifecycle) {
1424 self.manager.application_lifecycle.set(match lifecycle {
1425 PlatformLifecycle::Active | PlatformLifecycle::Resumed => {
1426 crate::application_types::ApplicationLifecycle::Active
1427 }
1428 PlatformLifecycle::Inactive => crate::application_types::ApplicationLifecycle::Inactive,
1429 PlatformLifecycle::Suspended => {
1430 crate::application_types::ApplicationLifecycle::Suspended
1431 }
1432 PlatformLifecycle::Stopping => crate::application_types::ApplicationLifecycle::Stopping,
1433 });
1434 if matches!(lifecycle, PlatformLifecycle::Stopping) {
1435 self.shutdown();
1436 return;
1437 }
1438 let ids = self.active_window_ids();
1439 for id in ids {
1440 let _ = self.with_window_mut(id, |record| {
1441 let _ = record
1442 .runtime
1443 .handle_platform_event(PlatformEvent::Lifecycle(lifecycle));
1444 });
1445 }
1446 if matches!(lifecycle, PlatformLifecycle::Suspended) {
1447 self.flush_restoration_and_wait();
1448 }
1449 }
1450
1451 pub fn record_platform_operation_error(
1454 &mut self,
1455 window_id: WindowId,
1456 error: PlatformOperationError,
1457 ) -> bool {
1458 self.with_window_mut(window_id, |record| {
1459 record.last_platform_error = Some(error);
1460 })
1461 .is_some()
1462 }
1463
1464 pub fn set_window_environment(
1465 &mut self,
1466 window_id: WindowId,
1467 environment: RuntimeEnvironment,
1468 ) -> bool {
1469 self.with_window_mut(window_id, |record| {
1470 record.runtime.set_environment(environment)
1471 })
1472 .unwrap_or(false)
1473 }
1474
1475 pub fn set_window_clipboard(&mut self, window_id: WindowId, clipboard: Box<dyn Clipboard>) {
1479 let _ = self.with_window_mut(window_id, |record| {
1480 record.runtime.set_clipboard(clipboard);
1481 });
1482 }
1483
1484 pub fn take_window_text_input_commands(
1487 &mut self,
1488 window_id: WindowId,
1489 ) -> Vec<TextInputCommand> {
1490 self.with_window_mut(window_id, |record| {
1491 record.runtime.take_text_input_commands()
1492 })
1493 .unwrap_or_default()
1494 }
1495
1496 #[must_use]
1497 pub fn frame_requested(&self, window_id: WindowId) -> bool {
1498 self.registry
1499 .borrow()
1500 .slots
1501 .get(window_id.index() as usize)
1502 .filter(|slot| slot.generation == window_id.generation())
1503 .and_then(|slot| slot.record.as_ref())
1504 .is_some_and(|record| record.runtime.frame_requested())
1505 }
1506
1507 pub fn note_frame_requested(&mut self, window_id: WindowId) {
1508 scheduler_counters::REDRAW_REQUESTS.fetch_add(1, Ordering::Relaxed);
1509 self.scheduler_counters.redraw_requests =
1510 self.scheduler_counters.redraw_requests.wrapping_add(1);
1511 let _ = self.with_window_mut(window_id, |record| {
1512 record.requested_frames = record.requested_frames.wrapping_add(1);
1513 });
1514 }
1515
1516 pub fn note_runtime_wake(&mut self) {
1520 scheduler_counters::RUNTIME_WAKES.fetch_add(1, Ordering::Relaxed);
1521 self.scheduler_counters.runtime_wakes =
1522 self.scheduler_counters.runtime_wakes.wrapping_add(1);
1523 }
1524
1525 pub fn run_window_frame_at(
1526 &mut self,
1527 window_id: WindowId,
1528 constraints: Constraints,
1529 now: Instant,
1530 ) -> Result<Option<(DisplayList, FrameStats)>, TreeError> {
1531 let mut sensitivity_update = None;
1532 let mut content_size_request = None;
1533 let outcome = self
1534 .with_window_mut(window_id, |record| {
1535 if record.metrics.physical_size.is_zero() {
1536 record.skipped_frames = record.skipped_frames.wrapping_add(1);
1537 scheduler_counters::FRAMES_SKIPPED.fetch_add(1, Ordering::Relaxed);
1538 Ok(None)
1539 } else {
1540 let frame_constraints =
1541 ContentSizeCoordinator::layout_constraints(&record.options, constraints);
1542 record
1543 .runtime
1544 .run_frame_at(frame_constraints, now)
1545 .map(|(list, stats)| {
1546 let sensitivity = record.runtime.content_sensitivity();
1547 if record.last_content_sensitivity != Some(sensitivity) {
1548 record.last_content_sensitivity = Some(sensitivity);
1549 sensitivity_update = Some(sensitivity);
1550 }
1551 content_size_request = record.content_sizing.reconcile(
1552 &record.options,
1553 record.metrics,
1554 record.runtime.tree().root_layout_size(),
1555 );
1556 record.last_frame = FrameRecord {
1557 frame: 0,
1558 timings: stats.timings,
1559 work: FrameWork {
1560 updated_elements: stats.updated_elements as u64,
1561 rebuilt_elements: stats.rebuilt_elements,
1562 laid_out_render_objects: stats.laid_out_render_objects,
1563 repainted_render_objects: stats.repainted_render_objects,
1564 composited_layers: stats.composited,
1565 active_animations: stats.active_animations,
1566 display_list_commands: stats.display_list_commands,
1567 requested_another_frame: stats.requested_another_frame,
1568 },
1569 over_budget: false,
1570 };
1571 Some((list, stats))
1572 })
1573 }
1574 })
1575 .unwrap_or(Ok(None));
1576 if let Some(size) = content_size_request {
1577 self.native_commands
1578 .borrow_mut()
1579 .push_back(NativeWindowCommand::Operate(WindowCommand::new(
1580 window_id,
1581 WindowOperation::SetLogicalSize(size),
1582 )));
1583 }
1584 if let Some(sensitivity) = sensitivity_update {
1585 self.native_commands
1586 .borrow_mut()
1587 .push_back(NativeWindowCommand::Operate(WindowCommand::new(
1588 window_id,
1589 WindowOperation::SetContentSensitivity(sensitivity),
1590 )));
1591 }
1592 if matches!(outcome, Ok(None)) && self.contains_window(window_id) {
1593 self.scheduler_counters.frames_skipped =
1594 self.scheduler_counters.frames_skipped.wrapping_add(1);
1595 }
1596 if matches!(outcome, Ok(Some(_))) {
1597 scheduler_counters::FRAMES_STARTED.fetch_add(1, Ordering::Relaxed);
1598 self.scheduler_counters.frames_started =
1599 self.scheduler_counters.frames_started.wrapping_add(1);
1600 }
1601 outcome
1602 }
1603
1604 pub fn sync_accessibility(
1610 &mut self,
1611 window_id: WindowId,
1612 projection: &mut AccessKitProjection,
1613 ) -> Option<NativeAccessibilityUpdate> {
1614 let update = self
1615 .with_window_mut(window_id, |record| {
1616 projection.sync(
1617 record.runtime.tree().semantics(),
1618 record.metrics.scale_factor,
1619 )
1620 })
1621 .flatten();
1622 let diagnostics = projection.diagnostics();
1623 let _ = self.with_window_mut(window_id, |record| {
1624 record.accessibility = diagnostics;
1625 });
1626 update
1627 }
1628
1629 pub fn sync_semantics_projection(
1633 &mut self,
1634 window_id: WindowId,
1635 projection: &mut MobileAccessibilityProjection,
1636 ) -> Option<MobileAccessibilityUpdate> {
1637 let update = self
1638 .with_window_mut(window_id, |record| {
1639 projection.sync(record.runtime.tree().semantics())
1640 })
1641 .flatten();
1642 let diagnostics = projection.diagnostics();
1643 let _ = self.with_window_mut(window_id, |record| {
1644 record.accessibility = diagnostics;
1645 });
1646 update
1647 }
1648
1649 pub fn dispatch_accessibility_action(
1653 &mut self,
1654 window_id: WindowId,
1655 request: SemanticActionRequest,
1656 ) -> bool {
1657 self.with_window_mut(window_id, |record| {
1658 record
1659 .runtime
1660 .dispatch_semantic_action(request.node, request.action)
1661 })
1662 .unwrap_or(false)
1663 }
1664
1665 pub fn set_semantics_enabled(&mut self, window_id: WindowId, enabled: bool) {
1669 let _ = self.with_window_mut(window_id, |record| {
1670 let runtime = &mut record.runtime;
1671 if runtime.tree().semantics_enabled() != enabled {
1672 runtime.tree_mut().set_semantics_enabled(enabled);
1673 if enabled {
1674 runtime.request_frame();
1675 }
1676 }
1677 });
1678 }
1679
1680 pub fn set_accessibility_diagnostics(
1683 &mut self,
1684 window_id: WindowId,
1685 diagnostics: AccessibilityDiagnostics,
1686 ) {
1687 let _ = self.with_window_mut(window_id, |record| {
1688 record.accessibility = diagnostics;
1689 });
1690 }
1691
1692 pub fn set_profiler_mode(&mut self, mode: ProfilerMode) {
1695 self.profiler.set_mode(mode);
1696 }
1697
1698 pub fn set_animation_time_scale(&mut self, scale: f32) {
1701 for window in self.active_window_ids() {
1702 let _ = self.with_window_mut(window, |record| {
1703 record.runtime.set_animation_time_scale(scale);
1704 });
1705 }
1706 }
1707
1708 #[cfg(feature = "hot-reload")]
1714 pub fn reassemble(&mut self) {
1715 for window in self.active_window_ids() {
1716 let _ = self.with_window_mut(window, |record| record.runtime.reassemble());
1717 }
1718 }
1719
1720 #[cfg(feature = "hot-reload")]
1728 pub unsafe fn apply_hot_patch(
1729 &mut self,
1730 patch: crate::HotPatch,
1731 ) -> Result<(), crate::HotPatchError> {
1732 unsafe { patch.apply() }?;
1734 self.reassemble();
1735 Ok(())
1736 }
1737
1738 #[must_use]
1739 pub const fn profiler_mode(&self) -> ProfilerMode {
1740 self.profiler.mode()
1741 }
1742
1743 pub fn set_refresh_rate_hz(&mut self, hz: Option<f32>) {
1745 self.profiler.set_refresh_rate_hz(hz);
1746 }
1747
1748 pub fn note_render_metrics(
1751 &mut self,
1752 window_id: WindowId,
1753 mut render: RenderFrameMetrics,
1754 gpu: Option<GpuSample>,
1755 ) {
1756 let frame_id = self.profiler.next_frame_id();
1757 render.frame = frame_id;
1758 let mut completed = None;
1759 {
1760 let mut registry = self.registry.borrow_mut();
1761 if let Some(slot) = registry.slots.get_mut(window_id.index() as usize)
1762 && slot.generation == window_id.generation()
1763 && let Some(record) = slot.record.as_mut()
1764 {
1765 record.render_metrics = render;
1766 record.gpu_sample = gpu;
1767 record.last_frame.frame = frame_id;
1768 let mut timings = record.last_frame.timings;
1770 timings.cpu_total = timings
1771 .cpu_total
1772 .saturating_add(render.prepare_us)
1773 .saturating_add(render.encode_us)
1774 .saturating_add(render.submit_us);
1775 record.last_frame.timings = timings;
1776 completed = Some(record.last_frame);
1777 }
1778 }
1779 if let Some(record) = completed {
1780 self.profiler.record(record);
1781 }
1782 if self.hub.observed()
1785 && self.profiler.mode() >= ProfilerMode::Diagnostic
1786 && self.hub.publish_due(std::time::Duration::from_millis(200))
1787 {
1788 self.hub.publish(self.performance_snapshot());
1789 }
1790 }
1791
1792 #[must_use]
1795 pub const fn performance_hub(&self) -> &PerformanceHub {
1796 &self.hub
1797 }
1798
1799 pub fn install_performance_overlay(&mut self, window_id: WindowId) -> Result<(), TreeError> {
1807 let hub = self.hub.clone();
1808 let installed = self.with_window_mut(window_id, |record| {
1809 let tree = record.runtime.tree();
1810 let Some(target) = tree.element_with_key(&Key::from(PERFORMANCE_OVERLAY_KEY)) else {
1811 return Err(TreeError::MissingElement(
1812 tree.root().expect("mounted window root"),
1813 ));
1814 };
1815 record.runtime.register_builder(target, move || {
1816 hub.set_observed(true);
1817 let _version = hub.version();
1819 overlay_widget(&hub.snapshot())
1820 })?;
1821 Ok(())
1822 });
1823 match installed {
1824 Some(result) => result,
1825 None => Err(TreeError::WindowUnknown),
1826 }
1827 }
1828
1829 #[must_use]
1830 pub fn profiler_history(&self) -> &FrameHistory {
1831 self.profiler.history()
1832 }
1833
1834 #[must_use]
1838 pub fn performance_snapshot(&self) -> PerformanceSnapshot {
1839 let mut windows = Vec::new();
1840 let registry = self.registry.borrow();
1841 for slot in ®istry.slots {
1842 let Some(record) = slot.record.as_ref() else {
1843 continue;
1844 };
1845 windows.push(WindowPerformance {
1846 requested_frames: record.requested_frames,
1847 presented_frames: record.presented_frames,
1848 skipped_frames: record.skipped_frames,
1849 latest: Some(record.last_frame),
1850 render: record.render_metrics,
1851 gpu: record.gpu_sample,
1852 });
1853 }
1854 let mut widgets = WidgetWorkSnapshot::default();
1857 let mut text = TextCacheSnapshot::default();
1858 let mut accessibility = AccessibilitySnapshot::default();
1859 for slot in ®istry.slots {
1860 let Some(record) = slot.record.as_ref() else {
1861 continue;
1862 };
1863 let tree = record.runtime.tree();
1864 if widgets.elements_total == 0 {
1865 let diagnostics = tree.diagnostics();
1866 widgets = WidgetWorkSnapshot {
1867 mounts: diagnostics.mounts,
1868 unmounts: diagnostics.unmounts,
1869 rebuilds: diagnostics.rebuilds,
1870 layouts: diagnostics.layouts,
1871 paints: diagnostics.paints,
1872 composites: diagnostics.composites,
1873 animation_ticks: diagnostics.animation_ticks,
1874 scroll_offset_updates: diagnostics.scroll_offset_updates,
1875 reconciliation_fast_paths: diagnostics.reconciliation_fast_paths,
1876 layout_cache_hits: diagnostics.layout_cache_hits,
1877 display_lists_reused: diagnostics.display_lists_reused,
1878 compositor_only_updates: diagnostics.compositor_only_updates,
1879 lazy_layouts: diagnostics.lazy_layouts,
1880 items_built: diagnostics.items_built,
1881 items_reused: diagnostics.items_reused,
1882 child_list_scans: diagnostics.child_list_scans,
1883 identical_child_bailouts: diagnostics.identical_child_bailouts,
1884 elements_created: diagnostics.elements_created,
1885 elements_removed: diagnostics.elements_removed,
1886 elements_moved: diagnostics.elements_moved,
1887 dirty_requests: diagnostics.dirty_requests,
1888 dirty_queue_deduplicated: diagnostics.dirty_queue_deduplicated,
1889 elements_total: tree.element_count(),
1890 render_objects_total: tree.render_object_count(),
1891 layers_total: usize::try_from(tree.compositor_diagnostics().layers)
1892 .unwrap_or(0),
1893 };
1894 let text_diagnostics = tree.text_diagnostics();
1895 text = TextCacheSnapshot {
1896 layouts_requested: text_diagnostics.layouts_requested,
1897 cache_hits: text_diagnostics.cache_hits,
1898 cache_misses: text_diagnostics.cache_misses,
1899 paragraphs_reshaped: text_diagnostics.paragraphs_reshaped,
1900 parley_layouts_reused: text_diagnostics.parley_layouts_reused,
1901 documents_composed: text_diagnostics.documents_composed,
1902 };
1903 }
1904 accessibility.nodes_published += record.accessibility.nodes_published;
1905 accessibility.updates_skipped_unchanged +=
1906 record.accessibility.semantic_updates_skipped_unchanged;
1907 }
1908 let mut scheduler = scheduler_counters();
1909 self.scheduler_counters.apply_to(&mut scheduler);
1910 PerformanceSnapshot {
1911 scheduler,
1912 budget: *self.profiler.budget(),
1913 fps: self.profiler.frames_per_second(),
1914 windows,
1915 frame_statistics: self.profiler.history().statistics(),
1916 widgets,
1917 text,
1918 accessibility,
1919 }
1920 }
1921
1922 #[must_use]
1923 pub const fn profiler_budget(&self) -> &BudgetStatistics {
1924 self.profiler.budget()
1925 }
1926
1927 pub fn note_presented(&mut self, window_id: WindowId, presented: bool) {
1928 if presented {
1929 scheduler_counters::FRAMES_PRESENTED.fetch_add(1, Ordering::Relaxed);
1930 self.scheduler_counters.frames_presented =
1931 self.scheduler_counters.frames_presented.wrapping_add(1);
1932 } else {
1933 scheduler_counters::FRAMES_SKIPPED.fetch_add(1, Ordering::Relaxed);
1934 self.scheduler_counters.frames_skipped =
1935 self.scheduler_counters.frames_skipped.wrapping_add(1);
1936 }
1937 self.profiler.note_present(presented);
1938 let _ = self.with_window_mut(window_id, |record| {
1939 if presented {
1940 record.presented_frames = record.presented_frames.wrapping_add(1);
1941 } else {
1942 record.skipped_frames = record.skipped_frames.wrapping_add(1);
1943 }
1944 });
1945 }
1946
1947 pub fn request_close(&mut self, window_id: WindowId) -> bool {
1948 if !self.contains_window(window_id) {
1949 return false;
1950 }
1951 if self
1952 .close_request
1953 .as_mut()
1954 .is_some_and(|callback| !callback(window_id))
1955 {
1956 return false;
1957 }
1958 self.close_window(window_id)
1959 }
1960
1961 pub fn close_window(&mut self, window_id: WindowId) -> bool {
1962 self.fail_simulation_window(window_id);
1963 self.fail_native_requests_for_window(window_id, PlatformOperationError::stale_resource());
1964 self.fail_file_dialog_requests_for_window(window_id, FileDialogError::ParentClosed);
1965 let record = self.registry.borrow_mut().close(window_id);
1966 let Some(mut record) = record else {
1967 self.registry.borrow_mut().stale_window_commands += 1;
1968 return false;
1969 };
1970 record
1971 .transient_presentations
1972 .write()
1973 .expect("transient presentation snapshot lock")
1974 .clear();
1975 record.scope.cancel();
1976 record.runtime.dispose_window();
1977 self.native_commands
1978 .borrow_mut()
1979 .push_back(NativeWindowCommand::Operate(WindowCommand::new(
1980 window_id,
1981 WindowOperation::Close,
1982 )));
1983 let closed_restoration = record.restoration.clone();
1988 if let (Some(metadata), Some(restoration)) = (&closed_restoration, &self.restoration) {
1989 restoration.remove_window_descriptor(metadata.id.as_key().as_str());
1990 restoration.remove_scope(&[
1991 RestorationKey::new("window").expect("static key"),
1992 metadata.id.as_key().clone(),
1993 ]);
1994 }
1995 self.manager.sync_restorable_windows(true);
1996 if self.registry.borrow().visible_count() == 0
1997 && self.last_window_policy == LastWindowPolicy::ExitOnLastWindow
1998 {
1999 self.should_exit = true;
2000 }
2001 true
2002 }
2003
2004 fn drain_window_commands(&mut self) {
2005 while let Ok(queued) = self.command_receiver.try_recv() {
2006 let QueuedWindowCommand { command, request } = queued;
2007 let window_id = command.window_id;
2008 match command.operation.clone() {
2009 WindowOperation::Close => {
2010 if let Some(request) = request {
2011 Self::resolve_queued_request(
2012 request,
2013 Err(PlatformOperationError::with_context(
2014 PlatformOperationErrorKind::InvalidState,
2015 "window close does not expose a native completion request",
2016 )),
2017 );
2018 }
2019 let _ = self.close_window(window_id);
2020 }
2021 operation => {
2022 let applied = self.with_window_mut(window_id, |record| {
2023 match &operation {
2024 WindowOperation::SetTitle(title) => {
2025 record.options.title = title.clone()
2026 }
2027 WindowOperation::SetVisible(visible) => {
2028 record.options.visible = *visible;
2029 record.requested_state.visible = *visible;
2030 record.lifecycle = if *visible {
2031 WindowLifecycle::Visible
2032 } else {
2033 WindowLifecycle::Hidden
2034 };
2035 }
2036 WindowOperation::SetLogicalSize(size) => {
2037 if size.width.is_finite()
2038 && size.height.is_finite()
2039 && size.width > 0.0
2040 && size.height > 0.0
2041 {
2042 record.content_sizing.reset();
2043 record.options.initial_logical_size = *size;
2044 } else {
2045 return false;
2046 }
2047 }
2048 WindowOperation::SetOuterPosition(_) => {}
2049 WindowOperation::BeginMoveDrag
2050 | WindowOperation::BeginResizeDrag(_) => {}
2051 WindowOperation::SetMinimized(minimized) => {
2052 record.requested_state.minimized = *minimized;
2053 }
2054 WindowOperation::SetMaximized(maximized) => {
2055 record.options.maximized = *maximized;
2056 record.requested_state.maximized = *maximized;
2057 }
2058 WindowOperation::SetFullscreen(fullscreen) => {
2059 record.options.fullscreen = *fullscreen;
2060 record.requested_state.fullscreen = *fullscreen;
2061 }
2062 WindowOperation::SetResizable(resizable) => {
2063 record.options.resizable = *resizable;
2064 record.requested_state.resizable = *resizable;
2065 }
2066 WindowOperation::SetDecorations(decorations) => {
2067 record.options.decorations = *decorations;
2068 record.requested_state.decorations = *decorations;
2069 }
2070 WindowOperation::SetLogicalSizeLimits(limits) => {
2071 record.options.minimum_logical_size = limits.minimum();
2072 record.options.maximum_logical_size = limits.maximum();
2073 record.requested_state.size_limits = *limits;
2074 record.content_sizing.reset();
2075 }
2076 WindowOperation::SetWindowLevel(level) => {
2077 record.options.window_level = *level;
2078 record.requested_state.level = *level;
2079 }
2080 WindowOperation::SetWindowIcon(icon) => {
2081 record.options.window_icon = icon.clone();
2082 }
2083 WindowOperation::RequestUserAttention(_) => {}
2084 WindowOperation::SetCursorGrab(_)
2085 | WindowOperation::SetCursorVisible(_)
2086 | WindowOperation::SetCursorPosition(_) => {}
2087 WindowOperation::SetContentSensitivity(sensitivity) => {
2088 record.last_content_sensitivity = Some(*sensitivity);
2089 }
2090 WindowOperation::RequestFocus => {}
2091 WindowOperation::RequestRedraw => record.runtime.request_frame(),
2092 WindowOperation::Close => unreachable!(),
2093 }
2094 true
2095 });
2096 if applied == Some(true) {
2097 if let Some(request) = request {
2098 let request_id = command
2099 .request_id
2100 .expect("result-bearing command has request id");
2101 if !request.cancelled.load(Ordering::Acquire) {
2102 self.pending_native_requests.insert(
2103 request_id,
2104 PendingNativeRequest {
2105 window_id,
2106 sender: request.sender,
2107 },
2108 );
2109 }
2110 } else {
2111 debug_assert!(command.request_id.is_none());
2112 }
2113 self.native_commands
2114 .borrow_mut()
2115 .push_back(NativeWindowCommand::Operate(command));
2116 if matches!(operation, WindowOperation::SetVisible(false))
2117 && self.registry.borrow().visible_count() == 0
2118 && self.last_window_policy == LastWindowPolicy::ExitOnLastWindow
2119 {
2120 self.should_exit = true;
2121 }
2122 self.manager.sync_restorable_windows(true);
2123 } else if applied == Some(false) {
2124 if let Some(request) = request {
2125 Self::resolve_queued_request(
2126 request,
2127 Err(PlatformOperationError::with_context(
2128 PlatformOperationErrorKind::InvalidState,
2129 "window operation arguments were rejected by the runtime",
2130 )),
2131 );
2132 }
2133 } else if applied.is_none() {
2134 if let Some(request) = request {
2135 Self::resolve_queued_request(
2136 request,
2137 Err(PlatformOperationError::stale_resource()),
2138 );
2139 }
2140 self.registry.borrow_mut().stale_window_commands += 1;
2141 }
2142 }
2143 }
2144 }
2145 }
2146
2147 fn resolve_queued_request(request: QueuedNativeRequest, result: PlatformOperationResult) {
2148 if !request.cancelled.load(Ordering::Acquire) {
2149 let _ = request.sender.send(result);
2150 }
2151 }
2152
2153 fn drain_native_request_cancellations(&mut self) {
2154 while let Ok(request_id) = self.request_cancellation_receiver.try_recv() {
2155 self.pending_native_requests.remove(&request_id);
2156 }
2157 }
2158
2159 fn fail_native_requests_for_window(
2160 &mut self,
2161 window_id: WindowId,
2162 error: PlatformOperationError,
2163 ) {
2164 let request_ids = self
2165 .pending_native_requests
2166 .iter()
2167 .filter_map(|(request_id, request)| {
2168 (request.window_id == window_id).then_some(*request_id)
2169 })
2170 .collect::<Vec<_>>();
2171 for request_id in request_ids {
2172 if let Some(request) = self.pending_native_requests.remove(&request_id) {
2173 let _ = request.sender.send(Err(error.clone()));
2174 }
2175 }
2176 }
2177
2178 fn fail_all_native_requests(&mut self, error: PlatformOperationError) {
2179 while self.request_cancellation_receiver.try_recv().is_ok() {}
2180 for (_, request) in self.pending_native_requests.drain() {
2181 let _ = request.sender.send(Err(error.clone()));
2182 }
2183 while let Ok(queued) = self.command_receiver.try_recv() {
2184 if let Some(request) = queued.request {
2185 Self::resolve_queued_request(request, Err(error.clone()));
2186 }
2187 }
2188 }
2189
2190 fn drain_file_dialog_requests(&mut self) {
2191 while let Ok(queued) = self.file_dialog_receiver.try_recv() {
2192 if queued.cancelled.load(std::sync::atomic::Ordering::Acquire) {
2193 continue;
2194 }
2195 if !self.contains_window(queued.window_id) {
2196 let _ = queued.sender.send(Err(FileDialogError::ParentClosed));
2197 continue;
2198 }
2199 let capabilities = self
2200 .window_capabilities(queued.window_id)
2201 .expect("live file-dialog parent has capabilities")
2202 .application_services
2203 .file_dialogs;
2204 if let Some(error) = explicitly_unsupported_request(capabilities, &queued.request) {
2205 let _ = queued.sender.send(Err(error));
2206 continue;
2207 }
2208 let request_id = queued.request_id;
2209 let window_id = queued.window_id;
2210 self.pending_file_dialog_requests.insert(
2211 request_id,
2212 PendingFileDialogRequest {
2213 window_id,
2214 request: queued.request,
2215 sender: Some(queued.sender),
2216 cancelled: queued.cancelled,
2217 },
2218 );
2219 if self.active_file_dialog_requests.contains_key(&window_id) {
2220 self.queued_file_dialog_requests
2221 .entry(window_id)
2222 .or_default()
2223 .push_back(request_id);
2224 } else {
2225 self.activate_file_dialog(request_id);
2226 }
2227 }
2228 }
2229
2230 fn activate_file_dialog(&mut self, request_id: FileDialogRequestId) {
2231 let Some(pending) = self.pending_file_dialog_requests.get(&request_id) else {
2232 return;
2233 };
2234 if pending.cancelled.load(std::sync::atomic::Ordering::Acquire) {
2235 let window_id = pending.window_id;
2236 self.pending_file_dialog_requests.remove(&request_id);
2237 self.promote_next_file_dialog(window_id);
2238 return;
2239 }
2240 let window_id = pending.window_id;
2241 debug_assert!(!self.active_file_dialog_requests.contains_key(&window_id));
2242 self.active_file_dialog_requests
2243 .insert(window_id, request_id);
2244 self.native_file_dialog_requests
2245 .push_back(NativeFileDialogRequest {
2246 request_id,
2247 window_id,
2248 request: pending.request.clone(),
2249 });
2250 }
2251
2252 fn promote_next_file_dialog(&mut self, window_id: WindowId) {
2253 if self.active_file_dialog_requests.contains_key(&window_id)
2254 || !self.contains_window(window_id)
2255 {
2256 return;
2257 }
2258 loop {
2259 let next = self
2260 .queued_file_dialog_requests
2261 .get_mut(&window_id)
2262 .and_then(VecDeque::pop_front);
2263 let Some(request_id) = next else {
2264 self.queued_file_dialog_requests.remove(&window_id);
2265 return;
2266 };
2267 let cancelled = self
2268 .pending_file_dialog_requests
2269 .get(&request_id)
2270 .is_none_or(|pending| pending.cancelled.load(std::sync::atomic::Ordering::Acquire));
2271 if cancelled {
2272 self.pending_file_dialog_requests.remove(&request_id);
2273 continue;
2274 }
2275 self.activate_file_dialog(request_id);
2276 return;
2277 }
2278 }
2279
2280 fn drain_file_dialog_cancellations(&mut self) {
2281 while let Ok(request_id) = self.file_dialog_cancellation_receiver.try_recv() {
2282 let Some(window_id) = self
2283 .pending_file_dialog_requests
2284 .get(&request_id)
2285 .map(|pending| pending.window_id)
2286 else {
2287 continue;
2288 };
2289 let is_active = self.active_file_dialog_requests.get(&window_id) == Some(&request_id);
2290 if is_active {
2291 let not_started = self.pending_file_dialog_requests.phase(&request_id)
2294 == Some(RequestPhase::Queued);
2295 self.native_file_dialog_requests
2296 .retain(|request| request.request_id != request_id);
2297 if not_started {
2298 self.active_file_dialog_requests.remove(&window_id);
2299 self.pending_file_dialog_requests.remove(&request_id);
2300 self.promote_next_file_dialog(window_id);
2301 } else if let Some(pending) = self.pending_file_dialog_requests.get_mut(&request_id)
2302 {
2303 pending.sender = None;
2306 self.pending_file_dialog_requests.abandon(&request_id);
2307 }
2308 } else {
2309 if let Some(queue) = self.queued_file_dialog_requests.get_mut(&window_id) {
2310 queue.retain(|queued| *queued != request_id);
2311 if queue.is_empty() {
2312 self.queued_file_dialog_requests.remove(&window_id);
2313 }
2314 }
2315 self.pending_file_dialog_requests.remove(&request_id);
2316 }
2317 }
2318 }
2319
2320 fn fail_file_dialog_requests_for_window(
2321 &mut self,
2322 window_id: WindowId,
2323 error: FileDialogError,
2324 ) {
2325 self.native_file_dialog_requests
2326 .retain(|request| request.window_id != window_id);
2327 self.active_file_dialog_requests.remove(&window_id);
2328 self.queued_file_dialog_requests.remove(&window_id);
2329 let request_ids = self
2330 .pending_file_dialog_requests
2331 .iter()
2332 .filter_map(|(request_id, pending)| {
2333 (pending.window_id == window_id).then_some(*request_id)
2334 })
2335 .collect::<Vec<_>>();
2336 for request_id in request_ids {
2337 if let Some(mut pending) = self.pending_file_dialog_requests.remove(&request_id)
2338 && !pending.cancelled.load(std::sync::atomic::Ordering::Acquire)
2339 && let Some(sender) = pending.sender.take()
2340 {
2341 let _ = sender.send(Err(error.clone()));
2342 }
2343 }
2344 }
2345
2346 fn fail_all_file_dialog_requests(&mut self, error: FileDialogError) {
2347 self.file_dialog_bridge.stop();
2348 while self.file_dialog_cancellation_receiver.try_recv().is_ok() {}
2349 while let Ok(queued) = self.file_dialog_receiver.try_recv() {
2350 if !queued.cancelled.load(std::sync::atomic::Ordering::Acquire) {
2351 let _ = queued.sender.send(Err(error.clone()));
2352 }
2353 }
2354 for (_, mut pending) in self.pending_file_dialog_requests.drain() {
2355 if !pending.cancelled.load(std::sync::atomic::Ordering::Acquire)
2356 && let Some(sender) = pending.sender.take()
2357 {
2358 let _ = sender.send(Err(error.clone()));
2359 }
2360 }
2361 self.active_file_dialog_requests.clear();
2362 self.queued_file_dialog_requests.clear();
2363 self.native_file_dialog_requests.clear();
2364 }
2365
2366 pub fn take_native_file_dialog_requests(&mut self) -> Vec<NativeFileDialogRequest> {
2370 self.drain_file_dialog_requests();
2371 self.drain_file_dialog_cancellations();
2372 let requests: Vec<_> = self.native_file_dialog_requests.drain(..).collect();
2373 for request in &requests {
2374 self.pending_file_dialog_requests
2375 .dispatch(&request.request_id);
2376 }
2377 requests
2378 }
2379
2380 pub fn complete_file_dialog(
2384 &mut self,
2385 completion: NativeFileDialogCompletion,
2386 ) -> FileDialogCompletionStatus {
2387 let Some(pending) = self
2388 .pending_file_dialog_requests
2389 .get(&completion.request_id)
2390 else {
2391 return FileDialogCompletionStatus::UnknownRequest;
2392 };
2393 if pending.window_id != completion.window_id {
2394 return FileDialogCompletionStatus::TargetMismatch;
2395 }
2396 if self.active_file_dialog_requests.get(&completion.window_id)
2397 != Some(&completion.request_id)
2398 {
2399 return FileDialogCompletionStatus::UnknownRequest;
2400 }
2401
2402 let Ok(mut pending) = self
2403 .pending_file_dialog_requests
2404 .complete(&completion.request_id, |_| true)
2405 else {
2406 return FileDialogCompletionStatus::UnknownRequest;
2407 };
2408 self.active_file_dialog_requests
2409 .remove(&completion.window_id);
2410
2411 let (result, status) = match completion.result {
2412 Ok(outcome) if result_matches_request(&pending.request, &outcome) => {
2413 (Ok(outcome), FileDialogCompletionStatus::Completed)
2414 }
2415 Ok(_) => (
2416 Err(FileDialogError::InvalidResult),
2417 FileDialogCompletionStatus::InvalidResult,
2418 ),
2419 Err(error) => (Err(error), FileDialogCompletionStatus::Completed),
2420 };
2421 if !pending.cancelled.load(std::sync::atomic::Ordering::Acquire)
2422 && let Some(sender) = pending.sender.take()
2423 {
2424 let _ = sender.send(result);
2425 }
2426 self.promote_next_file_dialog(completion.window_id);
2427 status
2428 }
2429
2430 fn drain_global_shortcut_requests(&mut self) {
2431 drain_queued(
2432 &self.global_shortcut_receiver,
2433 &mut self.pending_global_shortcut_requests,
2434 &mut self.native_global_shortcut_requests,
2435 );
2436 let abandoned: Vec<_> = self
2437 .pending_global_shortcut_requests
2438 .iter()
2439 .filter(|(_, pending)| pending.reply.is_abandoned())
2440 .map(|(id, pending)| (*id, pending.operation))
2441 .collect();
2442 for (id, operation) in abandoned {
2443 if self.pending_global_shortcut_requests.phase(&id) == Some(RequestPhase::Queued) {
2444 if matches!(operation, NativeGlobalShortcutOperation::Register { .. }) {
2445 self.pending_global_shortcut_requests.remove(&id);
2446 self.native_global_shortcut_requests
2447 .retain(|request| request.request_id != id);
2448 }
2449 } else {
2450 self.pending_global_shortcut_requests.abandon(&id);
2451 }
2452 }
2453 }
2454
2455 fn fail_all_global_shortcut_requests(&mut self) {
2456 self.global_shortcut_bridge.stop();
2457 while let Ok(queued) = self.global_shortcut_receiver.try_recv() {
2458 queued.reply.finish(
2459 queued.operation,
2460 Err(incular_platform::GlobalShortcutError::ApplicationStopped),
2461 );
2462 }
2463 for (_, pending) in self.pending_global_shortcut_requests.drain() {
2464 pending.reply.finish(
2465 pending.operation,
2466 Err(incular_platform::GlobalShortcutError::ApplicationStopped),
2467 );
2468 }
2469 self.native_global_shortcut_requests.clear();
2470 }
2471
2472 #[doc(hidden)]
2476 pub fn take_native_global_shortcut_requests(&mut self) -> Vec<NativeGlobalShortcutRequest> {
2477 self.drain_global_shortcut_requests();
2478 let requests: Vec<_> = self.native_global_shortcut_requests.drain(..).collect();
2479 for request in &requests {
2480 self.pending_global_shortcut_requests
2481 .dispatch(&request.request_id);
2482 }
2483 requests
2484 }
2485
2486 #[doc(hidden)]
2490 pub fn complete_global_shortcut_request(
2491 &mut self,
2492 completion: NativeGlobalShortcutCompletion,
2493 ) -> GlobalShortcutCompletionStatus {
2494 let Ok(pending) = self
2495 .pending_global_shortcut_requests
2496 .complete(&completion.request_id, |_| true)
2497 else {
2498 return GlobalShortcutCompletionStatus::UnknownRequest;
2499 };
2500 pending.reply.finish(pending.operation, completion.result)
2501 }
2502
2503 pub fn complete_native_operation(
2506 &mut self,
2507 completion: NativeOperationCompletion,
2508 ) -> NativeOperationCompletionStatus {
2509 let pending = match self
2510 .pending_native_requests
2511 .complete(&completion.request_id, |pending| {
2512 pending.window_id == completion.window_id
2513 }) {
2514 Ok(pending) => pending,
2515 Err(CompletionRejection::TargetMismatch) => {
2516 return NativeOperationCompletionStatus::TargetMismatch;
2517 }
2518 Err(_) => return NativeOperationCompletionStatus::UnknownRequest,
2519 };
2520 if !self.contains_window(completion.window_id) {
2521 let _ = pending
2522 .sender
2523 .send(Err(PlatformOperationError::stale_resource()));
2524 return NativeOperationCompletionStatus::StaleTarget;
2525 }
2526 if let Err(error) = &completion.result {
2527 let error = error.clone();
2528 let _ = self.with_window_mut(completion.window_id, |record| {
2529 record.last_platform_error = Some(error);
2530 });
2531 }
2532 let _ = pending.sender.send(completion.result);
2533 NativeOperationCompletionStatus::Completed
2534 }
2535
2536 pub fn take_native_window_commands(&mut self) -> Vec<NativeWindowCommand> {
2540 self.drain_window_commands();
2541 self.drain_native_request_cancellations();
2542 let commands: Vec<_> = self.native_commands.borrow_mut().drain(..).collect();
2543 for command in &commands {
2544 if let NativeWindowCommand::Operate(command) = command
2545 && let Some(id) = command.request_id
2546 {
2547 self.pending_native_requests.dispatch(&id);
2548 }
2549 }
2550 commands
2551 }
2552
2553 #[must_use]
2554 pub const fn should_exit(&self) -> bool {
2555 self.should_exit
2556 }
2557
2558 #[must_use]
2559 pub fn window_diagnostics(&self, window_id: WindowId) -> Option<WindowDiagnostics> {
2560 let registry = self.registry.borrow();
2561 let slot = registry.slots.get(window_id.index() as usize)?;
2562 if slot.generation != window_id.generation() {
2563 return None;
2564 }
2565 let record = slot.record.as_ref()?;
2566 Some(WindowDiagnostics {
2567 id: window_id,
2568 title: record.options.title.clone(),
2569 logical_size: record.metrics.logical_size(),
2570 physical_size: record.metrics.physical_size,
2571 scale_factor: record.metrics.scale_factor,
2572 visible: record.options.visible,
2573 native_focused: record.native_focused,
2574 lifecycle: record.lifecycle,
2575 application_lifecycle: record.runtime.lifecycle(),
2576 frame_requested: record.runtime.frame_requested(),
2577 requested_frames: record.requested_frames,
2578 presented_frames: record.presented_frames,
2579 skipped_frames: record.skipped_frames,
2580 input_events: record.input_events,
2581 surface_generation: record.surface_generation,
2582 content_sensitivity: record.runtime.content_sensitivity(),
2583 elements: record.runtime.tree().element_count(),
2584 render_objects: record.runtime.tree().render_object_count(),
2585 semantics: record.runtime.tree().semantics().len(),
2586 accessibility: record.accessibility,
2587 capabilities: *record
2588 .capabilities
2589 .read()
2590 .expect("window capability snapshot lock"),
2591 last_platform_error: record.last_platform_error.clone(),
2592 requested_state: record.requested_state,
2593 observed_state: *record
2594 .observed_state
2595 .read()
2596 .expect("window observed-state snapshot lock"),
2597 transient_presentations: record
2598 .transient_presentations
2599 .read()
2600 .expect("transient presentation snapshot lock")
2601 .clone(),
2602 })
2603 }
2604
2605 #[must_use]
2606 pub fn diagnostics(&self) -> ApplicationDiagnostics {
2607 let registry = self.registry.borrow();
2608 ApplicationDiagnostics {
2609 windows_created: registry.windows_created,
2610 windows_closed: registry.windows_closed,
2611 active_windows: registry.ids().len(),
2612 stale_window_commands: registry.stale_window_commands,
2613 pending_native_requests: self.pending_native_requests.len(),
2614 pending_file_dialog_requests: self.pending_file_dialog_requests.len(),
2615 }
2616 }
2617
2618 #[must_use]
2621 pub fn restoration_diagnostics(&self) -> Option<RestorationDiagnostics> {
2622 self.restoration
2623 .as_ref()
2624 .map(restoration::RestorationManager::diagnostics)
2625 }
2626
2627 #[must_use]
2630 pub fn restoration_path(&self) -> Option<std::path::PathBuf> {
2631 self.restoration
2632 .as_ref()
2633 .and_then(restoration::RestorationManager::store_location)
2634 }
2635
2636 pub fn reset_restoration(&mut self) -> bool {
2641 let Some(restoration) = &self.restoration else {
2642 return false;
2643 };
2644 restoration.reset();
2645 true
2646 }
2647
2648 pub fn flush_restoration(&mut self) -> bool {
2651 let Some(restoration) = &self.restoration else {
2652 return false;
2653 };
2654 restoration.flush();
2655 true
2656 }
2657
2658 fn flush_restoration_and_wait(&mut self) {
2662 let Some(restoration) = self.restoration.clone() else {
2663 return;
2664 };
2665 restoration.flush();
2666 #[cfg(not(target_arch = "wasm32"))]
2668 {
2669 let deadline = Instant::now() + std::time::Duration::from_millis(100);
2670 while !restoration.is_clean() && Instant::now() < deadline {
2671 self.process_runtime_work();
2672 std::thread::sleep(std::time::Duration::from_millis(1));
2673 }
2674 }
2675 }
2676
2677 #[must_use]
2678 pub fn debug_dump(&self) -> String {
2679 let mut lines = vec!["Application".to_owned()];
2680 if let Some(restoration) = &self.restoration {
2681 lines.push(restoration.debug_dump());
2682 }
2683 for id in self.active_window_ids() {
2684 if let Some(window) = self.window_diagnostics(id) {
2685 lines.push(format!(
2686 " {id} title={:?} {:.0}x{:.0} @{} focused={}\\n root elements={} render={} semantics={} dirty={}",
2687 window.title,
2688 window.logical_size.width,
2689 window.logical_size.height,
2690 window.scale_factor,
2691 window.native_focused,
2692 window.elements,
2693 window.render_objects,
2694 window.semantics,
2695 window.frame_requested,
2696 ));
2697 }
2698 }
2699 lines.join("\\n")
2700 }
2701
2702 pub fn shutdown(&mut self) {
2703 self.flush_restoration_before_shutdown();
2704 self.application_shell.stop();
2705 self.manager.bridge.stop();
2706 self.fail_all_file_dialog_requests(FileDialogError::ApplicationStopped);
2707 self.fail_all_global_shortcut_requests();
2708 self.fail_all_native_requests(PlatformOperationError::with_context(
2709 PlatformOperationErrorKind::Unavailable,
2710 "application runtime stopped before native completion",
2711 ));
2712 let ids = self.active_window_ids();
2713 for id in ids {
2714 self.fail_simulation_window(id);
2715 if let Some(mut record) = self.registry.borrow_mut().close(id) {
2716 record.scope.cancel();
2717 record.runtime.dispose_window();
2718 }
2719 }
2720 self.native_commands.borrow_mut().clear();
2721 self.scheduler.borrow_mut().shutdown();
2722 self.stop_simulation();
2723 self.should_exit = true;
2724 }
2725
2726 fn flush_restoration_before_shutdown(&mut self) {
2727 self.flush_restoration_and_wait();
2728 }
2729
2730 #[must_use]
2735 pub fn into_runtime(mut self) -> Runtime {
2736 self.flush_restoration_and_wait();
2737 self.manager.bridge.stop();
2738 self.fail_all_file_dialog_requests(FileDialogError::ApplicationStopped);
2739 self.fail_all_global_shortcut_requests();
2740 let primary = self.primary_window;
2741 let mut primary_record = self
2742 .registry
2743 .borrow_mut()
2744 .close(primary)
2745 .expect("application primary window exists");
2746 for id in self.active_window_ids() {
2747 if let Some(record) = self.registry.borrow_mut().close(id) {
2748 record.scope.cancel();
2749 }
2750 }
2751 primary_record.runtime.window_manager = None;
2752 primary_record.runtime.window_id = None;
2753 primary_record.runtime
2754 }
2755}
2756pub const PERFORMANCE_OVERLAY_KEY: &str = incular_widgets::internal::PERFORMANCE_OVERLAY_KEY;
2758
2759fn overlay_widget(snapshot: &PerformanceSnapshot) -> incular_widgets::Widget {
2761 use incular_widgets::{DecoratedBox, Padding, Text, Widget};
2762 let lines = snapshot.overlay_lines();
2763 let monospace = || {
2764 incular_widgets::TextStyle::default()
2765 .family(incular_text::FontFamily::Monospace)
2766 .font_size(11.)
2767 .color(incular_core::Color::rgba(190, 220, 255, 255))
2768 };
2769 let rows: Vec<Widget> = lines
2770 .into_iter()
2771 .map(|line| Widget::from(Text::new(line).style(monospace())))
2772 .collect();
2773 Widget::from(
2778 incular_widgets::IgnorePointer::new(Widget::from(incular_widgets::RepaintBoundary::new(
2779 Widget::from(
2780 DecoratedBox::new(Padding::all(
2781 6.,
2782 Widget::from(incular_widgets::Column::new(rows)),
2783 ))
2784 .background(incular_core::Color::rgba(12, 14, 18, 216))
2785 .radius(4.)
2786 .border(incular_rendering::Border {
2787 color: incular_core::Color::rgba(120, 170, 245, 90),
2788 width: 1.,
2789 }),
2790 ),
2791 )))
2792 .ignoring(true),
2793 )
2794}