Skip to main content

incular_runtime/
frame.rs

1use crate::application_types::{
2    ApplicationLifecycle, LifecycleTransition, RuntimeErrorReport, WindowError,
3};
4#[cfg(feature = "devtools")]
5use crate::environment::DEV_TASK_COMPLETION;
6#[cfg(feature = "devtools")]
7use crate::environment::ReactiveRootId;
8use crate::environment::{
9    BuildScope, BuildScopeGuard, EnvironmentFields, InitialBuildDependencies, ReactiveQueue,
10    environment_change_mask, install_focus_scope_watch,
11};
12use crate::profiling;
13use crate::profiling::FrameTimings;
14#[cfg(not(target_arch = "wasm32"))]
15use crate::tasks::TokioHandle;
16use crate::tasks::{
17    self, RuntimeDiagnostics, RuntimeSpawner, RuntimeWake, Task, TaskFailure, TaskHandle,
18    TaskScope, UiDispatcher,
19};
20use crate::text_shortcuts::TextShortcutPlatform;
21use crate::undo::UndoHistoryController;
22use crate::window_commands::{WindowHandle, WindowOpener};
23use crate::window_state::WindowManager;
24use incular_config::{Constraints, RuntimeEnvironment};
25use incular_core::time::Instant;
26use incular_core::{
27    Code, ImeEvent, InputEvent, KeyboardEvent, Offset, PRIMARY_POINTER_BUTTON, PointerPhase,
28    PointerSampleMetadata, Rect,
29};
30use incular_platform::{
31    Clipboard, ClipboardCapabilities, ClipboardError, ClipboardWriteReport, DataTransfer,
32    ExternalDragEvent, ExternalDragResponse, MemoryClipboard, PlatformEvent, PlatformLifecycle,
33    TextInputAction, TextInputClientId, TextInputCommand, TextInputConfiguration, TextInputState,
34    TextInputType, TransferReadRequest, WindowId, WindowMetrics, WindowOperation, WindowOptions,
35};
36use incular_rendering::DisplayList;
37use incular_semantics::{SemanticAction, SemanticNodeId};
38use incular_text::TextEditingValue;
39#[cfg(feature = "devtools")]
40use incular_widgets::internal::InvalidationCause;
41use incular_widgets::internal::{
42    ActionId, Diagnostics, ElementId, MouseCursor, PlatformMenuBinding, PointerDeviceKind,
43    PointerEvent, PointerHitTest, RawPointerEvent, TextEditingController, TextRange, TextSelection,
44    TreeError, WidgetTree, WindowInteraction,
45};
46use incular_widgets::{TextInputActionHint, TextInputTypeHint, Widget};
47use std::{
48    cell::{Cell, RefCell},
49    collections::{HashMap, VecDeque},
50    rc::{Rc, Weak},
51    sync::atomic::{AtomicU64, Ordering},
52};
53
54mod text_actions;
55mod text_gestures;
56
57/// Sequence numbering runtime driver domains so outlet ownership errors
58/// can name the runtimes involved.
59static RUNTIME_SEQUENCE: AtomicU64 = AtomicU64::new(1);
60
61#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
62pub struct FrameStats {
63    pub updated_elements: usize,
64    pub rebuilt_elements: u64,
65    pub laid_out_render_objects: u64,
66    pub repainted_render_objects: u64,
67    pub composited: u64,
68    pub active_animations: u64,
69    pub display_list_commands: usize,
70    pub requested_another_frame: bool,
71    /// Monotonic per-phase CPU durations. Phases never nest.
72    pub timings: FrameTimings,
73}
74#[derive(Clone, Copy, Debug, PartialEq, Eq)]
75pub struct EventTarget {
76    pub element: ElementId,
77    pub action: Option<ActionId>,
78}
79/// Debug-facing focus state without exposing native event-loop details.
80#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
81pub struct FocusDiagnostics {
82    pub focused_element: Option<ElementId>,
83    pub text_pointer_capture: Option<ElementId>,
84}
85#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
86pub struct EditingDiagnostics {
87    pub key_down_received: u64,
88    pub key_up_received: u64,
89    pub backspace_commands: u64,
90    pub delete_commands: u64,
91    pub text_commits: u64,
92    pub ime_events: u64,
93}
94
95/// Open IME composition. Native IME events carry no session identity,
96/// so the field and controller that received the latest Preedit are
97/// the only routing authority for the matching Commit/End. The
98/// controller handle is retained so cleanup always addresses the
99/// composition's own editor, never the currently focused one.
100///
101/// Preedit → update → Commit → End: Commit retires the owner; End is ignored.
102/// Preedit → End: cancellation; the owner's preedit clears, no text changes.
103/// Preedit → focus moves: focus change cancels first; later events start fresh.
104/// Preedit → controller replaced, field unmounted, or field no longer
105///   editable: the owner stops validating and is cancelled, never committed.
106/// Commit without Preedit: ownerless, applies to the focused field directly.
107struct ImeCompositionOwner {
108    field: ElementId,
109    controller: TextEditingController,
110}
111#[derive(Clone, Copy, PartialEq, Eq)]
112enum InitialFocus {
113    AwaitingLayout,
114    Settled,
115}
116
117/// Retained application execution, input dispatch and frame coordination.
118pub struct Runtime {
119    pub(crate) tree: WidgetTree,
120    /// Input-dispatch time accumulated since the last frame consumed it.
121    pub(crate) pending_event_processing_us: u32,
122    pub(crate) pending: HashMap<ElementId, Widget>,
123    pub(crate) order: VecDeque<ElementId>,
124    pub(crate) reactive: Rc<RefCell<ReactiveQueue>>,
125    pub(crate) builders: HashMap<ElementId, Box<dyn FnMut() -> Widget>>,
126    pub(crate) handlers: HashMap<ActionId, Rc<dyn Fn()>>,
127    /// Set when handlers were bound or elements unmounted, so the next
128    /// prune scans the tree for live actions; otherwise none can be stale.
129    handlers_stale: bool,
130    pub(crate) legacy_action_handler: Option<Box<dyn FnMut(ActionId)>>,
131    pub(crate) hovered_button: Option<ElementId>,
132    pub(crate) pressed_button: Option<ElementId>,
133    /// The contact currently allowed to drive single-contact controls. Other
134    /// contacts still reach retained gesture regions for scale/pan input.
135    pub(crate) legacy_pointer: Option<(u64, u64)>,
136    pub(crate) last_pointer: Offset,
137    pub(crate) pointer_inside: bool,
138    pub(crate) pointer_position_known: bool,
139    /// Latest positioned sample from a hover-capable device, replayed at
140    /// `last_pointer` when layout or scrolling moves content under it.
141    last_hover_sample: Option<RawPointerEvent>,
142    pub(crate) focused: Option<ElementId>,
143    initial_focus: InitialFocus,
144    pub(crate) captured_text_field: Option<ElementId>,
145    pub(crate) captured_selectable_text: Option<ElementId>,
146    pub(crate) secondary_text_press: Option<text_gestures::SecondaryTextPress>,
147    /// `SelectableRegionState._lastSecondaryTapDownPosition`.
148    pub(crate) last_selectable_secondary: Option<Offset>,
149    /// Consecutive primary taps on a text field (`TapAndPanGestureRecognizer`).
150    pub(crate) text_taps: Option<text_gestures::TextTapRun>,
151    /// Modifier keys held, from the latest key event (`HardwareKeyboard`).
152    pub(crate) keyboard_modifiers: incular_core::Modifiers,
153    /// The focused field, content revision and selection whose caret was
154    /// last shown on screen (Flutter's `_scheduleShowCaretOnScreen`).
155    pub(crate) caret_reveal_key: Option<(ElementId, u64, TextSelection)>,
156    ime_composition: Option<ImeCompositionOwner>,
157    pub(crate) text_histories: HashMap<ElementId, UndoHistoryController>,
158    pub(crate) text_input_commands: VecDeque<TextInputCommand>,
159    pub(crate) text_input_client: Option<TextInputClientId>,
160    pub(crate) text_input_configuration: Option<TextInputConfiguration>,
161    pub(crate) text_input_state: Option<TextInputState>,
162    pub(crate) text_input_caret_rect: Option<Rect>,
163    pub(crate) clipboard: Box<dyn Clipboard>,
164    pub(crate) editing_diagnostics: EditingDiagnostics,
165    /// Which platform's `DefaultTextEditingShortcuts` table applies.
166    pub(crate) text_shortcut_platform: TextShortcutPlatform,
167    pub(crate) frame_requested: bool,
168    pub(crate) scheduler: Rc<RefCell<tasks::TaskScheduler>>,
169    pub(crate) environment: Rc<RefCell<RuntimeEnvironment>>,
170    pub(crate) lifecycle: ApplicationLifecycle,
171    pub(crate) lifecycle_observers: Vec<Rc<dyn Fn(LifecycleTransition)>>,
172    pub(crate) error_observers: Vec<Rc<dyn Fn(RuntimeErrorReport)>>,
173    pub(crate) environment_generation: u64,
174    pub(crate) environment_dependencies: Rc<Cell<EnvironmentFields>>,
175    pub(crate) application_root: Option<ElementId>,
176    pub(crate) owner_scopes: HashMap<ElementId, TaskScope>,
177    pub(crate) window_id: Option<WindowId>,
178    pub(crate) window_scope: TaskScope,
179    pub(crate) window_manager: Option<WindowManager>,
180    /// Stable driver identity shared with outlet ownership claims: the id
181    /// names this runtime in [`OutletError`](super::route_outlet::OutletError)s,
182    /// while the shared ownership doubles as a liveness token — dropping
183    /// the runtime releases every claim without a global registry.
184    driver_domain: Rc<u64>,
185}
186
187pub(crate) fn diagnostic_input_trigger(event: &InputEvent) -> String {
188    match event {
189        InputEvent::Pointer { phase, position } => {
190            format!("pointer({phase:?}, {:.1}, {:.1})", position.x, position.y)
191        }
192        InputEvent::PointerWithId {
193            pointer,
194            phase,
195            position,
196        } => format!(
197            "pointer({pointer}, {phase:?}, {:.1}, {:.1})",
198            position.x, position.y
199        ),
200        InputEvent::PointerWithMetadata {
201            pointer,
202            device,
203            kind,
204            buttons,
205            button,
206            sample: _,
207            phase,
208            position,
209        } => format!(
210            "pointer({pointer}, device={device}, {kind:?}, buttons={buttons:#x}, changed={button:?}, {phase:?}, {:.1}, {:.1})",
211            position.x, position.y
212        ),
213        InputEvent::TrackpadGesture(event) => format!(
214            "trackpad(device={}, kind={:?}, phase={:?})",
215            event.device(),
216            event.kind(),
217            event.phase()
218        ),
219        InputEvent::Scroll { delta } => format!("scroll({:.1}, {:.1})", delta.x, delta.y),
220        InputEvent::Key(event) => format!("key({:?}, {:?})", event.state, event.code),
221        InputEvent::Text(text) => format!("text_input(chars={})", text.chars().count()),
222        InputEvent::Ime(ImeEvent::Preedit { text, .. }) => {
223            format!("ime_preedit(chars={})", text.chars().count())
224        }
225        InputEvent::Ime(ImeEvent::Commit(text)) => {
226            format!("ime_commit(chars={})", text.chars().count())
227        }
228        InputEvent::Ime(ImeEvent::End) => "ime_end".to_owned(),
229        InputEvent::WindowResized { size, scale_factor } => format!(
230            "window_resize({:.1}x{:.1}, scale={scale_factor:.2})",
231            size.width, size.height
232        ),
233    }
234}
235
236impl Runtime {
237    pub fn new(root: Widget) -> Result<Self, TreeError> {
238        Self::with_scheduler(root, tasks::TaskScheduler::new())
239    }
240    fn with_scheduler(
241        root: Widget,
242        scheduler: Rc<RefCell<tasks::TaskScheduler>>,
243    ) -> Result<Self, TreeError> {
244        let window_scope = tasks::TaskScheduler::spawner(&scheduler).scope();
245        Self::with_window(root, scheduler, None, window_scope, None)
246    }
247
248    pub(crate) fn with_window(
249        root: Widget,
250        scheduler: Rc<RefCell<tasks::TaskScheduler>>,
251        window_id: Option<WindowId>,
252        window_scope: TaskScope,
253        window_manager: Option<WindowManager>,
254    ) -> Result<Self, TreeError> {
255        Self::with_window_and_reactive(
256            root,
257            scheduler,
258            window_id,
259            window_scope,
260            window_manager,
261            ReactiveQueue::new(),
262        )
263    }
264    pub(crate) fn with_window_and_reactive(
265        mut root: Widget,
266        scheduler: Rc<RefCell<tasks::TaskScheduler>>,
267        window_id: Option<WindowId>,
268        window_scope: TaskScope,
269        window_manager: Option<WindowManager>,
270        reactive: Rc<RefCell<ReactiveQueue>>,
271    ) -> Result<Self, TreeError> {
272        let mut tree = WidgetTree::new();
273        let mut handlers = HashMap::new();
274        root.bind_callbacks(&mut |callback| {
275            let action = tree.allocate_action();
276            handlers.insert(action, callback);
277            action
278        });
279        tree.mount(root)?;
280        for (action, handler) in tree.take_pending_handlers() {
281            handlers.insert(action, handler);
282        }
283        let mut runtime = Self {
284            pending_event_processing_us: 0,
285            tree,
286            pending: HashMap::new(),
287            order: VecDeque::new(),
288            reactive,
289            builders: HashMap::new(),
290            handlers,
291            handlers_stale: false,
292            legacy_action_handler: None,
293            hovered_button: None,
294            pressed_button: None,
295            legacy_pointer: None,
296            last_pointer: Offset::ZERO,
297            pointer_inside: false,
298            pointer_position_known: false,
299            last_hover_sample: None,
300            focused: None,
301            initial_focus: InitialFocus::AwaitingLayout,
302            captured_text_field: None,
303            captured_selectable_text: None,
304            secondary_text_press: None,
305            last_selectable_secondary: None,
306            text_taps: None,
307            keyboard_modifiers: incular_core::Modifiers::empty(),
308            caret_reveal_key: None,
309            ime_composition: None,
310            text_histories: HashMap::new(),
311            text_input_commands: VecDeque::new(),
312            text_input_client: None,
313            text_input_configuration: None,
314            text_input_state: None,
315            text_input_caret_rect: None,
316            clipboard: Box::new(MemoryClipboard::default()),
317            editing_diagnostics: EditingDiagnostics::default(),
318            text_shortcut_platform: TextShortcutPlatform::current(),
319            frame_requested: true,
320            scheduler,
321            environment: Rc::new(RefCell::new(RuntimeEnvironment::default())),
322            lifecycle: ApplicationLifecycle::Starting,
323            lifecycle_observers: Vec::new(),
324            error_observers: Vec::new(),
325            environment_generation: 0,
326            environment_dependencies: Rc::new(Cell::new(EnvironmentFields::empty())),
327            application_root: None,
328            owner_scopes: HashMap::new(),
329            window_id,
330            window_scope,
331            window_manager,
332            driver_domain: Rc::new(
333                RUNTIME_SEQUENCE
334                    .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |id| id.checked_add(1))
335                    .expect("runtime driver identity space exhausted"),
336            ),
337        };
338        // Focus nodes live outside the retained tree so they may be shared by
339        // rebuildable descriptors. Resolve the first mounted autofocus listener
340        // once the element IDs exist, then keep runtime and node focus mirrored
341        // for subsequent keyboard traversal.
342        if let Some(autofocus) = runtime.tree.autofocus_element() {
343            runtime.set_focus(Some(autofocus));
344        }
345        Ok(runtime)
346    }
347    /// Stable identity of this runtime's driver domain, for outlet
348    /// ownership diagnostics: pairs with the outlet id to name both sides
349    /// of a driver conflict.
350    #[must_use]
351    pub fn id(&self) -> u64 {
352        *self.driver_domain
353    }
354
355    /// Liveness token for outlet driver claims. Dies with the runtime, so
356    /// teardown releases outlet ownership without any registry.
357    pub(crate) fn driver_token(&self) -> Weak<u64> {
358        Rc::downgrade(&self.driver_domain)
359    }
360
361    /// Starts work owned by this retained root. Component code should prefer
362    /// the build context's spawn API so completion lifetime follows its owner.
363    #[must_use]
364    pub fn spawner(&self) -> RuntimeSpawner {
365        self.window_id.map_or_else(
366            || tasks::TaskScheduler::spawner(&self.scheduler),
367            |window_id| tasks::TaskScheduler::spawner_for(&self.scheduler, window_id),
368        )
369    }
370    /// Sends a `Send` callback from any worker thread to the UI owner. The
371    /// callback is never run concurrently with retained-tree mutation.
372    #[must_use]
373    pub fn dispatcher(&self) -> UiDispatcher {
374        self.scheduler.borrow().dispatcher_for(self.window_id)
375    }
376    /// Returns the application's Tokio handle for advanced libraries. Direct
377    /// Tokio tasks have Tokio/application lifetime and are not component-owned.
378    #[must_use]
379    #[cfg(not(target_arch = "wasm32"))]
380    pub fn tokio_handle(&self) -> TokioHandle {
381        self.scheduler.borrow().tokio_handle()
382    }
383    pub fn set_wake_handler(&mut self, wake: std::sync::Arc<dyn RuntimeWake>) {
384        self.scheduler.borrow_mut().set_wake(wake);
385    }
386
387    /// Returns the owning normalized window when this runtime is managed by a
388    /// multi-window application. Standalone runtimes remain windowless.
389    #[must_use]
390    pub const fn window_id(&self) -> Option<WindowId> {
391        self.window_id
392    }
393
394    /// Returns default application-menu bridges retained by this window root.
395    /// Native desktop adapters use these after native window creation; normal
396    /// application code should use `PlatformMenuBar` instead.
397    #[doc(hidden)]
398    #[must_use]
399    pub fn platform_menu_bindings(&self) -> Vec<PlatformMenuBinding> {
400        self.tree.platform_menu_bindings()
401    }
402
403    /// Marks only this retained root for a future presentation.
404    pub fn request_frame(&mut self) {
405        self.frame_requested = true;
406    }
407
408    /// Drains native text-input commands produced since the last platform
409    /// event-loop turn. A desktop runner applies these to Winit; mobile hosts
410    /// can translate the same data to their platform text services.
411    pub fn take_text_input_commands(&mut self) -> Vec<TextInputCommand> {
412        self.text_input_commands.drain(..).collect()
413    }
414
415    /// Opens a second retained root through the owning application. This can
416    /// be called from a build or a UI completion; native creation is queued
417    /// for the desktop event-loop callback.
418    pub fn open_window(
419        &mut self,
420        options: WindowOptions,
421        root: Widget,
422    ) -> Result<WindowHandle, WindowError> {
423        self.window_manager
424            .as_ref()
425            .ok_or(WindowError::ApplicationStopped)?
426            .open_window(options, root)
427    }
428
429    /// Returns a retained UI capability that callbacks may use to open a
430    /// window after this build has returned.
431    #[must_use]
432    pub fn window_opener(&self) -> Option<WindowOpener> {
433        self.window_manager
434            .as_ref()
435            .cloned()
436            .map(|manager| WindowOpener { manager })
437    }
438
439    /// Returns a command handle for this retained root's owning native window.
440    #[must_use]
441    pub fn window_handle(&self) -> Option<WindowHandle> {
442        let id = self.window_id?;
443        self.window_manager.as_ref()?.handle(id)
444    }
445
446    /// Returns the scope cancelled as soon as this window closes. Child
447    /// component scopes inherit from it automatically.
448    #[must_use]
449    pub fn window_task_scope(&self) -> TaskScope {
450        self.window_scope.clone()
451    }
452
453    /// Starts bounded blocking work. Its completion callback runs later on the
454    /// UI thread, so it may safely update a local `Signal` or schedule a build.
455    pub fn spawn_blocking<T>(
456        &self,
457        work: impl FnOnce() -> T + Send + 'static,
458        complete: impl FnOnce(Result<T, TaskFailure>, &mut Runtime) + 'static,
459    ) -> TaskHandle
460    where
461        T: Send + 'static,
462    {
463        self.spawner()
464            .spawn_blocking_in(&self.window_scope, work, complete)
465    }
466    /// Starts Tokio work cancelled with this window. Use a `BuildContext` for
467    /// work whose completion must be tied to a mounted declarative owner.
468    pub fn spawn<F, T>(&self, future: F) -> Task<T>
469    where
470        F: std::future::Future<Output = T> + Send + 'static,
471        T: Send + 'static,
472    {
473        self.spawner().spawn_in(&self.window_scope, future)
474    }
475    /// Starts window-scoped Tokio work with a UI-thread completion.
476    pub fn spawn_into<F, T>(
477        &self,
478        future: F,
479        complete: impl FnOnce(Result<T, TaskFailure>, &mut Runtime) + 'static,
480    ) -> TaskHandle
481    where
482        F: std::future::Future<Output = T> + Send + 'static,
483        T: Send + 'static,
484    {
485        self.spawner()
486            .spawn_into_in(&self.window_scope, future, complete)
487    }
488    /// Processes a bounded batch of UI-relevant Tokio completion messages and
489    /// dispatch callbacks. Native runners call this only from their user-event
490    /// path; it never polls Tokio or implies a redraw.
491    pub fn process_runtime_work_at(&mut self, _now: Instant) {
492        let scheduler = self.scheduler.clone();
493        // Take message-owned work while borrowing the local scheduler, then
494        // release that borrow before any UI callback runs. A completion may
495        // safely start its next Tokio task without re-entering `RefCell`.
496        let work = scheduler.borrow_mut().take_turn();
497        for work in work {
498            self.process_ui_work(&scheduler, work);
499        }
500        // A UI completion may also complete a value a widget build reads,
501        // such as a `UiFuture` behind a `FutureBuilder`.
502        if self.reactive.borrow().has_work()
503            || !self.pending.is_empty()
504            || self.tree.has_dirty_dependencies()
505        {
506            self.frame_requested = true;
507        }
508    }
509
510    pub(crate) fn process_ui_work(
511        &mut self,
512        scheduler: &Rc<RefCell<tasks::TaskScheduler>>,
513        work: tasks::UiWork,
514    ) {
515        let mut failures = Vec::new();
516        match work {
517            tasks::UiWork::Dispatch { callback, .. } => {
518                if scheduler.borrow().accepting()
519                    && std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| callback(self)))
520                        .is_err()
521                {
522                    failures.push(TaskFailure::Panicked);
523                }
524            }
525            tasks::UiWork::Task(pending) => {
526                scheduler.borrow().detach_scope(&pending.control);
527                let stale_owner = pending.control.owner_is_stale(self);
528                if !scheduler.borrow().accepting() || pending.control.is_cancelled() || stale_owner
529                {
530                    (pending.discard)();
531                    scheduler
532                        .borrow_mut()
533                        .record_discard(pending.blocking, stale_owner);
534                } else {
535                    #[cfg(feature = "devtools")]
536                    let previous_task_context = DEV_TASK_COMPLETION.with(|context| {
537                        let previous = context.get();
538                        context.set(true);
539                        previous
540                    });
541                    let failure = (pending.finish)(self);
542                    #[cfg(feature = "devtools")]
543                    DEV_TASK_COMPLETION.with(|context| context.set(previous_task_context));
544                    if let Some(failure) = scheduler
545                        .borrow_mut()
546                        .record_completion(pending.blocking, failure)
547                    {
548                        failures.push(failure);
549                    }
550                }
551            }
552        }
553        for failure in failures {
554            if self.error_observers.is_empty() {
555                // Flutter's default FlutterError.onError (presentError) reports
556                // the error instead of dropping it; registered observers replace
557                // that default.
558                eprintln!("Incular runtime error: {failure:?}");
559            }
560            for observer in &self.error_observers {
561                observer(RuntimeErrorReport {
562                    failure: failure.clone(),
563                });
564            }
565        }
566        // A UI completion may also complete a value a widget build reads,
567        // such as a `UiFuture` behind a `FutureBuilder`.
568        if self.reactive.borrow().has_work()
569            || !self.pending.is_empty()
570            || self.tree.has_dirty_dependencies()
571        {
572            self.frame_requested = true;
573        }
574    }
575    pub fn process_runtime_work(&mut self) {
576        self.process_runtime_work_at(Instant::now());
577    }
578    #[must_use]
579    pub fn runtime_diagnostics(&self) -> RuntimeDiagnostics {
580        self.scheduler.borrow().diagnostics()
581    }
582    /// Compact ownership-facing state for framework diagnostics. Tokio's own
583    /// tracing ecosystem remains the source of executor-level telemetry.
584    #[must_use]
585    pub fn debug_dump(&self) -> String {
586        let diagnostics = self.runtime_diagnostics();
587        let (application_tasks, owner_tasks) = self.scheduler.borrow().active_ownership();
588        format!(
589            "Lifecycle: {:?}\nTokio tasks tracked: {}\n  application: {}\n  owner-scoped: {}\nPending UI messages: {}\nEnvironment generation: {}",
590            self.lifecycle,
591            diagnostics.active_tracked_tasks,
592            application_tasks,
593            owner_tasks,
594            diagnostics
595                .ui_messages_enqueued
596                .saturating_sub(diagnostics.ui_messages_processed),
597            self.environment_generation,
598        )
599    }
600    #[must_use]
601    pub fn environment(&self) -> RuntimeEnvironment {
602        self.environment.borrow().clone()
603    }
604    #[must_use]
605    pub const fn lifecycle(&self) -> ApplicationLifecycle {
606        self.lifecycle
607    }
608    #[must_use]
609    pub const fn environment_generation(&self) -> u64 {
610        self.environment_generation
611    }
612    /// Replaces normalized runtime values from a platform event. Rebuild is
613    /// requested only when the declarative root read a changed value.
614    pub fn set_environment(&mut self, environment: RuntimeEnvironment) -> bool {
615        let environment = environment.normalized();
616        let previous = self.environment.borrow().clone();
617        if previous == environment {
618            return false;
619        }
620        let changed = environment_change_mask(&previous, &environment);
621        *self.environment.borrow_mut() = environment.clone();
622        // WidgetTree owns renderer-neutral runtime geometry and direction used
623        // by retained policies such as SafeArea and transient placement. Keep
624        // that snapshot synchronized even when no declarative environment
625        // dependency requires rebuilding the application root.
626        let retained_environment_dirty = self.tree.set_environment(environment);
627        self.environment_generation = self
628            .environment_generation
629            .checked_add(1)
630            .expect("runtime environment generation exhausted");
631        if retained_environment_dirty {
632            self.frame_requested = true;
633        }
634        if self.environment_dependencies.get().intersects(changed)
635            && let Some(root) = self.application_root
636        {
637            #[cfg(feature = "devtools")]
638            self.tree
639                .note_invalidation(root, InvalidationCause::EnvironmentChanged);
640            let _ = self.rebuild_from_builder(root);
641            self.frame_requested = true;
642        }
643        true
644    }
645    pub fn transition_lifecycle(&mut self, lifecycle: ApplicationLifecycle) -> bool {
646        if self.lifecycle == lifecycle {
647            return false;
648        }
649        let transition = LifecycleTransition {
650            previous: self.lifecycle,
651            current: lifecycle,
652        };
653        self.lifecycle = lifecycle;
654        for observer in &self.lifecycle_observers {
655            observer(transition);
656        }
657        if matches!(
658            lifecycle,
659            ApplicationLifecycle::Stopping | ApplicationLifecycle::Terminated
660        ) {
661            self.reactive.borrow_mut().clear();
662            self.scheduler.borrow_mut().shutdown();
663        }
664        true
665    }
666    pub fn observe_lifecycle(&mut self, observer: impl Fn(LifecycleTransition) + 'static) {
667        self.lifecycle_observers.push(Rc::new(observer));
668    }
669    pub fn observe_runtime_error(&mut self, observer: impl Fn(RuntimeErrorReport) + 'static) {
670        self.error_observers.push(Rc::new(observer));
671    }
672    /// Cancels runtime-owned work and prevents later queued callbacks from
673    /// touching retained state. Native runners should call this before their
674    /// renderer/window teardown.
675    pub fn shutdown(&mut self) {
676        if self.focused.is_some() {
677            self.set_focus(None);
678        } else {
679            self.cancel_ime_composition();
680            self.sync_text_input_client();
681        }
682        self.tree.cancel_all_gesture_streams();
683        self.captured_text_field = None;
684        self.captured_selectable_text = None;
685        self.hovered_button = None;
686        self.pressed_button = None;
687        self.legacy_pointer = None;
688        self.text_histories.clear();
689        self.tree.release_edit_subscriptions();
690        let _ = self.transition_lifecycle(ApplicationLifecycle::Stopping);
691        for (_, scope) in self.owner_scopes.drain() {
692            scope.cancel();
693        }
694        self.scheduler.borrow_mut().shutdown();
695        self.lifecycle = ApplicationLifecycle::Terminated;
696    }
697
698    /// Releases window-owned subscriptions and callback state without
699    /// stopping the shared application scheduler. `Application` calls this
700    /// before dropping a closed retained root.
701    pub(crate) fn dispose_window(&mut self) {
702        if self.focused.is_some() {
703            self.set_focus(None);
704        } else {
705            self.cancel_ime_composition();
706            self.sync_text_input_client();
707        }
708        self.tree.cancel_all_gesture_streams();
709        self.tree.release_edit_subscriptions();
710        self.legacy_pointer = None;
711        self.window_scope.cancel();
712        for (_, scope) in self.owner_scopes.drain() {
713            scope.cancel();
714        }
715        self.reactive.borrow_mut().clear();
716        self.builders.clear();
717        self.pending.clear();
718        self.order.clear();
719        self.handlers.clear();
720        self.legacy_action_handler = None;
721        self.text_histories.clear();
722        self.text_input_commands.clear();
723        self.text_input_client = None;
724        self.text_input_configuration = None;
725        self.text_input_state = None;
726        self.text_input_caret_rect = None;
727        self.captured_text_field = None;
728        self.captured_selectable_text = None;
729        self.hovered_button = None;
730        self.pressed_button = None;
731    }
732    #[must_use]
733    pub fn tree(&self) -> &WidgetTree {
734        &self.tree
735    }
736
737    #[cfg(feature = "devtools")]
738    pub(crate) fn devtools_reactive_root(&self) -> ReactiveRootId {
739        self.reactive.borrow().root
740    }
741    #[must_use]
742    pub fn tree_mut(&mut self) -> &mut WidgetTree {
743        &mut self.tree
744    }
745
746    /// Returns the effective capture-protection policy contributed by the
747    /// mounted retained tree. The window/application boundary is responsible
748    /// for forwarding changes to its native adapter.
749    #[must_use]
750    pub fn content_sensitivity(&self) -> incular_config::ContentSensitivity {
751        self.tree.content_sensitivity()
752    }
753
754    /// Visible transient portals after the latest layout. Desktop adapters use
755    /// this retained snapshot to bind popup presentation to the owning window
756    /// without coupling controls to a native event loop.
757    #[must_use]
758    pub fn transient_surfaces(&self) -> Vec<incular_widgets::TransientSurfaceSnapshot> {
759        self.tree.transient_surfaces()
760    }
761
762    /// Updates the parent-local logical rectangle that confirmed native
763    /// transient hosts may use for collision placement.
764    pub fn set_native_transient_bounds(&mut self, bounds: Option<incular_core::Rect>) -> bool {
765        let changed = self.tree.set_native_transient_bounds(bounds);
766        self.frame_requested |= changed;
767        changed
768    }
769
770    /// Updates the set of retained transients whose native hosts have completed
771    /// negotiation. A presentation change requests a frame so placement,
772    /// semantics, hit testing, and rendering switch coordinate spaces together.
773    pub fn set_native_transient_presentations(
774        &mut self,
775        ids: impl IntoIterator<Item = incular_widgets::TransientSurfaceId>,
776    ) -> bool {
777        let changed = self.tree.set_native_transient_presentations(ids);
778        self.frame_requested |= changed;
779        changed
780    }
781
782    /// Applies a framework-level transient dismissal cause without changing
783    /// native focus ownership. Controllers receive the callback and the normal
784    /// retained rebuild path removes their popup subtree.
785    pub fn dismiss_transients(&mut self, reason: incular_widgets::TransientDismissReason) -> bool {
786        let dismissed = self.tree.dismiss_transients(reason) > 0;
787        self.frame_requested |= dismissed;
788        dismissed
789    }
790    #[cfg(feature = "devtools")]
791    pub(crate) fn devtools_edit_property(
792        &mut self,
793        id: incular_devtools_protocol::DevWidgetId,
794        name: &str,
795        value: &incular_devtools_protocol::DebugValue,
796    ) -> bool {
797        let edit = self.tree.devtools_edit_property(id, name, value);
798        self.frame_requested |= edit == incular_widgets::devtools::DevEdit::Applied;
799        edit != incular_widgets::devtools::DevEdit::Rejected
800    }
801    #[cfg(feature = "devtools")]
802    pub(crate) fn devtools_revert_property(
803        &mut self,
804        id: incular_devtools_protocol::DevWidgetId,
805        name: &str,
806        original: &incular_devtools_protocol::DebugValue,
807    ) -> bool {
808        let edit = self.tree.devtools_revert_property(id, name, original);
809        self.frame_requested |= edit == incular_widgets::devtools::DevEdit::Applied;
810        edit != incular_widgets::devtools::DevEdit::Rejected
811    }
812    /// Sets the animation-only time scale for this retained root. Runtime
813    /// messages, Tokio tasks and profiler wall-clock measurements remain on
814    /// the platform monotonic clock.
815    pub fn set_animation_time_scale(&mut self, scale: f32) {
816        self.tree.set_animation_time_scale(scale);
817    }
818    #[must_use]
819    pub fn focused_element(&self) -> Option<ElementId> {
820        self.focused
821    }
822    /// Dispatches an owned semantic action through the same control state used
823    /// by pointer and keyboard input. Native adapters queue these requests;
824    /// they never borrow mutable element storage. Eligibility reuses the
825    /// existing owners: explicit semantic callbacks always execute;
826    /// built-in Activate additionally requires the advertisement predicate
827    /// (enabled button with a bound callback), matching the pointer and
828    /// keyboard paths, which never see a disabled button past their own
829    /// enabled gate. SetText requires an editable field while SetSelection
830    /// requires only a controller — selection stays available in read-only
831    /// and disabled fields on every input path. Focus accepts only targets
832    /// the tree's own focusability owner admits (`can_request_focus`:
833    /// enabled controls, selectable text, or a focus node, unobstructed by
834    /// `exclude_focus` or a hidden IndexedStack slot), with an
835    /// already-focused valid target still reporting success; `set_focus`
836    /// remains the single focus owner for all paths.
837    pub fn dispatch_semantic_action(
838        &mut self,
839        node: SemanticNodeId,
840        action: SemanticAction,
841    ) -> bool {
842        let Some(element) = self.tree.element_for_semantic_node(node) else {
843            return false;
844        };
845        self.tree.note_semantic_action();
846        let handled = match action {
847            SemanticAction::Focus => {
848                if self.tree.can_request_focus(element) {
849                    self.set_focus(Some(element));
850                    true
851                } else {
852                    false
853                }
854            }
855            SemanticAction::Activate => {
856                if let Some(callback) = self.tree.semantic_action_callback(
857                    element,
858                    incular_semantics::SemanticActionKind::Activate,
859                ) {
860                    callback();
861                    true
862                } else if self.tree.semantic_activate_executable(element) {
863                    self.tree
864                        .action_for_element(element)
865                        .map(|action| self.activate_action(action))
866                        .unwrap_or(false)
867                } else {
868                    false
869                }
870            }
871            SemanticAction::SetText(text) => {
872                if !self.tree.text_field_is_editable(element) {
873                    false
874                } else {
875                    self.tree
876                        .text_controller(element)
877                        .map(|controller| {
878                            controller.set_text(text);
879                            true
880                        })
881                        .unwrap_or(false)
882                }
883            }
884            SemanticAction::SetSelection { base, extent } => self
885                .tree
886                .text_controller(element)
887                .map(|controller| {
888                    let length = controller.text().len();
889                    controller.set_selection(TextSelection {
890                        base: base.min(length),
891                        extent: extent.min(length),
892                    });
893                    true
894                })
895                .unwrap_or(false),
896            SemanticAction::ScrollForward => self
897                .tree
898                .semantic_action_callback(
899                    element,
900                    incular_semantics::SemanticActionKind::ScrollForward,
901                )
902                .map(|callback| {
903                    callback();
904                    true
905                })
906                .unwrap_or_else(|| self.tree.semantic_scroll(element, true)),
907            SemanticAction::ScrollBackward => self
908                .tree
909                .semantic_action_callback(
910                    element,
911                    incular_semantics::SemanticActionKind::ScrollBackward,
912                )
913                .map(|callback| {
914                    callback();
915                    true
916                })
917                .unwrap_or_else(|| self.tree.semantic_scroll(element, false)),
918            SemanticAction::Increment => self
919                .tree
920                .semantic_action_callback(element, incular_semantics::SemanticActionKind::Increment)
921                .map(|callback| {
922                    callback();
923                    true
924                })
925                .unwrap_or_else(|| self.tree.dispatch_semantic_increment(element, true)),
926            SemanticAction::Decrement => self
927                .tree
928                .semantic_action_callback(element, incular_semantics::SemanticActionKind::Decrement)
929                .map(|callback| {
930                    callback();
931                    true
932                })
933                .unwrap_or_else(|| self.tree.dispatch_semantic_increment(element, false)),
934        };
935        if handled {
936            self.frame_requested = true;
937        }
938        handled
939    }
940    #[must_use]
941    pub fn focus_diagnostics(&self) -> FocusDiagnostics {
942        FocusDiagnostics {
943            focused_element: self.focused,
944            text_pointer_capture: self.captured_text_field,
945        }
946    }
947    #[must_use]
948    pub const fn editing_diagnostics(&self) -> EditingDiagnostics {
949        self.editing_diagnostics
950    }
951    /// Chooses which platform's text-editing shortcut table text fields use
952    /// (Flutter's `defaultTargetPlatform`): Option for words and Command for
953    /// lines on macOS and iOS, Control for words elsewhere. Defaults to the
954    /// platform this build targets.
955    pub fn set_text_shortcut_platform(&mut self, platform: TextShortcutPlatform) {
956        self.text_shortcut_platform = platform;
957    }
958
959    /// The platform whose text-editing shortcuts are active.
960    #[must_use]
961    pub fn text_shortcut_platform(&self) -> TextShortcutPlatform {
962        self.text_shortcut_platform
963    }
964
965    pub fn set_clipboard(&mut self, clipboard: Box<dyn Clipboard>) {
966        self.clipboard = clipboard;
967    }
968    /// Returns the capabilities of the clipboard backend currently installed
969    /// for this retained window.
970    #[must_use]
971    pub fn clipboard_capabilities(&self) -> ClipboardCapabilities {
972        self.clipboard.capabilities()
973    }
974    /// Reads rich clipboard data without collapsing it to plain text.
975    pub fn read_clipboard(
976        &mut self,
977        request: TransferReadRequest,
978    ) -> Result<DataTransfer, ClipboardError> {
979        self.clipboard.read(request)
980    }
981    /// Writes one rich clipboard transfer. The report distinguishes formats
982    /// written atomically by the backend from representations it skipped.
983    pub fn write_clipboard(
984        &mut self,
985        transfer: DataTransfer,
986    ) -> Result<ClipboardWriteReport, ClipboardError> {
987        self.clipboard.write(transfer)
988    }
989    pub fn set_clipboard_text(&mut self, text: impl Into<String>) {
990        self.clipboard.set_text(text.into());
991    }
992    /// Routes platform-owned transfer data through external drop targets. This
993    /// path is intentionally independent of the local typed gesture arena.
994    #[must_use]
995    pub fn handle_external_drag(&mut self, event: ExternalDragEvent) -> ExternalDragResponse {
996        let response = self.tree.dispatch_external_drag(event);
997        self.frame_requested = true;
998        response
999    }
1000    /// Processes a normalized platform event without exposing native window or
1001    /// event-loop types to the runtime API.
1002    pub fn handle_platform_event(&mut self, event: PlatformEvent) -> Option<EventTarget> {
1003        match event {
1004            PlatformEvent::Input(input) => self.handle_input(input),
1005            PlatformEvent::TextInputAction(action) => {
1006                self.handle_text_input_action(action);
1007                None
1008            }
1009            PlatformEvent::Metrics(metrics) => {
1010                self.update_window_metrics(metrics);
1011                None
1012            }
1013            PlatformEvent::Environment(environment) => {
1014                self.set_environment(environment);
1015                None
1016            }
1017            PlatformEvent::Lifecycle(lifecycle) => {
1018                self.transition_lifecycle(match lifecycle {
1019                    PlatformLifecycle::Active => ApplicationLifecycle::Active,
1020                    PlatformLifecycle::Inactive => ApplicationLifecycle::Inactive,
1021                    PlatformLifecycle::Suspended => ApplicationLifecycle::Suspended,
1022                    PlatformLifecycle::Resumed => ApplicationLifecycle::Active,
1023                    PlatformLifecycle::Stopping => ApplicationLifecycle::Stopping,
1024                });
1025                None
1026            }
1027            PlatformEvent::ExternalDrag(event) => {
1028                let _ = self.handle_external_drag(event);
1029                None
1030            }
1031            PlatformEvent::CloseRequested => {
1032                self.shutdown();
1033                None
1034            }
1035        }
1036    }
1037    /// Applies the authoritative logical/physical conversion supplied by the
1038    /// platform metrics owner. Existing environment values such as locale are
1039    /// preserved while size and scale change together.
1040    pub fn update_window_metrics(&mut self, metrics: WindowMetrics) -> bool {
1041        let mut environment = self.environment();
1042        environment.viewport = metrics.logical_size();
1043        environment.physical_width = metrics.physical_size.width;
1044        environment.physical_height = metrics.physical_size.height;
1045        environment.scale_factor = metrics.scale_factor;
1046        self.set_environment(environment)
1047    }
1048    pub fn register_builder(
1049        &mut self,
1050        id: ElementId,
1051        builder: impl FnMut() -> Widget + 'static,
1052    ) -> Result<(), TreeError> {
1053        self.register_builder_without_rebuild(id, builder)?;
1054        self.rebuild_from_builder(id)
1055    }
1056    pub(crate) fn register_builder_without_rebuild(
1057        &mut self,
1058        id: ElementId,
1059        builder: impl FnMut() -> Widget + 'static,
1060    ) -> Result<(), TreeError> {
1061        if !self.tree.element_exists(id) {
1062            return Err(TreeError::MissingElement(id));
1063        }
1064        self.builders.insert(id, Box::new(builder));
1065        Ok(())
1066    }
1067    pub(crate) fn install_initial_dependencies(
1068        &mut self,
1069        id: ElementId,
1070        initial_dependencies: Rc<RefCell<InitialBuildDependencies>>,
1071    ) {
1072        let initial_dependencies = std::mem::take(&mut *initial_dependencies.borrow_mut());
1073        let queue = self.reactive.clone();
1074        let root = queue.borrow().root;
1075        let queue_weak = Rc::downgrade(&queue);
1076        for dependency in initial_dependencies.signals {
1077            let tracked =
1078                crate::reactive::subscribe_element(dependency, root, id, queue_weak.clone());
1079            queue.borrow_mut().record(id, tracked);
1080        }
1081        for scope in initial_dependencies.focus_scopes {
1082            install_focus_scope_watch(&queue, id, &scope);
1083        }
1084    }
1085    pub fn schedule_update(&mut self, id: ElementId, widget: Widget) -> Result<(), TreeError> {
1086        if !self.tree.element_exists(id) {
1087            return Err(TreeError::MissingElement(id));
1088        }
1089        #[cfg(feature = "devtools")]
1090        self.tree
1091            .note_invalidation(id, InvalidationCause::WidgetConfigurationChanged);
1092        self.tree.mark_build(id)?;
1093        let mut widget = widget;
1094        self.prepare_widget(&mut widget);
1095        if self.pending.insert(id, widget).is_none() {
1096            self.order.push_back(id);
1097        }
1098        self.frame_requested = true;
1099        Ok(())
1100    }
1101    #[must_use]
1102    pub fn frame_requested(&self) -> bool {
1103        self.frame_requested
1104            || self.reactive.borrow().has_work()
1105            || self.tree.has_pending_application_fonts()
1106    }
1107    #[must_use]
1108    pub fn handle_input(&mut self, event: InputEvent) -> Option<EventTarget> {
1109        self.tree
1110            .set_diagnostic_input_trigger(None, &event, diagnostic_input_trigger);
1111        self.handle_input_timed(event)
1112    }
1113
1114    pub(crate) fn handle_input_with_trigger(
1115        &mut self,
1116        event: InputEvent,
1117        trigger: impl Into<String>,
1118    ) -> Option<EventTarget> {
1119        self.tree.set_diagnostic_trigger(trigger);
1120        self.handle_input_timed(event)
1121    }
1122
1123    pub(crate) fn handle_input_timed(&mut self, event: InputEvent) -> Option<EventTarget> {
1124        let started = incular_core::time::Instant::now();
1125        let target = self.handle_input_inner(event);
1126        self.pending_event_processing_us = self
1127            .pending_event_processing_us
1128            .saturating_add(us_since_instant(started));
1129        target
1130    }
1131
1132    /// Associates subsequent frame work with a higher-level command such as a
1133    /// semantic simulation click. The trigger survives the input callback so
1134    /// deferred build/layout failures retain their original cause.
1135    pub fn set_diagnostic_trigger(&self, trigger: impl Into<String>) {
1136        self.tree.set_diagnostic_trigger(trigger);
1137    }
1138    /// The retained tree's window key for gesture arenas and hover state.
1139    fn gesture_window(&self) -> u64 {
1140        self.window_id.map_or(0, |window| {
1141            (u64::from(window.index()) << 32) | u64::from(window.generation())
1142        })
1143    }
1144    fn handle_input_inner(&mut self, event: InputEvent) -> Option<EventTarget> {
1145        let gesture_window = self.gesture_window();
1146        let (pointer, device, kind, buttons, button, sample, phase, position, metadata_rich) =
1147            match event {
1148                InputEvent::Pointer { phase, position } => (
1149                    0,
1150                    0,
1151                    PointerDeviceKind::Mouse,
1152                    0,
1153                    None,
1154                    PointerSampleMetadata::default(),
1155                    phase,
1156                    position,
1157                    false,
1158                ),
1159                InputEvent::PointerWithId {
1160                    pointer,
1161                    phase,
1162                    position,
1163                } => (
1164                    pointer,
1165                    0,
1166                    PointerDeviceKind::Mouse,
1167                    0,
1168                    None,
1169                    PointerSampleMetadata::default(),
1170                    phase,
1171                    position,
1172                    false,
1173                ),
1174                InputEvent::PointerWithMetadata {
1175                    pointer,
1176                    device,
1177                    kind,
1178                    buttons,
1179                    button,
1180                    sample,
1181                    phase,
1182                    position,
1183                } => (
1184                    pointer, device, kind, buttons, button, sample, phase, position, true,
1185                ),
1186                event => {
1187                    match event {
1188                        InputEvent::Scroll { delta } => {
1189                            // The platform normalizes wheel values to logical pixels. The
1190                            // latest pointer position selects the nearest viewport.
1191                            if self.tree.scroll_at(self.last_pointer, delta) {
1192                                self.frame_requested = true;
1193                            }
1194                        }
1195                        InputEvent::Key(key) => {
1196                            self.handle_key(key);
1197                        }
1198                        InputEvent::Text(text) => {
1199                            self.insert_text(&text);
1200                        }
1201                        InputEvent::Ime(ime) => {
1202                            self.handle_ime(ime);
1203                        }
1204                        InputEvent::TrackpadGesture(gesture) => {
1205                            // Aggregate gestures do not carry a focal position in
1206                            // Winit. Use only a genuine cursor position already
1207                            // observed for this window; never invent a touch point.
1208                            if self.pointer_position_known
1209                                && let Some(element) =
1210                                    self.tree.dispatch_trackpad_gesture_in_window(
1211                                        gesture_window,
1212                                        self.last_pointer,
1213                                        gesture,
1214                                    )
1215                            {
1216                                self.frame_requested = true;
1217                                return Some(EventTarget {
1218                                    element,
1219                                    action: None,
1220                                });
1221                            }
1222                        }
1223                        InputEvent::WindowResized { .. } => {}
1224                        InputEvent::Pointer { .. } => unreachable!(),
1225                        InputEvent::PointerWithId { .. } => unreachable!(),
1226                        InputEvent::PointerWithMetadata { .. } => unreachable!(),
1227                    }
1228                    return None;
1229                }
1230            };
1231        let hover_capable = matches!(
1232            kind,
1233            PointerDeviceKind::Mouse
1234                | PointerDeviceKind::Trackpad
1235                | PointerDeviceKind::Stylus
1236                | PointerDeviceKind::InvertedStylus
1237        );
1238        if hover_capable {
1239            match phase {
1240                PointerPhase::Enter => {
1241                    self.pointer_inside = true;
1242                    self.pointer_position_known = false;
1243                }
1244                PointerPhase::Exit => {
1245                    self.pointer_inside = false;
1246                    self.pointer_position_known = false;
1247                }
1248                _ => {
1249                    self.pointer_inside = true;
1250                    self.pointer_position_known = true;
1251                }
1252            }
1253        }
1254        if phase != PointerPhase::Enter {
1255            self.last_pointer = position;
1256        }
1257        if phase == PointerPhase::Down {
1258            // A held press owns `kPressTimeout` and `kLongPressTimeout`
1259            // deadlines that fire on the frame clock, so a frame must run.
1260            self.frame_requested = true;
1261        }
1262        let text_menu_was_showing = phase == PointerPhase::Down
1263            && button == Some(incular_core::SECONDARY_POINTER_BUTTON)
1264            && self.text_menu_open_at(position);
1265        if phase == PointerPhase::Down && self.tree.dismiss_transients_for_pointer(position) > 0 {
1266            self.frame_requested = true;
1267        }
1268        let raw_event = RawPointerEvent::new(
1269            pointer,
1270            device,
1271            kind,
1272            buttons,
1273            position,
1274            phase,
1275            Instant::now(),
1276        )
1277        .with_button(button)
1278        .with_sample(sample);
1279        if hover_capable {
1280            self.last_hover_sample =
1281                (!matches!(phase, PointerPhase::Enter | PointerPhase::Exit)).then_some(raw_event);
1282        }
1283        // One hit test serves the whole event: raw regions and listeners,
1284        // retained gestures, text editing and buttons.
1285        let hit = if matches!(phase, PointerPhase::Enter | PointerPhase::Exit) {
1286            PointerHitTest::default()
1287        } else {
1288            self.tree.pointer_hit_test(position)
1289        };
1290        let raw_target = self
1291            .tree
1292            .dispatch_raw_pointer_with_hit(gesture_window, raw_event, &hit);
1293        if metadata_rich
1294            && self.text_secondary_pointer(
1295                phase,
1296                button,
1297                device,
1298                pointer,
1299                position,
1300                &hit,
1301                text_menu_was_showing,
1302            )
1303        {
1304            self.frame_requested = true;
1305        }
1306        if matches!(phase, PointerPhase::Enter | PointerPhase::Exit) {
1307            if phase == PointerPhase::Exit {
1308                self.set_hover(None);
1309                if self.tree.scrollbar_pointer(PointerPhase::Exit, position) {
1310                    self.frame_requested = true;
1311                }
1312            }
1313            return None;
1314        }
1315
1316        let primary_changed = !metadata_rich || button == Some(PRIMARY_POINTER_BUTTON);
1317        let stream = (device, pointer);
1318        let active_primary = self.legacy_pointer == Some(stream);
1319        let legacy_pointer = match phase {
1320            PointerPhase::Down if primary_changed && self.legacy_pointer.is_none() => {
1321                self.legacy_pointer = Some(stream);
1322                true
1323            }
1324            PointerPhase::Down => primary_changed && active_primary,
1325            PointerPhase::Move => {
1326                active_primary || (hover_capable && self.legacy_pointer.is_none())
1327            }
1328            PointerPhase::Up => primary_changed && active_primary,
1329            PointerPhase::Cancel => active_primary,
1330            PointerPhase::Enter | PointerPhase::Exit => false,
1331        };
1332
1333        if let Some(element) = raw_target {
1334            self.frame_requested = true;
1335            self.release_legacy_pointer(device, pointer, phase, primary_changed);
1336            return Some(EventTarget {
1337                element,
1338                action: None,
1339            });
1340        }
1341        let ordinary_gesture_allowed = legacy_pointer || !metadata_rich || !hover_capable;
1342        if ordinary_gesture_allowed
1343            && let Some(element) = self.tree.dispatch_pointer_gesture_with_hit(
1344                gesture_window,
1345                device,
1346                kind,
1347                PointerEvent {
1348                    pointer,
1349                    position,
1350                    phase,
1351                    time: Instant::now(),
1352                },
1353                &hit,
1354            )
1355        {
1356            self.frame_requested = true;
1357            self.release_legacy_pointer(device, pointer, phase, primary_changed);
1358            return Some(EventTarget {
1359                element,
1360                action: None,
1361            });
1362        }
1363        // Legacy controls and scrollbars admit just the primary contact.
1364        // Additional contacts remain available to retained gesture regions.
1365        if !legacy_pointer {
1366            return None;
1367        }
1368        if self.tree.scrollbar_pointer_with_kind(phase, position, kind) {
1369            self.frame_requested = true;
1370            self.release_legacy_pointer(device, pointer, phase, primary_changed);
1371            return None;
1372        }
1373        let text_target = self.tree.text_field_for_hit(&hit);
1374        let selectable_target = self.tree.selectable_text_for_hit(&hit);
1375        let target = self.button_target(&hit);
1376        let result = match phase {
1377            PointerPhase::Enter | PointerPhase::Exit => {
1378                unreachable!("pointer boundary handled above")
1379            }
1380            PointerPhase::Move => {
1381                if let Some(field) = self.captured_text_field {
1382                    self.text_field_primary_drag(field, position);
1383                    self.frame_requested = true;
1384                }
1385                if let Some(label) = self.captured_selectable_text {
1386                    if let Some(target) = selectable_target {
1387                        if self
1388                            .tree
1389                            .selectable_text_set_selection(target, position, true)
1390                        {
1391                            self.captured_selectable_text = Some(target);
1392                            self.set_focus(Some(target));
1393                            self.frame_requested = true;
1394                        }
1395                    } else if self.tree.is_selectable_text(label) {
1396                        self.frame_requested = true;
1397                    }
1398                }
1399                self.set_hover(target.map(|(element, _)| element));
1400                target.map(|(element, action)| EventTarget { element, action })
1401            }
1402            PointerPhase::Down => {
1403                if let Some(label) = selectable_target {
1404                    self.set_focus(Some(label));
1405                    self.captured_selectable_text = Some(label);
1406                    if self
1407                        .tree
1408                        .selectable_text_set_selection(label, position, false)
1409                    {
1410                        self.frame_requested = true;
1411                        return Some(EventTarget {
1412                            element: label,
1413                            action: None,
1414                        });
1415                    }
1416                }
1417                if selectable_target.is_none()
1418                    && text_target.is_none()
1419                    && target.is_none()
1420                    && let Some((element, interaction)) = self.tree.window_interaction_at(position)
1421                    && let (Some(window_id), Some(manager)) =
1422                        (self.window_id, self.window_manager.as_ref())
1423                {
1424                    let operation = match interaction {
1425                        WindowInteraction::Move => WindowOperation::BeginMoveDrag,
1426                        WindowInteraction::Resize(direction) => {
1427                            WindowOperation::BeginResizeDrag(direction)
1428                        }
1429                    };
1430                    if manager.send_window_operation(window_id, operation) {
1431                        self.release_legacy_pointer(device, pointer, phase, primary_changed);
1432                        return Some(EventTarget {
1433                            element,
1434                            action: None,
1435                        });
1436                    }
1437                }
1438                let text_target_was_focused =
1439                    text_target.is_some_and(|field| self.focused == Some(field));
1440                let keep_text_field_focus = self
1441                    .focused
1442                    .is_some_and(|focused| self.tree.is_text_field(focused))
1443                    && self.tree.preserves_text_field_focus(position);
1444                self.set_focus(if keep_text_field_focus {
1445                    self.focused
1446                } else {
1447                    text_target
1448                });
1449                // Flutter's EditableText only installs its selection gestures
1450                // while `_userSelectionEnabled` (`!readOnly || !obscureText`)
1451                // holds; a read-only obscured field keeps focus but no caret.
1452                self.captured_text_field =
1453                    text_target.filter(|field| self.text_field_pointer_selection_enabled(*field));
1454                if let Some(field) = text_target {
1455                    if self.captured_text_field.is_some() {
1456                        self.text_field_primary_down(field, position, text_target_was_focused);
1457                    }
1458                    self.frame_requested = true;
1459                    return Some(EventTarget {
1460                        element: field,
1461                        action: None,
1462                    });
1463                }
1464                self.set_hover(target.map(|(element, _)| element));
1465                self.pressed_button = target.map(|(element, _)| element);
1466                if let Some(element) = self.pressed_button {
1467                    let _ = self
1468                        .tree
1469                        .set_button_interaction(element, None, Some(true), None);
1470                    self.frame_requested = true;
1471                }
1472                target.map(|(element, action)| EventTarget { element, action })
1473            }
1474            PointerPhase::Up => {
1475                if self.captured_text_field.take().is_some() {
1476                    self.text_field_primary_up();
1477                }
1478                self.captured_selectable_text = None;
1479                let pressed = self.pressed_button.take();
1480                let valid = pressed
1481                    .zip(target)
1482                    .filter(|(pressed, (target, _))| pressed == target)
1483                    .map(|(_, target)| target);
1484                if let Some(element) = pressed {
1485                    let _ = self
1486                        .tree
1487                        .set_button_interaction(element, None, Some(false), None);
1488                }
1489                if let Some((element, action)) = valid {
1490                    if let Some(action) = action {
1491                        self.activate_action(action);
1492                    }
1493                    Some(EventTarget { element, action })
1494                } else {
1495                    None
1496                }
1497            }
1498            PointerPhase::Cancel => {
1499                self.captured_text_field = None;
1500                self.captured_selectable_text = None;
1501                if let Some(element) = self.pressed_button.take() {
1502                    let _ = self
1503                        .tree
1504                        .set_button_interaction(element, None, Some(false), None);
1505                    self.frame_requested = true;
1506                }
1507                None
1508            }
1509        };
1510        self.release_legacy_pointer(device, pointer, phase, primary_changed);
1511        result
1512    }
1513    fn button_target(&self, hit: &PointerHitTest) -> Option<(ElementId, Option<ActionId>)> {
1514        hit.target()
1515            .and_then(|render| self.tree.element_for_render(render))
1516            .and_then(|element| self.tree.button_ancestor(element))
1517    }
1518    /// Flutter's `MouseTracker.updateAllDevices` after layout: a still
1519    /// pointer over content that moved (a programmatic or wheel scroll, a
1520    /// relayout) hovers whatever is under it now. Mouse regions get their
1521    /// enter and exit callbacks, and button hover follows unless a press
1522    /// owns the single-contact controls.
1523    fn refresh_hover_after_layout(&mut self) {
1524        if !self.pointer_inside || !self.pointer_position_known {
1525            return;
1526        }
1527        let Some(sample) = self.last_hover_sample else {
1528            return;
1529        };
1530        let event = RawPointerEvent {
1531            position: self.last_pointer,
1532            local_position: self.last_pointer,
1533            delta: incular_core::Offset::ZERO,
1534            local_delta: incular_core::Offset::ZERO,
1535            time: Instant::now(),
1536            ..sample
1537        };
1538        let hit = self.tree.pointer_hit_test(self.last_pointer);
1539        let window = self.gesture_window();
1540        self.tree.refresh_mouse_hover_in_window(window, event, &hit);
1541        if self.legacy_pointer.is_none() {
1542            let target = self.button_target(&hit);
1543            self.set_hover(target.map(|(element, _)| element));
1544        }
1545    }
1546    fn release_legacy_pointer(
1547        &mut self,
1548        device: u64,
1549        pointer: u64,
1550        phase: PointerPhase,
1551        primary_changed: bool,
1552    ) {
1553        if (phase == PointerPhase::Cancel || (phase == PointerPhase::Up && primary_changed))
1554            && self.legacy_pointer == Some((device, pointer))
1555        {
1556            self.legacy_pointer = None;
1557        }
1558    }
1559
1560    /// Cursor selected by the current retained hover route. `Defer` is resolved
1561    /// to the ordinary platform cursor before crossing the native boundary.
1562    #[must_use]
1563    pub fn effective_mouse_cursor(&self) -> MouseCursor {
1564        if !self.pointer_inside || !self.pointer_position_known {
1565            return MouseCursor::Basic;
1566        }
1567        match self.tree.mouse_cursor_at(self.last_pointer) {
1568            MouseCursor::Defer => MouseCursor::Basic,
1569            cursor => cursor,
1570        }
1571    }
1572    pub(crate) fn set_focus(&mut self, next: Option<ElementId>) {
1573        // Explicit input (including clearing focus) wins over a pending initial
1574        // autofocus request, even when it leaves the focused element unchanged.
1575        self.initial_focus = InitialFocus::Settled;
1576        if self.focused == next {
1577            return;
1578        }
1579        // A focus change retires the native client behind any open
1580        // composition: without a session id later IME events are
1581        // unattributable, so the old composition ends here and later
1582        // events start fresh on the newly focused field.
1583        self.cancel_ime_composition();
1584        if let Some(next) = next
1585            && self.tree.dismiss_transients_for_focus(next) > 0
1586        {
1587            self.frame_requested = true;
1588        }
1589        if let Some(previous) = self.focused {
1590            self.tree.set_keyboard_focus(previous, false);
1591            let _ = self.tree.set_focused(previous, false, Instant::now());
1592        }
1593        self.focused = next;
1594        if let Some(current) = next {
1595            let _ = self.tree.set_focused(current, true, Instant::now());
1596            self.tree.set_keyboard_focus(current, true);
1597            if self.tree.is_text_field(current) {
1598                let _ = self.ensure_text_history(current);
1599            }
1600        }
1601        self.frame_requested = true;
1602        self.sync_text_input_client();
1603    }
1604
1605    fn reconcile_focus_and_selection_captures(&mut self) {
1606        if self.captured_text_field.is_some_and(|field| {
1607            !self.tree.is_text_field(field) || !self.tree.can_request_focus(field)
1608        }) {
1609            self.captured_text_field = None;
1610        }
1611        if self.captured_selectable_text.is_some_and(|label| {
1612            !self.tree.is_selectable_text(label) || !self.tree.can_request_focus(label)
1613        }) {
1614            self.captured_selectable_text = None;
1615        }
1616        if self
1617            .focused
1618            .is_some_and(|focused| !self.tree.can_request_focus(focused))
1619        {
1620            self.set_focus(None);
1621        }
1622        self.reconcile_button_tenure();
1623    }
1624
1625    fn reconcile_button_tenure(&mut self) {
1626        // Pressed tenure belongs to a still-pressable button element.
1627        // `button_ancestor` already filters disabled and missing elements,
1628        // so reuse it instead of restating button liveness here. A stale
1629        // press clears its visual when still mounted and never activates.
1630        if self.pressed_button.is_some_and(|pressed| {
1631            self.tree
1632                .button_ancestor(pressed)
1633                .map(|(ancestor, _)| ancestor)
1634                != Some(pressed)
1635        }) && let Some(pressed) = self.pressed_button.take()
1636        {
1637            let _ = self
1638                .tree
1639                .set_button_interaction(pressed, None, Some(false), None);
1640            self.frame_requested = true;
1641        }
1642        // Hover tenure reuses the single `set_hover` owner so exit callbacks
1643        // fire for still-mounted buttons; pruned handlers make unmounted exit
1644        // a silent visual release.
1645        if self.hovered_button.is_some_and(|hovered| {
1646            self.tree
1647                .button_ancestor(hovered)
1648                .map(|(ancestor, _)| ancestor)
1649                != Some(hovered)
1650        }) {
1651            self.set_hover(None);
1652        }
1653    }
1654
1655    fn ensure_text_history(&mut self, field: ElementId) -> Option<UndoHistoryController> {
1656        let editor = self.tree.text_controller(field)?;
1657        let replace = self
1658            .text_histories
1659            .get(&field)
1660            .is_none_or(|history| history.editor() != editor);
1661        if replace {
1662            self.text_histories
1663                .insert(field, UndoHistoryController::new(editor));
1664        }
1665        let history = self.text_histories.get(&field).cloned()?;
1666        if let Some(max_entries) = self.tree.text_field_history_max_entries(field)
1667            && history.max_entries() != max_entries
1668        {
1669            history.set_max_entries(max_entries);
1670        }
1671        Some(history)
1672    }
1673
1674    /// Flutter's `EditableTextState._scheduleShowCaretOnScreen`: when the
1675    /// focused field gains focus or its value or selection changes, its
1676    /// ancestor viewports scroll its caret, with `scrollPadding`, into view.
1677    fn show_focused_caret_on_screen(&mut self) -> bool {
1678        let Some(field) = self.focused.filter(|id| self.tree.is_text_field(*id)) else {
1679            self.caret_reveal_key = None;
1680            return false;
1681        };
1682        let Some(controller) = self.tree.text_controller(field) else {
1683            return false;
1684        };
1685        let key = (field, controller.revisions().0, controller.selection());
1686        if self.caret_reveal_key == Some(key) {
1687            return false;
1688        }
1689        self.caret_reveal_key = Some(key);
1690        self.tree.text_field_show_caret_on_screen(field)
1691    }
1692
1693    fn sync_text_input_client(&mut self) {
1694        let snapshot = self
1695            .focused
1696            .and_then(|id| self.tree.text_field_input_snapshot(id));
1697        let Some(snapshot) = snapshot else {
1698            if let Some(client) = self.text_input_client.take() {
1699                self.text_input_commands
1700                    .push_back(TextInputCommand::Hide { client });
1701                self.text_input_commands
1702                    .push_back(TextInputCommand::Clear { client });
1703            }
1704            self.text_input_configuration = None;
1705            self.text_input_state = None;
1706            self.text_input_caret_rect = None;
1707            return;
1708        };
1709
1710        let client = TextInputClientId::new(snapshot.client_id);
1711        let input_type = match snapshot.input_type {
1712            TextInputTypeHint::Text if snapshot.multiline => TextInputType::Multiline,
1713            TextInputTypeHint::Text => TextInputType::Text,
1714            TextInputTypeHint::Multiline => TextInputType::Multiline,
1715            TextInputTypeHint::Number => TextInputType::Number,
1716            TextInputTypeHint::Phone => TextInputType::Phone,
1717            TextInputTypeHint::Email => TextInputType::Email,
1718            TextInputTypeHint::Url => TextInputType::Url,
1719            TextInputTypeHint::Password => TextInputType::Password,
1720        };
1721        let action = match snapshot.input_action {
1722            TextInputActionHint::Unspecified => {
1723                if snapshot.multiline {
1724                    TextInputAction::Newline
1725                } else {
1726                    TextInputAction::Done
1727                }
1728            }
1729            TextInputActionHint::None => TextInputAction::None,
1730            TextInputActionHint::Done => TextInputAction::Done,
1731            TextInputActionHint::Go => TextInputAction::Go,
1732            TextInputActionHint::Search => TextInputAction::Search,
1733            TextInputActionHint::Send => TextInputAction::Send,
1734            TextInputActionHint::Next => TextInputAction::Next,
1735            TextInputActionHint::Previous => TextInputAction::Previous,
1736            TextInputActionHint::Newline => TextInputAction::Newline,
1737        };
1738        let configuration = TextInputConfiguration {
1739            client,
1740            input_type: if snapshot.obscure_text {
1741                TextInputType::Password
1742            } else {
1743                input_type
1744            },
1745            action,
1746            multiline: snapshot.multiline,
1747            enabled: snapshot.enabled,
1748            read_only: snapshot.read_only,
1749            obscure_text: snapshot.obscure_text,
1750            traits: snapshot.traits,
1751        };
1752        let state = TextInputState {
1753            text: snapshot.text,
1754            selection_start: snapshot.selection.base,
1755            selection_end: snapshot.selection.extent,
1756            composing: snapshot.composing.map(|range| (range.start, range.end)),
1757        };
1758        let client_changed = self.text_input_client != Some(client);
1759        let configuration_changed = self.text_input_configuration.as_ref() != Some(&configuration);
1760        if client_changed || configuration_changed {
1761            self.text_input_commands
1762                .push_back(TextInputCommand::SetClient {
1763                    configuration: configuration.clone(),
1764                    state: state.clone(),
1765                    caret_rect: snapshot.caret_rect,
1766                });
1767        } else if self.text_input_state.as_ref() != Some(&state) {
1768            self.text_input_commands
1769                .push_back(TextInputCommand::Update {
1770                    client,
1771                    state: state.clone(),
1772                });
1773        }
1774        if !client_changed
1775            && self.text_input_caret_rect != Some(snapshot.caret_rect)
1776            && !configuration_changed
1777        {
1778            self.text_input_commands
1779                .push_back(TextInputCommand::SetCaretRect {
1780                    client,
1781                    rect: snapshot.caret_rect,
1782                });
1783        }
1784        self.text_input_client = Some(client);
1785        self.text_input_configuration = Some(configuration);
1786        self.text_input_state = Some(state);
1787        self.text_input_caret_rect = Some(snapshot.caret_rect);
1788    }
1789    fn focus_next(&mut self, reverse: bool) {
1790        let fields = self.tree.focusable_elements();
1791        if fields.is_empty() {
1792            return;
1793        }
1794        let index = self
1795            .focused
1796            .and_then(|focused| fields.iter().position(|id| *id == focused));
1797        let next = match index {
1798            Some(index) if reverse => fields[(index + fields.len() - 1) % fields.len()],
1799            Some(index) => fields[(index + 1) % fields.len()],
1800            None if reverse => *fields.last().expect("not empty"),
1801            None => fields[0],
1802        };
1803        self.set_focus(Some(next));
1804    }
1805    fn handle_key(&mut self, event: KeyboardEvent) {
1806        self.track_keyboard_modifiers(&event);
1807        // Widgets that show held modifiers (menu accelerator underlines)
1808        // rebuild when one is pressed or released.
1809        if matches!(
1810            event.code,
1811            Code::AltLeft
1812                | Code::AltRight
1813                | Code::ControlLeft
1814                | Code::ControlRight
1815                | Code::ShiftLeft
1816                | Code::ShiftRight
1817                | Code::MetaLeft
1818                | Code::MetaRight
1819        ) {
1820            self.frame_requested = true;
1821        }
1822        if event.state.is_down() {
1823            self.editing_diagnostics.key_down_received += 1;
1824        } else {
1825            self.editing_diagnostics.key_up_received += 1;
1826        }
1827
1828        // A FocusNode may be requested by application code or a FocusScopeNode
1829        // without going through runtime pointer/Tab handling. Adopt that
1830        // external selection before routing the event through retained parents.
1831        if let Some(external) = self.tree.focused_keyboard_element()
1832            && self.focused != Some(external)
1833        {
1834            self.set_focus(Some(external));
1835        }
1836
1837        // KeyboardListener/Shortcuts are the nearest retained command scopes.
1838        // Give them first refusal for every transition, including key-up and
1839        // auto-repeat; text editing and Tab traversal remain fallthrough paths.
1840        if self.tree.dispatch_keyboard(self.focused, event.clone()) {
1841            self.frame_requested = true;
1842            return;
1843        }
1844        if !event.state.is_down() {
1845            return;
1846        }
1847        if event.code == Code::Escape
1848            && self
1849                .tree
1850                .dismiss_transients(incular_widgets::TransientDismissReason::Escape)
1851                > 0
1852        {
1853            self.frame_requested = true;
1854            return;
1855        }
1856        if matches!(
1857            event.code,
1858            Code::ArrowUp | Code::ArrowDown | Code::ArrowLeft | Code::ArrowRight
1859        ) && let Some(focused) = self.focused
1860            && let Some(next) = self.tree.menu_directional_focus_target(focused, event.code)
1861        {
1862            self.set_focus(Some(next));
1863            return;
1864        }
1865        // Activate the focused surface itself. Only menus delegate activation
1866        // from a focused descendant; nested editors retain their own keys.
1867        if matches!(event.code, Code::Enter | Code::NumpadEnter | Code::Space)
1868            && let Some(focused) = self.focused
1869            && let Some((owner, Some(action))) = self.tree.button_ancestor(focused)
1870            && (owner == focused || self.tree.is_menu_item_focus(focused))
1871            && self.activate_action(action)
1872        {
1873            return;
1874        }
1875        // Flutter's NextFocusIntent/PreviousFocusIntent activators require
1876        // control, alt and meta to be released; Ctrl+Tab matches nothing.
1877        if event.code == Code::Tab
1878            && !event.modifiers.ctrl()
1879            && !event.modifiers.alt()
1880            && !event.modifiers.meta()
1881        {
1882            self.focus_next(event.modifiers.shift());
1883            return;
1884        }
1885        if let Some(label) = self.focused.filter(|id| self.tree.is_selectable_text(*id)) {
1886            if self.run_selectable_text_key(label, &event) {
1887                self.frame_requested = true;
1888            }
1889            return;
1890        }
1891        let Some(field) = self.focused.filter(|id| self.tree.is_text_field(*id)) else {
1892            return;
1893        };
1894        let Some(controller) = self.tree.text_controller(field) else {
1895            return;
1896        };
1897        // Flutter's `DefaultTextEditingShortcuts` pick the intent for the
1898        // chord on the target platform and the field's actions run it.
1899        if !self.run_text_field_key(field, &controller, &event) {
1900            return;
1901        }
1902        self.hide_text_field_menu(field);
1903        controller.reset_caret(Instant::now());
1904        self.frame_requested = true;
1905    }
1906    /// Whether the field hides its text (Flutter `obscureText`).
1907    fn text_field_obscured(&self, field: ElementId) -> bool {
1908        self.tree
1909            .text_field_input_snapshot(field)
1910            .is_some_and(|snapshot| snapshot.obscure_text)
1911    }
1912
1913    /// Flutter's `EditableText._userSelectionEnabled` with the default
1914    /// `enableInteractiveSelection`: `!readOnly || !obscureText`.
1915    fn text_field_pointer_selection_enabled(&self, field: ElementId) -> bool {
1916        self.tree
1917            .text_field_input_snapshot(field)
1918            .is_none_or(|snapshot| !(snapshot.read_only && snapshot.obscure_text))
1919    }
1920
1921    /// Flutter's `copySelection` gate: a collapsed selection or obscured text
1922    /// copies nothing and leaves the clipboard untouched.
1923    fn text_field_copy_allowed(
1924        &self,
1925        field: ElementId,
1926        controller: &TextEditingController,
1927    ) -> bool {
1928        !controller.value().selection.is_collapsed() && !self.text_field_obscured(field)
1929    }
1930
1931    /// Flutter's `cutSelection` gate: read-only or obscured fields and collapsed
1932    /// selections leave both the clipboard and the text unchanged.
1933    fn cut_text_field_selection(
1934        &mut self,
1935        field: ElementId,
1936        controller: &TextEditingController,
1937    ) -> bool {
1938        if !self.tree.text_field_is_editable(field)
1939            || !self.text_field_copy_allowed(field, controller)
1940        {
1941            return false;
1942        }
1943        self.clipboard.set_text(controller.selected_text());
1944        controller.replace_selection("");
1945        true
1946    }
1947
1948    fn insert_text(&mut self, text: &str) {
1949        let Some(field) = self.focused.filter(|id| self.tree.is_text_field(*id)) else {
1950            return;
1951        };
1952        if !self.tree.text_field_is_editable(field) {
1953            return;
1954        }
1955        if text.chars().any(char::is_control) {
1956            return;
1957        }
1958        if let Some(controller) = self.tree.text_controller(field) {
1959            controller.insert(text);
1960            self.editing_diagnostics.text_commits += 1;
1961            controller.reset_caret(Instant::now());
1962            self.frame_requested = true;
1963        }
1964    }
1965
1966    /// Handles the action chosen by a native software keyboard. Desktop
1967    /// Winit backends reach the same code path through Enter, while mobile
1968    /// hosts can preserve the IME's explicit Done/Next/Search intent.
1969    pub fn handle_text_input_action(&mut self, action: TextInputAction) -> bool {
1970        let Some(field) = self.focused.filter(|id| self.tree.is_text_field(*id)) else {
1971            return false;
1972        };
1973        let editable = self.tree.text_field_is_editable(field);
1974        if !editable {
1975            return false;
1976        }
1977        let handled = match action {
1978            TextInputAction::Newline => {
1979                if self.tree.is_multiline_text_field(field) {
1980                    self.tree.text_controller(field).is_some_and(|controller| {
1981                        controller.insert("\n");
1982                        controller.reset_caret(Instant::now());
1983                        true
1984                    })
1985                } else {
1986                    self.tree.submit_text_field(field)
1987                }
1988            }
1989            TextInputAction::Next => {
1990                let submitted = self.tree.submit_text_field(field);
1991                self.focus_next(false);
1992                submitted
1993            }
1994            TextInputAction::Previous => {
1995                let submitted = self.tree.submit_text_field(field);
1996                self.focus_next(true);
1997                submitted
1998            }
1999            TextInputAction::Done
2000            | TextInputAction::Go
2001            | TextInputAction::Search
2002            | TextInputAction::Send => self.tree.submit_text_field(field),
2003            TextInputAction::Unspecified | TextInputAction::None => false,
2004        };
2005        if handled {
2006            self.frame_requested = true;
2007        }
2008        handled
2009    }
2010    /// Drops the open composition, clearing the owner's stale preedit.
2011    /// Single cleanup path: the owner's own controller is the only
2012    /// writer, so cleanup never lands in another editor. Clearing an
2013    /// unset preedit notifies nothing.
2014    fn cancel_ime_composition(&mut self) {
2015        let owner = self.ime_composition.take();
2016        if let Some(owner) = owner {
2017            owner.controller.clear_preedit();
2018        }
2019    }
2020
2021    /// Whether the retained owner still names the live editing target:
2022    /// the focused field, with the same controller identity, still
2023    /// editable. Anything else means the composition's client is gone.
2024    fn ime_owner_current(&self) -> bool {
2025        self.ime_composition.as_ref().is_some_and(|owner| {
2026            self.focused == Some(owner.field)
2027                && self.tree.is_text_field(owner.field)
2028                && self.tree.text_field_is_editable(owner.field)
2029                && self
2030                    .tree
2031                    .text_controller(owner.field)
2032                    .is_some_and(|current| current == owner.controller)
2033        })
2034    }
2035
2036    fn ime_focused_target(&self) -> Option<ImeCompositionOwner> {
2037        let field = self
2038            .focused
2039            .filter(|id| self.tree.text_field_is_editable(*id))?;
2040        Some(ImeCompositionOwner {
2041            field,
2042            controller: self.tree.text_controller(field)?,
2043        })
2044    }
2045
2046    fn validate_ime_composition(&mut self) {
2047        if !self.ime_owner_current() {
2048            self.cancel_ime_composition();
2049        }
2050    }
2051
2052    /// Routes one native IME event. An owner that no longer validates
2053    /// (focus moved off-path, controller replaced, field unmounted or
2054    /// no longer editable) is cancelled before routing, so its preedit
2055    /// clears on its own controller and is never committed elsewhere.
2056    /// Owned events address the owner; only ownerless Commits fall back
2057    /// to the focused field, and only ownerless Preedits adopt a target.
2058    fn handle_ime(&mut self, event: ImeEvent) {
2059        self.editing_diagnostics.ime_events += 1;
2060        self.validate_ime_composition();
2061        let target = match &event {
2062            ImeEvent::Preedit { .. } => {
2063                if self.ime_composition.is_none() {
2064                    self.ime_composition = self.ime_focused_target();
2065                }
2066                self.ime_composition
2067                    .as_ref()
2068                    .map(|owner| owner.controller.clone())
2069            }
2070            ImeEvent::Commit(_) => self
2071                .ime_composition
2072                .take()
2073                .or_else(|| self.ime_focused_target())
2074                .map(|owner| owner.controller),
2075            ImeEvent::End => self.ime_composition.take().map(|owner| owner.controller),
2076        };
2077        let Some(controller) = target else {
2078            return;
2079        };
2080        match event {
2081            ImeEvent::Preedit { text, selection } => {
2082                controller.set_preedit(
2083                    text,
2084                    selection.map(|(start, end)| TextRange::new(start, end)),
2085                );
2086            }
2087            ImeEvent::Commit(text) => {
2088                // Ownerless commit policy: without a session or client
2089                // identity, a late Commit after focus changes cannot be
2090                // distinguished from a legitimate direct commit, so it
2091                // applies to the focused editable field. This is a routing
2092                // policy, not stale-commit protection.
2093                controller.commit_preedit(&text);
2094            }
2095            ImeEvent::End => {
2096                controller.clear_preedit();
2097            }
2098        }
2099        controller.reset_caret(Instant::now());
2100        self.frame_requested = true;
2101    }
2102    pub fn run_frame(
2103        &mut self,
2104        constraints: Constraints,
2105    ) -> Result<(DisplayList, FrameStats), TreeError> {
2106        self.run_frame_at(constraints, Instant::now())
2107    }
2108    /// Deterministic frame entry point used by animation tests and embedders
2109    /// with an existing monotonic clock.
2110    pub fn run_frame_at(
2111        &mut self,
2112        constraints: Constraints,
2113        now: Instant,
2114    ) -> Result<(DisplayList, FrameStats), TreeError> {
2115        let message_guard = tracing::info_span!("incular.messages").entered();
2116        let message_span = profiling::PhaseSpan::start();
2117        self.process_runtime_work_at(now);
2118        let runtime_messages = message_span.elapsed_us();
2119        drop(message_guard);
2120        let before = self.tree.diagnostics();
2121        let build_guard =
2122            tracing::info_span!("incular.build", elements_updated = tracing::field::Empty,)
2123                .entered();
2124        let build_span = profiling::PhaseSpan::start();
2125        let mut updated = 0;
2126        while let Some(id) = self.order.pop_front() {
2127            if let Some(widget) = self.pending.remove(&id)
2128                && self.tree.element_exists(id)
2129            {
2130                self.tree.update(id, widget)?;
2131                updated += 1;
2132            }
2133        }
2134        loop {
2135            while let Some((root, node)) = { self.reactive.borrow_mut().take_node() } {
2136                if let Some(node) = node.upgrade() {
2137                    node.run(root);
2138                }
2139            }
2140            while let Some(id) = { self.reactive.borrow_mut().take() } {
2141                if self.tree.element_exists(id) && self.builders.contains_key(&id) {
2142                    #[cfg(feature = "devtools")]
2143                    for cause in self.reactive.borrow_mut().take_causes(id) {
2144                        self.tree.note_invalidation(id, cause);
2145                    }
2146                    self.rebuild_from_builder(id)?;
2147                    updated += 1;
2148                }
2149            }
2150            if !self.reactive.borrow().has_work() {
2151                break;
2152            }
2153        }
2154        for id in self.tree.take_unmounted() {
2155            self.handlers_stale = true;
2156            self.builders.remove(&id);
2157            self.reactive.borrow_mut().forget(id);
2158            if let Some(scope) = self.owner_scopes.remove(&id) {
2159                scope.cancel();
2160            }
2161            self.text_histories.remove(&id);
2162        }
2163        self.reconcile_focus_and_selection_captures();
2164        self.validate_ime_composition();
2165        self.prune_handlers();
2166        let build = build_span.elapsed_us();
2167        drop(build_guard);
2168        // Ballistic tenures pump on the frame clock before layout, so
2169        // pumped offsets lay out and paint in the same frame. The pump
2170        // reports live drivers; that liveness is captured here and
2171        // ORed into the end-of-frame demand below, so the reset point
2172        // cannot discard it — the scheduler stays awake while a driver
2173        // lives and idles otherwise. No timer loop, just frame demand.
2174        let reduced_motion = self.environment.borrow().reduced_motion;
2175        let fling_live = self.tree.pump_scroll_flings(now, reduced_motion);
2176        // The overlay scrollbar's reveal/fade-out follows scrolling and hover,
2177        // so it steps after flings and before paint.
2178        let scrollbar_live = self.tree.pump_scrollbar_fades(now);
2179        // Press and long-press deadlines (`kPressTimeout`, `kLongPressTimeout`)
2180        // fire on the frame clock while a pointer is held without moving.
2181        let gesture_timers_live = self.tree.pump_gesture_timers(now);
2182        let _layout_guard = tracing::info_span!("incular.layout").entered();
2183        let layout_span = profiling::PhaseSpan::start();
2184        self.tree.layout(constraints)?;
2185        let layout = layout_span.elapsed_us();
2186        drop(_layout_guard);
2187        for (action, handler) in self.tree.take_pending_handlers() {
2188            self.handlers_stale = true;
2189            self.handlers.insert(action, handler);
2190        }
2191        // Lazy viewport expiry occurs during layout, after the ordinary dirty
2192        // queue drain above. Release those builder subscriptions and callbacks
2193        // in the same frame rather than retaining one stale cache generation.
2194        for id in self.tree.take_unmounted() {
2195            self.handlers_stale = true;
2196            self.builders.remove(&id);
2197            self.reactive.borrow_mut().forget(id);
2198            if let Some(scope) = self.owner_scopes.remove(&id) {
2199                scope.cancel();
2200            }
2201            self.text_histories.remove(&id);
2202        }
2203        self.reconcile_focus_and_selection_captures();
2204        self.validate_ime_composition();
2205        self.prune_handlers();
2206        // Layout builders create their retained controls during the first
2207        // layout. Resolve initial autofocus once those controls exist, without
2208        // reclaiming focus on later frames or after an explicit input decision.
2209        if self.initial_focus == InitialFocus::AwaitingLayout {
2210            self.set_focus(self.tree.autofocus_element());
2211        }
2212        let _composite_guard = tracing::info_span!("incular.composite").entered();
2213        let composite_span = profiling::PhaseSpan::start();
2214        let (composited, animations_active) = self.tree.update_compositor(now)?;
2215        let composite = composite_span.elapsed_us();
2216        drop(_composite_guard);
2217        // Layout and the compositor's scroll translations are final here,
2218        // so hover catches up with content that moved under the pointer
2219        // and its paint lands in this frame.
2220        let geometry = self.tree.diagnostics();
2221        if geometry.layouts != before.layouts
2222            || geometry.scroll_offset_updates != before.scroll_offset_updates
2223        {
2224            self.refresh_hover_after_layout();
2225        }
2226        let _semantics_guard = tracing::info_span!("incular.semantics").entered();
2227        let semantics_span = profiling::PhaseSpan::start();
2228        self.tree.update_semantics();
2229        let semantics = semantics_span.elapsed_us();
2230        drop(_semantics_guard);
2231        let _paint_guard = tracing::info_span!("incular.paint").entered();
2232        let paint_span = profiling::PhaseSpan::start();
2233        let display_list = self.tree.paint();
2234        let paint = paint_span.elapsed_us();
2235        drop(_paint_guard);
2236        self.sync_text_input_client();
2237        // A focused field whose value or selection changed scrolls its
2238        // ancestors to show the caret; the next frame lays that out.
2239        let caret_revealed = self.show_focused_caret_on_screen();
2240        // End-of-frame reset: recomputed from every demand source, not
2241        // blindly cleared. `fling_live` above is one source — a write
2242        // before layout alone would not survive this assignment, so it
2243        // is ORed back in. Nothing else in this frame clears the flag:
2244        // intermediate paths only ever set it.
2245        self.frame_requested = fling_live
2246            || caret_revealed
2247            || gesture_timers_live
2248            || scrollbar_live
2249            || !self.pending.is_empty()
2250            || self.reactive.borrow().has_work()
2251            || animations_active;
2252        let after = self.tree.diagnostics();
2253        let timings = FrameTimings {
2254            event_processing: std::mem::take(&mut self.pending_event_processing_us),
2255            runtime_messages,
2256            build,
2257            layout,
2258            composite,
2259            semantics,
2260            paint,
2261            cpu_total: runtime_messages + build + layout + composite + semantics + paint,
2262        };
2263        tracing::debug!(
2264            target: "incular::frame",
2265            cpu_total_us = timings.cpu_total,
2266            build_us = timings.build,
2267            layout_us = timings.layout,
2268            paint_us = timings.paint,
2269        );
2270        Ok((
2271            display_list.clone(),
2272            FrameStats {
2273                updated_elements: updated,
2274                rebuilt_elements: after.rebuilds - before.rebuilds,
2275                laid_out_render_objects: after.layouts - before.layouts,
2276                repainted_render_objects: after.paints - before.paints,
2277                composited: u64::from(composited),
2278                active_animations: u64::from(animations_active),
2279                display_list_commands: display_list.len(),
2280                requested_another_frame: self.frame_requested,
2281                timings,
2282            },
2283        ))
2284    }
2285    #[must_use]
2286    pub fn diagnostics(&self) -> Diagnostics {
2287        self.tree.diagnostics()
2288    }
2289    /// Queues every registered builder for the next frame's build drain,
2290    /// application root first so descendants it unmounts are skipped.
2291    #[cfg(feature = "hot-reload")]
2292    pub(crate) fn reassemble(&mut self) {
2293        let root = self.application_root;
2294        let ids = root
2295            .into_iter()
2296            .chain(self.builders.keys().copied().filter(|id| Some(*id) != root))
2297            .collect::<Vec<_>>();
2298        let mut reactive = self.reactive.borrow_mut();
2299        for id in ids {
2300            #[cfg(feature = "devtools")]
2301            reactive.note_cause(id, InvalidationCause::Manual);
2302            reactive.enqueue(id);
2303        }
2304        drop(reactive);
2305        self.frame_requested = true;
2306    }
2307    /// Rebuilds one registered builder outside the frame drain. Route
2308    /// outlets use this to refresh mounted content deterministically
2309    /// before framing, rather than only when reactive dependencies fire.
2310    pub(crate) fn rebuild_from_builder(&mut self, id: ElementId) -> Result<(), TreeError> {
2311        self.reactive.borrow_mut().refresh(id);
2312        let widget = {
2313            let _build_scope = BuildScopeGuard::enter(BuildScope {
2314                root: self.reactive.borrow().root,
2315                element: Some(id),
2316                queue: Rc::downgrade(&self.reactive),
2317                initial_dependencies: None,
2318                spawner: self.spawner(),
2319                owner_scope: self
2320                    .owner_scopes
2321                    .get(&id)
2322                    .cloned()
2323                    .unwrap_or_else(|| self.window_scope.clone()),
2324            });
2325            self.builders.get_mut(&id).expect("registered builder")()
2326        };
2327        let mut widget = widget;
2328        self.prepare_widget(&mut widget);
2329        self.tree.update(id, widget)?;
2330        Ok(())
2331    }
2332    fn prepare_widget(&mut self, widget: &mut Widget) {
2333        self.handlers_stale = true;
2334        let handlers = &mut self.handlers;
2335        let tree = &mut self.tree;
2336        widget.bind_callbacks(&mut |callback| {
2337            let id = tree.allocate_action();
2338            handlers.insert(id, callback);
2339            id
2340        });
2341    }
2342    fn activate_action(&mut self, action: ActionId) -> bool {
2343        let mut handled = false;
2344        if let Some(callback) = self.handlers.get(&action).cloned() {
2345            callback();
2346            handled = true;
2347        }
2348        if let Some(callback) = self.legacy_action_handler.as_mut() {
2349            callback(action);
2350            handled = true;
2351        }
2352        self.frame_requested |= handled;
2353        handled
2354    }
2355
2356    fn prune_handlers(&mut self) {
2357        if !std::mem::take(&mut self.handlers_stale) {
2358            return;
2359        }
2360        let active = self.tree.action_ids();
2361        self.handlers.retain(|id, _| active.contains(id));
2362    }
2363    fn set_hover(&mut self, next: Option<ElementId>) {
2364        if self.hovered_button == next {
2365            return;
2366        }
2367        if let Some(previous) = self.hovered_button {
2368            let _ = self
2369                .tree
2370                .set_button_interaction(previous, Some(false), None, None);
2371            if let Some(action) = self.tree.hover_actions_for_element(previous).1
2372                && let Some(callback) = self.handlers.get(&action).cloned()
2373            {
2374                callback();
2375            }
2376        }
2377        self.hovered_button = next;
2378        if let Some(current) = next {
2379            if self.pressed_button != Some(current) {
2380                let _ = self
2381                    .tree
2382                    .set_button_interaction(current, Some(true), None, None);
2383            }
2384            if let Some(action) = self.tree.hover_actions_for_element(current).0
2385                && let Some(callback) = self.handlers.get(&action).cloned()
2386            {
2387                callback();
2388            }
2389        }
2390        self.frame_requested = true;
2391    }
2392}
2393
2394/// Framework-controlled build scope for declarative roots. It intentionally
2395/// exposes no element IDs or scheduler handles. Signals use the runtime's
2396/// scoped collector while a builder executes; reads outside a build simply
2397/// do not create dependencies.
2398fn us_since_instant(started: incular_core::time::Instant) -> u32 {
2399    started.elapsed().as_micros().try_into().unwrap_or(u32::MAX)
2400}