#24

`view!`: motion and built-in virtualized lists

Open1byeopened 6d ago

view! has no animation support, and long lists mean dropping out of the markup into raw GPUI calls. This issue covers both. They can ship separately, but they meet at animated list rows, so they're designed together.

Part 1: motion

Problem

To animate anything today, an app has to leave view!, call a motion library by hand and wrap the built element. gpui-motion already provides declarative, interruptible animation for GPUI: springs, enter and exit, keyed presence, layout, text and gestures. It respects the user's reduced-motion setting. view! should make it a first-class part of the markup.

GPUI version constraint

Motion's native types must come from the same GPUI as Rake, or they won't fit together:

CrateGPUI it uses
gpui-ce-motion 0.4.0gpui-ce =0.2.2 from crates.io
gpui-motion-kit 0.5.0gpui-kit =0.7.3, on Kit's gpui-pre backend
Rake todaygpui_ce from Git at rev 99109e3
Rake after its planned movegpui-pre 0.3.11 through gpui-base 0.9.0

Neither motion crate matches Rake today, even the CE one, because a Git source and a crates.io release are different crates to Cargo. Integrate after Rake moves to gpui-pre, through gpui-motion-kit. Release a gpui-motion-kit version on the same gpui-pre and Kit versions Rake pins.

Generated code should depend only on Rake paths, such as rake::motion, so apps get motion through a Rake feature and never pick a GPUI version themselves.

Proposed syntax

Start with an attribute that takes a frame the app already computed:

let card = self.card_motion.frame(
    Motion::new(Target::default().width(width)).initial(Target::default().opacity(0.0)),
    window,
    cx,
);

rake::view! {
    <div id="card" class="p-4 rounded-lg bg-surface" motion={card}>
        "Click to resize"
    </div>
}
  • The macro applies the frame to the element it builds, and nothing else. State stays in the app's view, as with the rest of view!, and the macro never relies on a cx or window in scope.
  • Allowed on built-in layout tags and buttons. Components opt in by accepting a frame property.
  • Keyed enter and exit in loops (keyed presence) uses each row's explicit stable ID, which loops already require. Removed rows stay until their exit animation finishes.
  • Shorter forms, such as motion:animate={target}, can come later once real use shows what's common.

Inspection and agents

  • Inspection and the CDP agent client should report the animated, on-screen bounds, and say an element is animating. Agents should wait for motion to settle before acting on it.
  • rake ctl and test helpers should be able to turn motion off, so automated runs and screenshots are deterministic.

Part 2: virtualized lists

Problem

Long lists mean writing gpui::uniform_list or gpui::list by hand inside view!. docs/experimental-overlays.md shows the pattern, and examples/view/src/documents.rs uses it. The app must give the viewport a fixed height, keep its own scroll handle and, for rows of different heights, keep a ListState and update it by hand on every insertion and deletion. The docs say "the macro adds no reconciliation for those APIs", so this is easy to get wrong.

Proposed syntax

rake::view! {
    <list id="documents" class="h-[480px]"
        items={&self.documents}
        key={|doc| doc.id}
        state={&self.documents_list}>
        {|doc, index| rake::view! {
            <div class="px-3 py-2">{doc.title.clone()}</div>
        }}
    </list>
}
  • items and key replace hand-written ranges. The key must be stable, and the generated row ID comes from it, so rows don't need their own ID.
  • state is a retained rake::ui::ListState stored on the app's view. It owns scrolling and, for variable heights, compares keys between renders to update GPUI's ListState. This is the missing reconciliation.
  • Fixed-height rows (row-height="32px") use uniform_list. Otherwise use list.
  • Built-ins: an empty-state slot, scroll_to_key, keeping the scroll position when items are inserted above, and keyboard navigation.
  • A compile error or debug check when the viewport has no bounded height, since that silently breaks virtualization.

Accessibility and inspection

  • The list has the list role, and each row has the list item role. The assistive tree should report the full item count and each row's position, not only the rows on screen. Check what GPUI's AccessKit integration supports.
  • Inspection and CDP queries should say the list is virtualized and give its total count. An agent should be able to scroll a row into view by its key before acting on it.

Relationship to GPUI Kit

After the move, check gpui-base and gpui-component's list support first. If they already handle keyed, virtualized lists well, <list> should build on them instead of on raw GPUI.

Where they meet

Rows in a <list> can take motion, so inserts, removals and reorders animate using the list's keys. Exit animations and reorder movement for rows that scroll off-screen need a clear rule. A first version can animate only rows that are on screen.

Out of scope

  • Grids and tables with column virtualization.
  • Gesture-driven reordering inside virtualized lists. That's a follow-up once both parts exist.
  • Animation in browsers is not out of scope, but measure its cost before enabling it everywhere.

Done when

  • Motion: motion={...} works on built-in tags in native and Web builds, keyed presence works in loops, and reduced motion is respected. Inspection reports the animated bounds, and rake ctl can turn motion off. An example in examples/view, docs/experimental-view.md updated and macro snapshot tests added.
  • Lists: <list> handles fixed and variable heights with key-based updates. Tests cover inserting, removing and reordering, keeping the scroll position, the empty state and an unbounded-height error. examples/view/src/documents.rs uses <list>, and a benchmark with at least 100,000 rows is recorded.
  • Accessibility: total count and row positions are checked with a real screen reader on at least one platform, and the results are recorded.

Depends on

  • The move from GPUI CE to gpui-pre.
  • A gpui-motion-kit release on the gpui-pre and Kit versions Rake pins.

Activity

0

No activity yet.