CRITICAL: Use for Makepad 2.0 event and action handling. Triggers on: makepad event, makepad action, MatchEvent, handle_event, handle_actions, on_click, on_render, on_return, on_startup, script_eval!, script_apply_eval!, button clicked, text changed, slider changed, checkbox toggled, Hit, FingerDown, FingerUp, KeyDown, KeyUp, Focus, ids!, TextCopy, TextCut, SelectionHandleDrag, PopupDismissed, clipboard, selection, IME, ImeAction, popup window events, video inputs, camera events, 事件, 动作, 点击, 输入, 回调, 交互, 事件处理, 剪贴板, 选择, 弹出窗口
Makepad 2.0 uses a two-layer event system:
Splash Layer -- Inline event handlers written directly in script_mod! Splash code
(on_click, on_render, on_return, on_startup). These handle UI interactions
declaratively inside the script, close to the widget definitions.
Rust Layer -- The MatchEvent trait with handle_actions, handle_timer,
handle_http_response, etc. These handle business logic, external I/O, and
anything that needs full Rust power.
Both layers communicate through two bridge macros:
script_eval!(cx, { ... }) -- Execute Splash code from Rust (update state, trigger renders)script_apply_eval!(cx, widget_ref, { ... }) -- Patch widget properties from Rust at runtimeEvent handlers are attached directly to widgets inside script_mod! blocks. They use
closure syntax with || for no arguments or |arg| for callbacks that receive a value.
Fires when the user clicks a button or clickable widget. No arguments for plain buttons,
or |checked| for CheckBox which passes the new boolean state.
// Plain button click
add_button := Button{
text: "Add"
on_click: ||{
let text = ui.todo_input.text()
if text != "" {
add_todo(text, "")
ui.todo_input.set_text("")
}
}
}
// CheckBox click with checked state argument
check.on_click: |checked| toggle_todo(i, checked)
// Inline delete with closure capturing loop variable
delete.on_click: || delete_todo(i)
// Calling another widget's click programmatically
clear_done := ButtonFlatter{
text: "Clear completed"
on_click: ||{
todos.retain(|todo| !todo.done)
ui.todo_list.render()
}
}
Fires when .render() is called on the target view. This is the primary mechanism for
dynamic content. The body replaces the previous draw content of the view.
main_view := View{
width: Fill
height: Fill
on_render: ||{
counter_label := Label{
text: "Count: " + state.counter
draw_text.text_style.font_size: 24
}
}
}
// List rendering with for loop and per-item event handlers
todo_list := ScrollYView{
width: Fill height: Fill
new_batch: true
on_render: ||{
if todos.len() == 0
EmptyState{}
else for i, todo in todos {
TodoItem{
label.text: todo.text
check.active: todo.done
check.on_click: |checked| toggle_todo(i, checked)
delete.on_click: || delete_todo(i)
}
}
}
EmptyState{}
}
Key point: on_render is NOT called automatically. You must call ui.widget_name.render()
to trigger it. The new_batch: true property on a view tells the system to clear previous
draw content before re-rendering.
Fires when the user presses Enter/Return inside a TextInput. Commonly used to submit forms.
todo_input := TextInput{
width: Fill height: 9. * theme.space_1
empty_text: "What needs to be done?"
on_return: || ui.add_button.on_click()
}
Fires once when the application starts. Defined at the Root level. Commonly used
to trigger initial renders.