gpui-animation 0.2.0

A lightweight and fluent animation wrapper for GPUI, enabling smooth state-driven transitions with minimal boilerplate.
Documentation
gpui-animation-0.2.0 has been yanked.

GPUI Animation

gpui-animation is a lightweight, fluent animation wrapper for the GPUI framework. It aims to simplify the creation of smooth, state-driven transitions and animations on standard GPUI elements with minimal boilerplate.

[!WARNING]

This crate is currently in its early development stage. The API is subject to change.

✨ Features

  • Fluent API: Transform any compatible GPUI element into an animated one using .with_transition().
  • Zero-Copy Interpolation: High-performance "in-place" style updates to minimize memory cloning during animation frames.
  • Smart Transitions: Automatic shortest-path interpolation for HSLA colors (no more hue-jumping!) and support for complex types like Gradients and Sizes.
  • Composable: AnimatedWrapper implements standard GPUI traits (Styled, ParentElement, etc.), so you can keep using the GPUI methods you already know.

🚀 Getting Started

Any element that implements IntoElement + StatefulInteractiveElement + ParentElement + FluentBuilder + Styled can be wrapped.

Basic Usage

fn render(cx: &mut WindowContext) -> impl IntoElement {
        div()
            .id("my-animated-box")
            // Initialize the animation wrapper with a unique ID
            .with_transition("my-animated-box")
            .size_32()
            .bg(rgb(0x2e2e2e))
            // Define a hover transition
            .transition_on_hover(
                std::time::Duration::from_millis(300),
                gpui_animation::transition::general::Linear,
                |hovered, style| {
                    if *hovered {
                        style.bg(rgb(0xff0000)).size_64()
                    } else {
                        style.bg(rgb(0x2e2e2e)).size_32()
                    }
                },
            )
}

🛠 Supported Properties

Category Supported Styles
Colors Background (Solid, LinearGradient), Border Color, Text Color
Layout Size (Width, Height), Min/Max Size, Margin, Padding
Visual Opacity, Corner Radii (Border Radius), Box Shadows

📖 API Reference

Initialization

  • .with_transition(id): Wraps the element. Requires a unique ElementId to track animation state across frames.

Event-Driven Transitions

These methods automatically trigger the animation cycle when the event occurs:

  • .transition_on_click(duration, transition, modifier)
  • .transition_on_hover(duration, transition, modifier)

Declarative Transitions

Used for reactive state changes:

  • .transition_when(condition, duration, transition, modifier)
  • .transition_when_some(option, ...) / .transition_when_none(...)

[!IMPORTANT]

Note on Declarative Styling: > Changes made via .transition_when() and its variants do not automatically proactive-propagate the App context. Unlike event-based listeners that manage the context internally, you may need to manually invoke a refresh (e.g., cx.notify() or cx.refresh()) to start the transition when external state changes.

🎨 Custom Animation Algorithms

You are not limited to built-in transitions. You can create your own animation curves (Easing functions) by implementing the Transition trait.

1. Implement the Trait

Only the calculate method is required. It maps the linear time progress ($t \in [0, 1]$) to your desired easing value.

use gpui_animation::transition::Transition;

pub struct MyCustomBounce;

impl Transition for MyCustomBounce {
    fn calculate(&self, t: f32) -> f32 {
        // Example: A simple square curve
        t * t
    }
}

2. Use it in your UI

Since Transition is implemented for Arc<T>, and we provide IntoArcTransition helpers, you can pass your struct directly:

div()
		.id("box-1")
    .with_transition("box-1")
    .transition_on_hover(
        Duration::from_millis(500),
        MyCustomBounce, // Your custom algorithm
        |hovered, style| {
            if *hovered { style.mt_10() } else { style.mt_0() }
        }
    )
		.mt_0()

⚡ Performance

This crate is optimized for high-frequency updates (60/120 FPS):

  • ShadowBackground: Uses #[repr(C)] memory layouts to interpolate private GPUI fields without overhead.
  • FastInterpolatable: Employs an in-place update strategy to avoid full StyleRefinement cloning on every frame.

🤝 Contributing

Contributions are welcome! If you find a bug or have a suggestion for new interpolation support (like more Layout properties), please feel free to open an issue or submit a pull request.