- skills/README.md: provenance + index - SKILL.md: merged design-engineering doctrine, Makepad-native (animator, script_mod!) - EASING.md: CSS cubic-bezier -> Ease.Bezier translation verified against dev@b41e740 - RECIPES.md: press scale 0.96, toast enter/exit, sheet settle w/ velocity handoff, stagger, reduced motion - PORTS-AUDIT.md: Before/After audit of rider/koboyo/insurance with file:line refs
3.6 KiB
Easing translation: CSS / Motion → Makepad Ease
Verified against widgets/src/animator.rs on the dev branch (b41e740).
The key fact
Makepad's Ease::Bezier {cp0, cp1, cp2, cp3} is CSS cubic-bezier(x1, y1, x2, y2):
cp0 = x1, cp1 = y1, cp2 = x2, cp3 = y2 (confirmed in Ease::map — the x-solve uses
3.0 * cp0, the y-evaluation uses 3.0 * cp1). Every named curve Emil ships translates exactly.
Emil's required custom curves
| Name | CSS | Makepad DSL |
|---|---|---|
| Strong ease-out (UI default) | cubic-bezier(0.23, 1, 0.32, 1) |
ease: Ease.Bezier {cp0: 0.23, cp1: 1.0, cp2: 0.32, cp3: 1.0} |
| Strong ease-in-out (on-screen movement) | cubic-bezier(0.77, 0, 0.175, 1) |
ease: Ease.Bezier {cp0: 0.77, cp1: 0.0, cp2: 0.175, cp3: 1.0} |
| iOS drawer/sheet curve (Ionic/Vaul) | cubic-bezier(0.32, 0.72, 0, 1) |
ease: Ease.Bezier {cp0: 0.32, cp1: 0.72, cp2: 0.0, cp3: 1.0} |
| Jakub's polish curve | cubic-bezier(0.2, 0, 0, 1) |
ease: Ease.Bezier {cp0: 0.2, cp1: 0.0, cp2: 0.0, cp3: 1.0} |
Built-in equivalents (cheaper to type, slightly weaker)
| Intent | CSS-ish | Makepad built-in | Strength |
|---|---|---|---|
| Enter/exit default | ease-out |
OutQuad → OutCubic → OutQuart → OutQuint → OutExp |
pick further right for more punch |
| Movement/morph | ease-in-out |
InOutQuad → InOutCubic → InOutQuart |
|
| Hover/color | ease |
OutQuad (upstream check_box.rs uses this) |
|
| Constant motion | linear |
Linear |
|
| Overshoot (rare, gesture-born only) | spring w/ bounce | OutBack (subtle) — avoid OutElastic/OutBounce in product UI |
Banned in UI: InQuad/InCubic/InQuart/InQuint/InExp/InSine/InCirc/InElastic/InBack/InBounce
as the ease of anything entering, exiting, or responding to the user. (In* is only legitimate
for an element accelerating away as part of a choreographed hand-off, e.g. upstream
slide_panel.rs uses InQuad for the outgoing panel.)
The spring stand-in: ExpDecay
Makepad has no parametric spring in the animator, but Ease.ExpDecay {d1, d2} is an
exponential-decay settle that reads like a critically-damped spring and is what upstream
widgets ship for "alive" motion:
| Upstream use | Values |
|---|---|
file_tree.rs open/close |
Ease.ExpDecay {d1: 0.80, d2: 0.97} / {d1: 0.82, d2: 0.95} |
fold_button.rs / fold_header.rs |
ExpDecay {d1: 0.96, d2: 0.97} open, {d1: 0.98, d2: 0.95} close |
| Default | {d1: 0.82, d2: 0.97} |
Rules of thumb: lower d1 = faster initial move (snappier); d2 shapes the tail. Stay inside
d1 0.80–0.98, d2 0.95–0.97 unless you can defend the value. No overshoot ever comes out of
ExpDecay — for gesture-released momentum with a real bounce use
scroll_motion::rubber_band_bounce driven by NextFrame (see RECIPES §4).
Duration reference (Play::Forward {duration} is seconds)
| Element | Seconds |
|---|---|
| Press feedback release | 0.1–0.16 |
| Hover | 0.0–0.1 |
| Tooltip/popover | 0.125–0.2 |
| Dropdown/select/side drawer | 0.15–0.25 |
| Modal/bottom sheet/toast | 0.2–0.5 |
| UI ceiling | 0.3 (sheets/modals may reach 0.5) |
Play variants: Forward {duration}, Snap (instant — use for press-down),
Reverse, Loop {duration, end} (spinners: keep them fast — a faster spinner makes the same
load feel shorter), ReverseLoop, BounceLoop.
Keyframes inside a state: timeline(t0 v0 t1 v1 …) / [{time: 0.0, value: 0.0}, …] — reserve
for one-shot choreography (the CSS-keyframes analog); plain state targets retarget smoothly and
are what interactive toggles should use.