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