gpui-ai

Effects · Decoration

Pulse

The same rings on the motion channel instead of a press, so they keep arriving — and stop when the panel scrolls away.

Pulse — gpui-ai

Starts when it scrolls into view

Pop out

NORD FROST · DARK · RADIUS 6/10 · BASE 16PX · SHADOW

The decorations story, opened on Pulse. The switcher above the card is the story's own — every state it offers has a page here.

Code

Cut from the gallery’s own decorations, so it is the code that drew the frame above rather than a description of it.

crates/gallery/src/decorations.rsOpen in the galleryImplementation
fn pulse(cx: &App) -> impl IntoElement {
    const RINGS: usize = 3;
    const POINTS: usize = 110;
    const REACH: f32 = 420.0;
    let ink = cx.theme().primary;
    let radius = f32::from(decoration::frame_radius(cx));
    decoration::animated("pulse", Duration::from_millis(2600), move |delta| {
        div()
            .size_full()
            .children((0..RINGS * POINTS).filter_map(move |index| {
                let (ring, step) = (index / POINTS, index % POINTS);
                // Thirds of a cycle apart, so one front is always arriving as
                // another leaves.
                let travel = (delta + ring as f32 / RINGS as f32).fract();
                let reach = 26.0 + travel * REACH;
                let angle = step as f32 / POINTS as f32 * std::f32::consts::TAU;
                let size = 2.0 + (1.0 - travel) * 3.0;
                Some(
                    dot(
                        CARD.width / 2.0 + reach * angle.cos(),
                        CARD.height / 2.0 + reach * angle.sin(),
                        size,
                        radius,
                    )?
                    .bg(ink.opacity((1.0 - travel) * 0.8)),
                )
            }))
    })
}

Applying it

Every framed component takes a Decoration, and the layer decides for itself whether it belongs to the shape — either by carrying the frame’s radius, or by not reaching a corner. The two rules are the whole contract.

crates/gallery/src/decorations.rs
let card = ApprovalCard::new("decorated-gate", "Publish the launch plan?")
    .description(
        "The card is unchanged. Everything behind and in front of it \
         is the application's.",
    )
    .decoration(kind.build(cx))