T11c motion: transitions, stagger, presence that folds, flip that glides, drag to reorder; motion keeps each row's identity
This commit is contained in:
10 files changed
+1156
-10
No files matched your search
@@ -1,4 +1,5 @@
|
||||
mod loading;
|
||||
mod moves;
|
||||
mod progress;
|
||||
|
||||
use gpui::{AnyElement, App, IntoElement, ParentElement, Window, div};
|
||||
@@ -42,6 +43,16 @@ const SCRIPT: &[Step] = &[
|
||||
Step::Click("uploads-tick"),
|
||||
Step::Wait(400),
|
||||
Step::Shot("uploads"),
|
||||
Step::Click("transition-toggle"),
|
||||
Step::Wait(80),
|
||||
Step::Shot("transition-leaving"),
|
||||
Step::Click("transition-toggle"),
|
||||
Step::Click("presence-add"),
|
||||
Step::Wait(80),
|
||||
Step::Shot("presence-arriving"),
|
||||
Step::Click("flip-shuffle"),
|
||||
Step::Wait(100),
|
||||
Step::Shot("flip-gliding"),
|
||||
Step::Rest,
|
||||
];
|
||||
|
||||
@@ -58,5 +69,10 @@ fn render(window: &mut Window, cx: &mut App) -> AnyElement {
|
||||
.child(progress::suspense(window, cx))
|
||||
.child(progress::refresh(window, cx))
|
||||
.child(progress::uploads(window, cx))
|
||||
.child(moves::transitions(window, cx))
|
||||
.child(moves::stagger(window, cx))
|
||||
.child(moves::presence(window, cx))
|
||||
.child(moves::flip(window, cx))
|
||||
.child(moves::reorder(window, cx))
|
||||
.into_any_element()
|
||||
}
|
||||
@@ -0,0 +1,251 @@
|
||||
use ely_gpui_component::{
|
||||
buttons::{Button, ButtonVariant, IconButton},
|
||||
motion::{AnimatePresence, Entrance, Flip, Reorder, Stagger, Transition},
|
||||
primitives::{Icon, IconName},
|
||||
theme::{ActiveTheme, ControlSize, IconSize, Radius},
|
||||
typography::Caption,
|
||||
};
|
||||
use gpui::{App, Div, IntoElement, ParentElement, SharedString, Styled, Window, div};
|
||||
|
||||
use crate::{
|
||||
probe::probe,
|
||||
ui::{keep, row, section, set, specimen, specimens},
|
||||
};
|
||||
|
||||
fn card(text: impl Into<SharedString>, cx: &App) -> Div {
|
||||
let theme = cx.theme();
|
||||
div()
|
||||
.px_4()
|
||||
.py_3()
|
||||
.rounded(theme.radius(Radius::Lg))
|
||||
.border_1()
|
||||
.border_color(theme.colors.border)
|
||||
.bg(theme.colors.surface)
|
||||
.child(text.into())
|
||||
}
|
||||
|
||||
/// A row in a list demo: a hairline under it.
|
||||
fn line(text: impl Into<SharedString>, cx: &App) -> Div {
|
||||
let theme = cx.theme();
|
||||
div()
|
||||
.flex()
|
||||
.items_center()
|
||||
.gap_3()
|
||||
.px_4()
|
||||
.py_2()
|
||||
.border_b_1()
|
||||
.border_color(theme.colors.border)
|
||||
.bg(theme.colors.surface)
|
||||
.child(text.into())
|
||||
}
|
||||
|
||||
pub fn transitions(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
||||
let shown = keep("transition-shown", || true, window, cx);
|
||||
let (now, toggle) = (*shown.read(cx), shown.clone());
|
||||
let entrances = [
|
||||
(Entrance::Fade, "fade"),
|
||||
(Entrance::Rise, "rise"),
|
||||
(Entrance::Slide, "slide"),
|
||||
];
|
||||
section(
|
||||
"Transition",
|
||||
"Content that fades, rises or slides in, and leaves the way it came.",
|
||||
cx,
|
||||
)
|
||||
.child(specimens().children(entrances.map(|(entrance, name)| {
|
||||
specimen(
|
||||
name,
|
||||
div().w_48().h_12().child(
|
||||
Transition::new(SharedString::from(format!("transition-{name}")), now)
|
||||
.entrance(entrance)
|
||||
.child(card("Hello", cx)),
|
||||
),
|
||||
cx,
|
||||
)
|
||||
})))
|
||||
.child(
|
||||
row().child(probe(
|
||||
"transition-toggle",
|
||||
Button::new("transition-toggle", if now { "Hide" } else { "Show" })
|
||||
.variant(ButtonVariant::Ghost)
|
||||
.on_click(move |_, _, cx| set(&toggle, !now, cx)),
|
||||
)),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn stagger(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
||||
let turn = keep("stagger-turn", || 0usize, window, cx);
|
||||
let (now, replay) = (*turn.read(cx), turn.clone());
|
||||
section(
|
||||
"Stagger",
|
||||
"Children arrive one after another, a few frames apart.",
|
||||
cx,
|
||||
)
|
||||
.child(
|
||||
div().w_96().child(
|
||||
Stagger::new(SharedString::from(format!("stagger-{now}"))).children(
|
||||
["Inbox", "Drafts", "Sent", "Archive", "Trash"].map(|name| line(name, cx)),
|
||||
),
|
||||
),
|
||||
)
|
||||
.child(
|
||||
row().child(probe(
|
||||
"stagger-replay",
|
||||
Button::new("stagger-replay", "Replay")
|
||||
.variant(ButtonVariant::Ghost)
|
||||
.on_click(move |_, _, cx| set(&replay, now + 1, cx)),
|
||||
)),
|
||||
)
|
||||
}
|
||||
|
||||
/// A tag in the presence demo: a stable number, its name, and whether it shows.
|
||||
type Tag = (usize, &'static str, bool);
|
||||
|
||||
pub fn presence(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
||||
const MORE: [&str; 4] = ["Typography", "Color", "Layout", "Sound"];
|
||||
let tags = keep(
|
||||
"presence-tags",
|
||||
|| vec![(0, "Design", true), (1, "Motion", true), (2, "Rust", true)],
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
let list: Vec<Tag> = tags.read(cx).clone();
|
||||
let (add, gone) = (tags.clone(), tags.clone());
|
||||
let rows = list.iter().fold(
|
||||
AnimatePresence::new("presence").on_gone(move |key, _, cx| {
|
||||
let left: Vec<Tag> = gone
|
||||
.read(cx)
|
||||
.iter()
|
||||
.filter(|(number, _, _)| format!("tag-{number}") != key.as_ref())
|
||||
.copied()
|
||||
.collect();
|
||||
set(&gone, left, cx)
|
||||
}),
|
||||
|rows, (number, name, shown)| {
|
||||
let (hide, number) = (tags.clone(), *number);
|
||||
rows.row(
|
||||
SharedString::from(format!("tag-{number}")),
|
||||
*shown,
|
||||
line(*name, cx).justify_between().child(
|
||||
IconButton::new(
|
||||
SharedString::from(format!("presence-remove-{number}")),
|
||||
IconName::X,
|
||||
)
|
||||
.variant(ButtonVariant::Ghost)
|
||||
.size(ControlSize::Sm)
|
||||
.on_click(move |_, _, cx| {
|
||||
let then: Vec<Tag> = hide
|
||||
.read(cx)
|
||||
.iter()
|
||||
.map(|tag| {
|
||||
if tag.0 == number {
|
||||
(tag.0, tag.1, false)
|
||||
} else {
|
||||
*tag
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
set(&hide, then, cx)
|
||||
}),
|
||||
),
|
||||
)
|
||||
},
|
||||
);
|
||||
section(
|
||||
"AnimatePresence",
|
||||
"Rows fold open as they arrive and fold away as they go; the rest glide to close the gap.",
|
||||
cx,
|
||||
)
|
||||
.child(div().w_96().child(rows))
|
||||
.child(
|
||||
row().child(probe(
|
||||
"presence-add",
|
||||
Button::new("presence-add", "Add a tag")
|
||||
.variant(ButtonVariant::Ghost)
|
||||
.on_click(move |_, _, cx| {
|
||||
let mut then = add.read(cx).clone();
|
||||
let number = then.iter().map(|tag| tag.0 + 1).max().unwrap_or(0);
|
||||
then.push((number, MORE[number % MORE.len()], true));
|
||||
set(&add, then, cx);
|
||||
}),
|
||||
)),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn flip(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
||||
let order = keep(
|
||||
"flip-order",
|
||||
|| vec!["Aurora", "Basalt", "Cinder", "Dune", "Ember"],
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
let (now, shuffle) = (order.read(cx).clone(), order.clone());
|
||||
let list = now.iter().fold(Flip::new("flip"), |list, name| {
|
||||
list.row(*name, line(*name, cx))
|
||||
});
|
||||
section(
|
||||
"Flip",
|
||||
"When the order changes, each row glides from where it was to where it goes.",
|
||||
cx,
|
||||
)
|
||||
.child(div().w_96().child(list))
|
||||
.child(
|
||||
row().child(probe(
|
||||
"flip-shuffle",
|
||||
Button::new("flip-shuffle", "Shuffle")
|
||||
.variant(ButtonVariant::Ghost)
|
||||
.on_click(move |_, _, cx| {
|
||||
let mut then = shuffle.read(cx).clone();
|
||||
then.rotate_left(2);
|
||||
then.swap(0, 3);
|
||||
set(&shuffle, then, cx)
|
||||
}),
|
||||
)),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn reorder(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
||||
let tasks = keep(
|
||||
"reorder-tasks",
|
||||
|| {
|
||||
vec![
|
||||
"Write the brief",
|
||||
"Sketch the flows",
|
||||
"Pick the type",
|
||||
"Build the prototype",
|
||||
"Test with five people",
|
||||
]
|
||||
},
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
let (now, moved) = (tasks.read(cx).clone(), tasks.clone());
|
||||
let muted = cx.theme().colors.fg_subtle;
|
||||
let list = now.iter().fold(
|
||||
Reorder::new("reorder").on_reorder(move |from, to, _, cx| {
|
||||
let mut then = moved.read(cx).clone();
|
||||
let task = then.remove(from);
|
||||
then.insert(to, task);
|
||||
set(&moved, then, cx)
|
||||
}),
|
||||
|list, task| {
|
||||
list.row(
|
||||
*task,
|
||||
line(*task, cx).child(div().flex_1()).child(
|
||||
Icon::new(IconName::GripVertical)
|
||||
.size(IconSize::Sm)
|
||||
.color(muted),
|
||||
),
|
||||
)
|
||||
},
|
||||
);
|
||||
section(
|
||||
"Reorder",
|
||||
"Drag a row: it follows the pointer and the others glide aside to make room.",
|
||||
cx,
|
||||
)
|
||||
.child(div().w_96().child(list))
|
||||
.child(Caption::new(
|
||||
"Motion is gpui's with_animation, timed by motion::duration and eased by the curves in motion. Spring is motion::spring. CountUp is typography::AnimatedNumber::count_up. A collapse is layout::Collapsible. A scale entrance is blocked: gpui 0.2.2 transforms only icons.",
|
||||
))
|
||||
}
|
||||
Reference in new issue
Block a user