311 lines
16 KiB
Markdown
311 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.
|
|
|
|
Manual verification checklist: `docs/window-animations-testing.md`.
|
|
|
|
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.
|