$npx -y skills add ZhangHanDong/makepad-skills --skill makepad-2.0-eventsCRITICAL: 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 togg
| 1 | # Makepad 2.0 Event & Action System |
| 2 | |
| 3 | ## Overview |
| 4 | |
| 5 | Makepad 2.0 uses a **two-layer event system**: |
| 6 | |
| 7 | 1. **Splash Layer** -- Inline event handlers written directly in `script_mod!` Splash code |
| 8 | (`on_click`, `on_render`, `on_return`, `on_startup`). These handle UI interactions |
| 9 | declaratively inside the script, close to the widget definitions. |
| 10 | |
| 11 | 2. **Rust Layer** -- The `MatchEvent` trait with `handle_actions`, `handle_timer`, |
| 12 | `handle_http_response`, etc. These handle business logic, external I/O, and |
| 13 | anything that needs full Rust power. |
| 14 | |
| 15 | Both layers communicate through two bridge macros: |
| 16 | - `script_eval!(cx, { ... })` -- Execute Splash code from Rust (update state, trigger renders) |
| 17 | - `script_apply_eval!(cx, widget_ref, { ... })` -- Patch widget properties from Rust at runtime |
| 18 | |
| 19 | --- |
| 20 | |
| 21 | ## 1. Splash Inline Event Handlers |
| 22 | |
| 23 | Event handlers are attached directly to widgets inside `script_mod!` blocks. They use |
| 24 | closure syntax with `||` for no arguments or `|arg|` for callbacks that receive a value. |
| 25 | |
| 26 | ### on_click -- Button/widget click |
| 27 | |
| 28 | Fires when the user clicks a button or clickable widget. No arguments for plain buttons, |
| 29 | or `|checked|` for CheckBox which passes the new boolean state. |
| 30 | |
| 31 | ```splash |
| 32 | // Plain button click |
| 33 | add_button := Button{ |
| 34 | text: "Add" |
| 35 | on_click: ||{ |
| 36 | let text = ui.todo_input.text() |
| 37 | if text != "" { |
| 38 | add_todo(text, "") |
| 39 | ui.todo_input.set_text("") |
| 40 | } |
| 41 | } |
| 42 | } |
| 43 | |
| 44 | // CheckBox click with checked state argument |
| 45 | check.on_click: |checked| toggle_todo(i, checked) |
| 46 | |
| 47 | // Inline delete with closure capturing loop variable |
| 48 | delete.on_click: || delete_todo(i) |
| 49 | |
| 50 | // Calling another widget's click programmatically |
| 51 | clear_done := ButtonFlatter{ |
| 52 | text: "Clear completed" |
| 53 | on_click: ||{ |
| 54 | todos.retain(|todo| !todo.done) |
| 55 | ui.todo_list.render() |
| 56 | } |
| 57 | } |
| 58 | ``` |
| 59 | |
| 60 | ### on_render -- Dynamic rendering |
| 61 | |
| 62 | Fires when `.render()` is called on the target view. This is the primary mechanism for |
| 63 | dynamic content. The body replaces the previous draw content of the view. |
| 64 | |
| 65 | ```splash |
| 66 | main_view := View{ |
| 67 | width: Fill |
| 68 | height: Fill |
| 69 | on_render: ||{ |
| 70 | counter_label := Label{ |
| 71 | text: "Count: " + state.counter |
| 72 | draw_text.text_style.font_size: 24 |
| 73 | } |
| 74 | } |
| 75 | } |
| 76 | |
| 77 | // List rendering with for loop and per-item event handlers |
| 78 | todo_list := ScrollYView{ |
| 79 | width: Fill height: Fill |
| 80 | new_batch: true |
| 81 | on_render: ||{ |
| 82 | if todos.len() == 0 |
| 83 | EmptyState{} |
| 84 | else for i, todo in todos { |
| 85 | TodoItem{ |
| 86 | label.text: todo.text |
| 87 | check.active: todo.done |
| 88 | check.on_click: |checked| toggle_todo(i, checked) |
| 89 | delete.on_click: || delete_todo(i) |
| 90 | } |
| 91 | } |
| 92 | } |
| 93 | EmptyState{} |
| 94 | } |
| 95 | ``` |
| 96 | |
| 97 | **Key point**: `on_render` is NOT called automatically. You must call `ui.widget_name.render()` |
| 98 | to trigger it. The `new_batch: true` property on a view tells the system to clear previous |
| 99 | draw content before re-rendering. |
| 100 | |
| 101 | ### on_return -- TextInput enter key |
| 102 | |
| 103 | Fires when the user presses Enter/Return inside a TextInput. Commonly used to submit forms. |
| 104 | |
| 105 | ```splash |
| 106 | todo_input := TextInput{ |
| 107 | width: Fill height: 9. * theme.space_1 |
| 108 | empty_text: "What needs to be done?" |
| 109 | on_return: || ui.add_button.on_click() |
| 110 | } |
| 111 | ``` |
| 112 | |
| 113 | ### on_startup -- App startup |
| 114 | |
| 115 | Fires once when the application starts. Defined at the `Root` level. Commonly used |
| 116 | to trigger initial renders. |
| 117 | |
| 118 | ```splash |
| 119 | ui: Root{ |
| 120 | on_startup: ||{ |
| 121 | ui.main_view.render() |
| 122 | } |
| 123 | main_window := Window{ |
| 124 | // ... |
| 125 | } |
| 126 | } |
| 127 | ``` |
| 128 | |
| 129 | ### Event handler capabilities |
| 130 | |
| 131 | Inside event handlers you can: |
| 132 | - Call Splash functions: `add_todo(text, "dev")` |
| 133 | - Read widget values: `let text = ui.todo_input.text()` |
| 134 | - Set widget values: `ui.todo_input.set_text("")` |
| 135 | - Trigger re-renders: `ui.todo_list.render()` |
| 136 | - Trigger other widget clicks: `ui.add_button.on_click()` |
| 137 | - Modify state variables: `state.counter += 1` |
| 138 | - Use array methods: `todos.push({text: "new", done: false})` |
| 139 | - Use control flow: `if text != "" { ... }` |
| 140 | |
| 141 | --- |
| 142 | |
| 143 | ## 2. Rust Event Handling -- MatchEvent Trait |
| 144 | |
| 145 | The `MatchEvent` trait is the Rust-side event dispatcher. It receives platform events |
| 146 | and widget actions through a set of handler methods. |
| 147 | |
| 148 | ### Core trait definition (from `draw/src/match_event.rs`) |
| 149 | |
| 150 | ```rust |
| 151 | pub trait MatchEvent { |
| 152 | // Lifecycle |
| 153 | fn handle_startup(&mut self, _cx: &mut Cx) {} |
| 154 | fn handle |