opencode-skills-collection 4.0.69 → 4.0.70
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bundled-skills/.antigravity-install-manifest.json +39 -1
- package/bundled-skills/api-integration-architect/SKILL.md +241 -0
- package/bundled-skills/apify-generate-output-schema/SKILL.md +438 -0
- package/bundled-skills/apify-integration-development/SKILL.md +168 -0
- package/bundled-skills/apify-integration-development/references/ai-framework-package.md +158 -0
- package/bundled-skills/apify-integration-development/references/ai-harness-plugin.md +192 -0
- package/bundled-skills/apify-integration-development/references/sdk-integration.md +236 -0
- package/bundled-skills/apify-integration-development/references/workflow-automation.md +163 -0
- package/bundled-skills/architecture-review/README.md +42 -0
- package/bundled-skills/architecture-review/SKILL.md +77 -0
- package/bundled-skills/architecture-review/examples.md +11 -0
- package/bundled-skills/architecture-review/reference/best-practices.md +7 -0
- package/bundled-skills/architecture-review/reference/capabilities.md +20 -0
- package/bundled-skills/architecture-review/reference/fallbacks.md +11 -0
- package/bundled-skills/architecture-review/reference/graph.md +15 -0
- package/bundled-skills/architecture-review/reference/mcp.md +14 -0
- package/bundled-skills/architecture-review/reference/workflow.md +15 -0
- package/bundled-skills/architecture-review/templates/architecture-review.md +21 -0
- package/bundled-skills/code-review-sensei/SKILL.md +177 -0
- package/bundled-skills/codebase-onboarding/README.md +42 -0
- package/bundled-skills/codebase-onboarding/SKILL.md +77 -0
- package/bundled-skills/codebase-onboarding/examples.md +11 -0
- package/bundled-skills/codebase-onboarding/reference/best-practices.md +7 -0
- package/bundled-skills/codebase-onboarding/reference/capabilities.md +20 -0
- package/bundled-skills/codebase-onboarding/reference/fallbacks.md +11 -0
- package/bundled-skills/codebase-onboarding/reference/graph.md +15 -0
- package/bundled-skills/codebase-onboarding/reference/mcp.md +14 -0
- package/bundled-skills/codebase-onboarding/reference/workflow.md +15 -0
- package/bundled-skills/codebase-onboarding/templates/repository-onboarding.md +21 -0
- package/bundled-skills/connection-auth-rules/SKILL.md +199 -0
- package/bundled-skills/connection-auth-rules/fetch_schema.py +320 -0
- package/bundled-skills/dependency-analysis/README.md +42 -0
- package/bundled-skills/dependency-analysis/SKILL.md +76 -0
- package/bundled-skills/dependency-analysis/examples.md +11 -0
- package/bundled-skills/dependency-analysis/reference/best-practices.md +7 -0
- package/bundled-skills/dependency-analysis/reference/capabilities.md +20 -0
- package/bundled-skills/dependency-analysis/reference/fallbacks.md +11 -0
- package/bundled-skills/dependency-analysis/reference/graph.md +15 -0
- package/bundled-skills/dependency-analysis/reference/mcp.md +14 -0
- package/bundled-skills/dependency-analysis/reference/workflow.md +15 -0
- package/bundled-skills/dependency-analysis/templates/dependency-review.md +21 -0
- package/bundled-skills/devops-pipeline-builder/SKILL.md +200 -0
- package/bundled-skills/eas-app-stores/SKILL.md +197 -0
- package/bundled-skills/eas-app-stores/agents/openai.yaml +4 -0
- package/bundled-skills/eas-app-stores/references/app-store-metadata.md +497 -0
- package/bundled-skills/eas-app-stores/references/ios-app-store.md +376 -0
- package/bundled-skills/eas-app-stores/references/native-ios.md +167 -0
- package/bundled-skills/eas-app-stores/references/play-store.md +244 -0
- package/bundled-skills/eas-app-stores/references/testflight.md +62 -0
- package/bundled-skills/eas-app-stores/references/workflows.md +120 -0
- package/bundled-skills/eas-hosting/SKILL.md +448 -0
- package/bundled-skills/eas-hosting/agents/openai.yaml +4 -0
- package/bundled-skills/eas-observe/SKILL.md +75 -0
- package/bundled-skills/eas-observe/agents/openai.yaml +4 -0
- package/bundled-skills/eas-observe/references/metrics.md +98 -0
- package/bundled-skills/eas-observe/references/queries.md +403 -0
- package/bundled-skills/eas-observe/references/setup.md +476 -0
- package/bundled-skills/eas-observe/references/third-party.md +136 -0
- package/bundled-skills/eas-simulator/SKILL.md +251 -0
- package/bundled-skills/eas-simulator/agents/openai.yaml +4 -0
- package/bundled-skills/eas-simulator/references/controllers.md +135 -0
- package/bundled-skills/eas-simulator/references/run-your-app.md +240 -0
- package/bundled-skills/eas-simulator/references/troubleshooting.md +47 -0
- package/bundled-skills/eas-workflows/SKILL.md +119 -0
- package/bundled-skills/eas-workflows/agents/openai.yaml +4 -0
- package/bundled-skills/eas-workflows/scripts/fetch.js +109 -0
- package/bundled-skills/expo-animation/LICENSE +21 -0
- package/bundled-skills/expo-animation/RECIPES.md +385 -0
- package/bundled-skills/expo-animation/SKILL.md +295 -0
- package/bundled-skills/expo-animation/agents/openai.yaml +4 -0
- package/bundled-skills/fact-check-x-unified/SKILL.md +178 -0
- package/bundled-skills/fact-check-x-unified/agents/openai.yaml +4 -0
- package/bundled-skills/fact-check-x-unified/references/acceptance-criteria.md +44 -0
- package/bundled-skills/fact-check-x-unified/references/contracts.md +39 -0
- package/bundled-skills/fact-check-x-unified/scripts/common.py +31 -0
- package/bundled-skills/fact-check-x-unified/scripts/fact_check_x.py +1832 -0
- package/bundled-skills/fact-check-x-unified/scripts/trusted_search_config.py +324 -0
- package/bundled-skills/fact-check-x-unified/tests/anchor_downgrade_test.py +90 -0
- package/bundled-skills/fact-check-x-unified/tests/multi_platform_test.py +369 -0
- package/bundled-skills/fact-check-x-unified/tests/smoke_test.py +740 -0
- package/bundled-skills/fact-check-x-unified/tests/stage_checkpoint_test.py +103 -0
- package/bundled-skills/fact-check-x-unified/tests/trusted_search_config_test.py +156 -0
- package/bundled-skills/gpt-taste/SKILL.md +8 -1
- package/bundled-skills/hf-cli/SKILL.md +263 -0
- package/bundled-skills/huggingface-community-evals/SKILL.md +228 -0
- package/bundled-skills/huggingface-community-evals/examples/.env.example +3 -0
- package/bundled-skills/huggingface-community-evals/examples/USAGE_EXAMPLES.md +101 -0
- package/bundled-skills/huggingface-community-evals/scripts/inspect_eval_uv.py +104 -0
- package/bundled-skills/huggingface-community-evals/scripts/inspect_vllm_uv.py +306 -0
- package/bundled-skills/huggingface-community-evals/scripts/lighteval_vllm_uv.py +297 -0
- package/bundled-skills/huggingface-datasets/SKILL.md +130 -0
- package/bundled-skills/jev-social/SKILL.md +182 -0
- package/bundled-skills/longbridge-derivatives/SKILL.md +117 -0
- package/bundled-skills/longbridge-derivatives/references/option.md +36 -0
- package/bundled-skills/longbridge-derivatives/references/options-advanced.md +101 -0
- package/bundled-skills/longbridge-derivatives/references/options-pnl.md +74 -0
- package/bundled-skills/longbridge-derivatives/references/options-strategy.md +82 -0
- package/bundled-skills/longbridge-derivatives/references/options-volatility.md +70 -0
- package/bundled-skills/longbridge-derivatives/references/warrant.md +12 -0
- package/bundled-skills/longbridge-quant/SKILL.md +151 -0
- package/bundled-skills/longbridge-quant/references/correlation.md +51 -0
- package/bundled-skills/longbridge-quant/references/execution-model.md +68 -0
- package/bundled-skills/longbridge-quant/references/factor-research.md +95 -0
- package/bundled-skills/longbridge-quant/references/factor-screen.md +101 -0
- package/bundled-skills/longbridge-quant/references/hedging.md +136 -0
- package/bundled-skills/longbridge-quant/references/ml-strategy.md +77 -0
- package/bundled-skills/longbridge-quant/references/multifactor.md +68 -0
- package/bundled-skills/longbridge-quant/references/pairs-trading.md +61 -0
- package/bundled-skills/longbridge-quant/references/quant-cli.md +133 -0
- package/bundled-skills/longbridge-quant/references/quant-stats.md +150 -0
- package/bundled-skills/longbridge-quant/references/seasonality.md +50 -0
- package/bundled-skills/longbridge-quant/references/strategy-optimizer.md +68 -0
- package/bundled-skills/longbridge-quant/references/volatility-strategy.md +52 -0
- package/bundled-skills/longbridge-research/SKILL.md +187 -0
- package/bundled-skills/longbridge-research/references/company-profile.md +96 -0
- package/bundled-skills/longbridge-research/references/company-tearsheet.md +82 -0
- package/bundled-skills/longbridge-research/references/competitive-analysis.md +81 -0
- package/bundled-skills/longbridge-research/references/consensus.md +92 -0
- package/bundled-skills/longbridge-research/references/coverage-initiation.md +76 -0
- package/bundled-skills/longbridge-research/references/defi-yield.md +60 -0
- package/bundled-skills/longbridge-research/references/finance-calendar.md +165 -0
- package/bundled-skills/longbridge-research/references/financial-planning.md +77 -0
- package/bundled-skills/longbridge-research/references/forecast-eps.md +39 -0
- package/bundled-skills/longbridge-research/references/fund-holder.md +44 -0
- package/bundled-skills/longbridge-research/references/hkipo-analysis.md +101 -0
- package/bundled-skills/longbridge-research/references/industry-peers.md +46 -0
- package/bundled-skills/longbridge-research/references/industry-rank.md +62 -0
- package/bundled-skills/longbridge-research/references/insider-trades.md +48 -0
- package/bundled-skills/longbridge-research/references/institution-rating.md +62 -0
- package/bundled-skills/longbridge-research/references/investment-ideas.md +69 -0
- package/bundled-skills/longbridge-research/references/investment-proposal.md +95 -0
- package/bundled-skills/longbridge-research/references/investors.md +87 -0
- package/bundled-skills/longbridge-research/references/onchain.md +70 -0
- package/bundled-skills/longbridge-research/references/post-investment.md +76 -0
- package/bundled-skills/longbridge-research/references/shareholder.md +72 -0
- package/bundled-skills/longbridge-research/references/short-positions.md +50 -0
- package/bundled-skills/longbridge-research/references/short-trades.md +50 -0
- package/bundled-skills/longbridge-research/references/stock-research.md +61 -0
- package/bundled-skills/longbridge-research/references/thesis-tracker.md +64 -0
- package/bundled-skills/makepad-2-0-animation/SKILL.md +318 -0
- package/bundled-skills/makepad-2-0-animation/references/animator-reference.md +433 -0
- package/bundled-skills/makepad-2-0-dsl/SKILL.md +492 -0
- package/bundled-skills/makepad-2-0-dsl/references/dsl-syntax-reference.md +511 -0
- package/bundled-skills/makepad-2-0-dsl/references/extended-guide.md +56 -0
- package/bundled-skills/makepad-2-0-dsl/references/property-system.md +757 -0
- package/bundled-skills/makepad-2-0-events/SKILL.md +497 -0
- package/bundled-skills/makepad-2-0-events/references/event-patterns.md +802 -0
- package/bundled-skills/makepad-2-0-events/references/extended-guide.md +590 -0
- package/bundled-skills/makepad-2-0-layout/SKILL.md +499 -0
- package/bundled-skills/makepad-2-0-layout/references/extended-guide.md +243 -0
- package/bundled-skills/makepad-2-0-layout/references/layout-patterns.md +881 -0
- package/bundled-skills/makepad-2-0-widgets/SKILL.md +261 -0
- package/bundled-skills/makepad-2-0-widgets/references/widget-advanced.md +648 -0
- package/bundled-skills/makepad-2-0-widgets/references/widget-catalog.md +547 -0
- package/bundled-skills/meeting-distiller-pro/SKILL.md +120 -0
- package/bundled-skills/monte-carlo-analyze-root-cause/SKILL.md +12 -1
- package/bundled-skills/monte-carlo-asset-health/SKILL.md +12 -1
- package/bundled-skills/monte-carlo-context-detection/SKILL.md +170 -0
- package/bundled-skills/monte-carlo-context-detection/references/signal-definitions.md +46 -0
- package/bundled-skills/remotion-captions/SKILL.md +57 -0
- package/bundled-skills/remotion-captions/agents/openai.yaml +7 -0
- package/bundled-skills/remotion-captions/assets/remotion-icon.svg +4 -0
- package/bundled-skills/remotion-captions/display-captions.md +190 -0
- package/bundled-skills/remotion-captions/import-srt-captions.md +73 -0
- package/bundled-skills/remotion-captions/transcribe-captions.md +70 -0
- package/bundled-skills/remotion-create/SKILL.md +106 -0
- package/bundled-skills/remotion-create/agents/openai.yaml +7 -0
- package/bundled-skills/remotion-create/assets/remotion-icon.svg +4 -0
- package/bundled-skills/remotion-create/tailwind.md +11 -0
- package/bundled-skills/remotion-create/video-layout.md +9 -0
- package/bundled-skills/remotion-docs/SKILL.md +67 -0
- package/bundled-skills/remotion-docs/agents/openai.yaml +7 -0
- package/bundled-skills/remotion-docs/assets/remotion-icon.svg +4 -0
- package/bundled-skills/remotion-interactivity/SKILL.md +270 -0
- package/bundled-skills/remotion-interactivity/agents/openai.yaml +7 -0
- package/bundled-skills/remotion-interactivity/assets/remotion-icon.svg +4 -0
- package/bundled-skills/remotion-render/SKILL.md +48 -0
- package/bundled-skills/remotion-render/agents/openai.yaml +7 -0
- package/bundled-skills/remotion-render/assets/remotion-icon.svg +4 -0
- package/bundled-skills/remotion-render/transparent-videos.md +106 -0
- package/bundled-skills/saas-pricing-strategist/SKILL.md +169 -0
- package/bundled-skills/score-eval/SKILL.md +35 -0
- package/bundled-skills/writing-guidelines/SKILL.md +60 -0
- package/package.json +1 -1
- package/skills_index.json +980 -3
|
@@ -0,0 +1,802 @@
|
|
|
1
|
+
# Makepad 2.0 Event Patterns -- Working Examples
|
|
2
|
+
|
|
3
|
+
This reference contains complete working patterns extracted from the Makepad 2.0 codebase.
|
|
4
|
+
All Splash code uses Makepad 2.0 syntax (no commas, no semicolons, colon properties).
|
|
5
|
+
All Rust code uses `makepad_widgets::*` imports.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 1. Counter App -- Button Click -> State Update -> Re-render
|
|
10
|
+
|
|
11
|
+
The simplest complete event flow. A button click in Rust triggers `script_eval!`
|
|
12
|
+
which updates Splash state and calls `.render()` to refresh the view.
|
|
13
|
+
|
|
14
|
+
**Source**: `examples/counter/src/app.rs`
|
|
15
|
+
|
|
16
|
+
### Splash side (inside script_mod!)
|
|
17
|
+
|
|
18
|
+
```splash
|
|
19
|
+
use mod.prelude.widgets.*
|
|
20
|
+
let state = {
|
|
21
|
+
counter: 0
|
|
22
|
+
}
|
|
23
|
+
mod.state = state
|
|
24
|
+
startup() do #(App::script_component(vm)){
|
|
25
|
+
ui: Root{
|
|
26
|
+
on_startup: ||{
|
|
27
|
+
ui.main_view.render()
|
|
28
|
+
}
|
|
29
|
+
main_window := Window{
|
|
30
|
+
window.inner_size: vec2(420, 220)
|
|
31
|
+
body +: {
|
|
32
|
+
main_view := View{
|
|
33
|
+
width: Fill
|
|
34
|
+
height: Fill
|
|
35
|
+
flow: Down
|
|
36
|
+
spacing: 12
|
|
37
|
+
align: Center
|
|
38
|
+
on_render: ||{
|
|
39
|
+
counter_label := Label{
|
|
40
|
+
text: "Count: " + state.counter
|
|
41
|
+
draw_text.text_style.font_size: 24
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
increment_button := Button{
|
|
46
|
+
text: "Increment"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Rust side
|
|
55
|
+
|
|
56
|
+
```rust
|
|
57
|
+
use makepad_widgets::*;
|
|
58
|
+
|
|
59
|
+
app_main!(App);
|
|
60
|
+
|
|
61
|
+
// script_mod! { ... } (Splash code above)
|
|
62
|
+
|
|
63
|
+
impl App {
|
|
64
|
+
fn run(vm: &mut ScriptVm) -> Self {
|
|
65
|
+
crate::makepad_widgets::script_mod(vm);
|
|
66
|
+
App::from_script_mod(vm, self::script_mod)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
#[derive(Script, ScriptHook)]
|
|
71
|
+
pub struct App {
|
|
72
|
+
#[live]
|
|
73
|
+
ui: WidgetRef,
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
impl MatchEvent for App {
|
|
77
|
+
fn handle_actions(&mut self, cx: &mut Cx, actions: &Actions) {
|
|
78
|
+
if self.ui.button(cx, ids!(increment_button)).clicked(actions) {
|
|
79
|
+
script_eval!(cx, {
|
|
80
|
+
mod.state.counter += 1
|
|
81
|
+
ui.main_view.render()
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
impl AppMain for App {
|
|
88
|
+
fn handle_event(&mut self, cx: &mut Cx, event: &Event) {
|
|
89
|
+
self.match_event(cx, event);
|
|
90
|
+
self.ui.handle_event(cx, event, &mut Scope::empty());
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Event flow
|
|
96
|
+
|
|
97
|
+
1. User clicks the "Increment" button
|
|
98
|
+
2. Button widget emits `ButtonAction::Clicked` into `Actions`
|
|
99
|
+
3. `AppMain::handle_event` calls `self.match_event(cx, event)`
|
|
100
|
+
4. `MatchEvent::match_event` dispatches `Event::Actions` to `handle_actions`
|
|
101
|
+
5. `self.ui.button(cx, ids!(increment_button)).clicked(actions)` returns `true`
|
|
102
|
+
6. `script_eval!` runs Splash code: increments `state.counter`, calls `render()`
|
|
103
|
+
7. `render()` schedules `main_view` for redraw
|
|
104
|
+
8. On next draw pass, `on_render` fires, creating a Label with the new count
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## 2. Todo App -- List Rendering with Per-Item Events
|
|
109
|
+
|
|
110
|
+
Demonstrates dynamic list rendering with `for` loops, per-item click handlers,
|
|
111
|
+
and multiple Splash functions coordinating state.
|
|
112
|
+
|
|
113
|
+
**Source**: `examples/todo/src/app.rs`
|
|
114
|
+
|
|
115
|
+
### State and functions
|
|
116
|
+
|
|
117
|
+
```splash
|
|
118
|
+
let todos = []
|
|
119
|
+
|
|
120
|
+
// Seed sample data
|
|
121
|
+
todos.push({text: "Get AI to control UI", tag: "dev", done: true})
|
|
122
|
+
|
|
123
|
+
fn add_todo(text, tag){
|
|
124
|
+
todos.push({text: text, tag: tag, done: false})
|
|
125
|
+
ui.todo_list.render()
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
fn toggle_todo(index, checked){
|
|
129
|
+
todos[index].done = checked
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
fn delete_todo(index){
|
|
133
|
+
todos.remove(index)
|
|
134
|
+
ui.todo_list.render()
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
fn count_remaining(){
|
|
138
|
+
let n = 0
|
|
139
|
+
for todo in todos {
|
|
140
|
+
if !todo.done { n = n + 1 }
|
|
141
|
+
}
|
|
142
|
+
n
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Template definition
|
|
147
|
+
|
|
148
|
+
```splash
|
|
149
|
+
let TodoItem = RoundedView{
|
|
150
|
+
width: Fill height: Fit
|
|
151
|
+
padding: theme.mspace_2{left: theme.space_3, right: theme.space_3}
|
|
152
|
+
flow: Right spacing: theme.space_2
|
|
153
|
+
align: Align{y: 0.5}
|
|
154
|
+
draw_bg.color: theme.color_bg_container
|
|
155
|
+
draw_bg.border_radius: 10.0
|
|
156
|
+
|
|
157
|
+
check := CheckBox{text: ""}
|
|
158
|
+
label := Label{
|
|
159
|
+
width: Fill
|
|
160
|
+
text: "task"
|
|
161
|
+
draw_text.color: theme.color_label_inner
|
|
162
|
+
}
|
|
163
|
+
delete := ButtonFlatter{
|
|
164
|
+
text: "x"
|
|
165
|
+
width: 28 height: 28
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Input bar with on_return and on_click
|
|
171
|
+
|
|
172
|
+
```splash
|
|
173
|
+
todo_input := TextInput{
|
|
174
|
+
width: Fill height: 9. * theme.space_1
|
|
175
|
+
empty_text: "What needs to be done?"
|
|
176
|
+
on_return: || ui.add_button.on_click()
|
|
177
|
+
}
|
|
178
|
+
add_button := Button{
|
|
179
|
+
text: "+"
|
|
180
|
+
width: 40 height: 34
|
|
181
|
+
on_click: ||{
|
|
182
|
+
let text = ui.todo_input.text()
|
|
183
|
+
if text != "" {
|
|
184
|
+
add_todo(text, "")
|
|
185
|
+
ui.todo_input.set_text("")
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Dynamic list with on_render
|
|
192
|
+
|
|
193
|
+
```splash
|
|
194
|
+
todo_list := ScrollYView{
|
|
195
|
+
width: Fill height: Fill
|
|
196
|
+
padding: theme.mspace_2{left: theme.space_3, right: theme.space_3}
|
|
197
|
+
flow: Down spacing: theme.space_1
|
|
198
|
+
new_batch: true
|
|
199
|
+
on_render: ||{
|
|
200
|
+
if todos.len() == 0
|
|
201
|
+
EmptyState{}
|
|
202
|
+
else for i, todo in todos {
|
|
203
|
+
TodoItem{
|
|
204
|
+
label.text: todo.text
|
|
205
|
+
check.active: todo.done
|
|
206
|
+
check.on_click: |checked| toggle_todo(i, checked)
|
|
207
|
+
delete.on_click: || delete_todo(i)
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
EmptyState{}
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Clear completed with array retain
|
|
216
|
+
|
|
217
|
+
```splash
|
|
218
|
+
clear_done := ButtonFlatter{
|
|
219
|
+
text: "Clear completed"
|
|
220
|
+
on_click: ||{
|
|
221
|
+
todos.retain(|todo| !todo.done)
|
|
222
|
+
ui.todo_list.render()
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### Key patterns demonstrated
|
|
228
|
+
|
|
229
|
+
- `on_return` on TextInput delegates to another widget's `on_click`
|
|
230
|
+
- `on_render` with `for i, todo in todos` creates per-item UI with closures
|
|
231
|
+
- Each closure captures the loop variable `i` for per-item actions
|
|
232
|
+
- `CheckBox.on_click: |checked|` receives the new boolean state
|
|
233
|
+
- `todos.retain(|todo| !todo.done)` filters the array using a predicate
|
|
234
|
+
- `ui.todo_list.render()` triggers re-render after any state mutation
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
## 3. Form Validation Pattern
|
|
239
|
+
|
|
240
|
+
A pattern for input validation before submission, using Splash-only event handlers.
|
|
241
|
+
|
|
242
|
+
```splash
|
|
243
|
+
let form_errors = {
|
|
244
|
+
name: ""
|
|
245
|
+
email: ""
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
fn validate_email(email){
|
|
249
|
+
// Simple check -- Splash does not have regex by default
|
|
250
|
+
if email == "" "Email is required"
|
|
251
|
+
else if !email.contains("@") "Invalid email address"
|
|
252
|
+
else ""
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
fn validate_name(name){
|
|
256
|
+
if name == "" "Name is required"
|
|
257
|
+
else if name.len() < 2 "Name too short"
|
|
258
|
+
else ""
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
fn submit_form(){
|
|
262
|
+
let name = ui.name_input.text()
|
|
263
|
+
let email = ui.email_input.text()
|
|
264
|
+
|
|
265
|
+
form_errors.name = validate_name(name)
|
|
266
|
+
form_errors.email = validate_email(email)
|
|
267
|
+
|
|
268
|
+
ui.error_view.render()
|
|
269
|
+
|
|
270
|
+
if form_errors.name == "" && form_errors.email == "" {
|
|
271
|
+
// All valid -- process submission
|
|
272
|
+
ui.name_input.set_text("")
|
|
273
|
+
ui.email_input.set_text("")
|
|
274
|
+
ui.success_view.render()
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// UI definition
|
|
279
|
+
View{
|
|
280
|
+
width: Fill height: Fit
|
|
281
|
+
flow: Down spacing: theme.space_2
|
|
282
|
+
|
|
283
|
+
name_input := TextInput{
|
|
284
|
+
width: Fill
|
|
285
|
+
empty_text: "Your name"
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
email_input := TextInput{
|
|
289
|
+
width: Fill
|
|
290
|
+
empty_text: "your@email.com"
|
|
291
|
+
on_return: || submit_form()
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
error_view := View{
|
|
295
|
+
width: Fill height: Fit
|
|
296
|
+
flow: Down
|
|
297
|
+
on_render: ||{
|
|
298
|
+
if form_errors.name != "" {
|
|
299
|
+
Label{text: form_errors.name draw_text.color: #f44}
|
|
300
|
+
}
|
|
301
|
+
if form_errors.email != "" {
|
|
302
|
+
Label{text: form_errors.email draw_text.color: #f44}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
submit_button := Button{
|
|
308
|
+
text: "Submit"
|
|
309
|
+
on_click: || submit_form()
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
success_view := View{
|
|
313
|
+
width: Fill height: Fit
|
|
314
|
+
on_render: ||{
|
|
315
|
+
Label{text: "Submitted!" draw_text.color: #4f4}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
---
|
|
322
|
+
|
|
323
|
+
## 4. Custom Widget Event Handling
|
|
324
|
+
|
|
325
|
+
When building a custom widget in Rust, use the `Hit` enum to handle raw input events
|
|
326
|
+
and emit widget-specific actions.
|
|
327
|
+
|
|
328
|
+
```rust
|
|
329
|
+
use makepad_widgets::*;
|
|
330
|
+
|
|
331
|
+
// Define custom action
|
|
332
|
+
#[derive(Clone, Debug, DefaultNone)]
|
|
333
|
+
pub enum ColorPickerAction {
|
|
334
|
+
Changed(Vec4),
|
|
335
|
+
DragStart,
|
|
336
|
+
DragEnd,
|
|
337
|
+
None,
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
#[derive(Script, ScriptHook, Widget)]
|
|
341
|
+
pub struct ColorPicker {
|
|
342
|
+
#[deref]
|
|
343
|
+
view: View,
|
|
344
|
+
#[live]
|
|
345
|
+
draw_bg: DrawQuad,
|
|
346
|
+
#[rust]
|
|
347
|
+
current_color: Vec4,
|
|
348
|
+
#[rust]
|
|
349
|
+
dragging: bool,
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
impl Widget for ColorPicker {
|
|
353
|
+
fn handle_event(&mut self, cx: &mut Cx, event: &Event, _scope: &mut Scope) {
|
|
354
|
+
let uid = self.widget_uid();
|
|
355
|
+
|
|
356
|
+
match event.hits(cx, self.draw_bg.area()) {
|
|
357
|
+
Hit::FingerDown(fd) => {
|
|
358
|
+
self.dragging = true;
|
|
359
|
+
cx.set_key_focus(self.draw_bg.area());
|
|
360
|
+
self.update_color_from_pos(fd.rel);
|
|
361
|
+
cx.widget_action(uid, ColorPickerAction::DragStart);
|
|
362
|
+
cx.widget_action(uid, ColorPickerAction::Changed(self.current_color));
|
|
363
|
+
self.draw_bg.redraw(cx);
|
|
364
|
+
}
|
|
365
|
+
Hit::FingerMove(fm) => {
|
|
366
|
+
if self.dragging {
|
|
367
|
+
self.update_color_from_pos(fm.rel);
|
|
368
|
+
cx.widget_action(uid, ColorPickerAction::Changed(self.current_color));
|
|
369
|
+
self.draw_bg.redraw(cx);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
Hit::FingerUp(_fu) => {
|
|
373
|
+
if self.dragging {
|
|
374
|
+
self.dragging = false;
|
|
375
|
+
cx.widget_action(uid, ColorPickerAction::DragEnd);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
Hit::FingerHoverIn(_) => {
|
|
379
|
+
cx.set_cursor(MouseCursor::Crosshair);
|
|
380
|
+
}
|
|
381
|
+
Hit::FingerHoverOut(_) => {
|
|
382
|
+
cx.set_cursor(MouseCursor::Default);
|
|
383
|
+
}
|
|
384
|
+
_ => {}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
fn draw_walk(&mut self, cx: &mut Cx2d, _scope: &mut Scope, walk: Walk) -> DrawStep {
|
|
389
|
+
self.draw_bg.begin(cx, walk, Layout::default());
|
|
390
|
+
self.draw_bg.end(cx);
|
|
391
|
+
DrawStep::done()
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
impl ColorPicker {
|
|
396
|
+
fn update_color_from_pos(&mut self, pos: DVec2) {
|
|
397
|
+
// Convert position to color...
|
|
398
|
+
self.current_color = vec4(pos.x as f32, pos.y as f32, 0.5, 1.0);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Consuming the custom action from the App
|
|
403
|
+
impl MatchEvent for App {
|
|
404
|
+
fn handle_actions(&mut self, cx: &mut Cx, actions: &Actions) {
|
|
405
|
+
if let Some(item) = actions.find_widget_action(
|
|
406
|
+
self.ui.widget(cx, ids!(color_picker)).widget_uid()
|
|
407
|
+
) {
|
|
408
|
+
if let ColorPickerAction::Changed(color) = item.cast() {
|
|
409
|
+
// Use the selected color
|
|
410
|
+
script_eval!(cx, {
|
|
411
|
+
mod.state.selected_color = #(color)
|
|
412
|
+
ui.preview.render()
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
---
|
|
421
|
+
|
|
422
|
+
## 5. Keyboard Shortcut Handling
|
|
423
|
+
|
|
424
|
+
Global keyboard shortcuts are handled through `handle_key_down` on the `MatchEvent` trait.
|
|
425
|
+
|
|
426
|
+
```rust
|
|
427
|
+
impl MatchEvent for App {
|
|
428
|
+
fn handle_key_down(&mut self, cx: &mut Cx, event: &KeyEvent) {
|
|
429
|
+
// Ctrl+S / Cmd+S -- Save
|
|
430
|
+
if event.modifiers.control || event.modifiers.logo {
|
|
431
|
+
match event.key_code {
|
|
432
|
+
KeyCode::KeyS => {
|
|
433
|
+
self.save_document(cx);
|
|
434
|
+
}
|
|
435
|
+
KeyCode::KeyZ => {
|
|
436
|
+
if event.modifiers.shift {
|
|
437
|
+
self.redo(cx);
|
|
438
|
+
} else {
|
|
439
|
+
self.undo(cx);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
KeyCode::KeyN => {
|
|
443
|
+
self.new_document(cx);
|
|
444
|
+
}
|
|
445
|
+
_ => {}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// Plain keys
|
|
450
|
+
match event.key_code {
|
|
451
|
+
KeyCode::Escape => {
|
|
452
|
+
self.cancel_current_operation(cx);
|
|
453
|
+
}
|
|
454
|
+
KeyCode::F5 => {
|
|
455
|
+
self.refresh(cx);
|
|
456
|
+
}
|
|
457
|
+
_ => {}
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
### Widget-level key handling (inside a custom widget)
|
|
464
|
+
|
|
465
|
+
```rust
|
|
466
|
+
impl Widget for MyEditor {
|
|
467
|
+
fn handle_event(&mut self, cx: &mut Cx, event: &Event, _scope: &mut Scope) {
|
|
468
|
+
match event.hits(cx, self.draw_bg.area()) {
|
|
469
|
+
Hit::KeyDown(ke) => {
|
|
470
|
+
match ke.key_code {
|
|
471
|
+
KeyCode::ReturnKey => {
|
|
472
|
+
let uid = self.widget_uid();
|
|
473
|
+
cx.widget_action(uid, EditorAction::Submit);
|
|
474
|
+
}
|
|
475
|
+
KeyCode::Escape => {
|
|
476
|
+
let uid = self.widget_uid();
|
|
477
|
+
cx.widget_action(uid, EditorAction::Cancel);
|
|
478
|
+
}
|
|
479
|
+
KeyCode::Tab => {
|
|
480
|
+
// Insert tab or move focus
|
|
481
|
+
}
|
|
482
|
+
_ => {}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
Hit::KeyFocus(_) => {
|
|
486
|
+
self.animator_play(cx, ids!(focus.on));
|
|
487
|
+
}
|
|
488
|
+
Hit::KeyFocusLost(_) => {
|
|
489
|
+
self.animator_play(cx, ids!(focus.off));
|
|
490
|
+
}
|
|
491
|
+
_ => {}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
---
|
|
498
|
+
|
|
499
|
+
## 6. Timer and Animation Events
|
|
500
|
+
|
|
501
|
+
### Polling timer (from Git example)
|
|
502
|
+
|
|
503
|
+
**Source**: `examples/git/src/app.rs`
|
|
504
|
+
|
|
505
|
+
```rust
|
|
506
|
+
#[derive(Script, ScriptHook)]
|
|
507
|
+
pub struct App {
|
|
508
|
+
#[live]
|
|
509
|
+
ui: WidgetRef,
|
|
510
|
+
#[rust]
|
|
511
|
+
poll_timer: Timer,
|
|
512
|
+
#[rust]
|
|
513
|
+
active_request_id: Option<LiveId>,
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
impl MatchEvent for App {
|
|
517
|
+
fn handle_startup(&mut self, cx: &mut Cx) {
|
|
518
|
+
// Start polling every 30 seconds
|
|
519
|
+
self.poll_timer = cx.start_interval(30.0);
|
|
520
|
+
self.poll_remote(cx);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
fn handle_timer(&mut self, cx: &mut Cx, event: &TimerEvent) {
|
|
524
|
+
if self.poll_timer.is_timer(event).is_some() {
|
|
525
|
+
self.poll_remote(cx);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
### One-shot delay timer
|
|
532
|
+
|
|
533
|
+
```rust
|
|
534
|
+
#[derive(Script, ScriptHook)]
|
|
535
|
+
pub struct App {
|
|
536
|
+
#[live]
|
|
537
|
+
ui: WidgetRef,
|
|
538
|
+
#[rust]
|
|
539
|
+
debounce_timer: Timer,
|
|
540
|
+
#[rust]
|
|
541
|
+
pending_search: String,
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
impl MatchEvent for App {
|
|
545
|
+
fn handle_actions(&mut self, cx: &mut Cx, actions: &Actions) {
|
|
546
|
+
// Debounce text input -- wait 300ms after last keystroke
|
|
547
|
+
if let Some(text) = self.ui.text_input(cx, ids!(search_input)).changed(actions) {
|
|
548
|
+
self.pending_search = text;
|
|
549
|
+
// Cancel previous timer, start new one
|
|
550
|
+
cx.stop_timer(self.debounce_timer);
|
|
551
|
+
self.debounce_timer = cx.start_timeout(0.3);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
fn handle_timer(&mut self, cx: &mut Cx, event: &TimerEvent) {
|
|
556
|
+
if self.debounce_timer.is_timer(event).is_some() {
|
|
557
|
+
// Timer fired -- execute the search
|
|
558
|
+
self.execute_search(cx, &self.pending_search.clone());
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
### NextFrame animation
|
|
565
|
+
|
|
566
|
+
For smooth per-frame animations, use `handle_next_frame`:
|
|
567
|
+
|
|
568
|
+
```rust
|
|
569
|
+
#[derive(Script, ScriptHook)]
|
|
570
|
+
pub struct App {
|
|
571
|
+
#[live]
|
|
572
|
+
ui: WidgetRef,
|
|
573
|
+
#[rust]
|
|
574
|
+
animating: bool,
|
|
575
|
+
#[rust]
|
|
576
|
+
animation_progress: f64,
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
impl MatchEvent for App {
|
|
580
|
+
fn handle_actions(&mut self, cx: &mut Cx, actions: &Actions) {
|
|
581
|
+
if self.ui.button(cx, ids!(animate_button)).clicked(actions) {
|
|
582
|
+
self.animating = true;
|
|
583
|
+
self.animation_progress = 0.0;
|
|
584
|
+
cx.request_next_frame();
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
fn handle_next_frame(&mut self, cx: &mut Cx, _event: &NextFrameEvent) {
|
|
589
|
+
if self.animating {
|
|
590
|
+
self.animation_progress += 0.016; // ~60fps
|
|
591
|
+
if self.animation_progress >= 1.0 {
|
|
592
|
+
self.animation_progress = 1.0;
|
|
593
|
+
self.animating = false;
|
|
594
|
+
} else {
|
|
595
|
+
cx.request_next_frame(); // Request another frame
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
let progress = self.animation_progress;
|
|
599
|
+
script_eval!(cx, {
|
|
600
|
+
mod.state.progress = #(progress)
|
|
601
|
+
ui.animated_view.render()
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
---
|
|
609
|
+
|
|
610
|
+
## 7. Network Response Handling (Full Pattern)
|
|
611
|
+
|
|
612
|
+
Complete pattern showing HTTP requests initiated from Rust, with response handling
|
|
613
|
+
that updates Splash UI state.
|
|
614
|
+
|
|
615
|
+
**Source**: adapted from `examples/git/src/app.rs`
|
|
616
|
+
|
|
617
|
+
```rust
|
|
618
|
+
use makepad_widgets::*;
|
|
619
|
+
|
|
620
|
+
app_main!(App);
|
|
621
|
+
|
|
622
|
+
script_mod! {
|
|
623
|
+
use mod.prelude.widgets.*
|
|
624
|
+
|
|
625
|
+
let state = {
|
|
626
|
+
loading: false
|
|
627
|
+
data: ""
|
|
628
|
+
error: ""
|
|
629
|
+
}
|
|
630
|
+
mod.state = state
|
|
631
|
+
|
|
632
|
+
startup() do #(App::script_component(vm)){
|
|
633
|
+
ui: Root{
|
|
634
|
+
on_startup: ||{
|
|
635
|
+
ui.content_view.render()
|
|
636
|
+
}
|
|
637
|
+
main_window := Window{
|
|
638
|
+
window.inner_size: vec2(600, 400)
|
|
639
|
+
body +: {
|
|
640
|
+
width: Fill height: Fill
|
|
641
|
+
flow: Down spacing: 12
|
|
642
|
+
padding: 20
|
|
643
|
+
|
|
644
|
+
fetch_button := Button{
|
|
645
|
+
text: "Fetch Data"
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
content_view := View{
|
|
649
|
+
width: Fill height: Fill
|
|
650
|
+
flow: Down
|
|
651
|
+
on_render: ||{
|
|
652
|
+
if state.loading {
|
|
653
|
+
Label{text: "Loading..." draw_text.color: #888}
|
|
654
|
+
} else if state.error != "" {
|
|
655
|
+
Label{text: "Error: " + state.error draw_text.color: #f44}
|
|
656
|
+
} else if state.data != "" {
|
|
657
|
+
Label{text: state.data}
|
|
658
|
+
} else {
|
|
659
|
+
Label{text: "Click Fetch to load data" draw_text.color: #888}
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
impl App {
|
|
670
|
+
fn run(vm: &mut ScriptVm) -> Self {
|
|
671
|
+
crate::makepad_widgets::script_mod(vm);
|
|
672
|
+
App::from_script_mod(vm, self::script_mod)
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
#[derive(Script, ScriptHook)]
|
|
677
|
+
pub struct App {
|
|
678
|
+
#[live]
|
|
679
|
+
ui: WidgetRef,
|
|
680
|
+
#[rust]
|
|
681
|
+
active_request: Option<LiveId>,
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
impl MatchEvent for App {
|
|
685
|
+
fn handle_actions(&mut self, cx: &mut Cx, actions: &Actions) {
|
|
686
|
+
if self.ui.button(cx, ids!(fetch_button)).clicked(actions) {
|
|
687
|
+
// Show loading state
|
|
688
|
+
script_eval!(cx, {
|
|
689
|
+
mod.state.loading = true
|
|
690
|
+
mod.state.error = ""
|
|
691
|
+
ui.content_view.render()
|
|
692
|
+
});
|
|
693
|
+
|
|
694
|
+
// Fire HTTP request
|
|
695
|
+
let request_id = live_id!(fetch_data);
|
|
696
|
+
let req = HttpRequest::new(
|
|
697
|
+
"https://api.example.com/data",
|
|
698
|
+
HttpMethod::Get,
|
|
699
|
+
);
|
|
700
|
+
cx.http_request(request_id, req);
|
|
701
|
+
self.active_request = Some(request_id);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
fn handle_http_response(
|
|
706
|
+
&mut self,
|
|
707
|
+
cx: &mut Cx,
|
|
708
|
+
request_id: LiveId,
|
|
709
|
+
response: &HttpResponse,
|
|
710
|
+
) {
|
|
711
|
+
if Some(request_id) != self.active_request {
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
self.active_request = None;
|
|
715
|
+
|
|
716
|
+
let body = String::from_utf8_lossy(&response.body).to_string();
|
|
717
|
+
script_eval!(cx, {
|
|
718
|
+
mod.state.loading = false
|
|
719
|
+
mod.state.data = #(body)
|
|
720
|
+
ui.content_view.render()
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
fn handle_http_request_error(
|
|
725
|
+
&mut self,
|
|
726
|
+
cx: &mut Cx,
|
|
727
|
+
request_id: LiveId,
|
|
728
|
+
err: &HttpError,
|
|
729
|
+
) {
|
|
730
|
+
if Some(request_id) != self.active_request {
|
|
731
|
+
return;
|
|
732
|
+
}
|
|
733
|
+
self.active_request = None;
|
|
734
|
+
|
|
735
|
+
let message = err.message.clone();
|
|
736
|
+
script_eval!(cx, {
|
|
737
|
+
mod.state.loading = false
|
|
738
|
+
mod.state.error = #(message)
|
|
739
|
+
ui.content_view.render()
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
fn handle_http_progress(
|
|
744
|
+
&mut self,
|
|
745
|
+
cx: &mut Cx,
|
|
746
|
+
request_id: LiveId,
|
|
747
|
+
progress: &HttpProgress,
|
|
748
|
+
) {
|
|
749
|
+
if Some(request_id) != self.active_request {
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
let loaded = progress.loaded;
|
|
753
|
+
let total = progress.total;
|
|
754
|
+
log!("Download progress: {} / {} bytes", loaded, total);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
impl AppMain for App {
|
|
759
|
+
fn handle_event(&mut self, cx: &mut Cx, event: &Event) {
|
|
760
|
+
self.match_event(cx, event);
|
|
761
|
+
self.ui.handle_event(cx, event, &mut Scope::empty());
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
```
|
|
765
|
+
|
|
766
|
+
---
|
|
767
|
+
|
|
768
|
+
## Summary of Key Rules
|
|
769
|
+
|
|
770
|
+
1. **Splash handlers** (`on_click`, `on_render`, `on_return`, `on_startup`) are
|
|
771
|
+
written inline in `script_mod!` and handle UI-level interactions.
|
|
772
|
+
|
|
773
|
+
2. **Rust handlers** (`handle_actions`, `handle_timer`, `handle_http_response`, etc.)
|
|
774
|
+
handle business logic, I/O, and anything that needs typed Rust code.
|
|
775
|
+
|
|
776
|
+
3. **Bridge macros**: Use `script_eval!(cx, { ... })` to push state changes from
|
|
777
|
+
Rust into Splash and trigger re-renders.
|
|
778
|
+
|
|
779
|
+
4. **Widget actions**: Access widgets with `self.ui.widget_type(cx, ids!(name))`
|
|
780
|
+
and query actions with `.clicked(actions)`, `.changed(actions)`, etc.
|
|
781
|
+
|
|
782
|
+
5. **on_render is not automatic**: You must call `ui.widget_name.render()` to
|
|
783
|
+
trigger a view's `on_render` handler. Set `new_batch: true` to clear old content.
|
|
784
|
+
|
|
785
|
+
6. **Closures capture loop variables**: In `for i, item in list { ... }`, event
|
|
786
|
+
handlers like `on_click: || delete(i)` correctly capture the current `i`.
|
|
787
|
+
|
|
788
|
+
7. **AppMain::handle_event must call both**:
|
|
789
|
+
- `self.match_event(cx, event)` -- dispatches to MatchEvent handlers
|
|
790
|
+
- `self.ui.handle_event(cx, event, &mut Scope::empty())` -- propagates to widgets
|
|
791
|
+
|
|
792
|
+
8. **`fn tick()` auto-timer**: Define `fn tick()` in Splash code and the Splash widget
|
|
793
|
+
automatically starts a 1-second interval timer that calls it. No setup needed.
|
|
794
|
+
|
|
795
|
+
9. **`fn on_audio()` callback**: Canvas injects audio state globals (`_playing`, `_pos`,
|
|
796
|
+
`_dur`, `_amp`, `_b0`–`_b15`) and calls `fn on_audio()` ~10Hz during playback.
|
|
797
|
+
Use this for real-time audio-reactive UI updates.
|
|
798
|
+
|
|
799
|
+
10. **Splash button events don't reach `handle_actions`**: Dynamically created Splash
|
|
800
|
+
buttons (via `POST /splash`) have unstable UIDs. The `uid_map` may not rebuild
|
|
801
|
+
in time. Always use `on_click:` handlers inside Splash for button interactions.
|
|
802
|
+
The HTTP `GET /event` bridge is unreliable for this reason.
|