Skip to main content

RenderRoot

Struct RenderRoot 

Source
pub struct RenderRoot<State: 'static, V: View<State>> { /* private fields */ }
Expand description

Owns the retained tree and drives the rebuild/layout/paint passes for a single-root application.

Generic over the application State and the concrete root view type V returned by the build closure.

Implementations§

Source§

impl<State: 'static, V: View<State>> RenderRoot<State, V>

Source

pub fn new() -> Self

Create an empty render root with no widget yet built.

Source

pub fn set_theme(&mut self, theme: Box<dyn Any>)

Store the app’s active theme, threaded into every subsequent layout/paint pass as Option<&dyn Any> and recovered by widgets via crate::widget::PaintCtx::theme_as/crate::widget::LayoutCtx::theme_as.

The theme is boxed type-erased (Box<dyn Any>) so this crate stays independent of frust-theme; the shell boxes the concrete Theme (and re-boxes it on a live appearance change, e.g. dark-mode toggle). Calling again replaces the stored theme.

Marks LAYOUT | PAINT pending (drained by RenderRoot::take_change_flags): a theme swap can change baked-in paint state a widget resolves at layout time (e.g. Text’s themed glyph color, cached into its TextLayout — see frust-widgets::text), so a shell that later gates layout/paint on this seam must still see a bare set_theme as dirty even though no view changed.

Source

pub fn set_insets(&mut self, insets: WindowInsets)

Store the window’s insets (WindowInsets), threaded into every subsequent layout/paint pass and recovered by widgets via crate::widget::LayoutCtx::window_insets/ crate::widget::PaintCtx::window_insets.

Mirrors RenderRoot::set_theme’s dirty-tracking contract: a change marks LAYOUT | PAINT pending (drained by RenderRoot::take_change_flags) so a shell gating layout/paint on that seam still relayouts when the insets move — a SafeArea widget resolves its inset at layout time, so the mobile layout-skip gate must see a bare set_insets as dirty even though no view changed (the same reasoning as the theme swap — see docs/ARCHITECTURE.md’s Theme delivery and Frame gate). A change also bumps the semantics generation, since a moved inset shifts laid-out node bounds.

No-op guarded by WindowInsets’s PartialEq: pushing the current value marks nothing dirty, so a shell that polls the platform insets every frame and forwards unconditionally never forces a needless relayout. (A shell may also skip the call itself by comparing first — this is the same guard, held on the core side.)

Source

pub fn insets(&self) -> WindowInsets

The window’s insets currently threaded into the layout/paint passes.

Source

pub fn set_presented_frames(&mut self, presented: u64)

Store the shell’s running count of frames the render thread has actually presented, threaded into every subsequent paint pass and recovered by widgets via crate::widget::PaintCtx::presented_frames. A shell loads the atomic its render side increments (once per presented frame) and pushes it here once per UI frame, before paint.

Deliberately dirties nothing. Unlike RenderRoot::set_theme and RenderRoot::set_insets — which mark LAYOUT | PAINT pending because a widget bakes their value in at layout time — this setter marks NO ChangeFlags and bumps NO semantics generation. The presented count is a paint-only observation a widget reads live every paint (never baked at layout), so treating it as dirty would be wrong twice over: it would force a needless relayout, and — critically — on the mobile shells a monotonically ticking counter would keep the frame gate’s pending-flags input perpetually true, so the menu would never idle (the 32s-idle behavior must survive). Keeping this setter dirt-free is exactly what keeps the frame gate unaware of it (see docs/ARCHITECTURE.md’s Frame gate).

Source

pub fn presented_frames(&self) -> Option<u64>

The presented-frame count currently threaded into the paint pass, or None if no shell has pushed one.

Source

pub fn set_surface_translucent(&mut self, translucent: bool)

Store whether the shell’s GPU surface is translucent (alpha-channel, “Mode B”), threaded into every subsequent paint pass and recovered by widgets through crate::widget::PaintCtx::is_translucent. A shell pushes the surface’s resolved translucency here — what the GPU backend reports after the surface is installed, not what the app requested via frust-shell-common::surface_mode’s latch: a translucency request the platform refuses must degrade to the opaque contract, or every platform_view slot punches a hole in an opaque swapchain (black rectangles). Every desktop app leaves the default false (opaque, “Mode A”).

Marks PAINT pending on an actual change (PartialEq-guarded, mirroring RenderRoot::set_insets’s no-op guard): translucency is read purely at paint time (the hole-punch runs in paint, never baked at layout), so a flip must repaint but need not relayout. A flip is rare but real: a surface (re)install can resolve differently from the previous one, and both mobile shells re-push this every frame (the no-op-if-unchanged guard is what makes that free).

Source

pub fn is_surface_translucent(&self) -> bool

Whether the shell’s GPU surface is currently marked translucent.

Source

pub fn is_pointer_captured(&self) -> bool

Whether a captured pointer gesture is currently in flight.

Source

pub fn pointer_capture_claimant(&self) -> Option<PointerId>

The contact that claimed the pointer capture in flight — the only one whose Up/Cancel can end it — or None while nothing is captured.

Source

pub fn pointer_capture_contacts(&self) -> bool

Whether the capture in flight routes the gesture’s other contacts to its captor — the captor opted in with EventCtx::capture_contacts on the Down it captured with, and no container has since taken the gesture over from it (EventCtx::release_captured_child). false while nothing is captured.

Source

pub fn is_focus_active(&self) -> bool

Whether some widget in the tree currently holds keyboard/IME focus.

Source

pub fn is_hover_active(&self) -> bool

Whether some widget in the tree currently holds the hover link — i.e. whether the last hover pass (an uncaptured PointerPhase::Move) left the pointer over a widget that claimed it.

The hover analog of RenderRoot::is_focus_active, and a level accessor like it: hover is not a session (nothing has to be released), so there is no generation counterpart. false for any app whose widgets never call EventCtx::claim_hover. A touch app can still see it go true transiently: nothing distinguishes a touch contact from a mouse here, so an uncaptured touch drag over a non-capturing claimant is an ordinary hover pass — ended by the Up at lift (see docs/LIMITATIONS.md’s hover-window-leave-standing).

A RenderRoot::rebuild that removes the claimant ends the link too, so this never reports a hover held by a widget that no longer exists — the hover counterpart of the unmount focus release (see that method).

Source

pub fn cursor(&self) -> CursorIcon

The cursor the tree last asked the host to show — what a desktop shell pushes to its window (frust-shell-desktop maps it onto winit’s own cursor icons).

A level accessor like RenderRoot::is_hover_active, not an edge one: the value re-resolves on every pointer PointerPhase::Move and stands unchanged through every other pass, so a shell caches what it last applied and calls the platform only when this differs. There is deliberately no generation counter — a cursor is a value, not a session, and an unmoved cursor is indistinguishable from one re-resolved to the same shape.

CursorIcon::Default before the first Move, and after any Move in which no widget called EventCtx::set_cursor — so any app whose widgets never request a cursor reads Default forever, and the mobile shells never read this at all regardless of what resolves here.

Residual: a widget that is torn down (or moves out from under a stationary pointer) while its request stands leaves the last shape in place until the next Move re-resolves it — the same self-correction window hover has, and for the same reason: nothing re-resolves without pointer motion.

Source

pub fn take_clipboard_write(&mut self) -> Option<String>

Take (and clear) the text the tree asked the shell to put on the host clipboard — the drain a shell performs immediately after every RenderRoot::event, beside cursor() and ime_state().

Some exactly when some widget called EventCtx::write_clipboard during a pass since the last drain (answering a EditCommand::Copy/Cut, or a chord the widget decoded itself). The shell hands the text to its host clipboard — winit’s arboard on desktop, ClipboardManager on Android, UIPasteboard on iOS — and does nothing at all on None.

Destructive, unlike cursor(): a clipboard write is an edge, not a standing level, so a caller that drains and drops the result loses that write. Draining twice after one pass yields None the second time.

A widget that never copies leaves this None forever, so a shell with no clipboard (the mobile shells before their own clipboard work lands) may call it and discard the result, or not call it at all.

Source

pub fn take_paste_request(&mut self) -> bool

Take (and clear) whether the tree asked the shell to read the host clipboard back to it — drained beside take_clipboard_write after every RenderRoot::event.

true exactly when some widget called EventCtx::request_paste during a pass since the last drain. The shell answers by reading its host clipboard and dispatching InputEvent::EditCommand(EditCommand::Paste(text)) — a new dispatch, because the read may be asynchronous and the pass that asked is over. That answer carries no identity of its own and is focus-routed to whoever holds focus when it lands: a release in between drops it harmlessly, but a focus move in between lands it in the new field rather than the one that asked. A synchronous read has no such window; an asynchronous one snapshots focus_epoch at this drain and discards an answer whose epoch no longer matches — not focus_ime_generation, which also moves within a single session.

Destructive, for take_clipboard_write’s reason. A pass may both write and request (a cut that immediately re-reads, or a widget answering two chords) — the two drains are independent.

Source

pub fn ime_state(&self) -> Option<ImeState>

The IME surface the focused widget published, for the shell to drive the platform input method (winit set_ime_cursor_area, Android updateSelection, iOS inputDelegate). None when nothing is focused or the focused widget publishes no IME surface.

Written by the focused widget through EventCtx::publish_ime_state during the event pass and refreshed on every event; it survives a rebuild (so the shell can query it between frames) and is cleared when focus is lost.

§None is the only “no session” form — an inactive surface is never stored

A widget publishing ImeState { active: false, .. } is ending the session, not describing it, so both publish paths turn that into a full release (see RenderRoot::paint) and this returns None rather than Some(inactive). A shell therefore never has to distinguish the two, and is_some() means “a live IME session” with no second check.

The platform still sees the keyboard-hide. All three shells already map None onto the inactive form on the way out, so the observable wire behavior is unchanged: frust-shell-android‘s ime_state_to_json returns ImeJsonState::default() (active:false, empty text, -1 indices, null caret, "normal") and frust-shell-ios’ returns the byte-identical ime_state_json(false, "", -1, -1, -1, -1, None, "normal") — exactly what the navigator’s own cleared surface serialised to before. Kotlin’s pollImeAfterDispatch and Swift’s syncImeFocus both branch on active alone (an inactive surface’s text/caret/content-type are ignored), and the desktop shell’s sync_ime reads is_some_and(|s| s.active). Dropping the inactive surface’s payload also stops a disabled secret field’s text riding to the platform after its session ended.

Source

pub fn focus_ime_generation(&self) -> u64

The focus/IME session generation — bumped on every actual change of is_focus_active or ime_state, and on nothing else.

The edge counterpart of those two level accessors, for a shell that needs “did the focus/IME session move since I last looked?” rather than “is something focused?”. A shell caches the value it last saw and compares (mirroring semantics_generation’s cheap dirty gate) — that comparison is the mobile frame gate’s FrameInputs::focus_or_ime_changed input.

A same-value write never moves it: re-publishing an identical IME surface (which the paint pass does on every frame a field stays focused) or re-blurring an already-blurred root is not an edge. Wrapping is deliberate and harmless — a comparison, never an ordering.

§Not the session’s identity

This counts changes to the published surface, not sessions, and the two come apart in both directions — see focus_epoch, which is what to reach for when the question is “is this still the same focus session?”. Answering that one from this counter is wrong whenever focus moves between two fields without the published value changing.

Source

pub fn focus_epoch(&self) -> u64

The live focus session’s identity — advanced once per honoured focus claim and once per session release, and by nothing else.

The neighbour of focus_ime_generation and easy to mistake for it, so: that one counts changes to the published surface (the focus flag, or the ImeState value), this one counts sessions. They come apart in both directions, which is why both exist:

  • Focus moving from one field to another moves this one and can leave that one completely still. Claiming focus while some field already holds it writes true over true, and the surface the new field publishes may compare equal to the old field’s (ImeState is {active, editing, caret, content_type} and names no widget) — or may not be published at all, since a widget is free to take focus and publish nothing, which leaves the previous field’s surface standing.
  • An edit landing, a caret moving, or the field being repositioned under the user moves that one and leaves this one still: the session is the same session throughout.

So a caller binding an asynchronous answer to the session that asked for it wants this one; a caller asking “must I run a frame, or re-sync the platform IME?” wants that one.

Never 0. The counter is built at 1 and steps past 0 on wrap, because 0 is a never-claimed ChildPod’s stamp and a root publishing it would hand every unclaimed pod in the tree a live link. A caller is therefore free to use 0 as its own “no root / no answer” sentinel with no risk of colliding with a live value. Wrapping is otherwise deliberate and harmless: the value is compared for equality, never ordered.

Source

pub fn platform_view_frames(&self) -> &[PlatformViewFrame]

The PlatformViewFrames published during the most recent RenderRoot::paint, in paint order.

Replaced wholesale every pass (see the platform_view_frames field doc), so a pass with no publishers yields an empty slice — a shell never sees a stale frame for a slot that stopped painting.

Source

pub fn input_shields(&self) -> &[Rect]

The z-shield rects reported during the most recent RenderRoot::paint (see crate::widget::PaintCtx::report_input_shield), in paint order.

Replaced wholesale every pass, exactly like RenderRoot::platform_view_frames — a shell feeds both into the same differ ingest call, and the differ intersects these against each interactive slot’s rect.

Source

pub fn take_retired_platform_views(&mut self) -> Vec<u64>

Drain the slot ids whose platform_view widgets were torn down since the last call (View::teardown ran on them — see crate::widget::report_retired_slot).

The prompt-teardown channel: a shell calls this once per frame, right after its rebuild, and retires each id in its platform-view differ (PlatformViewState::retire) so a disposed slot’s native view goes away immediately instead of waiting out the differ’s missing-streak heuristic. Draining is destructive, mirroring RenderRoot::take_change_flags: an id is reported exactly once, so a shell that drains and drops the result loses the prompt path (the missing-streak backstop still covers it).

A merely culled slot (scrolled offscreen, a parent skipping paint) never appears here — culling doesn’t run teardown — which is what keeps the camera keep-alive contract intact.

Source

pub fn take_change_flags(&mut self) -> ChangeFlags

Take (and clear) the dirtiness accumulated since the last call.

A shell can consult this to skip the layout/paint passes when nothing has changed and no redraw was requested (a desktop optimisation; the mobile continuous-loop shells may ignore it and repaint every tick). Each RenderRoot::rebuild merges its result here; this drains it.

Source

pub fn has_pending_change_flags(&self) -> bool

Non-draining peek at the dirtiness accumulated since the last RenderRoot::take_change_flags — true when any LAYOUT/PAINT bit is pending, without clearing it.

Complements take_change_flags for a shell frame gate: the gate reads this as one of its “should this frame run” inputs before deciding, so a frame it chooses to skip leaves pending intact for the next non-skipped frame to drain and act on. Draining stays the job of take_change_flags, called only on a frame that actually runs its layout/paint passes. No behavioral change to rebuild/layout/paint.

Source

pub fn root_id(&self) -> Option<WidgetId>

The root widget id, once built.

Source

pub fn tree(&self) -> &WidgetTree

Shared access to the retained tree (for the shell / tests).

Source

pub fn inspect(&self) -> Vec<InspectNode>

A read-only, pre-order snapshot of the retained tree for tooling: per node an id, its parent and children, the concrete widget’s type name, an optional debug label, and its absolute border box in logical px.

Computed on demand in O(nodes) and takes &self — no per-frame bookkeeping, no mutation, and nothing here participates in build/layout/paint. Bounds reflect the last layout pass, so call it after one (before the first, every rect is zero-sized).

Scope: the walk covers the WidgetTree arena and the ChildPods containers own, reached through Widget::visit_children — so it is the real retained hierarchy, not just the arena (which holds little more than the root pod). A container that leaves that seam defaulted reads as a leaf.

Source

pub fn rebuild( &mut self, build: &mut impl FnMut(&mut State) -> V, state: &mut State, ) -> ChangeFlags

Run the build closure, then build (first call) or rebuild (subsequent calls) the root widget, returning what changed.

the build closure is expected to be cheap and re-entrant: it is re-run in full every rebuild.

§Deferred-callback flush

The view diff itself is state-free (rebuild_view below takes no State), so a widget applying a structural op there — the navigator draining its queued push/pop is the shipped case — cannot run an app callback that needs &mut State. It instead queues the callback and calls mark_pending_result_flush; this method drains that flag and dispatches an InputEvent::Housekeeping broadcast through the ordinary event plumbing, where state is in scope. This is the only unconditional per-frame pass that holds &mut State, which is why the dispatch lives here and not in a shell (flushing on the next real input meant waiting seconds for a touch, or forever when the next touch went to chrome outside the navigator).

A flushed callback mutates State, so the view built before it ran is stale — the build closure + rebuild_view cycle therefore re-runs after each flush, and the same frame shows the result. Results can queue further nav ops, so the loop is bounded; past the cap the flag is left standing and one more frame is requested rather than spinning (see MAX_PENDING_RESULT_FLUSH_PASSES, this module’s private cap constant).

The broadcast’s EventOutcome is propagated, not discarded: a needs_redraw coming back from the dispatch folds into this rebuild’s ChangeFlags::PAINT and the deferred frame request, so a callback whose only effect is EventCtx::request_redraw — invisible to the re-diff, since no view-visible state changed — still wakes both the mobile frame gate and the desktop Wait loop.

Source

pub fn layout(&mut self, window_size: Size) -> Size

Lay out the root widget against window_size and record its geometry.

The root receives loose constraints (zero up to the window size) and is placed at the origin. Returns the size the root chose. No text context is threaded in (use RenderRoot::layout_with_text when the tree contains text widgets); the stored theme, if any, is still threaded down.

Source

pub fn layout_with_text( &mut self, window_size: Size, text_ctx: &mut dyn Any, ) -> Size

Lay out the root widget, threading a shared text-shaping context down to text widgets.

text_ctx is the shell-owned frust_text::TextContext, passed type-erased so this crate needs no frust-text dependency. Text widgets recover it via crate::widget::LayoutCtx::text_context. The stored theme, if any, is threaded down alongside it.

Source

pub fn paint( &mut self, scene: &mut dyn PaintScene, frame_time: FrameTime, ) -> PaintOutcome

Paint the root widget into scene, returning whether the tree wants another frame to continue an animation.

A widget whose paint advances animation state (e.g. a scroll fling) signals PaintCtx::request_frame; that flag bubbles up through the container ChildPods and out here as PaintOutcome::needs_frame, which the shell honors by scheduling the next frame (desktop window.request_redraw(); the mobile continuous loops already do so). Mirrors how RenderRoot::event surfaces needs_redraw.

A widget whose animation changes its layout signals PaintCtx::request_layout instead (or as well); that bubbles up the same way and is folded here into the render root’s pending ChangeFlags (LAYOUT), so the next frame’s take_change_flags().needs_layout() reports it and the mobile intra-frame layout skip relayouts while the animation is in flight. It is also surfaced on the returned PaintOutcome::needs_layout.

frame_time is the shell’s shared monotonic clock for this frame (time enters frust-core from the shell, never Instant::now() here). It is seeded onto the root PaintCtx and threaded unchanged to every child (crate::widget::ChildPod::paint_child), so an animating widget advances against one consistent timestamp — see PaintCtx::frame_time.

§The overlay post-pass

Painting the main tree is only the first half. Widgets registering a floated surface during that walk (PaintCtx::register_overlay) are drained here and painted after it, in band order — which is the only way a popover, menu or tooltip escapes its owner’s paint order and every ancestor’s clip. Their routing rects are retained (see RenderRoot::overlay_hits) for the next event pass to hit-test first, and their paint outcomes merge into this pass’s own, so an animating overlay keeps the frames coming exactly like an animating widget in the tree.

Source

pub fn selection_toolbar(&self) -> Option<SelectionToolbarRequest>

The selection-toolbar request the focused field published during the most recent RenderRoot::paint, or None when no field is focused at all.

The shell half of the platform edit-menu route (SelectionToolbarPolicy::Native): a shell reads it beside RenderRoot::ime_state, answers “may I offer this verb?” from SelectionToolbarRequest::actions whenever the platform asks, and presents the host’s own menu at SelectionToolbarRequest::anchor when SelectionToolbarRequest::present_menu says so. A level, not an edge — re-read it as often as you like; pair it with RenderRoot::selection_toolbar_generation to notice the changes worth presenting or dismissing for.

Present for a focused field with no selection at all, which is not a wasted answer: paste applies to a bare caret, and a platform asking whether it may offer one needs a reply before any bar exists.

A field under the framework policy publishes this too (it floats its own toolbar through crate::overlay as well), so a shell that drives the platform menu must decide on the policy, not on the presence of a request.

Source

pub fn selection_toolbar_generation(&self) -> u64

A monotonically-increasing generation bumped on every change to the menu-significant part of RenderRoot::selection_toolbar — its present_menu flag and its actions — including the clearing that a blur produces, so a shell sees the menu going away as an edge too.

A moved anchor is not an edge: it is republished (and recomputed) every painted frame, so a shell re-reads it from RenderRoot::selection_toolbar rather than waiting for this to move — bumping on it would re-present a menu on every touch sample of a drag that widens a selection.

The focus_ime_generation contract one channel over: a shell caches the last value it acted on and acts only when it moves, which is what keeps a standing selection — republished every single frame — from asking the platform to re-present its menu on every vsync.

Source

pub fn semantics(&self) -> SemanticsUpdate

Collect the accessibility tree for the current frame, returning a SemanticsUpdate a platform adapter (accesskit_*) can consume.

Pull-based and stateless: the shell calls this when a platform a11y client asks for the tree (or after a change), never per frame — this crate owns no scheduling. Must run after RenderRoot::layout, since node bounds come from the pods’ post-layout geometry.

The result is always rooted at a synthetic accesskit::Role::Window node covering the window, whose children are whatever the root widget contributed. An unbuilt tree yields a bare window node with no children.

Source

pub fn semantics_generation(&self) -> u64

The current semantics generation — bumped by every rebuild/theme swap that could have changed the accessibility tree (the semantics dirty gate).

A shell records the value it last pushed and compares; see RenderRoot::semantics_if_changed.

Source

pub fn semantics_if_changed(&self, last_seen: u64) -> Option<SemanticsUpdate>

Pull a fresh SemanticsUpdate only if the semantics tree may have changed since generation last_seen.

Returns None when nothing relevant changed, letting a shell skip both the tree walk and the platform accesskit_* push. Call it post-layout (bounds must be valid). A shell threads its stored generation in and, on Some, updates it from RenderRoot::semantics_generation. v1 pushes the whole tree when it does recompute (stable ids make that valid); finer-grained diffing is a later optimization.

Source

pub fn event(&mut self, state: &mut State, event: &InputEvent) -> EventOutcome

Deliver an input event to the widget tree, returning what happened.

Builds a root EventCtx over the (type-erased) state, dispatches to the root widget — which routes the event down through its container children — and folds the result into an EventOutcome. The outcome’s needs_redraw is set whenever a widget consumed the event or explicitly requested a redraw; the shell turns that into a window.request_redraw().

Root capture bookkeeping mirrors the per-container active-child model: a Down whose dispatch requested capture marks a gesture in flight and latches the contact that sent it as the gesture’s claimant; the claimant’s Up and Cancel release it (never a window-leave, and never another contact’s release).

Pointer contacts are gated first, before anything else runs: an InputEvent::PointerContact is unwrapped into the plain InputEvent::Pointer every widget matches on, with EventCtx::pointer_id reporting its id, and a contact the multi-contact contract does not route — an additional contact with nothing captured, or one the captor did not opt into — is dropped here with an empty outcome (see that variant’s Multi-contact contract). A bare InputEvent::Pointer is the mouse.

Root hover bookkeeping is the third recorded path, and the one this pass derives rather than merely mirrors: an uncaptured Move opens a hover pass (widgets on the hit-tested path may claim it — see EventCtx::claim_hover), a Down/Up/Cancel ends whatever hover stood, and every other event leaves it alone. There is nothing to release and no generation to bump: the epoch advance strands the previous claimant’s path by itself, and the outcome’s needs_redraw carries the one repaint no widget can ask for — a hover that ended with nothing taking it. A hover that begins or moves from one claimant to another is repainted by the new claimant’s own change-gated request_redraw, which is why keeping an internal hover flag is part of the consumer contract rather than an optimization (see claim_hover). No shell change is required for hover — the desktop shell already dispatches a Move on every cursor move.

The cursor is hover’s sibling channel and the fourth thing this pass resolves: any pointer Move (captured included) re-resolves RenderRoot::cursor from the pass’s last EventCtx::set_cursor, defaulting to CursorIcon::Default when nothing asked. It deliberately does not fold into the outcome’s needs_redraw: applying a cursor is a platform call a desktop shell makes straight after this pass returns, with no frame involved, and folding it in would repaint the tree on every hover move.

The clipboard channel rides the same bracket and is the fifth thing this pass resolves: whatever the dispatch asked for through EventCtx::write_clipboard and EventCtx::request_paste lands in RenderRoot::take_clipboard_write / RenderRoot::take_paste_request, which a shell drains immediately after this returns, beside RenderRoot::cursor and RenderRoot::ime_state. Unlike the cursor, both commit on every pass rather than on a pointer Move alone — a copy can be answered from a key chord, a context-menu tap, or an InputEvent::EditCommand — and both are one-shot drains rather than standing levels. Like the cursor, neither folds into needs_redraw: talking to the host clipboard paints nothing (a Cut that mutates the document asks for its own redraw, for the mutation).

§Reentrancy

This pass never rebuilds or repaints. Event handlers mutate state synchronously through the context; the shell is expected to run a single RenderRoot::rebuild (then layout/paint) after the event pass returns, driven by the outcome. Rebuilding re-entrantly here would invalidate the widget references the dispatch still holds and turn the event→state→view feedback into recursion.

RenderRoot::rebuild calls this itself with InputEvent::Housekeeping to flush deferred state-bearing callbacks. That is sequential, not re-entrant — the dispatch fully returns before the next diff starts — so the rule above is intact.

Source

pub fn perform_accessibility_action( &mut self, state: &mut State, node_id: NodeId, action: Action, ) -> EventOutcome

Perform a platform accessibility action, returning the same EventOutcome the synthesized input produced.

A platform accesskit_* adapter delivers an ActionRequest(node_id, action); a shell forwards it here. v1 routes actions through the normal event path by synthesizing pointer events at the target node’s absolute bounds center (recovered from a fresh semantics pass — the id → bounds map the pass produces), so every fire-on-up-inside widget is operable with zero widget-side changes:

  • accesskit::Action::Click → a Down then an Up at the center, activating any button/switch/checkbox exactly as a real tap would.
  • accesskit::Action::Focus → a Down then a synthetic Cancel at the center: the Down claims focus for a widget that opts in on Down (the recorded-focus contract), and the Cancel releases the capture that same Down opened without touching the recorded focus path — so the action claims focus without leaving the widget permanently capturing every later pointer event. A widget that does not claim focus on Down is unaffected, and Cancel never fires an on-press callback.
  • any other action → ignored (a no-op EventOutcome); richer actions are deferred.

An unknown node_id (not in the current tree) is a benign no-op. Requires a prior RenderRoot::layout so the bounds are valid. Synthetic-pointer activation cannot drive widgets that require a real drag (e.g. a slider) — an accepted v1 limitation.

Trait Implementations§

Source§

impl<State: 'static, V: View<State>> Default for RenderRoot<State, V>

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

§

impl<State, V> !Freeze for RenderRoot<State, V>

§

impl<State, V> !RefUnwindSafe for RenderRoot<State, V>

§

impl<State, V> !Send for RenderRoot<State, V>

§

impl<State, V> !Sync for RenderRoot<State, V>

§

impl<State, V> !UnwindSafe for RenderRoot<State, V>

§

impl<State, V> Unpin for RenderRoot<State, V>

§

impl<State, V> UnsafeUnpin for RenderRoot<State, V>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> StorageAccess<T> for T

Source§

fn as_borrowed(&self) -> &T

Borrows the value.
Source§

fn into_taken(self) -> T

Takes the value.
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.