Foundations / Motion
Catalog
Markdown crates/motion/src/lib.rs docs.rs
use motion::{MotionSpec, MENU_IN};
element.with_animation("menu", MENU_IN.animation(), |el, t| el.opacity(t))No component inlines a duration or a curve; it names a spec.
Hover washes
let fade = Fade::new(Painter::of(cx), "row-3");
div()
.on_hover(motion::hover_listener(fade.clone()))
.bg(motion::hover_blend(&fade, theme.surface, theme.element_hover))A Fade is which view paints the wash and which element inside it — the Painter is half the identity, so two views using "row-3" never trade fades.
Your own repeating animation
const BREATHE: MotionSpec = MotionSpec::new(1800, motion::EASE_IN_OUT);
let phase = motion::pulse_delta(&BREATHE, Painter::of(cx), cx);Not with_animation(..).repeat(): its request is the window's, at the display's rate, for as long as the element stays mounted — one spinner row measured 36% CPU at 120Hz.
App settings
use motion::AppExt as _;
cx.set_reduced_motion(true);
cx.set_pause_when_inactive(false);API
// motion
impl MotionSpec {
/// `const`, with public fields. gpui has no native delay, so a spec with
/// one runs for `delay + duration` and holds `progress` at 0 until it
/// elapses.
pub const fn new(duration_ms: u64, curve: CubicBezier) -> Self;
pub fn animation(&self) -> Animation;
/// Pure, which is what makes the catalog testable.
pub fn progress(&self, raw_delta: f32) -> f32;
// ...
}
// The wrapped entrances: fade_in, fade_quick, menu_in, dialog_in, splash_out.
// `menu_out` takes its progress from the caller, because `with_animation`'s
// clock replays from 0 on remount and a replay mid-exit is a full-opacity flash.
impl Fade {
/// Which view paints the wash and which element inside it — the `Painter`
/// is half the identity, so two views using `"row-3"` never trade fades.
pub fn new(painter: Painter, key: impl Into<SharedString>) -> Self;
// ...
}
pub fn hover_listener(fade: Fade) -> impl Fn(&bool, &mut Window, &mut App) + 'static;
pub fn hover_blend(fade: &Fade, rest: Hsla, hover: Hsla) -> Hsla;
pub fn pulse_delta(spec: &MotionSpec, painter: Painter, cx: &mut App) -> f32;
impl Painter {
/// Claims a rate for one view. The app runs a single timer, notifies only
/// the view that is owed a frame, and parks when the last claim lapses.
pub fn lease(self, fps: f32, until: Duration, cx: &mut App);
// ...
}
/// Stretches every timeline, for sampling a 200ms tween frame by frame.
pub fn set_speed(scale: f32);
pub trait AppExt {
fn reduced_motion(&self) -> bool;
/// Elements snap to their end state and schedule nothing; `pulse_delta`
/// returns a static 0.
fn set_reduced_motion(&mut self, reduced: bool);
fn pause_when_inactive(&self) -> bool;
/// On by default. A spinner in a backgrounded window held 30fps and 22% of
/// a core indefinitely, against 2% with this on.
fn set_pause_when_inactive(&mut self, pause: bool);
}