Skip to content

feat(layers): let a layer slide animation start from a point of its own - #865

Merged
hm21 merged 3 commits into
stablefrom
feat/layer-slide-from-point
Sep 10, 2026
Merged

hm21 merged 3 commits into
stablefrom
feat/layer-slide-from-point

Conversation

@hm21

@hm21 hm21 commented Sep 10, 2026

Copy link
Copy Markdown
Owner

Ports pro_video_editor#194 to the image editor's layer timeline, so a slide animation built here keeps matching the exported video.

What

A slide could only travel to and from a canvas edge, named by slideDirection. LayerAnimation.slideFrom now takes a start point instead:

LayerAnimation(
  type: LayerAnimationType.slide,
  phase: AnimationPhase.animateIn,
  duration: Duration(milliseconds: 600),
  slideFrom: Offset(-400, -400), // in diagonally from beyond the top-left
  curve: AnimationCurve.easeOutCubic,
)

The point may sit outside the canvas, and it overrides slideDirection when both are set — so nothing existing changes. animateOut leaves towards it, animateInOut does both.

How

The start point and the layer's resting place are both anchor points, so their difference is the distance travelled: the layer's own size cancels out, and with it the fractional component the edge-aware slide needs to clear the border. The preview composes a point slide as a plain pixel translation, which is what the native renderer does too.

The one deliberate difference from pro_video_editor: it measures its slideFrom from the video frame's top-left corner, because that is where its own layer offsets start. This one follows Layer.offset and measures from the canvas center. The doc comment says so.

Also

  • slideFrom is serialized as {dx, dy} and carried through copyWith, equality and toString; fromMap stays lenient and ignores a non-map value.
  • New safeParseOffset, the sibling of the existing safeParseSize, parses it.
  • The slide assert now accepts either a direction or a point.

Testing

flutter analyze clean, dart format clean, all 594 tests pass — including 5 new preview cases (starts on the point, travels a linear fraction, settles at rest, overrides a direction, leaves back towards the point), 8 new model cases and a parser suite.

A slide could only travel to and from a canvas edge, named by
`slideDirection`. `LayerAnimation.slideFrom` now takes a start point
instead: the layer's position in the same coordinates as `Layer.offset`,
measured from the center of the editor canvas. It may sit outside the
canvas, and it overrides `slideDirection` when both are set, so nothing
existing changes.

The start point and the layer's resting place are both anchor points, so
their difference is the distance travelled — the layer's own size cancels
out, and with it the fractional component the edge-aware slide needs to
clear the border. The preview therefore composes the point slide as a
plain pixel translation.

This mirrors `pro_video_editor`'s feature of the same name, so a layer
timeline built here keeps matching the exported video. The one difference
is the origin: `pro_video_editor` measures its `slideFrom` from the video
frame's top-left corner because that is where its own layer offsets start,
while this one follows `Layer.offset` and measures from the canvas center.

`slideFrom` is serialized as `{dx, dy}` and carried through `copyWith`,
equality and `toString`. Parsing it reuses a new `safeParseOffset`, the
sibling of the existing `safeParseSize`.
…o_video_editor

The two packages agree on the maths - only the distance between the start
point and the layer's resting anchor is ever used - but disagree on the
origin, because each follows its own layer offsets. An exporter that
converts `Layer.offset` and forgets `slideFrom` therefore parks the layer
correctly and moves it the wrong distance, silently.
The timeline preview cached the composed frame and only rebuilt it when
the video position or an animation setting changed. The slide effect also
reads the layer's offset, its center and the canvas size, so dragging a
layer or resizing the canvas left the layer travelling the distance
measured for the old geometry until the next time tick.

`Layer` is mutable and is mutated in place while it is dragged, so the
old and new widget hold the same instance and comparing them cannot see
the move. Record the geometry each frame was computed from and compare
against that instead.

Also export `safeParseOffset` from the package barrel next to its
`safeParseSize` sibling, which was left out when it was added.
@hm21
hm21 merged commit 17d393d into stable Sep 10, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant