Skip to content
Draft
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
27 changes: 17 additions & 10 deletions packages/mix/lib/src/animation/style_animation_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,20 @@ class StyleAnimationBuilder<S extends Spec<S>> extends StatefulWidget {
class _StyleAnimationBuilderState<S extends Spec<S>>
extends State<StyleAnimationBuilder<S>>
with TickerProviderStateMixin {
late StyleAnimationDriver<S> animationDriver;
StyleAnimationDriver<S>? _animationDriver;

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

StyleAnimationDriver<S> _createAnimationDriver({
StyleAnimationDriver<S>? _createAnimationDriver({
required AnimationConfig? config,
required StyleSpec<S> initialSpec,
}) {
Expand Down Expand Up @@ -73,14 +76,13 @@ class _StyleAnimationBuilderState<S extends Spec<S>>
initialSpec: initialSpec,
context: context,
),
// ignore: avoid-undisposed-instances
null => NoAnimationDriver(vsync: this, initialSpec: initialSpec),
null => null,
};
}

@override
void dispose() {
animationDriver.dispose();
_animationDriver?.dispose();
super.dispose();
}

Expand All @@ -92,22 +94,27 @@ class _StyleAnimationBuilderState<S extends Spec<S>>
final oldConfig = oldWidget.spec.animation;

if ((oldConfig.runtimeType == config.runtimeType) && config != null) {
animationDriver.updateDriver(config);
_animationDriver!.updateDriver(config);
} else {
animationDriver.dispose();
animationDriver = _createAnimationDriver(
_animationDriver?.dispose();
_animationDriver = _createAnimationDriver(
config: config ?? oldConfig,
initialSpec: oldWidget.spec,
);
}

if (oldWidget.spec != widget.spec) {
animationDriver.didUpdateSpec(oldWidget.spec, widget.spec);
_animationDriver?.didUpdateSpec(oldWidget.spec, widget.spec);
}
}

@override
Widget build(BuildContext context) {
final animationDriver = _animationDriver;
if (animationDriver == null) {
return widget.builder(context, widget.spec);
}

return AnimatedBuilder(
animation: animationDriver.animation,
builder: (context, child) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,14 @@ class WidgetStateProvider extends InheritedModel<WidgetState> {
);
}

/// Whether a [WidgetStateProvider] exists above [context].
///
/// Unlike [of], this lookup does not register a dependency on any state
/// aspect.
static bool hasProvider(BuildContext context) {
return context.getInheritedWidgetOfExactType<WidgetStateProvider>() != null;
}

/// Checks if a specific [WidgetState] is currently active.
///
/// Returns false if no [WidgetStateProvider] is found in the widget tree.
Expand Down
25 changes: 24 additions & 1 deletion packages/mix/lib/src/core/style.dart
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,24 @@ abstract class Style<S extends Spec<S>> extends Mix<StyleSpec<S>>
.toSet();
}

/// Whether this style reacts to a state produced by automatic pointer
/// tracking.
@internal
bool get needsPointerTracking {
final variants = $variants;
if (variants == null) return false;

for (final variantStyle in variants) {
final variant = variantStyle.variant;
if (variant is WidgetStateVariant &&
(variant.state == .hovered || variant.state == .pressed)) {
return true;
}
}

return false;
}

/// Merges all active variants with their nested variants recursively.
///
/// This method evaluates which variants should be active based on the current
Expand All @@ -88,8 +106,13 @@ abstract class Style<S extends Spec<S>> extends Mix<StyleSpec<S>>
BuildContext context, {
required Set<NamedVariant> namedVariants,
}) {
final variants = $variants;
if (variants == null || variants.isEmpty) {
return this;
}

// Filter variants that should be active in this context
final activeVariants = ($variants ?? [])
final activeVariants = variants
.where(
(variantAttr) => switch (variantAttr.variant) {
(ContextVariant variant) => variant.when(context),
Expand Down
81 changes: 21 additions & 60 deletions packages/mix/lib/src/core/style_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -46,45 +46,8 @@ class StyleBuilder<S extends Spec<S>> extends StatefulWidget {
State<StyleBuilder<S>> createState() => _StyleBuilderState<S>();
}

class _StyleBuilderState<S extends Spec<S>> extends State<StyleBuilder<S>>
with TickerProviderStateMixin {
late WidgetStatesController _controller;

/// Tracks whether we created the controller internally (and thus own it)
bool _ownsController = false;

@override
void initState() {
super.initState();
_initController();
}

void _initController() {
if (widget.controller != null) {
_controller = widget.controller!;
_ownsController = false;
} else {
_controller = WidgetStatesController();
_ownsController = true;
}
}

void _handleControllerChange(StyleBuilder<S> oldWidget) {
// Dispose old internal controller if we owned it
if (_ownsController) {
_controller.dispose();
}

// Set up new controller
if (widget.controller != null) {
_controller = widget.controller!;
_ownsController = false;
} else {
// Create internal controller, preserving state from old external controller
_controller = WidgetStatesController(oldWidget.controller?.value ?? {});
_ownsController = true;
}
}
class _StyleBuilderState<S extends Spec<S>> extends State<StyleBuilder<S>> {
WidgetStatesController? _preservedController;

Style<S> _buildStyle(BuildContext context) {
final inheritedStyle = Style.maybeOf<S>(context);
Expand All @@ -105,31 +68,28 @@ class _StyleBuilderState<S extends Spec<S>> extends State<StyleBuilder<S>>
void didUpdateWidget(covariant StyleBuilder<S> oldWidget) {
super.didUpdateWidget(oldWidget);

// Handle controller changes
if (oldWidget.controller != widget.controller) {
_handleControllerChange(oldWidget);
if (oldWidget.controller == widget.controller) return;

if (widget.controller != null) {
_preservedController?.dispose();
_preservedController = null;
} else {
_preservedController = WidgetStatesController(
oldWidget.controller?.value ?? {},
);
}
}

@override
void dispose() {
// Only dispose controllers we created internally
if (_ownsController) {
_controller.dispose();
}

_preservedController?.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
final style = _buildStyle(context);

// Calculate interactivity need early
final needsToTrackWidgetState =
widget.controller == null && style.widgetStates.isNotEmpty;

final alreadyHasWidgetStateScope = WidgetStateProvider.of(context) != null;
final externalController = widget.controller;

Widget current = Builder(
builder: (context) {
Expand All @@ -142,14 +102,15 @@ class _StyleBuilderState<S extends Spec<S>> extends State<StyleBuilder<S>>
},
);

if (needsToTrackWidgetState && !alreadyHasWidgetStateScope) {
// If we need interactivity and no MixWidgetStateModel is present,
// wrap in MixInteractionDetector
current = MixInteractionDetector(controller: _controller, child: current);
} else if (widget.controller != null) {
// If we have an external controller, wrap with _ExternalControllerProvider
if (externalController != null) {
current = _ExternalControllerProvider(
controller: _controller,
controller: externalController,
child: current,
);
} else if (style.needsPointerTracking &&
!WidgetStateProvider.hasProvider(context)) {
current = MixInteractionDetector(
controller: _preservedController,
child: current,
);
}
Expand Down
Loading
Loading