Keyboard shortcuts

Press or to navigate between chapters

Press ? to show this help

Press Esc to hide this help

The on Handlers

The on EventKind clause attaches event handlers to a widget.

on is a reserved keyword — registered as a custom token so on Foo is never mistaken for a widget named on.

Form B — modifier chain after the body

use xrune::ui;
fn save() {}
fn app(parent: i32) {
ui! {
    :(
        parent: parent
    :)
button (text: "Save") {} on Tap {
    save();
}
}
}
fn main() {}

Inside a nested body, Form-B attaches to the nearest preceding sibling widget, not to the parent:

use xrune::ui;
fn save() {}
fn cancel() {}
fn app(parent: i32) {
ui! {
    :(
        parent: parent
    :)
column {
    button (text: "Save") {}
    on Tap { save(); }            /* attaches to the button above */

    button (text: "Cancel") {}
    on Tap { cancel(); }          /* attaches to the cancel button */
}
}
}
fn main() {}

Multiple Form-B clauses chain on the same widget:

use xrune::ui;
fn save() {}
fn hint() {}
fn app(parent: i32) {
ui! {
    :(
        parent: parent
    :)
button (text: "Save") {}
    on Tap { save(); }
    on Hover { hint(); }
}
}
fn main() {}

Form C — between attrs and body

use xrune::ui;
fn commit() {}
fn lock() {}
fn app(parent: i32) {
ui! {
    :(
        parent: parent
    :)
slider (min: 0, max: 100)
    on ValueChanged(2) { commit(); }
    on DragStart { lock(); }
    {}
}
}
fn main() {}

Multiple Form-C clauses stack on the same widget. The trailing {} is optional; without it, the widget simply has no children:

use xrune::ui;
fn fire() {}
fn app(parent: i32) {
ui! {
    :(
        parent: parent
    :)
view ()
    on Tap { fire(); }
}
}
fn main() {}

Form B and Form C accumulate into the same Vec<DsOn> retrieved via DsWidget::get_on_handlers(). Mixing both on one widget is allowed.

Body form vs callback form

A handler can carry either a body block:

use xrune::ui;
struct State; impl State { fn toggle(&self) {} }
fn app(parent: i32, state: State) {
ui! {
    :(
        parent: parent
    :)
button () {}
on Tap {
    state.toggle();
}
}
}
fn main() {}

…or a trailing callback expression with no body:

use xrune::ui;
fn callback() {}
fn app(parent: i32) {
ui! {
    :(
        parent: parent
    :)
button () {}
on Tap(callback)
on Tap(2, callback)
}
}
fn main() {}

For the body form, DsOn::get_body() returns Some(&syn::Block). For the callback form, it returns None, and the rune decides what the trailing get_args() element means — the convention is “callable expression to invoke when the event fires.”

A clause with neither body nor args is a parse error.

Qualified events

use xrune::ui;
struct Slider;
fn commit() {}
fn app(parent: i32) {
ui! {
    :(
        parent: parent
    :)
slider () {}
on Slider::ValueChanged { commit(); }
}
}
fn main() {}

get_qualifier() returns Some(Slider) and get_name() returns ValueChanged. Only one segment of qualification is allowed — Foo::Bar::Baz is rejected.

Args

Arguments inside on EventKind(…) are a comma-separated list of syn::Expr. The rune retrieves them via get_args(). Common shapes:

on Tap { … }                        /* args: [], body present */
on Tap(2) { … }                     /* args: [2], body present */
on Tap(cb)                          /* callback form, body absent */
on Tap(2, cb)                       /* count + callback */

A clause with neither body nor args is a parse error: every on must carry at least one of the two.

Source-of-truth

ds_on.rs and the form_b_* / form_c_* test cases in tests.rs. The shape is exhaustively round-tripped by xrune-fmt’s formatter — every change to this surface lands a fmt update in the same commit.