1
0
Fork 0
forked from wry/wry
wry/docs/window-animations-plan.md

309 lines
16 KiB
Markdown

# Window Animations Plan
This document is the working plan for Wry's window animation system. It records
the decisions already made, the implementation phases, and the risks that must
be handled deliberately.
## Accepted Decisions
- The first landed slice is linear interpolation only, disabled by default.
- Animation is presentation-only. Logical layout, input hit testing, focus, and
Wayland configure state use final geometry immediately.
- Pointer drag and resize initiated by the mouse or tablet do not animate.
- Linear animations restart only for windows whose destination changes. Other
in-flight windows keep their existing timelines.
- Spawn-in uses scale and position for newly mapped tiled and floating app
windows. Layer-shell, overlay, override-redirect, and fullscreen surfaces do
not use this path. Spawn-out uses retained visual content after the live node
is gone, when a stable retained surface tree can be captured before unmap or
destroy.
- Command-driven tile-to-float and float-to-tile transitions may animate.
Protocol drag/drop paths do not.
- The no-overlap multiphase system is a separate phase after the linear path is
working and testable.
- Content freezing will use retained per-surface texture references, not a full
offscreen snapshot as the default design.
- Retained records should keep using the existing renderer behavior for now,
including clipping and edge stretch/clamp behavior for undersized contents. A
dedicated retained-tree scaling path is deferred to a later polish phase.
- The multiphase animation concept is original to Wry. Hy3 is relevant only as
partial inspiration for tiling style and titlebar/grouping behavior.
- Mono mode should mostly avoid animations. Exceptions are windows entering or
exiting mono mode, where a visual transition can clarify the hierarchy change.
- Multiphase shrink steps should not normally need to reduce a tiled window far
below roughly one quarter of the relevant full size. The implementation may
enforce a conservative sanity minimum, and pathological cases may fall back.
- If the no-overlap planner cannot produce a legal sequence, only the affected
group should fall back to linear animation. This is expected to be rare for
valid tiling layouts.
- When entering mono mode, the active child should animate to the mono geometry.
Inactive siblings may snap invisible. Floats may overlap normally and do not
need the no-overlap planner.
## Texture Freezing Decision
The approved freezing design is to capture a renderable surface tree at animation
start:
- texture references
- source sample rects
- target sizes
- alpha and color metadata
- subsurface offsets and stacking order
- enough synchronization/release state to keep referenced buffers alive safely
This is lighter than rendering every toplevel into a compositor-owned offscreen
texture, and it should handle normal GPU-backed windows without an extra full
window copy. It also gives spawn-out a path: capture the surface tree before the
toplevel is logically destroyed, then animate the retained records after the live
node is gone.
Tradeoffs:
- Retained references can delay buffer release. For dmabuf clients this can
increase memory pressure or throttle clients if many large windows animate.
- SHM buffers still matter for simple clients, fallback paths, some utilities,
and cursor-like surfaces. They are probably not the common case for large app
windows, but the implementation must still treat SHM texture flipping as a
correctness issue.
- The release/sync contract must be explicit. A retained texture must not be
released back to the client while the compositor may still render it.
- True offscreen snapshots remain a possible fallback for cases where retained
references cannot safely preserve the rendered content.
## Phase 1: Linear Presentation Animations
Goal: add the smallest correct animation layer without changing layout semantics.
Implementation shape:
- Add animation state owned by `State`.
- Track per-toplevel animation entries keyed by `NodeId`.
- Store logical target rect, current presentation rect, previous damaged rect,
start time, duration, and curve.
- On command-driven tiled layout geometry changes, animate from current
presentation rect to new final rect.
- On interruption, restart only the affected window from its current
presentation rect.
- Drive frames from the existing output latch/presentation event flow.
- Damage the union of previous presentation rect, current presentation rect, and
final logical rect.
Initial scope:
- Tiled reflow animation.
- Floating command-driven moves and resizes are animated. Pointer and tablet
drag/resize paths still snap directly to the live cursor position.
- Cross-output and cross-scale movements snap for now.
- Linear mode may overlap windows during swaps. That is expected for the classic
interpolation mode; no-overlap is Phase 3.
- Live client buffers are rendered in Phase 1. Retained content freezing is
deferred, but animated windows must still be clipped to their presentation
bounds and must preserve the existing stretch behavior for undersized contents.
- Spawn-out is retained-content-only. If the surface cannot be retained safely
the window snaps out instead of animating an empty frame.
- No multiphase no-overlap planner.
Tests:
- rect interpolation is direction-independent
- interruption restarts only changed windows
- unchanged in-flight windows keep their original timeline
- drag-driven floating movement bypasses animation
- damage includes old, current, and final rects
- command-driven tile-to-float and float-to-tile transitions use linear motion
- command-driven floating moves and resizes animate without affecting pointer
drag/resize behavior
- pointer/header double-click unfloat bypasses the command-animation gate
## Phase 2: Retained Texture Freezing
Goal: freeze visual contents during movement and enable spawn-out.
Initial retained-record implementation status:
- Tiled animation can retain GPU/dmabuf-backed XDG and Xwayland surface trees.
- Spawn-in animation can retain GPU/dmabuf-backed XDG and Xwayland surface trees
for both tiled windows and floating child contents.
- Tile-to-float and float-to-tile transitions retain GPU/dmabuf-backed child
contents while the presentation geometry changes.
- Spawn-out captures retained app-window contents before XDG/Xwayland unmap or
destroy, then renders a detached shrinking presentation record until the
animation completes.
- Retained records hold both `GfxTexture` and `SurfaceBuffer` references so the
existing buffer release/sync path remains authoritative.
- Single-pixel buffers can be retained as color records.
- Retained records render through the same texture and stretch/clamp paths used
by live surfaces. This is the expected Phase 2 behavior.
- Async SHM textures are not retained yet because Wry's per-surface SHM
front/back textures can be reused by later commits while an animation is still
running. Those surfaces fall back to live rendering until an explicit offscreen
copy fallback exists.
Implementation shape:
- Add a retained render-record tree for toplevel surfaces.
- Capture records before movement animations that require freezing.
- Capture records before destroy/unmap for spawn-out.
- Render retained records through the normal renderer primitives where possible.
- Extend event/sync handling so retained buffers remain valid until the animation
is complete.
Deferred/future polish:
- Whether retained records participate in frame callbacks or presentation
feedback. Default assumption: no, because they are compositor animation frames,
not client commits.
- How to fall back when a buffer cannot be safely retained.
- A distinct retained-tree scaling render path for true spawn-in/spawn-out
content scaling. If added, start with retained GPU-backed records only, keep
the animated frame as the clip boundary, and avoid live SHM scaling until there
is an explicit snapshot/copy fallback.
## Phase 3: Multiphase No-Overlap Animations
Goal: implement Wry's staged no-overlap planner while preserving the rule that
windows never overlap.
Core rules:
- Each phase is a discrete animation using the full curve.
- A phase performs only one action kind per window: move or scale.
- Movement and scaling are split by axis.
- No diagonal motion. Mixed-action phases may combine different per-window
actions, but no single window may move or resize on more than one axis in one
step.
- A window or synchronized group owns its own timeline.
- New layout changes interrupt only windows/groups with changed destinations.
- Current hierarchy and target hierarchy both matter. The planner must know
whether a window is ascending toward a higher-level/toplevel position,
descending into a container, or moving between containers at the same depth.
- If some child windows require fewer phases than their parent/container
context, parent/container-space changes generally happen first so space exists
before the child moves into it. This rule can be overridden only when the
non-overlap invariant still clearly holds.
- Windows that become peers in the target hierarchy may synchronize later
phases even if they were not peers in the source hierarchy.
Important parent/child synchronization issue:
The planner must not let a parent container and child window animate independent
axes at the same time in a way that violates the visual rules. For example, a
parent scaling horizontally while a child scales vertically can accidentally
produce a diagonal or multi-axis motion in screen space.
Preferred approach:
- Plan in terms of leaf toplevel visual rectangles first.
- Treat containers as constraints and grouping boundaries, not as independently
animated visual actors.
- Derive every leaf's per-phase rect from one phase schedule so parent and child
effects cannot compose into forbidden motion.
- Build the planner as pure geometry first. Live integration should collect
eligible leaf `(old, new)` rects across a command-driven layout pass, then
submit planner-produced phases as a batch. Per-node `tl_change_extents` calls
are too incremental to plan safely by themselves.
- Add container-level grouping only after the leaf planner proves correct.
- Include hierarchy-transition metadata in the planner input: source parent,
target parent, source depth, target depth, and whether the window is ascending,
descending, or staying at the same hierarchy level.
- For mono containers, suppress ordinary in-mono focus/tab changes. Animate only
transitions into mono, out of mono, or across the mono boundary.
- When entering mono, the active child animates to the full mono area and
inactive siblings snap invisible. When exiting mono, ordinary tiled geometry
may animate from the mono child where that produces a clear hierarchy
transition.
- If a legal no-overlap sequence cannot be found for a group, fall back to the
linear animator for that group only. Float windows are outside this invariant.
Current pure planner status:
- Two-window same-axis swaps use shrink lanes, move, then grow.
- Swap lane choice follows motion direction, not node identity: right/down
moving windows take the first lane, and left/up moving windows take the second
lane.
- Stack extraction/return patterns are covered in both horizontal and vertical
orientations: peer/container space scales first, the extracted child moves
only after space exists, and orthogonal growth happens in the final phase.
- Same-axis size redistribution is handled as a single scale phase when the
exact validator proves adjacent windows stay non-overlapping.
- Nested size redistribution can use hierarchy metadata to decompose two-axis
resizing into parent-axis then child-axis scale phases, but only when the
source/target ancestor split depths give a deterministic order.
- A phase can carry mixed per-window actions when each window still performs one
classified move/scale on one axis and the exact validator proves the combined
phase is non-overlapping.
- Every produced plan is checked analytically for overlap over the full duration
of each phase before it is accepted. This solves the linear edge inequalities
for each pair of moving rectangles instead of relying on sampled frames.
- Live layout batches are partitioned by overlapping motion bounds, so unrelated
groups can still use multiphase animation when another group falls back to
linear motion.
- Planner requests now carry per-window hierarchy metadata for source/target
parent, depth, sibling index, split axis, nearest ancestor split depth per
axis, mono state, and transition kind. The current planner uses this for
parent-before-child scale ordering, but not yet for full nested move planning.
- Multiphase planning has a diagnostic entry point used by live fallback logs.
It distinguishes request validation errors, missing patterns, shrink-bound
rejections, invalid phase steps, and exact validation failures such as stale
starts or phase overlap.
- Multiphase planning also has an explained-plan entry point. Accepted plans
report the deterministic strategy, phase reasons, participating nodes, and
validation result; rejected plans report every attempted strategy and failure.
- Rejection diagnostics are treated as contractual test output for unsupported
patterns and analytically invalid candidate plans, including attempted strategy
order and exact validation failure.
- Planner tests now include a deterministic split-tree generator. It builds
valid weighted tiling layouts, derives leaf hierarchy metadata, mutates them
through supported transitions, and runs the real planner plus exact validator.
- The generated tests also include a bounded corpus of supported split-tree
transitions across both axes and directions. Each case is planned twice and
compared exactly to catch nondeterministic planner output.
Tests:
- horizontal swaps shrink, move, then grow without overlap
- extraction from a stack creates space before moving the extracted window
- nested size redistribution scales the parent axis before the child axis
- nested containers do not produce simultaneous cross-axis motion
- interruption restarts only affected phase groups
- reversing direction produces equivalent motion in reverse
- accepted and rejected plans expose deterministic strategy explanations
- bounded generated split-tree corpus produces identical plans on repeated runs
- unsupported and invalid candidate plans produce exact expected diagnostics
- mixed-action phases are accepted only under exact continuous validation
- diagonal per-window motion remains a hard rejection even inside mixed phases
- child waits for parent/container-space phases when moving upward into a
toplevel peer position
- mono-mode tab switches do not animate, while entering/exiting mono can animate
## Configuration
Phase 1 should expose a disabled-by-default setting for:
- enabled/disabled
- duration
- curve preset or cubic bezier
Initial TOML shape:
```toml
[animations]
enabled = false
duration-ms = 160
curve = "ease-out"
# or:
curve = [0.25, 0.1, 0.25, 1.0]
```
Bezier curves are analyzed when configuration is applied and stored as a
piecewise curve that is cheap to evaluate during rendering. Custom curves use
CSS cubic-bezier semantics: `(0, 0)` and `(1, 1)` are implicit, while the four
configured numbers are `x1`, `y1`, `x2`, and `y2`. The x control points must be
between `0` and `1`.
## Existing Note
`docs/animation-integration.md` appears to document a prior animation attempt
whose `src/animation/` implementation is not present in this checkout. Treat
this plan as the current source of truth until implementation docs are updated.