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
@@ -150,3 +150,192 @@ fn a_sweeping_bar_keeps_its_thickness_in_a_tall_column(cx: &mut TestAppContext)
|
||||
});
|
||||
assert_eq!(after.get(), thickness, "the bar took only its thickness");
|
||||
}
|
||||
|
||||
/// A transition, a presence list and a reorderable list, recording what they report.
|
||||
struct Stage {
|
||||
shown: bool,
|
||||
rows: Vec<(&'static str, bool)>,
|
||||
order: Vec<&'static str>,
|
||||
/// The top of what sits below the transition.
|
||||
drawn: Rc<Cell<usize>>,
|
||||
/// Each reorder row's keyed state, by row.
|
||||
seen: Rc<std::cell::RefCell<std::collections::HashMap<&'static str, gpui::EntityId>>>,
|
||||
log: Vec<String>,
|
||||
}
|
||||
|
||||
/// A 20px row that keeps keyed state and says which entity holds it.
|
||||
#[derive(IntoElement)]
|
||||
struct Marker {
|
||||
key: &'static str,
|
||||
seen: Rc<std::cell::RefCell<std::collections::HashMap<&'static str, gpui::EntityId>>>,
|
||||
}
|
||||
|
||||
impl gpui::RenderOnce for Marker {
|
||||
fn render(self, window: &mut Window, cx: &mut gpui::App) -> impl IntoElement {
|
||||
let state = window.use_keyed_state(self.key, cx, |_, _| ());
|
||||
self.seen.borrow_mut().insert(self.key, state.entity_id());
|
||||
div().h(px(20.0)).child(self.key)
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for Stage {
|
||||
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let drawn = self.drawn.clone();
|
||||
let (gone, moved) = (cx.entity(), cx.entity());
|
||||
let presence = self.rows.iter().fold(
|
||||
super::AnimatePresence::new("presence").on_gone(move |key, _, cx| {
|
||||
gone.update(cx, |stage, _| stage.log.push(format!("gone {key}")))
|
||||
}),
|
||||
|list, (key, shown)| list.row(*key, *shown, div().h(px(20.0)).child(*key)),
|
||||
);
|
||||
let reorder = self.order.iter().fold(
|
||||
super::Reorder::new("reorder").on_reorder(move |from, to, _, cx| {
|
||||
moved.update(cx, |stage, cx| {
|
||||
let row = stage.order.remove(from);
|
||||
stage.order.insert(to, row);
|
||||
stage.log.push(format!("moved {from} to {to}"));
|
||||
cx.notify();
|
||||
})
|
||||
}),
|
||||
|list, key| {
|
||||
list.row(
|
||||
*key,
|
||||
Marker {
|
||||
key,
|
||||
seen: self.seen.clone(),
|
||||
},
|
||||
)
|
||||
},
|
||||
);
|
||||
div()
|
||||
.w(px(300.0))
|
||||
.child(reorder)
|
||||
.child(super::Transition::new("fade", self.shown).child(div().h(px(10.0))))
|
||||
.child(
|
||||
crate::primitives::Measure::new("below", move |bounds, _, _| {
|
||||
drawn.set(f32::from(bounds.origin.y) as usize)
|
||||
})
|
||||
.h(px(1.0)),
|
||||
)
|
||||
.child(presence)
|
||||
}
|
||||
}
|
||||
|
||||
fn stage(cx: &mut TestAppContext) -> (gpui::Entity<Stage>, &mut VisualTestContext) {
|
||||
cx.update(|cx| {
|
||||
Theme::init(cx);
|
||||
Theme::update(cx, |theme| theme.reduced_motion = true);
|
||||
});
|
||||
let (view, cx) = cx.add_window_view(|_, _| Stage {
|
||||
shown: true,
|
||||
rows: vec![("a", true), ("b", true)],
|
||||
order: vec!["one", "two", "three", "four", "five"],
|
||||
drawn: Rc::new(Cell::new(0)),
|
||||
seen: Rc::default(),
|
||||
log: Vec::new(),
|
||||
});
|
||||
settle(cx);
|
||||
(view, cx)
|
||||
}
|
||||
|
||||
fn settle(cx: &mut VisualTestContext) {
|
||||
for _ in 0..3 {
|
||||
std::thread::sleep(std::time::Duration::from_millis(2));
|
||||
cx.update(|window, _| window.refresh());
|
||||
cx.run_until_parked();
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn a_hidden_transition_leaves_then_stops_drawing(cx: &mut TestAppContext) {
|
||||
let (view, cx) = stage(cx);
|
||||
let below = view.read_with(cx, |stage, _| stage.drawn.clone());
|
||||
assert_eq!(below.get(), 110, "the reorder list, then the shown content");
|
||||
view.update(cx, |stage, cx| {
|
||||
stage.shown = false;
|
||||
cx.notify();
|
||||
});
|
||||
settle(cx);
|
||||
assert_eq!(below.get(), 100, "the content left the layout");
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn a_row_marked_gone_is_reported_once(cx: &mut TestAppContext) {
|
||||
let (view, cx) = stage(cx);
|
||||
view.update(cx, |stage, cx| {
|
||||
stage.rows[1].1 = false;
|
||||
cx.notify();
|
||||
});
|
||||
settle(cx);
|
||||
settle(cx);
|
||||
let log = view.read_with(cx, |stage, _| stage.log.clone());
|
||||
assert_eq!(log, ["gone b"]);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn dragging_a_row_down_moves_it_past_the_rows_it_passes(cx: &mut TestAppContext) {
|
||||
let (view, cx) = stage(cx);
|
||||
let at = |y: f32| point(px(100.0), px(y));
|
||||
cx.simulate_mouse_move(at(10.0), None, Modifiers::none());
|
||||
cx.simulate_mouse_down(at(10.0), gpui::MouseButton::Left, Modifiers::none());
|
||||
for y in [16.0, 30.0, 45.0, 50.0] {
|
||||
cx.simulate_mouse_move(at(y), Some(gpui::MouseButton::Left), Modifiers::none());
|
||||
cx.run_until_parked();
|
||||
}
|
||||
cx.simulate_mouse_up(at(50.0), gpui::MouseButton::Left, Modifiers::none());
|
||||
settle(cx);
|
||||
let (order, log) = view.read_with(cx, |stage, _| (stage.order.clone(), stage.log.clone()));
|
||||
assert_eq!(log, ["moved 0 to 2"]);
|
||||
assert_eq!(order, ["two", "three", "one", "four", "five"]);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn a_moved_row_keeps_its_state(cx: &mut TestAppContext) {
|
||||
let (view, cx) = stage(cx);
|
||||
let seen = view.read_with(cx, |stage, _| stage.seen.clone());
|
||||
let before = seen.borrow()["one"];
|
||||
view.update(cx, |stage, cx| {
|
||||
stage.order.reverse();
|
||||
cx.notify();
|
||||
});
|
||||
settle(cx);
|
||||
assert_eq!(
|
||||
seen.borrow()["one"],
|
||||
before,
|
||||
"the same state after the move"
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn a_list_that_shrinks_under_a_drag_lets_it_go(cx: &mut TestAppContext) {
|
||||
let (view, cx) = stage(cx);
|
||||
let at = |y: f32| point(px(100.0), px(y));
|
||||
cx.simulate_mouse_move(at(90.0), None, Modifiers::none());
|
||||
cx.simulate_mouse_down(at(90.0), gpui::MouseButton::Left, Modifiers::none());
|
||||
for y in [84.0, 60.0] {
|
||||
cx.simulate_mouse_move(at(y), Some(gpui::MouseButton::Left), Modifiers::none());
|
||||
cx.run_until_parked();
|
||||
}
|
||||
view.update(cx, |stage, cx| {
|
||||
stage.order.truncate(2);
|
||||
cx.notify();
|
||||
});
|
||||
settle(cx);
|
||||
cx.simulate_mouse_up(at(20.0), gpui::MouseButton::Left, Modifiers::none());
|
||||
settle(cx);
|
||||
let log = view.read_with(cx, |stage, _| stage.log.clone());
|
||||
assert!(log.is_empty(), "no move for a row that left: {log:?}");
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn a_row_hidden_from_the_start_is_gone_at_once(cx: &mut TestAppContext) {
|
||||
let (view, cx) = stage(cx);
|
||||
cx.update(|_, cx| Theme::update(cx, |theme| theme.reduced_motion = false));
|
||||
view.update(cx, |stage, cx| {
|
||||
stage.rows.push(("c", false));
|
||||
cx.notify();
|
||||
});
|
||||
settle(cx);
|
||||
let log = view.read_with(cx, |stage, _| stage.log.clone());
|
||||
assert_eq!(log, ["gone c"], "no fold to wait for");
|
||||
}
|
||||
Reference in new issue
Block a user