makepad-2-0-events – Coding skill for AI agents
Makepad 2.0 guidance for events; use when building or debugging Makepad UI code.
Install the makepad-2-0-events skill with one command
Pick the tool you use, copy the command and paste it in your terminal. The skill lands in that tool's skills folder and is ready in your next session.
Claude Code
mkdir -p ~/.claude/skills && curl -fsSL https://skill.dn1nguoi.com/z/makepad-2-0-events-fd9dd.tgz | tar xz -C ~/.claude/skills
Call it in Claude Code: /makepad-2-0-events – then describe the task you want done.
Codex
mkdir -p ~/.codex/skills && curl -fsSL https://skill.dn1nguoi.com/z/makepad-2-0-events-fd9dd.tgz | tar xz -C ~/.codex/skills
Call it in Codex: $makepad-2-0-events – then describe the task you want done.
Hermes Agent
mkdir -p ~/.hermes/skills && curl -fsSL https://skill.dn1nguoi.com/z/makepad-2-0-events-fd9dd.tgz | tar xz -C ~/.hermes/skills
Call it in Hermes Agent: /makepad-2-0-events – then describe the task you want done.
DeepSeek
mkdir -p ~/.deepcode/skills && curl -fsSL https://skill.dn1nguoi.com/z/makepad-2-0-events-fd9dd.tgz | tar xz -C ~/.deepcode/skills
Call it in DeepSeek: /makepad-2-0-events – then describe the task you want done.
SKILL.md content
Limitations
- Pin the Makepad Git revision from Cargo.lock and use that revision’s source/examples for every macro, event and widget method. “2.0” alone is not an API compatibility guarantee; these imported snippets span a changing development branch.
- Do not run bundled scripts without explicit user approval.
When to Use
- Use when building or debugging Makepad UI event routing, widget actions, timer-driven updates, or Rust/Splash communication.
- First identify whether the target uses script_mod/Splash or older live_design APIs; use matching examples.
Makepad 2.0 Event & Action System
Example and verification
For “debounce this search field”, identify the pinned event API, preserve pending input, cancel superseded timers, tag responses to prevent stale updates, and test typing, empty input, cancellation, focus changes and teardown. Repeated Enter/click events must not duplicate a mutation; UI validation is separate from server authorization. Do not log sensitive input or response bodies.
Overview
Makepad 2.0 uses a two-layer event system:
1. 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.
2. 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 runtime
---
1. Splash Inline Event Handlers
Event 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.
on_click -- Button/widget click
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()
}
}on_render -- Dynamic rendering
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.
on_return -- TextInput enter key
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()
}on_startup -- App startup
Fires once when the application starts. Defined at the Root level. Commonly used
to trigger initial renders.
ui: Root{
on_startup: ||{
ui.main_view.render()
}
main_window := Window{
// ...
}
}Event handler capabilities
Inside event handlers you can:
- Call Splash functions:
add_todo(text, "dev") - Read widget values:
let text = ui.todo_input.text() - Set widget values:
ui.todo_input.set_text("") - Trigger re-renders:
ui.todo_list.render() - Trigger other widget clicks:
ui.add_button.on_click() - Modify state variables:
state.counter += 1 - Use array methods:
todos.push({text: "new", done: false}) - Use control flow:
if text != "" { ... }
---
2. Rust Event Handling -- MatchEvent Trait
The MatchEvent trait is the Rust-side event dispatcher. It receives platform events
and widget actions through a set of handler methods.
Core trait definition (from draw/src/match_event.rs)
pub trait MatchEvent {
// Lifecycle
fn handle_startup(&mut self, _cx: &mut Cx) {}
fn handle_shutdown(&mut self, _cx: &mut Cx) {}
fn handle_foreground(&mut self, _cx: &mut Cx) {}
fn handle_background(&mut self, _cx: &mut Cx) {}
fn handle_pause(&mut self, _cx: &mut Cx) {}
fn handle_resume(&mut self, _cx: &mut Cx) {}
// Window focus
fn handle_window_got_focus(&mut self, _cx: &mut Cx, _window_id: &WindowId) {}
fn handle_window_lost_focus(&mut self, _cx: &mut Cx, _window_id: &WindowId) {}
// Frame
fn handle_next_frame(&mut self, _cx: &mut Cx, _e: &NextFrameEvent) {}
// Widget actions (most commonly used)
fn handle_action(&mut self, _cx: &mut Cx, _e: &Action) {}
fn handle_actions(&mut self, cx: &mut Cx, actions: &Actions) {
for action in actions {
self.handle_action(cx, action);
}
}
// Input
fn handle_key_down(&mut self, _cx: &mut Cx, _e: &KeyEvent) {}
fn handle_key_up(&mut self, _cx: &mut Cx, _e: &KeyEvent) {}
fn handle_back_pressed(&mut self, _cx: &mut Cx) -> bool { false }
// Timer
fn handle_timer(&mut self, _cx: &mut Cx, _e: &TimerEvent) {}
// Drawing
fn handle_draw(&mut self, _cx: &mut Cx, _e: &DrawEvent) {}
fn handle_draw_2d(&mut self, _cx: &mut Cx2d) {}
// Network
fn handle_http_response(&mut self, _cx: &mut Cx, _request_id: LiveId, _response: &HttpResponse) {}
fn handle_http_request_error(&mut self, _cx: &mut Cx, _request_id: LiveId, _err: &HttpError) {}
fn handle_http_progress(&mut self, _cx: &mut Cx, _request_id: LiveId, _progress: &HttpProgress) {}
fn handle_http_stream(&mut self, _cx: &mut Cx, _request_id: LiveId, _data: &HttpResponse) {}
fn handle_http_stream_complete(&mut self, _cx: &mut Cx, _request_id: LiveId, _data: &HttpResponse) {}
// Signals
fn handle_signal(&mut self, _cx: &mut Cx) {}
// Media devices
fn handle_audio_devices(&mut self, _cx: &mut Cx, _e: &AudioDevicesEvent) {}
fn handle_midi_ports(&mut self, _cx: &mut Cx, _e: &MidiPortsEvent) {}
fn handle_video_inputs(&mut self, _cx: &mut Cx, _e: &VideoInputsEvent) {}
}Standard App boilerplate (required)
This is an illustrative script-era app structure. Confirm bootstrap and event dispatch against the pinned example; it is not required boilerplate for every Makepad generation:
use makepad_widgets::*;
app_main!(App);
script_mod! {
// ... Splash UI code ...
}
impl App {
fn run(vm: &mut ScriptVm) -> Self {
crate::makepad_widgets::script_mod(vm);
App::from_script_mod(vm, self::script_mod)
}
}
#[derive(Script, ScriptHook)]
pub struct App {
#[live]
ui: WidgetRef,
}
impl MatchEvent for App {
fn handle_actions(&mut self, cx: &mut Cx, actions: &Actions) {
// Handle widget actions here
}
}
impl AppMain for App {
fn handle_event(&mut self, cx: &mut Cx, event: &Event) {
self.match_event(cx, event);
self.ui.handle_event(cx, event, &mut Scope::empty());
}
}CRITICAL: handle_event must call BOTH self.match_event(cx, event) (to dispatch
to the MatchEvent handlers) AND self.ui.handle_event(cx, event, &mut Scope::empty())
(to propagate events to widgets).
---
3. Widget Action API
Access widgets from Rust using self.ui.widget_type(cx, ids!(name)), then query their
action state by passing the &Actions reference.
Button
// Access: self.ui.button(cx, ids!(my_button))
// Returns ButtonRef
.clicked(actions) -> bool // Was clicked (finger down + up inside)
.pressed(actions) -> bool // Was pressed down
.long_pressed(actions) -> bool // Was long-pressed (not yet … (full content is in the download package)
Related skills
- makepad-2-0-layout – Makepad 2.0 guidance for layout; use when building or debugging Makepad UI code.
- makepad-2-0-widgets – Makepad 2.0 guidance for widgets; use when building or debugging Makepad UI code.
- makepad-widgets – Select and compose Makepad widgets, configure their properties and styling, and debug…
- markstream-custom-components – Override Markstream node renderers and add trusted custom tags across Vue, React…
- markstream-react – Integrate the beta markstream-react renderer into React 18+ or Next.js with correct…
- markstream-vue – Integrate markstream-vue into plain Vue 3 with renderer modes, code and DOM choices…
- markstream-vue2-cli – Integrate markstream-vue2 into Vue CLI or Webpack 4 with export-map-safe CSS, CDN worker…
- mason – Produces clean, functional code that matches the architecture and checklists.