Makepad 2.0 Skills

repository·main·Indexed 20 days ago

https://github.com/zhanghandong/makepad-skills

A collection of 14 specialized knowledge modules for building cross-platform UI applications with the Makepad 2.0 framework. Covers core mental models, Elm Architecture, GPU rendering, DSL syntax, layout systems, widget catalogs, event handling, shaders, and a detailed Animator system for driving instance shader variables via groups, states, and ease functions.

Tokens
123.5K
Snippets
314
Records
429
Agent score
72%

What's inside makepad-skills

  1. Overview of Makepad 2.0 Skills

    main

    The makepad-skills repository provides 14 specialized modules designed to help developers build cross-platform UI applications with Makepad 2.0.

    Recommended Learning Path: Start with makepad-2.0-design-judgment. This is the entry point that covers the core mental models (Elm Architecture, Presentational/Container, and GPU rendering). Once the design fundamentals are understood, you can co-load specific technical skills.

    Available Skills:

    • makepad-2.0-design-judgment: Core mental models (Elm Architecture, GPU rendering).
    • makepad-2.0-app-structure: app_main!, ScriptVm, Cargo setup, and hot reload.
    • makepad-2.0-dsl: DSL syntax, script_mod!, colon syntax, and mod.widgets.
    • makepad-2.0-layout: Layout system (Flow, Fill, Fit, Inset, alignment).
    • makepad-2.0-widgets: Widget catalog (View, Button, Label, TextInput, etc.).
    • makepad-2.0-events: Event handling (on_click, on_render, ids!).
    • makepad-2.0-animation: Animator, states, and easing functions.
    • makepad-2.0-shaders: Shader system (draw_bg, Sdf2d, DrawQuad).
    • makepad-2.0-splash: Splash scripting and streaming evaluation.
    • makepad-2.0-theme: Theme system (mod.themes, colors, fonts).
    • makepad-2.0-vector: Vector graphics (SVG paths, gradients, DropShadow).
    • makepad-2.0-performance: Optimization (GC, draw batching, ViewOptimize).
    • makepad-2.0-troubleshooting: Debugging and FAQ.
    • makepad-2.0-migration: Guide for moving from Makepad 1.x to 2.0.
  2. What is the Makepad 2.0 Splash Scripting Language?

    main
    Splash is the core runtime UI scripting language for Makepad 2.0 (released February 12, 2026). It replaces the compile-time live_design! macro system with a runtime script_mod! macro. This shift enables features like hot reload, streaming evaluation (optimized for AI/LLM code generation), and real-time UI updates without full recompilation.
  3. Use the Filler widget to push siblings apart

    main

    Filler{} is a spacer widget equivalent to View{width: Fill height: Fill}. It is used to occupy remaining space and push siblings away from each other.

    Critical Rule: Only use Filler{} between siblings that have width: Fit.

    Avoid: Do NOT use Filler{} next to a sibling that has width: Fill. Both the Filler{} and the width: Fill sibling will compete for the remaining space, splitting it 50/50, which often results in text being clipped.

    If you need a width: Fill element to push other elements to the edge, simply use the width: Fill element itself instead of a Filler{}.

    // CORRECT: Filler between Fit siblings
    View{
        width: Fill height: Fit
        flow: Right
        align: Align{y: 0.5}
        Label{text: "Left side"}
        Filler{}
        Label{text: "Right side"}
    }
    
    // WRONG: Filler next to a Fill sibling -- text gets clipped
    View{
        width: Fill height: Fit
        flow: Right
        Label{width: Fill text: "This gets clipped to half width"}
        Filler{}
        Label{text: "Tag"}
    }
  4. Best practices for themed UI development

    main

    Follow these rules to build robust, accessible, and consistent UIs with Makepad 2.0:

    1. Always use theme.* for colors: Never hardcode hex values like #ff0000 when theme.color_error is available. This enables theme switching and accessibility.
    2. Use theme fonts for typography: Prefer theme.font_bold{font_size: theme.font_size_2} over manual font family specifications.
    3. Use theme spacing for layout: Use theme.mspace_* and theme.space_* to maintain a harmonious rhythm; avoid magic numbers.
    4. Use {} syntax for overrides: Use theme.font_bold{font_size: 20} to extend a theme value (keeping the bold style) rather than replacing it.
    5. Use +: for partial overrides: Use the merge syntax draw_text +: {text_style +: {font_size: theme.font_size_3}} to change a single nested property.
    6. Select correct text variables:
      • theme.color_label_inner for primary UI text.
      • theme.color_label_inner_inactive for secondary/muted text.
      • theme.color_text for general content.
      • theme.color_text_placeholder for placeholders.
    7. Use state-aware color variants: Use suffixes like _hover, _focus, and _down for interactive widgets.
    8. Multiply for opacity: Use theme.color_label_inner_inactive * 0.8 for subtle variations.
    9. Order of operations: Always set the theme between theme_mod() and widgets_mod() calls.
    10. Simple apps: Use crate::makepad_widgets::script_mod(vm) to load everything with the default dark theme in one call.
  5. Override child properties in Splash using :=

    main

    In Splash, using : creates a static property that cannot be overridden per-instance. To make a child addressable and overridable, you must use := to create a named/dynamic child.

    Fix: Use := for any child you want to reference or override later.

    Important: Every container in the path from the root to the child must also be named using :=. If any parent in the hierarchy is an anonymous container, the child is unreachable via dot-notation.

    Example of correct pathing:

    let Item = View{
        height: Fit
        texts := View{
            flow: Down
            label := Label{text: "default"}
        }
    }
    Item{texts.label.text: "new"} // Works! Full dot-path through named containers
    // WRONG -- static child, override fails silently
    let Card = View{
        height: Fit
        title: Label{text: "default"}
    }
    Card{title.text: "new text"}  // Fails!
    
    // CORRECT -- named child with :=, override works
    let Card = View{
        height: Fit
        title := Label{text: "default"}
    }
    Card{title.text: "new text"}  // Works!
  6. Implement the Presentational / Container Split pattern

    main

    To separate visual logic from business logic, split widgets into two types:

    Presentational Widget

    Use this for widgets that only handle 'look and feel'.

    • Uses #[live] fields only (configured via DSL).
    • Implements #[deref] view: View to delegate rendering.
    • Contains no #[rust] business state.
    • Emits generic actions (e.g., clicked, changed).

    Container Widget

    Use this for widgets that manage data and business logic.

    • Uses #[rust] fields for business state.
    • Handles actions received from presentational children.
    • Calls redraw(cx) when state changes.
    • May use Cx::post_action for cross-component communication.
  7. Animate properties with Tween{}

    main

    Use Tween{} to animate individual shape properties such as fill, stroke, d (path data), x, y, r, etc.

    Tween Properties

    PropertyTypeDescription
    fromvalueStart value
    tovalueEnd value
    valuesarrayArray of keyframe values (alternative to from/to)
    durf32Duration in seconds
    beginf32Start delay in seconds
    loop_bool or numbertrue for indefinite, or a number for repeat count
    calcstring"linear" (default), "discrete", "paced", "spline"
    fill_modestring"remove" (default) or "freeze"
    // Animated path morphing
    Path{d: Tween{
        dur: 2.0 loop_: true
        values: ["M 10 80 Q 50 10 100 80" "M 10 80 Q 50 150 100 80"]
    } fill: #f0f}
    
    // Animated fill color
    Circle{cx: 50 cy: 50 r: 30
        fill: Tween{dur: 1.5 loop_: true from: #ff0000 to: #0000ff}
    }
  8. Pattern: Mark large UI trees as static

    main

    For large, stable UI tree definitions (like a Dock with many tabs), mark the root of the tree as static immediately after definition. This prevents the GC from traversing the tree and improves performance. It is recommended to run mod.gc.run() immediately after marking to clean up temporary objects used during construction.

    // Define a large widget tree
    let AppDock = Dock{
        // ... tabs, splitters, content templates ...
        TabEditor := TabEditor{}
        TabFileTree := TabFileTree{}
        TabSettings := TabSettings{}
    }
    
    // Mark the entire tree as static - it will never be GC'd
    mod.gc.set_static(AppDock)
    
    // Run GC immediately to clean up any temporaries from tree construction
    mod.gc.run()
    
    // Now start the app
    startup() do #(App::script_component(vm)){
        ui: Root{
            main_window := Window{
                body +: {
                    // ... use AppDock here ...
                }
            }
        }
    }
  9. How the Makepad 2.0 Event & Action System works

    main

    Makepad 2.0 utilizes a two-layer event system to separate UI interaction from business logic:

    1. Splash Layer: Declarative, inline event handlers written in script_mod! Splash code. These handle UI-specific interactions like on_click, on_render, on_return, and on_startup directly within the widget definitions.
    2. Rust Layer: The MatchEvent trait handles complex logic, external I/O (HTTP, timers), and platform-level events.

    Bridging the layers:

    • script_eval!(cx, { ... }): Executes Splash code from Rust (e.g., to update state or trigger renders).
    • script_apply_eval!(cx, widget_ref, { ... }): Patches widget properties from Rust at runtime.
    // Example of the two layers interacting via script_eval!
    // In Rust:
    script_eval!(cx, {
        mod.state.counter += 1
        ui.main_view.render()
    });
  10. Access widgets from Rust using the Widget Reference Pattern

    main

    To interact with widgets defined in your script_mod!, follow these two steps:

    1. In Splash/Script: Assign a name to the widget using the := operator.
    2. In Rust: Use the ids! macro to reference that name through the ui field (which should be a WidgetRef).

    Example

    Script definition:

    my_button := Button{text: "Click"}
    my_input := TextInput{empty_text: "Type here"}

    Rust usage:

    // Check if a button was clicked
    self.ui.button(cx, ids!(my_button)).clicked(actions)
    
    // Get text from an input
    self.ui.text_input(ids!(my_input)).text()
    // Named widget in Splash
    my_button := Button{text: "Click"}
    my_input := TextInput{empty_text: "Type here"}
    
    // Access in Rust
    self.ui.button(cx, ids!(my_button)).clicked(actions)
    self.ui.text_input(ids!(my_input)).text()