Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions packages/mix/lib/src/animation/animation_config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,28 @@ sealed class AnimationConfig {
);
}

/// Animation configuration that pairs a forward (enter) config with a reverse
/// (exit) config.
///
/// This wrapper lets a single style own both its enter and exit transition
/// timing. When a style becomes active, Mix uses [forward]; when the style is
/// no longer active, Mix uses the leaving style's [reverse].
///
/// `reverse` is an exit transition config. It does not mean calling
/// `AnimationController.reverse()`.
final class ReversibleAnimationConfig extends AnimationConfig with Equatable {
final AnimationConfig forward;
final AnimationConfig reverse;

const ReversibleAnimationConfig({
required this.forward,
required this.reverse,
});

@override
List<Object?> get props => [forward, reverse];
}

/// Curve-based animation configuration with fixed duration.
///
/// This configuration provides duration, curve, and optional completion callback
Expand Down
62 changes: 58 additions & 4 deletions packages/mix/lib/src/animation/style_animation_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,18 @@ class _StyleAnimationBuilderState<S extends Spec<S>>
with TickerProviderStateMixin {
late StyleAnimationDriver<S> animationDriver;

/// The config currently driving the active driver. Tracked separately from
/// the spec's config because a reverse transition can leave the driver using
/// a different kind from the current spec's forward config.
AnimationConfig? _activeConfig;

@override
void initState() {
super.initState();
final spec = widget.spec;
final config = spec.animation;
final config = _forwardOf(spec.animation);
animationDriver = _createAnimationDriver(config: config, initialSpec: spec);
_activeConfig = config;
}

StyleAnimationDriver<S> _createAnimationDriver({
Expand Down Expand Up @@ -73,6 +79,11 @@ class _StyleAnimationBuilderState<S extends Spec<S>>
initialSpec: initialSpec,
context: context,
),
// A reversible config is unwrapped to its forward config for driving.
ReversibleAnimationConfig() => _createAnimationDriver(
config: config.forward,
initialSpec: initialSpec,
),
// ignore: avoid-undisposed-instances
null => NoAnimationDriver(vsync: this, initialSpec: initialSpec),
};
Expand All @@ -88,18 +99,26 @@ class _StyleAnimationBuilderState<S extends Spec<S>>
void didUpdateWidget(StyleAnimationBuilder<S> oldWidget) {
super.didUpdateWidget(oldWidget);

final config = widget.spec.animation;
final newConfig = widget.spec.animation;
final oldConfig = oldWidget.spec.animation;

if ((oldConfig.runtimeType == config.runtimeType) && config != null) {
// Select the effective transition config: the leaving style's reverse
// config when leaving it, otherwise the target style's forward config.
final config =
_transitionConfig(oldConfig, newConfig) ?? _forwardOf(oldConfig);

// Compare against the active driver's config kind, not the spec's, because
// a reverse transition can leave the driver using a different kind.
if ((_activeConfig.runtimeType == config.runtimeType) && config != null) {
animationDriver.updateDriver(config);
} else {
animationDriver.dispose();
animationDriver = _createAnimationDriver(
config: config ?? oldConfig,
config: config,
initialSpec: oldWidget.spec,
);
}
_activeConfig = config;

if (oldWidget.spec != widget.spec) {
animationDriver.didUpdateSpec(oldWidget.spec, widget.spec);
Expand All @@ -118,3 +137,38 @@ class _StyleAnimationBuilderState<S extends Spec<S>>
);
}
}

/// Unwraps the forward (enter) config from a [ReversibleAnimationConfig].
AnimationConfig? _forwardOf(AnimationConfig? config) {
return switch (config) {
ReversibleAnimationConfig(:final forward) => forward,
_ => config,
};
}

/// Unwraps the reverse (exit) config from a [ReversibleAnimationConfig].
AnimationConfig? _reverseOf(AnimationConfig? config) {
return switch (config) {
ReversibleAnimationConfig(:final reverse) => reverse,
_ => null,
};
}

/// Selects the effective transition config when moving from [oldConfig] to
/// [newConfig].
///
/// Uses the leaving style's reverse config only when the old and new configs
/// are distinct; otherwise uses the target style's forward config. This keeps
/// an inherited base reversible config from being mistaken for an exit
/// animation on enter.
AnimationConfig? _transitionConfig(
AnimationConfig? oldConfig,
AnimationConfig? newConfig,
) {
if (oldConfig != newConfig) {
final reverse = _reverseOf(oldConfig);
if (reverse != null) return reverse;
}

return _forwardOf(newConfig);
}
17 changes: 13 additions & 4 deletions packages/mix/lib/src/specs/box/box_spec.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 9 additions & 2 deletions packages/mix/lib/src/specs/flex/flex_spec.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 13 additions & 4 deletions packages/mix/lib/src/specs/flexbox/flexbox_spec.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 9 additions & 2 deletions packages/mix/lib/src/specs/icon/icon_spec.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 9 additions & 2 deletions packages/mix/lib/src/specs/image/image_spec.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 9 additions & 2 deletions packages/mix/lib/src/specs/stack/stack_spec.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 13 additions & 4 deletions packages/mix/lib/src/specs/stackbox/stackbox_spec.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 9 additions & 2 deletions packages/mix/lib/src/specs/text/text_spec.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import '../../core/style.dart';

mixin AnimationStyleMixin<T extends Style<S>, S extends Spec<S>> on Style<S> {
@protected
T animate(AnimationConfig config);
T animate(AnimationConfig config, {AnimationConfig? reverse});

/// Creates a keyframe animation. It will animate through the given timeline.
T keyframeAnimation({
Expand Down
Loading
Loading