pub struct EventSpec {Show 19 fields
pub on_click: Option<Value>,
pub on_drag: Option<Value>,
pub on_key: Option<Value>,
pub key_up: bool,
pub modifier_keys: bool,
pub on_context_menu: Option<Value>,
pub on_force_click: Option<Value>,
pub on_button: Option<Value>,
pub buttons: Buttons,
pub on_scroll: Option<Value>,
pub scroll_axes: ScrollAxes,
pub scroll_mods: KeyMods,
pub on_hover: Option<Value>,
pub on_drop: Option<Value>,
pub on_layout: Option<Value>,
pub on_change: Option<Value>,
pub modal: Option<Value>,
pub keep_focus: bool,
pub on_focus: Option<Value>,
}Expand description
Event payloads a node declares. Boxed on NodeSpec because most
nodes declare none.
Fields§
§on_click: Option<Value>Payload emitted as a UiEvent when this node is clicked.
on_drag: Option<Value>Makes this node draggable: pressing it starts a pointer-captured
drag, and cursor motion until release emits UiEvents of the form
{kind="drag", phase="start"|"move"|"end", x, y, dx, dy, tag} with
this payload merged in under tag. A drag past the click slop
suppresses the node’s on_click, so both can coexist.
on_key: Option<Value>Marks this node as a key sink: while it holds key focus, key
presses arrive as UiEvents on it as {kind="key", phase="down", code, ...}, with this payload merged in under tag. Clicking the
node takes key focus. Releases are not delivered unless the sink
also declares key_up: a keymap is the common
case, and a keymap that heard both halves would run every binding
twice.
key_up: boolWith on_key: the sink hears releases too, as the same payload
with phase="up" (text null, repeat false). For a held-key
interaction — WASD, press-and-hold to preview, a key that arms a
mode while it is down. A key only comes up where it went down: a
release whose press the sink never got is dropped, and focus
leaving while a key is held delivers the up first, so nothing is
left stuck down. Without it a sink hears presses only, which is
what a keymap wants.
modifier_keys: boolWith on_key: the modifier and lock keys themselves arrive too
(Shift, Ctrl, Alt, Super, Caps Lock, Num Lock, Scroll Lock), as
codes of their own with their side in location.
Without it a modifier is only ever held — in the next key’s
shift / ctrl / … and the modifiers event — so a keymap
mid-sequence does not read Shift as a key between two others. A
terminal speaking kitty’s keyboard protocol asks, and so does a
game that binds a lone Shift.
Asks for a context menu: a secondary-button press over this node
emits {kind="contextmenu", x, y, tag} on it with this payload
under tag, and does nothing else — the press moves no focus,
places no caret and produces no click, so right-clicking a
selection leaves it selected. x/y are the press in logical
viewport coordinates, which is where the menu goes. Asked of the
topmost node under the pointer; when that node offers no menu the
press reaches the nearest enclosing node that does, the way an
unclaimed key reaches the enclosing sink:
the event carries the owner’s key and tag, a nested declaration
wins over its ancestor’s, a disabled node’s own is skipped, and
the walk stops at the modal boundary. Null = the behaviour without
a tag.
on_force_click: Option<Value>Force-click events: a press that deepened past the second stage of
a Force Touch trackpad over this node emits {kind="forceclick", x, y, tag} with this payload under tag. Routed as a secondary
press is — no focus moved, no caret, no click — but asked of the
topmost node only, with no walk to an enclosing declaration — and
the ordinary click the press produces still follows,
which is what macOS does.
Text does not need this: a force click over an editor or a
selectable scope selects the word and asks the host to look it
up, which is what the gesture means on that platform. This is for
what the core cannot guess — a force click on a chart, a map, a
timeline.
The non-primary buttons as events: a press of a
button in buttons over this node emits
{kind="button", phase="press", button, x, y, clicks, tag} on it
with this payload under tag, and the button is then captured by
the node — every pointer move while it is held arrives as
phase="move" and its release as phase="release", on this node
wherever the pointer is. button is "secondary", "middle" or,
for a button past those, its crate::MouseButton::code; x/y
are logical viewport coordinates, and on a cells grid the events
carry cell: {row, col} as a click does. Several buttons may be
held at once, each its own capture; a primary drag is untouched.
Asked of the topmost node under the pointer, and when that node
claims no such button the press reaches the nearest enclosing node
that does, as a context menu’s does: a disabled node’s own is
skipped and the walk stops at the modal boundary. A claimed
secondary press is this event instead of a contextmenu event
and the stock menu — a nearer on_context_menu still wins, being
the nested declaration. Like every non-primary press it moves no
focus, places no caret and touches no selection or scrollbar. What
it is for: a terminal’s middle-click paste, and the mouse reports a
program in it asked for. Null = the behaviour without a tag.
Which non-primary buttons on_button claims:
all three kinds unless the node says otherwise. A pane that wants
the middle button and leaves the secondary one to its context menu
says Buttons::MIDDLE.
on_scroll: Option<Value>Scroll events: the wheel over this node emits {kind="scroll", x, y, dx, dy, lines, tag} with this payload under tag — the delta
in logical px as the driver reported it (positive dy is the wheel
rolling up, toward earlier content), the pointer’s position, and
on a cells grid the whole lines the delta covers (null on any
other node), the fraction carried to the next notch. The node
takes the wheel on the axes scroll_axes
names: a scroll gesture that starts over it is its own, and stays
its own until it ends wherever the pointer goes, except on an axis
it also scrolls as a container (scroll_x, scroll_y, the offset
the app sets from what it hears), where it is answered by its room
as a container is: at its edge, a gesture that way passes to the
scroller around it; it
reaches no scroll container above it, and a container inside it
still takes the axes it scrolls while it can move that way,
passing this node the rest: the other axis, and a gesture that
begins with the container at its limit (unless it says
Overscroll::Contain). The core moves nothing — a grid’s
origin_line and a canvas’s zoom are the app’s to change. A
drag-select held past a grid’s top or bottom edge arrives here
too, as the lines the frame scrolled by.
scroll_axes: ScrollAxesWhich axes on_scroll takes: both unless the
node says otherwise. A scroll gesture on an axis
the node does not take passes it by, to the scroller around it —
a terminal that scrolls its history on y says
ScrollAxes::Y, and a sideways swipe that meets it goes on
moving the strip it sits in. Meaningless without on_scroll.
scroll_mods: KeyModsThe modifiers on_scroll is for (backlog
F122): with any named, the node hears only a scroll gesture that
began with one of them held — and hears it first, wherever in
the tree under the pointer the gesture began: ahead of every
scroll container and every handler that names none, the innermost
such node winning. A wheel with none of them held passes it by as
if it declared no on_scroll — a node that is a scroll container
too scrolls for it as any other does. What a Ctrl-wheel zoom is declared
with, on the window’s root or on the one canvas it zooms: a
modified wheel is the more specific ask, and no list under the
pointer scrolls for it. Its events carry mods, the modifiers
held when the gesture began — the gesture stays this node’s to
its end, glide included, whatever is let go meanwhile. None named
(the default): a handler like any other. Meaningless without
on_scroll.
on_hover: Option<Value>Hover events: the pointer entering or leaving this node emits
{kind="hover", phase="enter"|"leave", tag} with this payload under
tag — for hover-dependent layout (a close button that appears)
where a color swap isn’t enough. Implies hover tracking.
on_drop: Option<Value>Drop-zone events: files dragged in from the OS over this node emit
{kind="drop", phase="enter"|"move"|"leave"|"drop", paths, x, y, tag} with this payload under tag — paths the OS paths as
strings, x/y the pointer in viewport coordinates (absent on
leave). The zone under the files is the topmost zone by paint
order: a node inside a zone resolves to it, and a node that is no
zone and has none enclosing it is looked past, so an overlay shown
on enter cannot make the zone lose the files. No leave follows
a drop. Implies hover tracking.
on_layout: Option<Value>Layout events: the rect layout gave this node arrives as
{kind="layout", x, y, w, h, parent: {x, y, w, h}, tag} (logical px,
viewport coordinates, after scrolling and position easing) — on the
node’s first frame and again whenever the rect changes, never on a
frame that left it alone. The view reads the numbers layout already
produced instead of re-deriving them; a transition that moves the
node reports every frame it moves. Needs a stable key across frames.
on_change: Option<Value>Slider changes: on a node whose role is Slider, the core turns a
press into the value under the pointer, a drag into the value under
it, the arrows and assistive technology’s Increment / Decrement
into one value_step, PageUp / PageDown into ten, Home / End into
the range’s ends — clamped to value_min..value_max, snapped to the
step — and emits {kind="change", value, phase="move"|"end", tag}
with this payload under tag. The value is proposed, never
applied: nothing moves until the view declares it as value_now.
Without it a slider’s keys reach the app as the access nudge.
Ignored on any other role.
modal: Option<Value>Modal: while this node is declared, the Tab ring is its subtree,
everything outside it is inert to the pointer, the wheel and
assistive technology, and Escape or a press outside emits
{kind="dismiss", reason, tag} on it with this payload under
tag. The last node declaring it in tree order is the one in
effect (a confirm inside a dialog). Null = modal without a tag.
keep_focus: boolA press on this node, or anywhere inside it, leaves keyboard focus
where it was (keepFocus): a toolbar button, a tab, a divider
that acts without taking the keyboard from the editor beside it.
Its click, drag and hover are unchanged, and Tab
and assistive technology still reach a focusable node in it — it
is the pointer’s press alone that stops moving focus.
on_focus: Option<Value>Keyboard focus entering or leaving this node’s subtree — the node
itself or anything focused inside it — emits {kind="focus", phase="in"|"out", by, tag} with this payload under tag, where
by is "pointer", "keyboard", "assistive" or "program":
what moved it. Reported once the move has settled —
after the input that made it, or at the end of the frame that
declared it — so a view reads a change instead of diffing
key_focus every frame. Declares nothing interactive.