pub struct PaintCtx<'a> { /* private fields */ }Expand description
Context passed to Widget::paint.
Carries the widget’s resolved geometry (as stored in its pod after layout) so
paint code can position itself in the parent coordinate space, plus the v1
animation-driver signal (PaintCtx::request_frame): a widget whose paint
advances animation state (e.g. a scroll fling) must call it so the shell keeps
scheduling frames even absent external input. The flag bubbles up through
ChildPod::paint_child and out of crate::app::RenderRoot::paint as a
PaintOutcome, mirroring how EventCtx::request_redraw surfaces through
crate::event::EventOutcome.
A frame request also carries a TickClass (see PaintCtx::request_frame
vs PaintCtx::request_frame_paced): the aggregate class over the whole
paint pass — Transition-dominates-CosmeticLoop — surfaces on
PaintOutcome::needs_frame_paced_only for the mobile frame gate to pace a
purely-cosmetic frame.
Implementations§
Source§impl<'a> PaintCtx<'a>
impl<'a> PaintCtx<'a>
Sourcepub const MAX_PACED_INTERVAL: Duration
pub const MAX_PACED_INTERVAL: Duration
Ceiling on the interval Self::request_frame_paced_at accepts.
10 seconds comfortably clears every real cosmetic cadence in this
codebase — a bare Duration::ZERO “theme rate” request, the ~500ms
caret blink, muxr’s ~550ms blink, or any plausible slow pulse — while
keeping the shell’s downstream pacing arithmetic
(frust-shell-common::frame_gate’s interval * 2 /
interval.as_nanos() as u64, frust-shell-desktop::paced_wake’s
Instant + interval) far below overflow or truncation even at the
widest legal input. Self::request_frame_paced_at is the single
entry point every paced interval flows through (see
Self::merge_paced_interval’s doc comment), so clamping here bounds
every downstream consumer for free — this must never change behavior
for any existing caller, since every shipped cadence is orders of
magnitude under it.
Sourcepub fn new(origin: Point, size: Size) -> Self
pub fn new(origin: Point, size: Size) -> Self
Create a paint context for a widget at origin with size.
The frame time defaults to FrameTime::ZERO and no theme is threaded
in; the render root seeds the real shell clock via
PaintCtx::set_frame_time and the active theme via
PaintCtx::set_theme before painting the root widget, and both flow to
children through ChildPod::paint_child.
Sourcepub fn with_theme(self, theme: &'a dyn Any) -> Self
pub fn with_theme(self, theme: &'a dyn Any) -> Self
Attach the app’s active theme, type-erased. Chainable builder mirroring
LayoutCtx::with_theme — used by widget unit tests that paint against a
known theme; the render root threads it via PaintCtx::set_theme.
Sourcepub fn with_translucent(self, translucent: bool) -> Self
pub fn with_translucent(self, translucent: bool) -> Self
Mark this paint pass as running against a translucent (“Mode B”) surface.
Chainable builder mirroring PaintCtx::with_theme — used by widget unit
tests exercising the platform-view hole-punch; the render root threads the
real flag via PaintCtx::set_translucent (see
PaintCtx::is_translucent).
Sourcepub fn with_retained_ledger(self, ledger: &'a RetainedLedger) -> Self
pub fn with_retained_ledger(self, ledger: &'a RetainedLedger) -> Self
Thread a renderer’s resident-key ledger into this pass. Chainable
builder mirroring PaintCtx::with_theme — used by widget unit tests
exercising a retained-page boundary, which paints with no bracket at all
when no ledger is threaded (PaintCtx::retained_enabled); the render
root threads the real one (crate::app::RenderRoot::set_retained_ledger).
Sourcepub fn theme_as<T: Any>(&self) -> Option<&T>
pub fn theme_as<T: Any>(&self) -> Option<&T>
Recover the threaded theme as &T, or None if no theme was threaded
into this pass (a supported state — bare-core tests and pre-theme apps)
or its concrete type differs from T.
The paint-pass mirror of LayoutCtx::theme_as. Widgets that read
frust_theme::Theme downcast through this (or the
Theme::from_paint_ctx convenience wrapper).
Sourcepub fn window_insets(&self) -> WindowInsets
pub fn window_insets(&self) -> WindowInsets
The window’s insets (WindowInsets) for this paint pass (a cheap
copy). The paint-pass mirror of LayoutCtx::window_insets:
global/origin-independent, so every widget reads the same value (unless a
consuming ancestor narrowed it via PaintCtx::with_window_insets);
defaults to the zero inset when no shell pushed one.
Sourcepub fn with_window_insets<R>(
&mut self,
insets: WindowInsets,
f: impl FnOnce(&mut Self) -> R,
) -> R
pub fn with_window_insets<R>( &mut self, insets: WindowInsets, f: impl FnOnce(&mut Self) -> R, ) -> R
Run f with insets installed as this context’s window insets, then
restore the previous value and return f’s result.
The paint-pass mirror of LayoutCtx::with_window_insets. The value is
otherwise root-seeded and copied down unchanged by
ChildPod::paint_child; because paint_child copies it from the
parent context it is handed, wrapping paint_child in this scope hands
the override to the whole painted subtree. SafeArea does exactly that
with the same consumed value it laid its child out under, so a widget
that reads insets at paint time sees what it was laid out with.
The restore is a plain assignment after f returns — there is no drop
guard, so if f panics the override is not undone (the pass is being
unwound anyway).
Sourcepub fn presented_frames(&self) -> Option<u64>
pub fn presented_frames(&self) -> Option<u64>
The shell’s running count of frames the render thread has actually
presented, or None when no shell wired one in (bare-core tests,
pre-wiring shells) — a supported state, so a widget can fall back to a
paint-cadence measure.
Under the render-thread split the UI thread paints faster than the render
thread presents (a gate-skipped or coalesced frame is never presented), so
a widget measuring frames per second must difference this presented
count — not its own paint count — to report the rate a user actually sees
(examples/shadertoy’s HUD is the reference consumer). A widget only ever
differences two reads (wrapping_sub); the absolute value is a
free-running monotonic counter it must never interpret directly. Seeded
from the shell at the root (crate::app::RenderRoot::paint) and threaded
unchanged into every child by ChildPod::paint_child, mirroring
frame_time.
Sourcepub fn origin(&self) -> Point
pub fn origin(&self) -> Point
The widget’s absolute origin in window-space coordinates.
This origin is accumulated as the paint pass descends the widget tree:
ChildPod::paint_child adds the child’s parent-relative origin to the
parent’s already-absolute ctx.origin(), threading the result down through
nested levels. Contrast ChildPod::origin, which is parent-relative and
correct as documented, and EventCtx::origin, which is also
parent-relative — the event pass translates the event into the child’s
local space instead of accumulating the origin. This is therefore the only
context origin an overlay, popup, or reported window-space rect may be
anchored from.
Sourcepub fn has_focus(&self) -> bool
pub fn has_focus(&self) -> bool
Whether the widget being painted holds the focus path.
Threaded down from the widget’s pod (ChildPod::is_focused, seeded at
the root from RenderRoot::focus_active), this is the authoritative
focus signal during paint — a widget must prefer it over any focus flag
it tracks internally. A container-routed blur clears the pod’s focus path
but never dispatches to the widget’s event(), so a widget-internal flag
can lag; reading has_focus() here (and self-correcting the internal
flag against it) lets the widget converge one frame after the blur.
Mirrors EventCtx::has_focus.
Sourcepub fn is_hovered(&self) -> bool
pub fn is_hovered(&self) -> bool
Whether the pointer is over the widget being painted or over a descendant of it — i.e. whether this widget is on the recorded hover path.
A container therefore reads true while the pointer is over a claiming
child, the way CSS :hover applies to an element while the pointer is over
one of its descendants; a sibling or any other off-path widget reads
false.
This is the authoritative hover signal, for the same reason
PaintCtx::has_focus is authoritative for focus, only more strongly: a
pointer leaving a widget routes its next move to whatever it moved onto,
so the widget it left never receives an event telling it so. A hover
consumer keeps its own hover flag (that is what earns it a repaint on entry
— see EventCtx::claim_hover for the whole contract) and self-corrects
that flag from this read every paint, which is what fixes it whenever an
event never came.
Hover is opt-in: a widget in a tree where nothing ever calls
claim_hover always reads false here.
Sourcepub fn frame_time(&self) -> FrameTime
pub fn frame_time(&self) -> FrameTime
The shell-provided time for this frame (monotonic, arbitrary origin).
This is the single shared clock the whole paint pass sees: seeded from the
shell at the root (crate::app::RenderRoot::paint) and threaded
unchanged into every child by ChildPod::paint_child, so sibling and
nested animations advance against one consistent timestamp. A widget may
only difference it against an earlier frame_time it stored (via
FrameTime::saturating_sub / crate::anim::AnimationController::advance),
never interpret it absolutely — the origin varies per shell. Defaults to
FrameTime::ZERO when no clock was threaded in (leaf unit tests).
Sourcepub fn request_frame(&mut self)
pub fn request_frame(&mut self)
Signal that this paint advanced animation state and needs to be re-invoked to continue, even with no intervening input event.
The desktop shell honors this with a window.request_redraw() (its
ControlFlow::Wait loop would otherwise idle); the mobile shells’
continuous per-frame loops already schedule the next frame and can ignore
it. Mirrors EventCtx::request_redraw.
This requests a TickClass::Transition frame — the unpaced,
every-vsync class, unchanged from today’s behavior. A widget whose next
frame is a pacable decorative loop calls Self::request_frame_paced
(or Self::request_frame_class) instead so the mobile frame gate may
throttle it.
Sourcepub fn request_frame_paced(&mut self)
pub fn request_frame_paced(&mut self)
Request a continuation frame whose next tick is a pacable decorative
loop (TickClass::CosmeticLoop) — a shimmer, an idle pulse, a spinner
whose exact cadence is imperceptible.
Bubbles like Self::request_frame, but leaves the frame paceable: only
if every request this frame is CosmeticLoop may the frame gate throttle
it (see TickClass’s max-lattice aggregation). Any concurrent
Self::request_frame/Self::request_layout elsewhere in the tree
re-forces every-vsync cadence, so a paced request is never a downgrade of
user-visible motion.
This names no interval, which means “at the theme’s own
MotionScheme::cosmetic_loop_rate” — exactly
request_frame_paced_at(Duration::ZERO), since that rate is the cap every
paced request resolves against (see Self::request_frame_paced_at).
Sourcepub fn request_frame_paced_at(&mut self, interval: Duration)
pub fn request_frame_paced_at(&mut self, interval: Duration)
Request a pacable decorative repaint no more often than once per
interval — Self::request_frame_paced with an explicit cadence, for a
loop far slower than the theme’s cosmetic rate (a ~500ms caret blink
against a 30Hz shimmer cap).
Same class as Self::request_frame_paced (TickClass::CosmeticLoop);
only the requested cadence differs. Two contracts govern the value:
- MIN-lattice aggregation. Multiple paced requests in one paint pass
fold to the tightest interval, so every requester is repainted at
least as often as it asked. A 30Hz shimmer (a bare
Self::request_frame_paced, i.e.Duration::ZERO) beside a 500ms caret paces the frame at 30Hz — the caret is then simply repainted more often than it needs, which is visually indistinguishable from its own cadence and costs nothing beyond frames the shimmer already forced. That asymmetry is by design: a slow request can never starve a fast one. - The theme rate is a ceiling, not a target. The frame gate resolves
the aggregate against
1 / MotionScheme::cosmetic_loop_rateand takes the longer of the two, so an interval tighter than the cap is clamped to it. Motion that genuinely must run every vsync is not cosmetic — useSelf::request_frame(TickClass::Transition) for that.
frust-core never reads a clock or a theme, so neither the MIN-lattice
fold above nor the shell-side theme-cap clamp happens here: the
aggregate rides out on PaintOutcome::paced_interval and the shell’s
frame gate resolves it.
One clamp DOES happen here, though: interval is capped at
Self::MAX_PACED_INTERVAL before it is folded in, so no caller
(buggy or otherwise) can push a runaway value out to the shell’s
pacing arithmetic. See that constant’s doc comment for the full
rationale. This is a pure ceiling, never a target — every real cadence
in this codebase (a bare Duration::ZERO “theme rate” request, the
~500ms caret blink above, or any plausible slow pulse) sits far below
it and passes through completely unchanged.
Sourcepub fn request_frame_class(&mut self, class: TickClass)
pub fn request_frame_class(&mut self, class: TickClass)
Request a continuation frame of an explicit TickClass — the general
form behind Self::request_frame (Transition) and
Self::request_frame_paced (CosmeticLoop, at the theme’s own rate).
Always sets needs_frame; a TickClass::Transition request additionally
marks the aggregate unpaced (the max-lattice OR — see TickClass). A
CosmeticLoop request never clears an already-unpaced aggregate, and —
naming no interval — folds Duration::ZERO into the MIN-lattice like
Self::request_frame_paced does.
Sourcepub fn needs_frame(&self) -> bool
pub fn needs_frame(&self) -> bool
Whether a continuation frame was requested during this (sub)paint.
Sourcepub fn frame_class(&self) -> Option<TickClass>
pub fn frame_class(&self) -> Option<TickClass>
The aggregate TickClass requested during this (sub)paint, or None
if no frame was requested.
Follows the TickClass max-lattice: TickClass::Transition if any
request this pass was Transition-class (unpaced), else
TickClass::CosmeticLoop when at least one paced request was made and
no Transition one was. None means “as today — no continuation frame”.
Sourcepub fn needs_frame_paced_only(&self) -> bool
pub fn needs_frame_paced_only(&self) -> bool
Whether a frame was requested and every request this (sub)paint was
TickClass::CosmeticLoop — the paced-only state the mobile frame gate
may throttle. Convenience for
frame_class() == Some(TickClass::CosmeticLoop).
Sourcepub fn paced_interval(&self) -> Option<Duration>
pub fn paced_interval(&self) -> Option<Duration>
The tightest (MIN) interval any paced request named during this
(sub)paint, or None if no paced request was made at all.
Duration::ZERO — what a bare Self::request_frame_paced folds in —
means “at the theme’s own cosmetic_loop_rate”, so Some(Duration::ZERO)
and None resolve identically at the frame gate; the distinction is only
whether any paced request was made. Meaningful only while
Self::frame_class is TickClass::CosmeticLoop — a concurrent
Transition request makes the whole frame unpaced, at which point no
interval applies (see PaintOutcome::paced_interval).
Sourcepub fn request_layout(&mut self)
pub fn request_layout(&mut self)
Signal that this paint advanced animation state that changes the widget’s layout (not just its paint), so layout must re-run next frame.
This is the layout counterpart to Self::request_frame: a widget whose
animation only repaints (a color fade, a caret blink) calls
request_frame alone — its path is still laid out again, conservatively,
but that pass is mostly skipped and nothing reports
PaintOutcome::needs_layout — whereas a widget whose animation
resizes/repositions its children (an expanding accordion) calls this so
layout is re-run while the animation is in flight. The flag bubbles up through
ChildPod::paint_child exactly like needs_frame and out of
crate::app::RenderRoot::paint as PaintOutcome::needs_layout, which
folds into the render root’s pending crate::view::ChangeFlags
(LAYOUT) so the next frame relayouts.
Calling this also implies Self::request_frame (a widget animating its
layout necessarily wants another frame), so a caller needs only one call
per animating-layout frame. That implied frame is
TickClass::Transition (unpaced): a layout animation is user-visible
motion, so it never leaves the frame paceable.
Sourcepub fn needs_layout(&self) -> bool
pub fn needs_layout(&self) -> bool
Whether a layout re-run was requested during this (sub)paint.
Sourcepub fn publish_ime_state(&mut self, state: ImeState)
pub fn publish_ime_state(&mut self, state: ImeState)
Publish the focused editable’s current IME surface during paint.
The event pass (EventCtx::publish_ime_state) refreshes the shell’s
IME view on every edit, but an app-driven controlled change — a
submit clearing the field, applied by the next rebuild rather than by
an event — never crosses the event pass, so the event-published value
goes stale. A focused editable therefore also republishes here, in the
paint that runs after every rebuild, so crate::app::RenderRoot::ime_state
tracks the field’s current text/caret regardless of what drove the
change. Bubbles up through ChildPod::paint_child, mirroring
Self::request_frame.
Sourcepub fn take_ime_state(&mut self) -> Option<ImeState>
pub fn take_ime_state(&mut self) -> Option<ImeState>
Take the IME surface published during this (sub)paint, if any.
Sourcepub fn publish_platform_view(&mut self, frame: PlatformViewFrame)
pub fn publish_platform_view(&mut self, frame: PlatformViewFrame)
Publish a platform-view child’s paint-time frame (a
PlatformViewSlot) for this paint pass.
Pushes onto a Vec rather than setting an Option — deliberately NOT
the same shape as PaintCtx::publish_ime_state. IME state has a
single focused surface at most, so an overwrite is correct there; a
platform view has no such “the” instance, so two slots publishing in
one pass must both survive. ChildPod::paint_child bubbles this by
extend, never overwrite, for exactly that reason.
Sourcepub fn take_platform_views(&mut self) -> Vec<PlatformViewFrame>
pub fn take_platform_views(&mut self) -> Vec<PlatformViewFrame>
Take (and clear) every platform-view frame published during this (sub)paint, in paint order.
Sourcepub fn report_input_shield(&mut self, rect: Rect)
pub fn report_input_shield(&mut self, rect: Rect)
Report an absolute-coordinate region where frust content painted OVER a platform-view slot must keep winning pointer input (the “z-shield”).
The auto-collection half of the Mode B input contract: an interactive
slot hands a touch-DOWN inside its rect to the
native sibling, EXCEPT inside a shield. frust-widgets’ shield(child)
wrapper is the reporter — it paints its child unchanged and reports its
own painted rect here — so an app marks chrome that overlaps a slot
rather than hand-listing rects on the slot itself.
Core stays dumb, exactly as it does for PlatformViewFrame: this is a
flat, pass-scoped rect list with no slot association at all. The
shell-side differ (frust-shell-common::platform_view) owns the
intersection rule that decides which shields belong to which slot.
Pushes (never overwrites) — see the input_shields field doc.
Sourcepub fn take_input_shields(&mut self) -> Vec<Rect>
pub fn take_input_shields(&mut self) -> Vec<Rect>
Take (and clear) every z-shield rect reported during this (sub)paint, in
paint order. The PaintCtx::take_platform_views sibling for the shield
channel (see PaintCtx::report_input_shield).
Sourcepub fn register_overlay(&mut self, entry: OverlayEntry)
pub fn register_overlay(&mut self, entry: OverlayEntry)
Float entry’s pod above the whole app for this frame, and register its
rect for the next frame’s input routing.
§The owner must not paint the pod
A registered pod is painted by crate::app::RenderRoot::paint, after
the main tree — that is the only way it escapes its owner’s paint order
and every ancestor’s clip. An owner that also paints it itself draws the
surface twice: once clipped in place, once floated.
§Per pass, in registration order
The registry is cleared when each paint pass begins, so a surface stays alive only while its owner keeps registering it — there is nothing to unregister, and an owner that stops (or is unmounted) simply disappears from the routing table after the next paint. Within a band, later registration paints and hit-tests above earlier; the band itself outranks registration order.
OverlayEntry::window_rect
is absolute logical window space, so an owner computes it from
PaintCtx::origin — the only absolute anchor a widget has. Recomputing
it every paint is what makes an anchored surface follow its owner with no
subscription of any kind.
Registering outside a root-driven paint pass (a leaf unit test painting a
bare PaintCtx) is harmless: the entry is dropped by the next real
pass’s clear rather than leaking into it.
Sourcepub fn publish_selection_toolbar(&mut self, request: SelectionToolbarRequest)
pub fn publish_selection_toolbar(&mut self, request: SelectionToolbarRequest)
Publish “this field is focused, these verbs apply, and here is where a menu would go” for this frame.
Resolved by crate::app::RenderRoot::paint into
RenderRoot::selection_toolbar
plus a generation a shell diffs
(selection_toolbar_generation),
for the platform edit-menu route.
Publish under either policy. A field drawing its own toolbar through
the overlay portal (crate::selection_toolbar::SelectionToolbarPolicy::Framework)
publishes this too: it costs one pointer-sized write, and it keeps a single
code path rather than one per route.
A focused field publishes whether or not it has a selection, and whether or not any bar is up. The verbs are a level a platform responder chain must be able to read at any moment — a hardware Cmd+C/X/V/A is asked for with no menu on screen — so gating the publish on a bar being open is what used to leave those shortcuts unanswerable. Whether a menu should be presented rides in the request’s own flag instead.
Pass-scoped and last-writer-wins, like every other paint-time request: a pass in which nothing publishes resolves to “no field is focused”, which is what puts the toolbar away on a blur without any widget having to retract anything.
Sourcepub fn report_hero(&mut self, tag: &str, bounds: Rect) -> HeroDirective
pub fn report_hero(&mut self, tag: &str, bounds: Rect) -> HeroDirective
Report a tagged (“hero”) element’s absolute paint bounds and read back
what it should do this frame.
A no-op returning HeroDirective::Normal unless a container installed
a reporter via PaintCtx::with_hero_registry over this subtree
(the normal case — no shared-element transition in flight). When a
reporter is installed, bounds is recorded (page-local, i.e. relative
to the reporter’s reference origin, so it stays stable under a page’s
per-frame animated transition offset), and the directive the installer
set for tag is returned — HeroDirective::Suppress (skip painting,
this endpoint is morphed by its counterpart) or
HeroDirective::Morph (repaint under a rect→rect transform to the
morph destination).
Sourcepub fn hero_active(&self) -> bool
pub fn hero_active(&self) -> bool
Whether a shared-element (“hero”) transition is currently in flight over
this subtree — i.e. an ancestor installed a hero reporter via
PaintCtx::with_hero_registry, the same condition that makes
PaintCtx::report_hero record rather than no-op.
The public, boolean sibling of the crate-private
PaintCtx::hero_ref, exposed so a container that culls far-offscreen
children (a Flex under a ScrollView) can stop culling while a hero
is morphing: a tagged descendant scrolled beyond the warm margin would
otherwise never paint, and so never report its bounds
(PaintCtx::report_hero) for the morph. false in the normal case
(no transition), so culling is unaffected off the transition path.
Sourcepub fn with_hero_registry(
&mut self,
registry: &RefCell<HeroFrames>,
f: impl FnOnce(&mut PaintCtx<'_>),
)
pub fn with_hero_registry( &mut self, registry: &RefCell<HeroFrames>, f: impl FnOnce(&mut PaintCtx<'_>), )
Run f with a paint context that has registry installed as the
tagged-rect (“hero”) reporter, threading this context’s clock/theme/
focus/geometry down unchanged. A container paints a subtree inside the
closure; descendants report through PaintCtx::report_hero, and the
container reads the captured rects back from registry afterward. Any
continuation-frame request or IME publish made inside bubbles back onto
self, mirroring ChildPod::paint_child’s absorb.
Sourcepub fn visible_rect(&self) -> Option<Rect>
pub fn visible_rect(&self) -> Option<Rect>
The absolute-coordinate visible rectangle a scroll ancestor has threaded
down, or None when no viewport constraint is in effect (paint
everything). A container that culls offscreen children (Flex under a
ScrollView) tests each child’s absolute bounds against this; a widget
with no interest in culling ignores it entirely. See
PaintCtx::constrain_visible_rect for how a scroll surface sets it.
Sourcepub fn constrain_visible_rect(&mut self, rect: Rect)
pub fn constrain_visible_rect(&mut self, rect: Rect)
Constrain the threaded visible rectangle to rect (in absolute paint
coordinates), the seam a ScrollView uses to publish its
viewport to descendants.
When no rect is threaded yet this installs rect; when one already is
(a nested scroll surface), the two are intersected — the visible
region can only ever narrow, never widen, as scroll surfaces nest, so an
inner viewport never re-reveals content an outer one clipped away. The
value flows to children unchanged via ChildPod::paint_child, mirroring
how window_insets/theme are threaded.
Sourcepub fn is_translucent(&self) -> bool
pub fn is_translucent(&self) -> bool
Whether the shell created a translucent (alpha-channel, “Mode B”) GPU surface for this frame — threaded from the render root and copied down to every descendant like the theme/insets.
false in the normal opaque (“Mode A”) case, bare-core tests, and every
desktop app. The platform-view hole-punch reads it: a slot only clears
its rect (PaintScene::clear_rect) when this is true, so punching
never erases app content on an opaque surface (see frust-widgets’
PlatformViewWidget::paint).
Sourcepub fn paint_serial(&self) -> u64
pub fn paint_serial(&self) -> u64
The serial crate::app::RenderRoot::paint minted for this pass: 1
for a root’s first paint, one more for each paint after it, and 0 for
a context no root numbered (PaintCtx::new, [PaintCtx::for_test]).
The same value leaves the pass on PaintOutcome::paint_serial, rides
to the renderer with the scene, and comes back as the
frame of the ledger
published once that scene was committed.
A boundary widget remembers the serial it recorded its page in and
hands it to PaintCtx::retained_available: the ledger vouches for a
resident page only once it covers the scene that recorded it.
Sourcepub fn retained_available(
&self,
key: RetainedKey,
rect_px: Size,
scale: f32,
recorded_serial: u64,
) -> bool
pub fn retained_available( &self, key: RetainedKey, rect_px: Size, scale: f32, recorded_serial: u64, ) -> bool
Whether the renderer still holds a retained page for key at rect_px
(physical-pixel extent, rounded to whole pixels) and scale, and
had committed the scene painted at recorded_serial (the
PaintCtx::paint_serial of the pass that recorded the page) when it
said so — the query a boundary widget makes to choose between drawing
the page (PaintScene::draw_retained) and painting its body again.
Answers from the ledger the renderer published after an earlier frame,
so it can be stale; the serial is what keeps a stale answer from naming
an older recording than the widget’s latest one (a scene dropped on its
way to the renderer leaves the previous page resident under the same
key — see crate::retained). Always false when retained layers are
switched off (crate::app::RenderRoot::set_retained_layers) or no
ledger was set.
Sourcepub fn subtree_clean(&self, pod: &ChildPod) -> bool
pub fn subtree_clean(&self, pod: &ChildPod) -> bool
Whether nothing under pod changed since it was last painted — the
query a boundary widget makes about the child pod it owns before
painting it. The boundary’s own pod is dirty whenever anything beneath
it changed, so ask about the child, not about yourself.
Clean means: no dirty source reached the pod since its last paint (a
PAINT/LAYOUT rebuild, layout running, a frame or layout request, a
consumed or redraw-requesting event), and the global paint epoch (theme,
insets, window size, surface translucency, the hover link, the focus
session) is unchanged. Always false when retained layers are switched
off (crate::app::RenderRoot::set_retained_layers), and for a pod
that has never been painted.
Hover and focus. PaintCtx::is_hovered and
PaintCtx::has_focus are read at paint time from links the root moves
without visiting the pod that loses one (the pointer leaving a claimant
for a widget elsewhere routes along the new hit path only; a new focus
session strands the old holder by arithmetic). So the root folds both
into the paint epoch: a paint that follows a hover-link change (a hover
beginning, moving to another claimant, or ending) or a focus-session
change bumps it, and every boundary records its page again on that
paint. The cost is one re-record per boundary per hover/focus
transition — a transition already repaints the frame; a pointer move that
keeps the same claimant, or none, moves nothing.
Sourcepub fn retained_enabled(&self) -> bool
pub fn retained_enabled(&self) -> bool
Whether a boundary widget may bracket its body at all: retained layers
are switched on (crate::app::RenderRoot::set_retained_layers) and a
renderer ledger was threaded down (every crate::app::RenderRoot
paint threads one, empty until a shell publishes; a bare
PaintCtx::new carries none). When false a boundary paints its body
with no bracket — the renderer would still record a layer for one.
Sourcepub fn paint_publications(&self) -> u32
pub fn paint_publications(&self) -> u32
How many paint-time side-channel publications this (sub)paint has made
so far: PaintCtx::publish_ime_state,
PaintCtx::publish_platform_view, PaintCtx::report_input_shield,
PaintCtx::register_overlay, PaintCtx::publish_selection_toolbar
and PaintCtx::report_hero calls, including every descendant’s.
A boundary widget reads it before and after painting its body: those channels are re-published every paint, so a body that used one must be painted every frame — drawing its retained page instead would publish nothing, and the surface, view or rect would vanish.