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.
Starts when it scrolls into view
NORD FROST · DARK · RADIUS 6/10 · BASE 16PX · SHADOW
Code
Cut from the gallery’s own decorations, so it is the code that drew the frame above rather than a description of it.
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))