From c4955cca185761467cc20c396090864741101d30 Mon Sep 17 00:00:00 2001 From: Ren Amamiya Date: Fri, 30 Oct 2026 10:28:47 +0700 Subject: [PATCH] add motion to toast --- crates/core/src/button.rs | 2 -- crates/core/src/toast.rs | 12 +++++++++--- docs/PLAN.md | 4 ++-- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/crates/core/src/button.rs b/crates/core/src/button.rs index 3c792e7..dfaf681 100644 --- a/crates/core/src/button.rs +++ b/crates/core/src/button.rs @@ -194,8 +194,6 @@ impl RenderOnce for Button { .hover(move |style| style.bg(hover)) .active(move |style| style.bg(pressed)) .focus_visible(|style| { - // Paint-only keyboard ring; the width joins the native - // metrics table when that lands. style.shadow(vec![ BoxShadow::new(px(0.), px(0.), colors.ring).spread_radius(px(3.)), ]) diff --git a/crates/core/src/toast.rs b/crates/core/src/toast.rs index a01ae38..5ac31ea 100644 --- a/crates/core/src/toast.rs +++ b/crates/core/src/toast.rs @@ -3,9 +3,9 @@ use std::time::Duration; use gpui::prelude::FluentBuilder as _; use gpui::{ - App, Context, ElementId, Entity, InteractiveElement as _, IntoElement, ParentElement as _, - RenderOnce, SharedString, StatefulInteractiveElement as _, Styled as _, Window, deferred, div, - px, + Animation, AnimationExt as _, App, Context, ElementId, Entity, InteractiveElement as _, + IntoElement, ParentElement as _, RenderOnce, SharedString, StatefulInteractiveElement as _, + Styled as _, Window, deferred, div, ease_out_quint, px, }; use gpui_native_icons::{IconRole, icon}; use gpui_native_theme::ActiveTheme as _; @@ -201,6 +201,7 @@ impl RenderOnce for ToastsView { let radius = theme.radius.overlay; let shadows = theme.elevations.popover.shadows(); let icon_size = theme.sizes.small.icon_size; + let enter_duration = theme.motion.overlay; let entries: Vec = self.toasts.read(cx).entries().to_vec(); @@ -296,6 +297,11 @@ impl RenderOnce for ToastsView { }); }), ) + .with_animation( + ("toast-enter", entry.id), + Animation::new(enter_duration).with_easing(ease_out_quint()), + |toast, delta| toast.mt(px(8. * (1. - delta))), + ) })), ) } diff --git a/docs/PLAN.md b/docs/PLAN.md index b352215..dd8829a 100644 --- a/docs/PLAN.md +++ b/docs/PLAN.md @@ -215,7 +215,7 @@ 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 (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 | +| 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: ✅ landed — each toast slides up 8 px into its stack slot on `with_animation` at `Motion.overlay` with `ease_out_quint` (margin-driven, so only the entering toast moves; keyed per entry so the clock survives unrelated stack re-renders); (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. | @@ -319,7 +319,7 @@ 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. **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). +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: (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.