add motion to switch
This commit is contained in:
+10
-8
@@ -91,6 +91,7 @@ One-way, no cycles. Nothing depends on `gpui-component`, `gpui-kit`, `gpuikit`,
|
||||
- **Deliberate divergence from base:** our `Link` opens `href` through the platform by default (`cx.open_url`) — native feel is the product — with `on_click` as an override for apps that route internally. Base never opens URLs; we do unless told otherwise.
|
||||
- Each component has a named native reference per platform (NSButton / WinUI Button / Adwaita / Breeze) recorded on its gallery page; "native feel" is reviewed against those references.
|
||||
- Naming seams per AGENTS.md: `is_`/`has_` readers, `with_` builders, `set_` mutation, `on_` callbacks, `State`/`Delegate` suffixes, stable domain-derived `ElementId`s.
|
||||
- **Motion is inline and per component (D21).** Each component animates with gpui's own primitives (`with_animation`, `with_spring`, the gpui easings, `with_max_fps`) directly in its own file, a few lines at most, using theme durations where they're shared (`Motion.overlay`). No shared motion module, no ghost panels, no hover-wash plumbing; overlay exit animations and hover washes are dropped until a genuinely simple approach exists.
|
||||
|
||||
### 4.4 Icons (`crates/icons`)
|
||||
|
||||
@@ -153,7 +154,7 @@ The set stays role-minimal and grows one concept at a time, driven by the unit t
|
||||
- ~~**Overlay scrim + shadow/elevation tokens**~~ — ✅ landed with unit 11 (2026-10-29): `colors.overlay` (translucent-black scrim, denser in dark; TOML-overridable; no platform states one), `Radius.overlay` (10 pt), `Elevations.popover`/`dialog` (transcribed NSPopover-class shadows, builder → `Vec<BoxShadow>`), and `Motion.overlay` (200 ms enter transition). Popover consumes elevation + motion; the scrim waits for Dialog.
|
||||
- ~~**Scrollbar thumb token + metrics**~~ — ✅ landed with the ScrollArea unit (2026-10-29): `colors.scrollbar_thumb` (translucent over content: black in light, light gray in dark; TOML-overridable) and `Theme.scrollbar` (`width` 8 pt, `inset` 2 pt, `min_thumb` 24 pt).
|
||||
- ~~**`success` / `warning` roles**~~ — ✅ landed with Toast (2026-10-29): `colors.success`/`warning` (+ foregrounds; light and dark variants; TOML-overridable; no platform states them) and derived `Ramps.success`/`warning` via the OKLCH engine, matching the destructive treatment.
|
||||
- ~~**Motion tokens**~~ — attempted (unit 14, 2026-10-30) and reverted the same day: the working set (`Curve` + CSS cubic-bezier, `Springs` presets, `[motion]` TOML overrides, an ungated `motion` module with an exit-lifecycle state machine and spring-blended hover washes, exit ghosts on all three overlays, a spring Switch thumb, a store-held toast exiting list) judged too complex for the value, and the motion itself not good enough. The motion system goes back to the drawing board — see the unit 14 row (§6) for what was tried. Two cheap pieces remain worth taking regardless of the rethink: forwarding `reduce_motion` to `cx.set_reduce_motion` in `reapply` (the OS flag is still read but ignored — every animation shipped so far disobeys Reduce Motion), and a 30 fps cap on Progress's indeterminate repeat.
|
||||
- **Motion tokens** — after the reverted framework attempt (2026-10-30), motion stays minimal: `Motion.overlay` (200 ms) is the only token. Components reach for gpui's own easings and `SpringConfig` inline (D21); a token is added only when a third component shares the same value (D16). TOML `[motion]` overrides and per-platform motion presets wait for a real need. ~~The reduce-motion forwarding fix~~ ✅ landed (pass a, 2026-10-30): `reapply` calls `cx.set_reduce_motion(resolved.accessibility.reduce_motion)`, so gpui's animation elements snap under the OS setting.
|
||||
- **`knob` / control-fill role** — only if Slider (unit 7) confirms the Switch's borrowed roles (`primary_foreground` thumb, `border` off-track) as a second consumer; until then no expansion.
|
||||
|
||||
Controls through unit 10 paint from existing roles and ramps.
|
||||
@@ -175,7 +176,7 @@ Every target supplies one accent color (or a stated ramp). Where the platform st
|
||||
|
||||
### 5.5 Native metrics
|
||||
|
||||
Native feel is half geometry. Per-platform tables for: control heights and paddings, corner radii (WinUI control vs card rounding; macOS control radii), font fallback lists resolved through `.SystemUIFont` (SF Pro / Segoe UI Variable / Adwaita Sans / Noto Sans — no fonts bundled), focus-ring policy (macOS keyboard ring, WinUI two-layer ring, GTK outline), scrollbar style (overlay vs inline), popup elevation and menu metrics. Motion presets are metrics too: each platform states its curves and durations (macOS tuned to NSPopover/NSMenu transitions, Windows the Fluent decelerate cubic-bezier, GNOME/KDE shell timings) — the token shape is open again after the unit 14 revert, and Phase F sources fill it once the rethink lands. Values are transcribed from each toolkit's reference metrics and verified with side-by-side screenshots (macOS in Phase B, the rest in Phase F).
|
||||
Native feel is half geometry. Per-platform tables for: control heights and paddings, corner radii (WinUI control vs card rounding; macOS control radii), font fallback lists resolved through `.SystemUIFont` (SF Pro / Segoe UI Variable / Adwaita Sans / Noto Sans — no fonts bundled), focus-ring policy (macOS keyboard ring, WinUI two-layer ring, GTK outline), scrollbar style (overlay vs inline), popup elevation and menu metrics. Motion stays per-component timing on gpui's own primitives; when a platform source needs to state it (macOS NSPopover-class timing, Fluent's decelerate curve, GNOME/KDE shell timings), it states `Motion.overlay` first and further tokens arrive per the role-minimal rule. Values are transcribed from each toolkit's reference metrics and verified with side-by-side screenshots (macOS in Phase B, the rest in Phase F).
|
||||
|
||||
### 5.6 Custom themes
|
||||
|
||||
@@ -214,12 +215,12 @@ Each row is one review unit: build it, show it in the gallery, review, then move
|
||||
| 12 | core | Menu | ✅ Landed (2026-10-29). Controlled `open`/`on_change` + `trigger`/`items` (MenuItem: label, icon, shortcut hint, disabled, destructive, checked, separator). Popover shell patterns duplicated per D16 (promote at Dialog); panel = surface + overlay radius + popover elevation, role Menu; claims focus while open; keyboard nav — arrows/home/end over selectable items (wrapping, skipping separators + disabled), enter/space activates (closes menu before the action), escape dismisses; hover selects, click activates; trigger clicks toggle (popover-owned); selection resets on each open; long menus scroll via the ScrollArea once a height is styled. Deferred: submenus, type-ahead, scroll-selected-item-into-view, real shortcut handling |
|
||||
| 11 | core | Popover | ✅ Landed (2026-10-29). First overlay. Controlled `open` + `on_change` (Rc-shared handler); `trigger`/`content` element closures; panel = surface + `Radius.overlay` + `Elevations.popover` + border, role Dialog with label; `anchored()` + `deferred()` (window-overflow flip for free); enter fade via gpui `with_animation` at `Motion.overlay` (respects reduce-motion); claims keyboard focus while open; escape and click-outside dismiss (outside clicks swallowed). No scrim — native macOS popover model; the overlay token waits for Dialog. Known v1 gaps: no exit animation, trigger aria-expanded left to the app |
|
||||
| 11+ | core | Tooltip ✅ (2026-10-29), Dialog + AlertDialog ✅ (2026-10-29), Toast ✅ (2026-10-29) | One unit each, same gate. Tooltip rides gpui's own `.tooltip()` lifecycle (hover delay, anchoring, dismissal — all framework-owned); ours is the styled view (`surface` + border + `Radius.sm`, small text) plus a `Tooltip::text` closure and a Button passthrough. Dialog + AlertDialog (one component, `alert(true)` switches semantics): scrim (`colors.overlay`, occluding) + centered panel (surface, `Radius.overlay`, `Elevations.dialog`), role Dialog/AlertDialog, focus claimed while open; modal kind closes on escape + scrim click, alert kind is inert to both (the user answers through the actions); actions footer is caller-built elements closing through owner state. Toast: `Toasts` store entity (push/dismiss/clear + auto-dismiss timers on the GPUI executor, `Toast::DURATION` 5 s, zero = sticky) + `ToastsView` deferred stack (top-right, `Radius.overlay` + popover elevation, severity icon from the new success/warning ramps, close button, `on_dismiss` windowless handler). Toast landed the deferred `success`/`warning` tokens (§5.2). Sheet dropped from the roadmap |
|
||||
| 14 | core + theme | Motion | ⏪ Attempted and reverted (2026-10-30). What was tried: theme `Motion` tokens (overlay duration + `Curve` with a CSS cubic-bezier + spring presets, `[motion]` TOML); an ungated core `motion` module (curve→easing, `ExitLifecycle` exit ghosts via keyed window state, spring hover washes); consumers — exit ghosts on Popover/Menu/Dialog (scrim fading both ways), Switch thumb on `with_spring`, Toast fade/slide + a store-held exiting list, Button + toast-close hover washes, Progress capped at 30 fps. All reverted — judged too complex for the value and not good enough to watch. The rethink starts from what the attempt proved: gpui's `with_spring`/`with_animation` are the right substrate (D21) and keyed window state CAN drive overlay exits; the cost centered on the exit-lifecycle plumbing and per-control wash machinery. Keep the plan open, redesign before reimplementing |
|
||||
| 14 | core + theme | Motion (per component) | Replaces the reverted 2026-10-30 framework attempt — no token tower, no `motion` module, no ghost panels; just gpui's own primitives used inline, one separately reviewed pass per component, each a few lines: (a) theme: ✅ landed — `reapply` forwards `reduce_motion` via `cx.set_reduce_motion`, so gpui snaps every animation under the OS setting; (b) Switch: ✅ landed — the thumb absolute-positions in the track and slides on `with_spring` (inline `SpringConfig::new(500, 35, 1)`, `Pixels` target, mounts at its target so the initial render never animates; velocity survives mid-flight retargets); (c) Toast: enter fade via `with_animation`; (d) Dialog: the scrim enters with the panel instead of snapping; (e) Progress: cap the indeterminate sweep at 30 fps. Overlay enter fades already exist from units 11/12. Exits and hover washes stay out — they were the complexity sink (§ deferred list). Each pass reviewed in the gallery on its own |
|
||||
| then | data (new) | List, Table, Tree, Tabs, Sidebar | Created on demand, same gate. |
|
||||
| then | pickers or data | Calendar/DatePicker, TimeField, ColorPicker | Decide at unit time. |
|
||||
| editor | editor (new) | Text/code editor, syntax highlighting (D12) | Same gate. |
|
||||
|
||||
Deferred until a real need: Markdown/TextView, Dock, VirtualList, window-wide text selection, a motion system (unit 14 reverted; rethink before reimplementing), charts.
|
||||
Deferred until a real need: Markdown/TextView, Dock, VirtualList, window-wide text selection, overlay exit animations and hover washes (dropped with the 2026-10-30 revert; only return if a genuinely simple approach appears), charts.
|
||||
|
||||
### 6.1 After the core units: sizing API — ✅ landed
|
||||
|
||||
@@ -255,7 +256,7 @@ Deferred until a real need: bidi/RTL; prefix/suffix adornments and a form field
|
||||
| C — controls | One review unit at a time (§6, units 1–9), each: API per §4.3 + tests + gallery page → review gate. macOS SF Symbols resolver behind the icon seam lands with the first unit that shows it matters | Per unit: activation, controlled round-trip, and disabled inertness tested; gallery page shows the live control against the theme; your approval closes the unit |
|
||||
| D — input | Wave 2 | Single-line input with cursor, selection, clipboard; NumberInput |
|
||||
| E — overlays | Wave 3 | Focus trapping and escape/dismiss semantics per overlay |
|
||||
| E+ — motion | Unit 14 (§6): Motion tokens + reduce-motion forwarding + overlay exits + Switch thumb + hover washes | ⏪ Attempted and reverted (2026-10-30); rethink pending. The reduce-motion forwarding fix and the Progress 30 fps cap stay on the table as standalone wins |
|
||||
| E+ — motion | Unit 14 (§6): per-component passes, each a few lines on gpui's own primitives — forwarding, Switch thumb, Toast enter, Dialog scrim, Progress cap | Each pass reviewed in the gallery separately; reduce-motion snaps gpui-side once the flag is forwarded |
|
||||
| F — platforms + data | `crates/data` + Windows/GNOME/KDE/Omarchy theme sources + Segoe Fluent/freedesktop icon resolvers | Side-by-side gallery screenshots per platform |
|
||||
| G — pickers + polish | Wave 5 → 0.1; start `crates/editor` if wanted | — |
|
||||
|
||||
@@ -307,7 +308,7 @@ Pure tests for resolve/overlay chain, payload parsing (fixtures), icon resolutio
|
||||
| D18 | Crate split for consumption: `core` (every-app components) → `editor`, `data`, … (advanced) → thin `gpui-native-kit` facade; one cargo feature per component, additive, module-gated |
|
||||
| D19 | System icons via semantic `IconRole`s with per-platform resolvers (SF Symbols / Segoe Fluent Icons / freedesktop) and a bundled Lucide fallback; platform glyph sets are used at runtime only, never bundled |
|
||||
| D20 | Components land one element at a time behind a review gate: API sketch → implementation → tests → gallery page → review → next. gpui-kit `base` is the API-design reference for these units; its conventions (controlled state, one activation path, semantic states, a11y labels) and selected utilities are adopted, simplified |
|
||||
| D21 | Motion is a thin layer over gpui's pinned-rev animation/spring primitives (`with_animation`, `with_spring` with velocity retention, built-in reduce-motion snapping, `max_fps`) — no parallel motion framework (bezel's Painter/Fade store and pulse clock rejected); the theme owns durations, curves (`ease_out_quint` default + CSS-spec `CubicBezier`), and spring presets; hover washes ride settled-spring idleness on controls only, data rows snap. ⏪ Under re-evaluation after the unit 14 revert (2026-10-30): the substrate still holds, the execution complexity does not — the rethink may narrow scope to overlay enters/exits only |
|
||||
| D21 | Motion is per component and inline: each component uses gpui's own animation/spring primitives (`with_animation`, `with_spring`, the gpui easings, `with_max_fps`) directly in its file; the theme owns only the durations components actually share (today `Motion.overlay`), growing per the role-minimal rule. No shared motion module, no exit ghosts, no hover-wash machinery — exits and washes stay out until a genuinely simple approach exists. Reduce motion is gpui-side: forward the OS flag once in `reapply` and every primitive snaps. Supersedes the reverted 2026-10-30 framework attempt (which proved the substrate but over-built the plumbing) |
|
||||
|
||||
Dropped from rev 3: D2, D5, D9 (port-era decisions); the review gate returns in a lighter form as D20. D10 (no icon bundling) is superseded by D19.
|
||||
|
||||
@@ -318,7 +319,8 @@ Dropped from rev 3: D2, D5, D9 (port-era decisions); the review gate returns in
|
||||
3. ~~Phase C controls~~ ✅ — units 1–9 (Button → Avatar), each through the review gate; sizing API (§6.1).
|
||||
4. ~~Phase D input~~ ✅ — single-line Input (unit 10) + the §6.2 completion pass (modifiers, undo/redo, read-only, actions + key context, caret blink, paint polish). NumberInput and multiline remain open as deferred units.
|
||||
5. ~~Phase E overlays~~ ✅ — Popover (unit 11), ScrollArea (unit 11.5), Menu (unit 12), Tooltip + Dialog/AlertDialog + Toast (unit 11+ row; Sheet dropped). Phase gate held: escape/dismiss semantics and focus behavior per overlay.
|
||||
6. ~~Unit 14 — Motion~~ ⏪ Attempted and reverted (2026-10-30) — judged too complex, and the motion not good enough; the plan and the lessons stay (§6 row 14, D21). Next: **Phase F** — `crates/data` (List, Table, Tree, Tabs, Sidebar) + Windows/GNOME/KDE/Omarchy theme sources (each states its Motion preset, §5.5, once the rethink lands) + Segoe Fluent/freedesktop icon resolvers, with side-by-side gallery screenshots per platform. NumberInput/multiline return as deferred units when needed. Cheap standalone wins to fold in anywhere: `cx.set_reduce_motion` forwarding in `reapply` (§5.2), Progress 30 fps cap.
|
||||
7. Then — Phase G: pickers, polish, → 0.1; start `crates/editor` if wanted.
|
||||
6. **Next: unit 14 — motion, per component (§6 row 14, D21)** — ~~(a) theme `reduce_motion` → `cx.set_reduce_motion` in `reapply`~~ ✅ landed; ~~(b) Switch thumb `with_spring`~~ ✅ landed. Remaining passes, each small and separately reviewed: (c) Toast enter fade; (d) Dialog scrim enters with the panel; (e) Progress 30 fps cap. No shared machinery; exits and hover washes stay deferred (§6 deferred list).
|
||||
7. Then — Phase F: `crates/data` (List, Table, Tree, Tabs, Sidebar) + Windows/GNOME/KDE/Omarchy theme sources (a source may state `Motion.overlay` when it needs platform timing, §5.5) + Segoe Fluent/freedesktop icon resolvers, with side-by-side gallery screenshots per platform. NumberInput/multiline return as deferred units when needed.
|
||||
8. Then — Phase G: pickers, polish, → 0.1; start `crates/editor` if wanted.
|
||||
|
||||
Batched polish candidates between units: scrollbar auto-hide + horizontal thumb, `Undo`/`Redo` menu events.
|
||||
|
||||
Reference in New Issue
Block a user