# Open GPUI Motion
`open-gpui-motion` contains renderer-neutral motion primitives for Open GPUI components and domain
crates. It is a deterministic Rust foundation for layout-like UI motion; it is not a DOM animation
runtime.
## What This Crate Owns
- Motion preferences, duration tokens, easing tokens, and immediate reduced-motion semantics.
- Timeline and spring scalar sampling from explicit elapsed time.
- Policy-resolved execution plans for committed layout, continuity, affordance previews, and
input-coupled paths.
- Scalar controllers, retargeting, cancellation, explicit finish, terminal pruning, and
adapter-owned frame demand.
- `MotionProgressExecution` for policy-resolved normalized 0..1 adapter progress runs.
- `MotionSequence` for composing many keyed scalar tracks with absolute starts, append,
with-previous, after-previous, and staggered insertion while preserving renderer-neutral sampling.
- `MotionClockSample` for mapping adapter `Instant` values into deterministic controller
`Duration` samples with non-monotonic elapsed time clamped.
- `MotionFrameDemand::combine` and `MotionFrameDemand::combine_all` for aggregating many motion
sources into one adapter frame request.
- `MotionFrameHost` for keeping adapter-owned frame request decisions consistent without depending
on a GPUI window, browser scheduler, or renderer.
- Neutral logical-pixel geometry plus projection, reveal, and clip helpers for final-size content.
Adapters keep authority over rendering, input, focus, accessibility, and frame scheduling. A
Splitter, docking host, canvas, or application decides when to request a GPUI frame and how to map a
motion sample into painted elements.
## First-Party Proof Scope
The v0.2.0 stable proof is intentionally small:
- `Splitter` consumes scalar controller samples for programmatic panel layout transitions.
- `VirtualizedList` consumes scalar controller samples for an active-descendant indicator that
moves paint-only chrome by stable row key.
- Docking consumes neutral motion geometry and projection helpers for presentation and affordance
evidence.
These consumers prove deterministic clocks, normalized progress runs, retargeting, reduced-motion
final state, cancellation, terminal pruning, sequence composition, and `MotionFrameDemand`
aggregation. They do not prove row enter/exit animation, public presence, keyframes,
repeat/reverse/speed controls, full shared-layout orchestration, WAAPI, or a global scheduler.
## Where To See It
Run the component gallery to inspect Splitter and VirtualizedList motion in a normal checkout:
```sh
cargo run -p open-gpui-ui-foundation-gallery
```
Run the docking example to inspect layout and affordance motion through the docking host:
```sh
cargo run -p open-gpui-docking-native
```
Both examples keep frame scheduling in their GPUI adapters. `open-gpui-motion` only publishes
deterministic samples and frame demand.
## Boundaries
This crate deliberately does not provide React hooks, CSS parsing, DOM measurement, WAAPI behavior,
browser-native acceleration, global animation loops, drag-and-drop policy, asset animation, or full
shared-layout orchestration. `MotionProgressExecution` only owns a local 0..1 run lifecycle, and
`MotionSequence` only owns deterministic keyed timing and sampling; neither mutates properties or
schedules frames. Presence, keyframes, repeat/reverse/speed controls, public value subscriptions,
and high-level builders are deferred until a first-party Open GPUI adapter proves the shape.
`open-gpui-motion` must stay below `open-gpui-ui-core`, `open-gpui-ui-components`,
`open-gpui-docking`, `open-gpui-platform`, `open-gpui-web`, and renderer crates. Use conversion
helpers in adapter crates to map `MotionRect` to renderer-specific geometry.
## Example
```rust
use open_gpui_motion::{
MotionDuration, MotionEasing, MotionExecutionPlan, MotionFrameHost, MotionModel,
MotionPolicyContext, MotionPolicyInput, MotionPreference, MotionScalarExecution, MotionSpec,
};
use std::time::Duration;
let spec = MotionSpec::new(
MotionPreference::Animated,
MotionDuration::Custom(Duration::from_millis(180)),
MotionEasing::EaseOutStrong,
);
let plan = MotionExecutionPlan::resolve(
MotionPolicyInput::new(MotionPolicyContext::CommittedLayout, MotionModel::timeline(spec))
.with_spatial_motion(true)
.with_reduced_motion_final_state(true),
);
let execution = MotionScalarExecution::start(plan, 0.0, 1.0, 0.0, Duration::ZERO);
let mut frame_host = MotionFrameHost::new();
let sample = frame_host.sample_elapsed(Duration::from_millis(90), |clock| {
let sample = execution.sample_clock(clock);
(sample.value(), sample.frame_demand())
});
if sample.should_request_frame() {
// Ask the owning adapter to request a GPUI frame.
}
```
Reduced motion uses the same APIs and publishes the final semantic state immediately:
```rust
use open_gpui_motion::{MotionPreference, MotionSpec};
let spec = MotionSpec::committed_layout(MotionPreference::Reduced);
assert!(spec.is_immediate());
```
Lifecycle ordering is intentionally small: start or retarget creates active sampled state, each
sample returns a frame demand, `cancel` freezes the sampled value and goes idle without reaching the
semantic final state, `finish` publishes the target value as completed, reduced motion publishes
the final state immediately, and adapters may prune terminal tracks after observing idle demand.
## Testing
For focused changes in this crate, run:
```sh
cargo check -p open-gpui-motion --tests --locked
cargo nextest run -p open-gpui-motion --no-fail-fast
cargo test -p open-gpui-motion --doc
```
When changing geometry or policy used by first-party adapters, also run the focused Splitter and
docking gates documented in `docs/verification.md`.