// Copyright 2023 The Flutter team. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'dart:ui' as ui; import 'package:flutter/foundation.dart' show kDebugMode; import 'package:flutter/material.dart'; class WonkyChar extends StatefulWidget { final String text; final double size; final double baseRotation; final int animDurationMillis; final List animationSettings; const WonkyChar({ super.key, required this.text, required this.size, this.baseRotation = 0, this.animDurationMillis = 1000, this.animationSettings = const [], }); @override State createState() => WonkyCharState(); } class WonkyCharState extends State with SingleTickerProviderStateMixin { bool loopingAnimation = true; late AnimationController _animController; final List> _curves = []; late final List _fvAnimations = []; final List _fvAxes = []; // default curve and animations in case user sets nothing for them late final defaultCurve = CurvedAnimation( parent: _animController, curve: const Interval(0, 1, curve: Curves.linear)); late Animation _scaleAnimation = Tween(begin: 1, end: 1).animate(defaultCurve); late Animation _offsetXAnimation = Tween(begin: 0, end: 0).animate(defaultCurve); late Animation _offsetYAnimation = Tween(begin: 0, end: 0).animate(defaultCurve); late Animation _rotationAnimation = Tween(begin: 0, end: 0).animate(defaultCurve); late Animation _colorAnimation = ColorTween(begin: Colors.black, end: Colors.black).animate(defaultCurve); @override void initState() { super.initState(); initAnimations(widget.animationSettings); _animController ..addListener(() { setState(() {}); }) ..addStatusListener((status) { if (status == AnimationStatus.completed && loopingAnimation) { _animController.reverse(); } else if (status == AnimationStatus.dismissed && loopingAnimation) { _animController.forward(); } }); _animController.forward(); } @override void dispose() { _animController.dispose(); super.dispose(); } void stopAnimation() { _animController.stop(); } @override Widget build(BuildContext context) { List fontVariations = []; for (int i = 0; i < _fvAxes.length; i++) { fontVariations .add(ui.FontVariation(_fvAxes[i], _fvAnimations[i].value as double)); } return Transform( alignment: Alignment.center, transform: Matrix4.translationValues(_offsetXAnimation.value as double, _offsetYAnimation.value as double, 0) ..scale(_scaleAnimation.value) ..rotateZ(widget.baseRotation + (_rotationAnimation.value as double)), child: IgnorePointer( child: Text( widget.text, textAlign: TextAlign.center, style: TextStyle( color: _colorAnimation.value as Color?, fontFamily: 'Amstelvar', fontSize: widget.size, fontVariations: fontVariations, ), ), ), ); } void initAnimations(List settings) { _animController = AnimationController( vsync: this, duration: Duration(milliseconds: widget.animDurationMillis), ); for (WonkyAnimSetting s in settings) { final curve = CurvedAnimation( parent: _animController, curve: Interval(s.startAt, s.endAt, curve: s.curve), ); late Animation animation; if (s.property == 'color') { animation = ColorTween( begin: s.fromTo.fromValue() as Color?, end: s.fromTo.toValue() as Color?) .animate(curve); } else { animation = Tween( begin: s.fromTo.fromValue() as double, end: s.fromTo.toValue() as double) .animate(curve); } if (s.type == 'fv') { _fvAxes.add(s.property); _fvAnimations.add(animation); } else if (s.type == 'basic') { switch (s.property) { case 'scale': { _scaleAnimation = animation; } case 'rotation': { _rotationAnimation = animation; } case 'offsetX': { _offsetXAnimation = animation; } case 'offsetY': { _offsetYAnimation = animation; } case 'color': { _colorAnimation = animation; } default: { if (kDebugMode) { print( '**ERROR** unrecognized property to animate: ${s.property}'); } } break; } } // save refs to all curves just to persist in mem, don't need to touch them again _curves.add(curve); } } } abstract class WCRange { WCRange(); T fromValue(); T toValue(); } class RangeColor implements WCRange { Color from; Color to; RangeColor({required this.from, required this.to}); @override Color fromValue() { return from; } @override Color toValue() { return to; } } class RangeDbl implements WCRange { double from; double to; RangeDbl({required this.from, required this.to}); @override double fromValue() { return from; } @override double toValue() { return to; } } class WonkyAnimSetting { // just the animation String type; // 'fv' for fontVariation, 'basic' for everything else String property; //font variation axis, or 'size'/'rotation'/etc. WCRange fromTo; double startAt; // 0 to 1 rel to controller double endAt; // same as start Curve curve; WonkyAnimSetting({ required this.type, required this.property, required this.fromTo, required this.startAt, required this.endAt, required this.curve, }); }