Files
Ely-GPUI-Components/examples/gallery/pages/motion/effects.rs
T
ZacharyZhang-NY 3f1597fa28 Port · Parity: every page matches gpui 0.2.2's; the rules re-tested
Every page and scripted state was shot with gpui 0.2.2 and with the
port, light and dark, full width and at 280px, judged pair by pair by
agents with skeptics, and every page's log diffed. What differed is
fixed, each with a test that fails without it where the test platform
can show it:

- The gallery's harness takes posted mouse moves (gpui_macos listens
  to a tracking area), posts each drag move twice (it no longer
  replays a held drag), and waits three frames before each shot.
- Rolling digits: a block column, so taffy 0.13 lends the cell no
  baseline, and a line of whole device pixels, since gpui snaps each
  length on its own. DescriptionList's row aligns ends.
- Margins that taffy 0.13 collapses through a plain block: the
  drawer's pull and snap, the stagger and transition rise, the dialog
  and palette rises, the fullscreen dialog's slide.
- An Input keeps room to type in a squeezed row; the field rows drop
  w_full, whose reason was taffy 0.9's.

AGENTS.md's rules that named gpui 0.2.2 or taffy 0.9 were re-tested
against both sources and rewritten, and new ones say what the port
changed. TASKS.md's tags follow: what the new gpui makes possible is
prove. Captions, doc comments and README say what holds now.
2026-09-29 10:39:55 -04:00

241 lines
8.0 KiB
Rust

use ely_gpui_component::{
buttons::{Button, ButtonVariant},
feedback::InlineMessage,
motion::{
AnimatedGradient, Blink, Confetti, Flash, Glow, Marquee, ParticleBackground, Pulse, Ripple,
Shake,
},
primitives::Severity,
theme::{ActiveTheme, Radius, TextSize},
typography::tabular,
};
use gpui::{
App, FontWeight, IntoElement, ParentElement, Styled, Window, div, prelude::FluentBuilder,
};
use crate::{
probe::probe,
ui::{blocked, keep, row, section, set, specimen, specimens},
};
pub fn marquee(cx: &mut App) -> impl IntoElement + use<> {
let theme = cx.theme();
let (muted, size) = (theme.colors.fg_muted, theme.text_size(TextSize::Sm));
section(
"Marquee",
"A line that runs past at an even pace, with no seam where it loops.",
cx,
)
.child(
Marquee::new("marquee", move || {
div()
.flex()
.gap_8()
.pr_8()
.text_size(size)
.text_color(muted)
.children([
"Designed in the open",
"Built in Rust",
"Light and dark",
"Motion with restraint",
])
})
.w_128()
.py_2()
.border_t_1()
.border_b_1()
.border_color(theme.colors.border),
)
}
pub fn ripple(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
let bursts = keep("confetti-bursts", || 0usize, window, cx);
let (now, more) = (*bursts.read(cx), bursts.clone());
let theme = cx.theme();
section(
"Ripple / Confetti",
"A circle of light spreads from each press, cut to the card's corners. A burst of paper for a real win, in the chart's quiet colors.",
cx,
)
.child(
row()
.items_start()
.child(probe(
"ripple",
Ripple::new("ripple")
.w_64()
.h_32()
.flex()
.items_center()
.justify_center()
.rounded(theme.radius(Radius::Lg))
.border_1()
.border_color(theme.colors.border)
.bg(theme.colors.surface)
.child("Press anywhere"),
))
.child(
div()
.relative()
.w_64()
.h_32()
.flex()
.items_center()
.justify_center()
.child(probe(
"celebrate",
Button::new("celebrate", "Celebrate")
.primary()
.on_click(move |_, _, cx| set(&more, now + 1, cx)),
))
.child(Confetti::new("confetti", now)),
),
)
}
pub fn light(cx: &mut App) -> impl IntoElement + use<> {
let theme = cx.theme();
section(
"Glow / Pulse",
"A soft light that breathes around a card; rings that call out a live dot.",
cx,
)
.child(
specimens()
.child(specimen(
"glow",
Glow::new("glow")
.px_5()
.py_4()
.rounded(theme.radius(Radius::Lg))
.border_1()
.border_color(theme.colors.border)
.bg(theme.colors.surface)
.child("Focus mode"),
cx,
))
.child(specimen(
"pulse",
div()
.flex()
.items_center()
.gap_2()
.child(
Pulse::new("pulse")
.size_2()
.rounded_full()
.bg(theme.colors.success),
)
.child("Live"),
cx,
)),
)
}
pub fn cues(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
let price = keep("flash-price", || 1284u32, window, cx);
let tries = keep("shake-tries", || 0usize, window, cx);
let (value, tick) = (*price.read(cx), price.clone());
let (count, again) = (*tries.read(cx), tries.clone());
let theme = cx.theme();
section(
"Blink / Flash / Shake",
"A caret's rhythm; a tint that fades when a value moves; a side-to-side no for a wrong entry.",
cx,
)
.child(
specimens()
.child(specimen(
"blink",
div().flex().items_center().child("Type here").child(
Blink::new("blink").w_0p5().h_4().ml_0p5().bg(theme.colors.fg),
),
cx,
))
.child(specimen(
"flash",
row()
.child(
Flash::new("flash", value)
.px_2()
.rounded(theme.radius(Radius::Sm))
.child(tabular(div()).font_weight(FontWeight::MEDIUM).child(format!("{}.{:02}", value / 100, value % 100))),
)
.child(probe(
"flash-tick",
Button::new("flash-tick", "Tick")
.variant(ButtonVariant::Ghost)
.on_click(move |_, _, cx| set(&tick, value + 37, cx)),
)),
cx,
))
.child(specimen(
"shake",
row()
.child(
Shake::new("shake", count).child(
div()
.w_48()
.px_3()
.py_1p5()
.rounded(theme.radius(Radius::Md))
.border_1()
.border_color(if count > 0 { theme.colors.danger } else { theme.colors.border })
.child("••••••"),
),
)
.child(probe(
"shake-try",
Button::new("shake-try", "Sign in").on_click(move |_, _, cx| set(&again, count + 1, cx)),
))
.when(count > 0, |line| line.child(InlineMessage::new(Severity::Danger, "Wrong password"))),
cx,
)),
)
}
pub fn backdrops(cx: &mut App) -> impl IntoElement + use<> {
let theme = cx.theme();
let panel = |text: &'static str| {
div()
.size_full()
.flex()
.items_center()
.justify_center()
.text_size(theme.text_size(TextSize::Lg))
.font_weight(FontWeight::SEMIBOLD)
.child(text)
};
section(
"ParticleBackground / AnimatedGradient / Lottie Player",
"Faint motes that drift behind a panel; a tone that drifts, slowly, a breath toward the accent.",
cx,
)
.child(
row()
.child(
ParticleBackground::new("particles")
.w_80()
.h_40()
.rounded(theme.radius(Radius::Lg))
.border_1()
.border_color(theme.colors.border)
.child(panel("Quiet motion")),
)
.child(
AnimatedGradient::new("tide")
.w_80()
.h_40()
.rounded(theme.radius(Radius::Lg))
.border_1()
.border_color(theme.colors.border)
.child(panel("Tonal drift")),
),
)
.child(blocked(
"Lottie Player is blocked: gpui paints paths without the clip masks, mattes, blend modes and radial gradients Lottie files lean on, so frames would come out wrong.",
cx,
))
}