pub struct EventCtx<'a> { /* private fields */ }Expand description
Context threaded into crate::widget::Widget::event.
Gives an event handler three capabilities: mutate the (type-erased)
application state, request a redraw, and capture the pointer. It also carries
the receiving widget’s own geometry (EventCtx::origin/EventCtx::size)
so handlers can do local-coordinate math (the event position is already in
the widget’s local space; origin/size describe where that widget sits in and
how big it is within its parent).
State is erased as &mut dyn Any — the same pattern
crate::widget::LayoutCtx uses for the text context — so frust-core
carries no knowledge of the concrete app state type; a handler recovers it
with EventCtx::state_mut.
Implementations§
Source§impl<'a> EventCtx<'a>
impl<'a> EventCtx<'a>
Sourcepub fn new(
state: &'a mut (dyn Any + 'static),
origin: Point,
size: Size,
) -> EventCtx<'a>
pub fn new( state: &'a mut (dyn Any + 'static), origin: Point, size: Size, ) -> EventCtx<'a>
Build a root event context over the erased application state for a
widget placed at origin with size.
Sourcepub fn state_mut<T>(&mut self) -> &mut Twhere
T: Any,
pub fn state_mut<T>(&mut self) -> &mut Twhere
T: Any,
Recover the application state as &mut T.
Panics if T is not the concrete state type the render root erased — a
shell/wiring bug, not a runtime-data condition (mirrors
crate::widget::LayoutCtx::text_context).
Sourcepub fn request_redraw(&mut self)
pub fn request_redraw(&mut self)
Request that the shell schedule a repaint after this event pass.
Sourcepub fn needs_redraw(&self) -> bool
pub fn needs_redraw(&self) -> bool
Whether a redraw was requested during this (sub)dispatch.
Sourcepub fn capture_pointer(&mut self)
pub fn capture_pointer(&mut self)
Capture the pointer: subsequent moves/releases should route back to this
widget. The ChildPod::event_child
call that delivered the event reads EventCtx::is_pointer_captured
after the dispatch returns and records the active path on its pod; the
container clears it on Up/Cancel.
Capture is a Down-time concept here. For a hit-tested pointer event,
only the Down arm of RenderRoot::event
folds a request into the root’s own capture mirror, so a capture opened
from a Move records the pod’s active path (routing works) while the root
still reads uncaptured. (A floated overlay surface’s own input is the one
exception: the root mirrors a capture claimed through it on any phase —
see InputEvent::Overlay.) For hover that means a Move that both
captures and claim_hovers records the claim — the pod’s
eligibility gate reads the active flag as it stood before this dispatch —
and then lapses on the next Move, where the now-active pod is ineligible.
A gesture that wants hover chrome for its whole drag keeps its own pressed
flag rather than relying on the link.
Sourcepub fn is_pointer_captured(&self) -> bool
pub fn is_pointer_captured(&self) -> bool
Whether the widget requested pointer capture during this (sub)dispatch.
Sourcepub fn pointer_id(&self) -> PointerId
pub fn pointer_id(&self) -> PointerId
Which pointer contact this event comes from.
PointerId::MOUSE unless the dispatch says otherwise: a shell’s bare
InputEvent::Pointer is the mouse, and a touch contact arrives as
InputEvent::PointerContact, which the root unwraps into
InputEvent::Pointer while this reports its id. Meaningful for pointer
events only; any other event reports whatever contact the pass carries
(the mouse at the top level).
A widget that tracks one gesture at a time never needs this — the root
only ever delivers it the claimant’s contact unless it opted in with
EventCtx::capture_contacts. One that did opt in tells the contacts
apart with it.
Sourcepub fn capture_contacts(&mut self)
pub fn capture_contacts(&mut self)
Opt into the other contacts of the gesture this widget is capturing:
call it on the same Down that calls EventCtx::capture_pointer, and
while that capture lives every additional contact’s
Down/Move/Up/Cancel is delivered here down the captured path, as
InputEvent::Pointer with EventCtx::pointer_id naming the contact.
A pinch or rotate recognizer is the intended caller.
The opt-in is read with the capture it accompanies: on a Down that
captures nothing it does nothing, and it ends with the capture.
§Multi-contact contract
- (a) With no live capture, a slot-
0contact is hit-tested exactly like a plainInputEvent::Pointer; theDownthis widget captures on makes that contact the gesture’s claimant. - (b) With no live capture, a contact on slot
1or above is dropped at the root — so an additional finger only ever reaches a widget through this opt-in. - (c) While the capture lives, the claimant’s events arrive as usual;
every other contact’s events arrive only because of this call (a
captor that did not make it never sees them), and only here: the
containers between the root and this widget on the recorded active
path forward them without running their own pointer handling (see
ChildPod::event_child), so an enclosing scroll view or gesture detector never mistakes a second finger for its own. Only the claimant’sUp/Cancelends the capture — another contact’sUp/Cancelis delivered but releases nothing, so the handler must not treat it as the end of the gesture. When the claimant’sUp/Cancelarrives, the captor must drop every other contact it was tracking: their later events no longer reach it. The opt-in also ends early if an enclosing container takes the gesture over and releases this widget from the active path (EventCtx::release_captured_child).
See InputEvent::PointerContact for the full contract.
Sourcepub fn is_contact_capture_requested(&self) -> bool
pub fn is_contact_capture_requested(&self) -> bool
Whether a widget opted into the gesture’s other contacts
(EventCtx::capture_contacts) during this (sub)dispatch — the
container-side read, bubbled by
ChildPod::event_child exactly
like EventCtx::is_pointer_captured. The root does not depend on the
bubble: it reads the opt-in from the pass it opened, so a component
boundary (whose fresh inner context this flag does not cross) cannot
hide it.
Sourcepub fn release_captured_child(&mut self, child: &mut ChildPod)
pub fn release_captured_child(&mut self, child: &mut ChildPod)
Release a captured child: the container-side half of a takeover, for a container that cancels the gesture its captured child was handling and keeps the gesture for itself (a scroll view crossing its drag slop).
Clears child’s recorded active path exactly like
ChildPod::set_active(false)
(call it after delivering the child its Cancel), and when the released
subtree held the widget that opted into the gesture’s other contacts
(EventCtx::capture_contacts) it also tells the root, which then stops
routing those contacts — the widget that asked for them is no longer on
the active path, and nothing else asked. The signal bubbles like
EventCtx::is_pointer_captured (EventCtx::is_capture_released).
The capture itself stays with the gesture’s claimant: the container that
took over is still on the active path (it was the released child’s
ancestor), so the claimant’s later events keep reaching it and only the
claimant’s Up/Cancel ends the gesture. A container that wants the
other contacts for itself calls EventCtx::capture_contacts after
this, in the same dispatch.
Nothing is released or signalled while the child holds no active path,
while a non-claimant contact is being delivered (whose Up/Cancel
must never break the claimant’s gesture — see
ChildPod::set_active), or when
the opted-in widget is this container or one of its ancestors (it stays
on the active path, so its opt-in stands).
Sourcepub fn is_capture_released(&self) -> bool
pub fn is_capture_released(&self) -> bool
Whether a container released the gesture’s opted-in widget from the
active path during this (sub)dispatch
(EventCtx::release_captured_child) — bubbled by
ChildPod::event_child exactly
like EventCtx::is_pointer_captured. As with the opt-in, the root reads
the release from the pass it opened, so a component boundary cannot hide
it.
Sourcepub fn request_focus(&mut self)
pub fn request_focus(&mut self)
Request focus: subsequent keyboard/IME events should route to this widget.
The ChildPod::event_child call
that delivered the event reads the flag after the dispatch returns and
records its pod as the focused path, stamped with the live focus session
(the focus mirror of EventCtx::capture_pointer); the request bubbles,
so every pod up to the root records it. Focus-routed events are delivered
down that recorded chain with no hit test.
Sourcepub fn release_focus(&mut self)
pub fn release_focus(&mut self)
Release focus: drop the recorded focus path (e.g. Escape / blur).
Sourcepub fn has_focus(&self) -> bool
pub fn has_focus(&self) -> bool
Whether the receiving widget currently holds the focus path.
Threaded down from the widget’s pod (crate::widget::ChildPod::is_focused);
a keyboard/IME event only reaches a widget along this chain, so a widget
handling such an event is by construction focused.
Sourcepub fn claim_hover(&mut self)
pub fn claim_hover(&mut self)
Claim the hover link: the pointer is over this widget, so the next paint
pass reports PaintCtx::is_hovered
for it — and, because the claim is recorded as a path, for every ancestor
enclosing it as well (see the module docs).
§The consumer contract
Three things together, all three required:
- Claim from the
PointerPhase::Movearm, once the widget has hit-tested the event’spositioninside its own bounds — the same local test a press arm does onUp. - Keep an internal hover flag, updated from that same hit test, and
gate
request_redrawon its changed-return. This call requests no frame of its own (below), and the root manufactures one only when a hover ends with nothing taking it — so a widget without this flag paints no hover chrome on entry, and none when the link moves from a sibling to it. - Read
PaintCtx::is_hoveredinpaintand self-correct the flag from it. It is authoritative: the flag can be stale (a pointer that left the widget never delivers it another event; a container clearing or lapsing a link never tells the widget either), and this read is what fixes it.
PointerPhase::Move => {
if !self.captured {
// Uncaptured move: this is the hover pass.
let over = inside(p.position, ctx.size());
if over { ctx.claim_hover(); }
if self.state_layer.set_hovered(over) { ctx.request_redraw(); }
return EventResult::Ignored;
}
// ... captured drag handling
}
fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut dyn PaintScene) {
// Authoritative; corrects the flag above whenever it went stale.
self.state_layer.set_hovered(ctx.is_hovered());
// ... paint the overlay
}Claim on every qualifying Move, not just on entry. The claim is
per-pass, not sticky: a widget that stops claiming stops being hovered on
the next hover pass. That is the mechanism, not a defect — it is what makes
“the pointer moved somewhere else” self-clearing with no leave event to
deliver.
A container claims after routing the Move to its children, never
before. One claim per pass is recorded and the first one recorded wins, so
an ancestor that claims before it forwards makes every descendant ineligible
for the pass: the child under the pointer reads
is_hovered == false forever while step 2 above
keeps flipping its flag and asking for a frame on every move — hover chrome
that never appears, plus a repaint per event. Claiming after routing is
correct in every case: a descendant’s claim is recorded first and wins, the
container’s own late call is then a silent no-op yet it still reads hovered
through the stamped path (below), and when no descendant claims, the
container’s claim is what records, so its own chrome still works. A
container therefore never arbitrates — it orders.
Its sibling channel resolves the opposite way. A claim is
first-writer-wins; set_cursor is
last-writer-wins. So the same “claim/ask after routing” placement means
two different things in one handler: the container’s claim is a fallback
its child beats, while the container’s cursor request is an override
that beats its child’s. A container that wants the child’s cursor to win
must ask before it routes — the mirror image of the ordering here.
§When it does nothing
A call is silently ignored unless the pass is hover-eligible: a captured
pointer (anywhere on the path), any phase other than an uncaptured Move,
and any claim after the first one in the same pass all record nothing. A
leaf therefore never has to ask whether claiming is allowed — it claims
whenever the pointer is over it and the pipeline decides. A container
gets the same freedom only by claiming after it routes: it never asks
either, but when it claims decides whether its children may, per the
ordering rule above.
A Down, Up, or Cancel ends whatever hover stood without opening a
new one, so a consumer re-claims on the next Move rather than expecting
its chrome to survive a click.
§It does not request a redraw
Deliberately: a pointer moving within one widget claims on every event, and repainting each time would be pure waste. The widget owns the change detection instead — which is what makes step 2 above part of the contract rather than an optimization.
Sourcepub fn is_hovered(&self) -> bool
pub fn is_hovered(&self) -> bool
Whether the receiving widget or a descendant of it holds the hover link — i.e. whether the last completed hover pass recorded a claim path running through this widget.
So a container reads true while the pointer is over a claiming child
(CSS :hover semantics), and a widget that never claims can still read
true when a descendant does; a sibling or any other off-path widget reads
false.
Threaded down from the widget’s pod
(ChildPod::hover_epoch against
the live epoch) and seeded at the root from RenderRoot’s hover mirror, so
it reflects state as of before this dispatch: a
claim_hover made in this pass does not flip it.
Mirrors EventCtx::has_focus; the paint-pass form is
PaintCtx::is_hovered, which is the
authoritative read for a widget’s own hover chrome.
Sourcepub fn set_cursor(&mut self, icon: CursorIcon)
pub fn set_cursor(&mut self, icon: CursorIcon)
Ask the host to show icon while the pointer is where it is now.
The request is per-pass and stateless, exactly like
request_redraw and
claim_hover: it says what the cursor should be
for this pass, and a widget that stops asking falls back to
CursorIcon::Default with nothing to clear.
§When to call it
From a PointerPhase::Move arm, on the same hit test a
claim_hover rides — the two are siblings, and a
widget that wants hover chrome usually wants a cursor too:
PointerPhase::Move => {
if !self.captured {
if inside(p.position, ctx.size()) {
ctx.claim_hover();
ctx.set_cursor(CursorIcon::Pointer);
}
return EventResult::Ignored;
}
// Captured drag: this widget owns the pass, so its request wins
// wherever the pointer has gone.
ctx.set_cursor(CursorIcon::Grabbing);
// ... drag handling
}Ask on every Move, not just on entry, and ask from the captured
Moves too if a drag should keep its own shape: a captured pass routes
only to the capturing widget, so re-asking there is what keeps a
Grabbing cursor alive while the pointer is dragged outside the widget’s
own bounds.
§Which pass the root actually resolves
Only a pointer PointerPhase::Move — captured or not — re-resolves the
cursor (crate::app::RenderRoot::cursor). A request made on any other
pass records nothing, and, just as importantly, no other pass resets the
cursor: a Down/Up whose handlers say nothing about the cursor leaves
the standing shape alone rather than blinking it back to Default for the
duration of a click. A widget wanting a press-specific cursor therefore
keys it off its own pressed state from the Move arm rather than setting
it on Down.
§Last writer wins
One value is resolved per pass, and the last set_cursor of the pass is
it. Because a container routes to its child from the middle of its own
handler, the innermost widget the route reaches normally speaks last and
therefore wins — which is what makes a specific control override the
generic surface behind it. A container that deliberately overrides its
children sets the cursor after routing.
Note the asymmetry with claim_hover, which
is first-writer-wins: a container claiming after routing yields hover to
its child, while a container asking for a cursor after routing overrides
its child. Placing the two calls side by side in one Move arm — the
example above — is correct precisely because a leaf has no child to order
against; a container writing both has to place them separately.
§It does not request a redraw
Deliberately, for claim_hover’s reason: a
pointer moving within one widget re-asks on every event, and the shell
applies the resolved cursor whether or not a frame is painted.
Takes &mut self like every other request on this context even though the
pass’s request slot is not a field of it (CURSOR_REQUEST, above): asking
is something a widget does through its context, and keeping the signature
honest about that leaves the storage free to move.
Sourcepub fn write_clipboard(&mut self, text: String)
pub fn write_clipboard(&mut self, text: String)
Ask the shell to put text on the host clipboard.
The answer to an InputEvent::EditCommand(EditCommand::Copy) or
EditCommand::Cut: the widget owns the selection, so it is the only
thing that can say what “copy” means, and the shell owns the host
clipboard, so it is the only thing that can perform the write. A widget
with nothing selected simply does not call this, and nothing is written.
InputEvent::EditCommand(EditCommand::Cut) => {
if let Some(sel) = self.selected_text() {
ctx.write_clipboard(sel);
self.delete_selection();
ctx.request_redraw();
}
EventResult::Handled
}§Pass-scoped, last writer wins
The request rides the same kind of per-pass slot as
set_cursor (CLIPBOARD_WRITE, bracketed by the
same [RequestPass] guard), so exactly one write is resolved per dispatch
and the pass’s last caller is it — which, since a container routes to its
child from the middle of its own handler, normally makes the innermost
widget the route reaches the one that speaks. Nothing accumulates between
passes and there is nothing to clear: a widget that stops copying stops
writing.
The root resolves the slot at the end of every pass (not just a
clipboard one — a copy can be answered from a key chord a widget decoded
itself), and a shell drains it with
RenderRoot::take_clipboard_write
immediately after the dispatch, beside
cursor() and
ime_state().
§It does not request a redraw
set_cursor’s reason: copying paints nothing. A
Cut that mutates the document asks for its own redraw, for the mutation.
Takes &mut self like every other request on this context even though the
pass’s slot is not a field of it: asking is something a widget does
through its context, and keeping the signature honest about that leaves
the storage free to move.
Sourcepub fn request_paste(&mut self)
pub fn request_paste(&mut self)
Ask the shell to read the host clipboard and deliver it back as an
InputEvent::EditCommand(EditCommand::Paste).
The inverse of write_clipboard, and the
reason a paste arrives with its text already attached: only the shell can
touch the host clipboard, so a widget that wants a paste it was not given
— an in-widget context-menu item, a chord the widget decoded itself —
raises this flag and receives the text on a later dispatch rather than
inline.
§Idempotent, pass-scoped, and answered out of band
Data-free: two widgets asking in one pass owe exactly one clipboard read,
because there is one host clipboard and one focused widget to deliver it
to. The flag rides a per-pass slot bracketed by the same [RequestPass]
guard as the cursor, so an ask made outside any dispatch is dropped rather
than leaking into the next pass; the root resolves it at the end of every
pass and a shell drains it with
RenderRoot::take_paste_request.
The answer is a new dispatch, never a return value: the shell’s read
may be asynchronous (a permission prompt, a cross-process fetch), and by
the time it lands the pass that asked is long over. The synthesized
EditCommand::Paste carries text and no identity of its own, and focus
routing hands it to whoever holds focus at delivery: a release does
drop it — with nothing focused it reaches no widget — but a focus move
lands it in the new field, not in the one that asked.
A synchronous read has no in-flight window and needs no guard. An
asynchronous one must bind its answer to the session that asked:
snapshot RenderRoot::focus_epoch
(reached shell-side as AppTree::focus_epoch) when the request is
drained, and drop an answer whose epoch no longer matches — never
focus_ime_generation,
which an edit or a caret move inside one session also moves.
A Cut may write and ask in the same pass; the two slots are independent.
Sourcepub fn dispatch_edit_command(&mut self, cmd: EditCommand)
pub fn dispatch_edit_command(&mut self, cmd: EditCommand)
Hand an EditCommand to whichever widget drains the queue later in
this pass — the widget-to-widget half of the clipboard story.
§Why this is not just an InputEvent::EditCommand
A selection toolbar and the text input it acts on are two different
widgets, and the toolbar is a pod its owner floats rather than contains
(see crate::overlay), so there is no container path from the toolbar’s
“Copy” tap back down to the field. Re-entering
crate::app::RenderRoot::event with a focus-routed
InputEvent::EditCommand would be the other option, and is worse: a
dispatch may not re-enter the root (see that method’s reentrancy
contract), and the toolbar’s tap is already being routed as an overlay
broadcast when it decides. So the verb rides a pass-scoped FIFO the owner
drains the instant its forward returns, and applies to the field itself —
synchronously, inside the same dispatch.
Order is preserved: commands drain in the order they were dispatched.
A command nobody takes before the pass ends is dropped (with a debug-build diagnostic) rather than carried into the next pass, where it would apply to whatever happened to be selected by then.
Sourcepub fn take_edit_commands(&mut self) -> Vec<EditCommand>
pub fn take_edit_commands(&mut self) -> Vec<EditCommand>
Drain everything EventCtx::dispatch_edit_command queued so far in this
pass, in dispatch order, leaving the queue empty.
An overlay owner calls this immediately after forwarding an event into its floated pod, and applies what comes back to itself. Draining the queue (rather than peeking) is what keeps a verb from being applied twice when two owners forward in the same pass.
Sourcepub fn publish_ime_state(&mut self, state: ImeState)
pub fn publish_ime_state(&mut self, state: ImeState)
Publish this widget’s IME surface (editing state + caret) for the shell.
The value bubbles up the focus chain to crate::app::RenderRoot, where
the shell reads it via crate::app::RenderRoot::ime_state. Called by the
focused editable widget after any state change so the platform IME stays in
sync.
Core carries the whole ImeState — including its
ImeContentType hint — opaquely: nothing
between here and the shell inspects or rewrites it.
Sourcepub fn origin(&self) -> Point
pub fn origin(&self) -> Point
The receiving widget’s origin in its parent’s coordinate space.
Not the window-space origin, and not the same frame of reference as
PaintCtx::origin, which is absolute:
the paint pass accumulates each child’s parent-relative offset onto its
parent’s already-absolute origin, while the event pass instead translates
the event into the child’s local space
(ChildPod::event_child) and hands
down the pod’s own offset unaccumulated. So an event position is already
local (compare it against Point::ZERO and EventCtx::size, never
against this), and anything anchored in window space — an overlay, a
popup, a reported rect — must be computed from PaintCtx::origin in
paint, not from this value.
Auto Trait Implementations§
impl<'a> !RefUnwindSafe for EventCtx<'a>
impl<'a> !Send for EventCtx<'a>
impl<'a> !Sync for EventCtx<'a>
impl<'a> !UnwindSafe for EventCtx<'a>
impl<'a> Freeze for EventCtx<'a>
impl<'a> Unpin for EventCtx<'a>
impl<'a> UnsafeUnpin for EventCtx<'a>
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
Source§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>. Box<dyn Any> can
then be further downcast into Box<ConcreteType> where ConcreteType implements Trait.Source§fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
Rc<Trait> (where Trait: Downcast) to Rc<Any>. Rc<Any> can then be
further downcast into Rc<ConcreteType> where ConcreteType implements Trait.Source§fn as_any(&self) -> &(dyn Any + 'static)
fn as_any(&self) -> &(dyn Any + 'static)
&Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &Any’s vtable from &Trait’s.Source§fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
&mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &mut Any’s vtable from &mut Trait’s.