mirror of
https://github.com/flutter/samples.git
synced 2026-04-25 08:22:16 +00:00
added Type Jam puzzle app for review (#1554)
* added Type Jam puzzle app for review * pr round 2 prep * updated ci scripts for varfont_shader_puzzle * resolved unused and minor variable naming issues * rotator tiles row and col are final vars now * removed unused import and print from production * made constructors const where needed * pages_flow export refactored to directly come from that file * removed old api commented out section from FragmentShaded * updated pubspec yaml to correct project name * dart min version updated; removed unnecessary commented out dependencies from pubspec.yaml * updated pubspec.yaml min flutter version to ensure FragmentShader support * added/edited comments for explanation, esp on var fonts; removed obsolete comments * trailing newline added to pubspec.yaml eof
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
// 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.
|
||||
|
||||
export 'wonky_char.dart';
|
||||
export 'wonky_anim_palette.dart';
|
||||
export 'rotator_puzzle.dart';
|
||||
export 'lightboxed_panel.dart';
|
||||
export 'fragment_shaded.dart';
|
||||
@@ -0,0 +1,271 @@
|
||||
// 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/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
|
||||
class FragmentShaded extends StatefulWidget {
|
||||
final Widget child;
|
||||
final String shaderName;
|
||||
final int shaderDuration;
|
||||
static const int dampenDuration = 1000;
|
||||
static final Map<String, ui.FragmentProgram> fragmentPrograms = {};
|
||||
static const List<String> fragmentProgramNames = [
|
||||
'nothing',
|
||||
'bw_split',
|
||||
'color_split',
|
||||
'row_offset',
|
||||
'wavy_circ',
|
||||
'wavy',
|
||||
'wavy2'
|
||||
];
|
||||
|
||||
const FragmentShaded({
|
||||
required this.shaderName,
|
||||
required this.shaderDuration,
|
||||
required this.child,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<FragmentShaded> createState() => FragmentShadedState();
|
||||
}
|
||||
|
||||
class FragmentShadedState extends State<FragmentShaded>
|
||||
with TickerProviderStateMixin {
|
||||
late final AnimationController _controller;
|
||||
late final Animation<double> _dampenAnimation;
|
||||
late final Animation<double> _dampenCurve;
|
||||
late final AnimationController _dampenController;
|
||||
late AnimatingSamplerBuilder builder;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = AnimationController(
|
||||
vsync: this,
|
||||
duration: Duration(milliseconds: widget.shaderDuration),
|
||||
)..repeat(reverse: false);
|
||||
_dampenController = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: FragmentShaded.dampenDuration),
|
||||
);
|
||||
_dampenCurve = CurvedAnimation(
|
||||
parent: _dampenController,
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
_dampenAnimation =
|
||||
Tween<double>(begin: 1.0, end: 0.0).animate(_dampenCurve);
|
||||
initializeFragmentProgramsAndBuilder();
|
||||
}
|
||||
|
||||
void initializeFragmentProgramsAndBuilder() async {
|
||||
if (FragmentShaded.fragmentPrograms.isEmpty) {
|
||||
for (String s in FragmentShaded.fragmentProgramNames) {
|
||||
FragmentShaded.fragmentPrograms[s] =
|
||||
await ui.FragmentProgram.fromAsset('shaders/$s.frag');
|
||||
}
|
||||
}
|
||||
builder = AnimatingSamplerBuilder(_controller, _dampenAnimation,
|
||||
FragmentShaded.fragmentPrograms[widget.shaderName]!.fragmentShader());
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
_dampenController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (null == FragmentShaded.fragmentPrograms[widget.shaderName]) {
|
||||
setState(() {});
|
||||
return const SizedBox(
|
||||
width: 0,
|
||||
height: 0,
|
||||
);
|
||||
}
|
||||
return Transform.scale(
|
||||
scale: 0.5,
|
||||
child: ShaderSamplerBuilder(
|
||||
builder,
|
||||
child: widget.child,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void startDampening() {
|
||||
_dampenController.forward();
|
||||
}
|
||||
}
|
||||
|
||||
class AnimatingSamplerBuilder extends SamplerBuilder {
|
||||
AnimatingSamplerBuilder(
|
||||
this.animation, this.dampenAnimation, this.fragmentShader) {
|
||||
animation.addListener(notifyListeners);
|
||||
dampenAnimation.addListener(notifyListeners);
|
||||
}
|
||||
|
||||
final Animation<double> animation;
|
||||
final Animation<double> dampenAnimation;
|
||||
|
||||
final ui.FragmentShader fragmentShader;
|
||||
|
||||
@override
|
||||
void paint(ui.Image image, Size size, ui.Canvas canvas) {
|
||||
// animation
|
||||
fragmentShader.setFloat(0, animation.value);
|
||||
// width
|
||||
fragmentShader.setFloat(1, size.width);
|
||||
// height
|
||||
fragmentShader.setFloat(2, size.height);
|
||||
// dampener
|
||||
fragmentShader.setFloat(3, dampenAnimation.value);
|
||||
// sampler
|
||||
fragmentShader.setImageSampler(0, image);
|
||||
|
||||
canvas.drawRect(Offset.zero & size, Paint()..shader = fragmentShader);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class SamplerBuilder extends ChangeNotifier {
|
||||
void paint(ui.Image image, Size size, ui.Canvas canvas);
|
||||
}
|
||||
|
||||
class ShaderSamplerBuilder extends StatelessWidget {
|
||||
const ShaderSamplerBuilder(this.builder, {required this.child, super.key});
|
||||
|
||||
final SamplerBuilder builder;
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return RepaintBoundary(
|
||||
child: _ShaderSamplerImpl(
|
||||
builder,
|
||||
child: child,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
class _ShaderSamplerImpl extends SingleChildRenderObjectWidget {
|
||||
const _ShaderSamplerImpl(this.builder, {super.child});
|
||||
|
||||
final SamplerBuilder builder;
|
||||
|
||||
@override
|
||||
RenderObject createRenderObject(BuildContext context) {
|
||||
return _RenderShaderSamplerBuilderWidget(
|
||||
devicePixelRatio: MediaQuery.of(context).devicePixelRatio,
|
||||
builder: builder,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void updateRenderObject(
|
||||
BuildContext context, covariant RenderObject renderObject) {
|
||||
(renderObject as _RenderShaderSamplerBuilderWidget)
|
||||
..devicePixelRatio = MediaQuery.of(context).devicePixelRatio
|
||||
..builder = builder;
|
||||
}
|
||||
}
|
||||
|
||||
// A render object that conditionally converts its child into a [ui.Image]
|
||||
// and then paints it in place of the child.
|
||||
class _RenderShaderSamplerBuilderWidget extends RenderProxyBox {
|
||||
// Create a new [_RenderSnapshotWidget].
|
||||
_RenderShaderSamplerBuilderWidget({
|
||||
required double devicePixelRatio,
|
||||
required SamplerBuilder builder,
|
||||
}) : _devicePixelRatio = devicePixelRatio,
|
||||
_builder = builder;
|
||||
|
||||
/// The device pixel ratio used to create the child image.
|
||||
double get devicePixelRatio => _devicePixelRatio;
|
||||
double _devicePixelRatio;
|
||||
set devicePixelRatio(double value) {
|
||||
if (value == devicePixelRatio) {
|
||||
return;
|
||||
}
|
||||
_devicePixelRatio = value;
|
||||
if (_childRaster == null) {
|
||||
return;
|
||||
} else {
|
||||
_childRaster?.dispose();
|
||||
_childRaster = null;
|
||||
markNeedsPaint();
|
||||
}
|
||||
}
|
||||
|
||||
/// The painter used to paint the child snapshot or child widgets.
|
||||
SamplerBuilder get builder => _builder;
|
||||
SamplerBuilder _builder;
|
||||
set builder(SamplerBuilder value) {
|
||||
if (value == builder) {
|
||||
return;
|
||||
}
|
||||
builder.removeListener(markNeedsPaint);
|
||||
_builder = value;
|
||||
builder.addListener(markNeedsPaint);
|
||||
markNeedsPaint();
|
||||
}
|
||||
|
||||
ui.Image? _childRaster;
|
||||
|
||||
@override
|
||||
void attach(PipelineOwner owner) {
|
||||
builder.addListener(markNeedsPaint);
|
||||
super.attach(owner);
|
||||
}
|
||||
|
||||
@override
|
||||
void detach() {
|
||||
_childRaster?.dispose();
|
||||
_childRaster = null;
|
||||
builder.removeListener(markNeedsPaint);
|
||||
super.detach();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
builder.removeListener(markNeedsPaint);
|
||||
_childRaster?.dispose();
|
||||
_childRaster = null;
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
// Paint [child] with this painting context, then convert to a raster and detach all
|
||||
// children from this layer.
|
||||
ui.Image? _paintAndDetachToImage() {
|
||||
final OffsetLayer offsetLayer = OffsetLayer();
|
||||
final PaintingContext context =
|
||||
PaintingContext(offsetLayer, Offset.zero & size);
|
||||
super.paint(context, Offset.zero);
|
||||
// This ignore is here because this method is protected by the `PaintingContext`. Adding a new
|
||||
// method that performs the work of `_paintAndDetachToImage` would avoid the need for this, but
|
||||
// that would conflict with our goals of minimizing painting context.
|
||||
// ignore: invalid_use_of_protected_member
|
||||
context.stopRecordingIfNeeded();
|
||||
final ui.Image image = offsetLayer.toImageSync(Offset.zero & size,
|
||||
pixelRatio: devicePixelRatio);
|
||||
offsetLayer.dispose();
|
||||
return image;
|
||||
}
|
||||
|
||||
@override
|
||||
void paint(PaintingContext context, Offset offset) {
|
||||
if (size.isEmpty) {
|
||||
_childRaster?.dispose();
|
||||
_childRaster = null;
|
||||
return;
|
||||
}
|
||||
_childRaster?.dispose();
|
||||
_childRaster = _paintAndDetachToImage();
|
||||
builder.paint(_childRaster!, size, context.canvas);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
// 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 'package:flutter/material.dart';
|
||||
import '../page_content/pages_flow.dart';
|
||||
import '../styles.dart';
|
||||
|
||||
class LightboxedPanel extends StatefulWidget {
|
||||
final PageConfig pageConfig;
|
||||
final List<Widget> content;
|
||||
final double width = 300;
|
||||
final Function? onDismiss;
|
||||
final bool fadeOnDismiss;
|
||||
final int? autoDismissAfter;
|
||||
final bool buildButton;
|
||||
final Color lightBoxBgColor;
|
||||
final Color cardBgColor;
|
||||
|
||||
const LightboxedPanel({
|
||||
Key? key,
|
||||
required this.pageConfig,
|
||||
required this.content,
|
||||
this.onDismiss,
|
||||
this.fadeOnDismiss = true,
|
||||
this.autoDismissAfter,
|
||||
this.buildButton = true,
|
||||
this.lightBoxBgColor = const Color.fromARGB(200, 255, 255, 255),
|
||||
this.cardBgColor = Colors.white,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<LightboxedPanel> createState() => _LightboxedPanelState();
|
||||
}
|
||||
|
||||
class _LightboxedPanelState extends State<LightboxedPanel> {
|
||||
bool _fading = false;
|
||||
bool _show = true;
|
||||
late int _fadeOutDur = 200;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_fadeOutDur = widget.fadeOnDismiss ? _fadeOutDur : 0;
|
||||
if (null != widget.autoDismissAfter) {
|
||||
_fadeOutDur = 0;
|
||||
Future.delayed(
|
||||
Duration(milliseconds: widget.autoDismissAfter!),
|
||||
handleDismiss,
|
||||
);
|
||||
}
|
||||
super.initState();
|
||||
}
|
||||
|
||||
void handleDismiss() {
|
||||
if (widget.fadeOnDismiss) {
|
||||
setState(() {
|
||||
_fading = true;
|
||||
});
|
||||
}
|
||||
Future.delayed(Duration(milliseconds: _fadeOutDur), () {
|
||||
setState(() {
|
||||
if (widget.fadeOnDismiss) {
|
||||
_show = false;
|
||||
}
|
||||
if (null != widget.onDismiss) {
|
||||
widget.onDismiss!();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
List<Widget> buttonComponents() {
|
||||
return [
|
||||
Column(
|
||||
children: [
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
TextButton(
|
||||
onPressed: handleDismiss,
|
||||
style: ButtonStyles.style(),
|
||||
child: Text(
|
||||
'OK',
|
||||
style: TextStyles.bodyStyle()
|
||||
.copyWith(color: Colors.white, height: 1.2),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (_show) {
|
||||
return AnimatedOpacity(
|
||||
opacity: _fading ? 0 : 1,
|
||||
curve: Curves.easeOut,
|
||||
duration: Duration(milliseconds: _fadeOutDur),
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(color: widget.lightBoxBgColor),
|
||||
child: Center(
|
||||
child: SizedBox(
|
||||
width: widget.width,
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: widget.cardBgColor,
|
||||
border: Border.all(
|
||||
color: const Color.fromARGB(255, 200, 200, 200),
|
||||
width: 1.0,
|
||||
),
|
||||
boxShadow: const [
|
||||
BoxShadow(
|
||||
color: Color.fromARGB(30, 0, 0, 0),
|
||||
offset: Offset.zero,
|
||||
blurRadius: 4.0,
|
||||
spreadRadius: 2.0),
|
||||
],
|
||||
borderRadius: const BorderRadius.all(Radius.circular(10.0)),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(20.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: widget.content +
|
||||
(widget.buildButton ? buttonComponents() : []),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return const SizedBox(
|
||||
width: 0,
|
||||
height: 0,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,428 @@
|
||||
// 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:math';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'components.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'dart:ui' as ui;
|
||||
import '../model/puzzle_model.dart';
|
||||
import '../page_content/pages_flow.dart';
|
||||
|
||||
class RotatorPuzzle extends StatefulWidget {
|
||||
final PageConfig pageConfig;
|
||||
final int numTiles;
|
||||
final int puzzleNum;
|
||||
final String shaderKey;
|
||||
final int shaderDuration;
|
||||
|
||||
final String tileShadedString;
|
||||
final double tileShadedStringSize;
|
||||
final EdgeInsets tileShadedStringPadding;
|
||||
final int tileShadedStringAnimDuration;
|
||||
final List<WonkyAnimSetting> tileShadedStringAnimSettings;
|
||||
final double tileScaleModifier;
|
||||
|
||||
const RotatorPuzzle({
|
||||
Key? key,
|
||||
required this.pageConfig,
|
||||
required this.numTiles,
|
||||
required this.puzzleNum,
|
||||
required this.shaderKey,
|
||||
required this.shaderDuration,
|
||||
required this.tileShadedString,
|
||||
required this.tileShadedStringSize,
|
||||
required this.tileShadedStringPadding,
|
||||
required this.tileShadedStringAnimDuration,
|
||||
this.tileShadedStringAnimSettings = const [],
|
||||
this.tileScaleModifier = 1.0,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<RotatorPuzzle> createState() => RotatorPuzzleState();
|
||||
}
|
||||
|
||||
class RotatorPuzzleState extends State<RotatorPuzzle>
|
||||
with TickerProviderStateMixin {
|
||||
late PuzzleModel puzzleModel;
|
||||
bool solved = false;
|
||||
late final AnimationController animationController = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 1000),
|
||||
);
|
||||
late final CurvedAnimation animationCurve = CurvedAnimation(
|
||||
parent: animationController,
|
||||
curve: const Interval(
|
||||
0.2,
|
||||
0.45,
|
||||
curve: Curves.easeOut,
|
||||
),
|
||||
);
|
||||
late Animation<double> opacAnimation =
|
||||
Tween<double>(begin: 0.4, end: 1.0).animate(animationCurve)
|
||||
..addListener(() {
|
||||
setState(() {});
|
||||
});
|
||||
|
||||
List<GlobalKey<RotatorPuzzleTileState>> tileKeys = [];
|
||||
GlobalKey<FragmentShadedState> shadedWidgetStackHackStateKey = GlobalKey();
|
||||
GlobalKey shadedWidgetRepaintBoundaryKey = GlobalKey();
|
||||
GlobalKey<WonkyCharState> tileBgWonkyCharKey = GlobalKey();
|
||||
ui.Image? shadedImg;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
for (int i = 0; i < widget.numTiles; i++) {
|
||||
tileKeys.add(GlobalKey<RotatorPuzzleTileState>());
|
||||
}
|
||||
puzzleModel = PuzzleModel(
|
||||
dim: widget.numTiles,
|
||||
); //TODO check if correct; correlate dim and numTiles; probably get rid of numTiles
|
||||
generateTiles();
|
||||
shuffle();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
List<RotatorPuzzleTile> generateTiles() {
|
||||
// TODO move to build?
|
||||
List<RotatorPuzzleTile> tiles = [];
|
||||
int dim = sqrt(widget.numTiles).round();
|
||||
for (int i = 0; i < widget.numTiles; i++) {
|
||||
RotatorPuzzleTile tile = RotatorPuzzleTile(
|
||||
key: tileKeys[i],
|
||||
tileID: i,
|
||||
row: (i / dim).floor(),
|
||||
col: i % dim,
|
||||
parentState: this,
|
||||
shaderKey: widget.shaderKey,
|
||||
shaderDuration: widget.shaderDuration,
|
||||
tileShadedString: widget.tileShadedString,
|
||||
tileShadedStringSize: widget.tileShadedStringSize,
|
||||
tileShadedStringPadding: widget.tileShadedStringPadding,
|
||||
animationSettings: widget.tileShadedStringAnimSettings,
|
||||
tileShadedStringAnimDuration: widget.tileShadedStringAnimDuration,
|
||||
tileScaleModifier: widget.tileScaleModifier,
|
||||
);
|
||||
tiles.add(tile);
|
||||
}
|
||||
return tiles;
|
||||
}
|
||||
|
||||
void handlePointerDown({required int tileID}) {
|
||||
puzzleModel.rotateTile(tileID);
|
||||
if (puzzleModel.allRotationsCorrect()) {
|
||||
handleSolved();
|
||||
}
|
||||
}
|
||||
|
||||
void handleSolved() {
|
||||
animationController.addStatusListener((status) {
|
||||
solved = true;
|
||||
for (GlobalKey<RotatorPuzzleTileState> k in tileKeys) {
|
||||
if (null != k.currentState && k.currentState!.mounted) {
|
||||
startDampening();
|
||||
tileBgWonkyCharKey.currentState!.stopAnimation();
|
||||
}
|
||||
}
|
||||
if (status == AnimationStatus.completed) {
|
||||
Future.delayed(
|
||||
const Duration(milliseconds: FragmentShaded.dampenDuration + 250),
|
||||
() {
|
||||
widget.pageConfig.pageController.nextPage(
|
||||
duration:
|
||||
const Duration(milliseconds: PagesFlow.pageScrollDuration),
|
||||
curve: Curves.easeOut,
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
animationController.forward();
|
||||
}
|
||||
|
||||
void shuffle() {
|
||||
Random rng = Random(0xC00010FF);
|
||||
for (int i = 0; i < widget.numTiles; i++) {
|
||||
int rando = rng.nextInt(3);
|
||||
puzzleModel.setTileStatus(i, rando);
|
||||
if (puzzleModel.allRotationsCorrect()) {
|
||||
// fallback to prevent starting on solved puzzle
|
||||
puzzleModel.setTileStatus(0, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
double tileSize() {
|
||||
return widget.pageConfig.puzzleSize / sqrt(widget.numTiles);
|
||||
}
|
||||
|
||||
List<double> tileCoords({required int row, required int col}) {
|
||||
return <double>[col * tileSize(), row * tileSize()];
|
||||
}
|
||||
|
||||
void setImageFromRepaintBoundary(GlobalKey which) {
|
||||
final BuildContext? context = which.currentContext;
|
||||
if (null != context) {
|
||||
final RenderRepaintBoundary boundary =
|
||||
context.findRenderObject()! as RenderRepaintBoundary;
|
||||
final ui.Image img = boundary.toImageSync();
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
shadedImg = img;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void startDampening() {
|
||||
if (null != shadedWidgetStackHackStateKey.currentState &&
|
||||
shadedWidgetStackHackStateKey.currentState!.mounted) {
|
||||
shadedWidgetStackHackStateKey.currentState!.startDampening();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// TODO fix widget implementation to remove the need for this hack
|
||||
// to force a setState rebuild
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
}
|
||||
});
|
||||
// end hack ----------------
|
||||
setImageFromRepaintBoundary(shadedWidgetRepaintBoundaryKey);
|
||||
return Center(
|
||||
child: SizedBox(
|
||||
width: widget.pageConfig.puzzleSize,
|
||||
height: widget.pageConfig.puzzleSize,
|
||||
child: Opacity(
|
||||
opacity: opacAnimation.value,
|
||||
child: Stack(
|
||||
children: <Widget>[
|
||||
Positioned(
|
||||
left: -9999,
|
||||
top: -9999,
|
||||
child: RepaintBoundary(
|
||||
key: shadedWidgetRepaintBoundaryKey,
|
||||
child: SizedBox(
|
||||
width: widget.pageConfig.puzzleSize * 4,
|
||||
height: widget.pageConfig.puzzleSize * 4,
|
||||
child: Center(
|
||||
child: FragmentShaded(
|
||||
key: shadedWidgetStackHackStateKey,
|
||||
shaderName: widget.shaderKey,
|
||||
shaderDuration: widget.shaderDuration,
|
||||
child: Padding(
|
||||
padding: widget.tileShadedStringPadding,
|
||||
child: WonkyChar(
|
||||
key: tileBgWonkyCharKey,
|
||||
text: widget.tileShadedString,
|
||||
size: widget.tileShadedStringSize,
|
||||
animDurationMillis:
|
||||
widget.tileShadedStringAnimDuration,
|
||||
animationSettings:
|
||||
widget.tileShadedStringAnimSettings,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
] +
|
||||
generateTiles(),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
|
||||
class RotatorPuzzleTile extends StatefulWidget {
|
||||
final int tileID;
|
||||
final RotatorPuzzleState parentState;
|
||||
final String shaderKey;
|
||||
final int shaderDuration;
|
||||
final String tileShadedString;
|
||||
final double tileShadedStringSize;
|
||||
final EdgeInsets tileShadedStringPadding;
|
||||
final int tileShadedStringAnimDuration;
|
||||
final List<WonkyAnimSetting> animationSettings;
|
||||
final double tileScaleModifier;
|
||||
|
||||
// TODO get row/col out into model
|
||||
final int row;
|
||||
final int col;
|
||||
|
||||
RotatorPuzzleTile({
|
||||
Key? key,
|
||||
required this.tileID,
|
||||
required this.row,
|
||||
required this.col,
|
||||
required this.parentState,
|
||||
required this.shaderKey,
|
||||
required this.shaderDuration,
|
||||
required this.tileShadedString,
|
||||
required this.tileShadedStringSize,
|
||||
required this.tileShadedStringPadding,
|
||||
required this.animationSettings,
|
||||
required this.tileShadedStringAnimDuration,
|
||||
required this.tileScaleModifier,
|
||||
}) : super(key: key);
|
||||
|
||||
final State<RotatorPuzzleTile> tileState = RotatorPuzzleTileState();
|
||||
|
||||
@override
|
||||
State<RotatorPuzzleTile> createState() => RotatorPuzzleTileState();
|
||||
}
|
||||
|
||||
class RotatorPuzzleTileState extends State<RotatorPuzzleTile>
|
||||
with TickerProviderStateMixin {
|
||||
double touchedOpac = 0.0;
|
||||
Duration touchedOpacDur = const Duration(milliseconds: 50);
|
||||
late final AnimationController animationController = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(
|
||||
milliseconds: 100,
|
||||
),
|
||||
);
|
||||
late final CurvedAnimation animationCurve = CurvedAnimation(
|
||||
parent: animationController,
|
||||
curve: Curves.easeOut,
|
||||
);
|
||||
late Animation<double> animation;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
animation = Tween<double>(
|
||||
// initialize animation to starting point
|
||||
begin: currentStatus() * pi * 0.5,
|
||||
end: currentStatus() * pi * 0.5,
|
||||
).animate(animationController);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// TODO fix widget implementation to remove the need for this hack
|
||||
// to force a setState rebuild
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
}
|
||||
});
|
||||
// end hack ------------------------------
|
||||
List<double> coords =
|
||||
widget.parentState.tileCoords(row: widget.row, col: widget.col);
|
||||
double zeroPoint = widget.parentState.widget.pageConfig.puzzleSize * .5 -
|
||||
widget.parentState.tileSize() * 0.5;
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
left: coords[0],
|
||||
top: coords[1],
|
||||
child: Transform(
|
||||
transform: Matrix4.rotationZ(animation.value),
|
||||
alignment: Alignment.center,
|
||||
child: GestureDetector(
|
||||
onTap: handlePointerDown,
|
||||
child: ClipRect(
|
||||
child: SizedBox(
|
||||
width: widget.parentState.tileSize(),
|
||||
height: widget.parentState.tileSize(),
|
||||
child: OverflowBox(
|
||||
maxHeight:
|
||||
widget.parentState.widget.pageConfig.puzzleSize,
|
||||
maxWidth:
|
||||
widget.parentState.widget.pageConfig.puzzleSize,
|
||||
child: Transform.translate(
|
||||
offset: Offset(
|
||||
zeroPoint -
|
||||
widget.col * widget.parentState.tileSize(),
|
||||
zeroPoint -
|
||||
widget.row * widget.parentState.tileSize(),
|
||||
),
|
||||
child: SizedBox(
|
||||
width:
|
||||
widget.parentState.widget.pageConfig.puzzleSize,
|
||||
height:
|
||||
widget.parentState.widget.pageConfig.puzzleSize,
|
||||
child: Transform.scale(
|
||||
scale: widget.tileScaleModifier,
|
||||
child: RawImage(
|
||||
image: widget.parentState.shadedImg,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
// puzzle tile overlay fades in/out on tap, to indicate touched tile
|
||||
Positioned(
|
||||
left: coords[0],
|
||||
top: coords[1],
|
||||
child: IgnorePointer(
|
||||
child: AnimatedOpacity(
|
||||
opacity: touchedOpac,
|
||||
duration: touchedOpacDur,
|
||||
onEnd: () {
|
||||
if (touchedOpac == 1.0) {
|
||||
touchedOpac = 0.0;
|
||||
touchedOpacDur = const Duration(milliseconds: 300);
|
||||
setState(() {});
|
||||
}
|
||||
},
|
||||
child: DecoratedBox(
|
||||
decoration: const BoxDecoration(
|
||||
color: Color.fromARGB(120, 0, 0, 0)),
|
||||
child: SizedBox(
|
||||
width: widget.parentState.tileSize(),
|
||||
height: widget.parentState.tileSize(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
void handlePointerDown() {
|
||||
if (!widget.parentState.solved) {
|
||||
int oldStatus = currentStatus();
|
||||
widget.parentState.handlePointerDown(tileID: widget.tileID);
|
||||
touchedOpac = 1.0;
|
||||
touchedOpacDur = const Duration(milliseconds: 100);
|
||||
rotateTile(oldStatus: oldStatus);
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
|
||||
int currentStatus() {
|
||||
return widget.parentState.puzzleModel.getTileStatus(widget.tileID);
|
||||
}
|
||||
|
||||
void rotateTile({required int oldStatus}) {
|
||||
animation = Tween<double>(
|
||||
begin: oldStatus * pi * 0.5,
|
||||
end: currentStatus() * pi * 0.5,
|
||||
).animate(animationController)
|
||||
..addListener(() {
|
||||
setState(() {});
|
||||
});
|
||||
animationController.reset();
|
||||
animationController.forward();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,334 @@
|
||||
// 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:math';
|
||||
import 'package:flutter/material.dart';
|
||||
import '../components/components.dart';
|
||||
|
||||
// WonkyAnimPalette class is meant to be used with WonkyChar
|
||||
// to create animations based on variable font settings (aka 'axes'),
|
||||
// and a few basic settings like scale, rotation, etc.
|
||||
// The choice of variable font axes to implement in this class and
|
||||
// default min/max values for variable font axes are hard-coded
|
||||
// for Amstelvar font, packaged and used in this project.
|
||||
// Other variable fonts will have different available axes and min/max values.
|
||||
//
|
||||
// See articles on variable fonts at https://fonts.google.com/knowledge/topics/variable_fonts
|
||||
// See a list of variable fonts in the Google Fonts lineup, along with
|
||||
// an enumeration of variable font axes at https://fonts.google.com/variablefonts
|
||||
|
||||
class WonkyAnimPalette {
|
||||
const WonkyAnimPalette({
|
||||
Key? key,
|
||||
});
|
||||
static const Curve defaultCurve = Curves.easeInOut;
|
||||
|
||||
// basic (settings unrelated to variable font)
|
||||
static WonkyAnimSetting scale({
|
||||
double from = 1,
|
||||
double to = 2,
|
||||
double startAt = 0,
|
||||
double endAt = 1,
|
||||
Curve curve = defaultCurve,
|
||||
}) {
|
||||
return WonkyAnimSetting(
|
||||
type: 'basic',
|
||||
property: 'scale',
|
||||
fromTo: RangeDbl(from: from, to: to),
|
||||
startAt: startAt,
|
||||
endAt: endAt,
|
||||
curve: curve,
|
||||
);
|
||||
}
|
||||
|
||||
static WonkyAnimSetting offsetX({
|
||||
double from = -50,
|
||||
double to = 50,
|
||||
double startAt = 0,
|
||||
double endAt = 1,
|
||||
Curve curve = defaultCurve,
|
||||
}) {
|
||||
return WonkyAnimSetting(
|
||||
type: 'basic',
|
||||
property: 'offsetX',
|
||||
fromTo: RangeDbl(from: from, to: to),
|
||||
startAt: startAt,
|
||||
endAt: endAt,
|
||||
curve: curve,
|
||||
);
|
||||
}
|
||||
|
||||
static WonkyAnimSetting offsetY({
|
||||
double from = -50,
|
||||
double to = 50,
|
||||
double startAt = 0,
|
||||
double endAt = 1,
|
||||
Curve curve = defaultCurve,
|
||||
}) {
|
||||
return WonkyAnimSetting(
|
||||
type: 'basic',
|
||||
property: 'offsetY',
|
||||
fromTo: RangeDbl(from: from, to: to),
|
||||
startAt: startAt,
|
||||
endAt: endAt,
|
||||
curve: curve,
|
||||
);
|
||||
}
|
||||
|
||||
static WonkyAnimSetting rotation({
|
||||
double from = -pi,
|
||||
double to = pi,
|
||||
double startAt = 0,
|
||||
double endAt = 1,
|
||||
Curve curve = defaultCurve,
|
||||
}) {
|
||||
return WonkyAnimSetting(
|
||||
type: 'basic',
|
||||
property: 'rotation',
|
||||
fromTo: RangeDbl(from: from, to: to),
|
||||
startAt: startAt,
|
||||
endAt: endAt,
|
||||
curve: curve,
|
||||
);
|
||||
}
|
||||
|
||||
static WonkyAnimSetting color({
|
||||
Color from = Colors.blue,
|
||||
Color to = Colors.red,
|
||||
double startAt = 0,
|
||||
double endAt = 1,
|
||||
Curve curve = defaultCurve,
|
||||
}) {
|
||||
return WonkyAnimSetting(
|
||||
type: 'basic',
|
||||
property: 'color',
|
||||
fromTo: RangeColor(from: from, to: to),
|
||||
startAt: startAt,
|
||||
endAt: endAt,
|
||||
curve: curve,
|
||||
);
|
||||
}
|
||||
|
||||
// font variants (variable font settings)
|
||||
static WonkyAnimSetting opticalSize({
|
||||
double from = 8,
|
||||
double to = 144,
|
||||
double startAt = 0,
|
||||
double endAt = 1,
|
||||
Curve curve = defaultCurve,
|
||||
}) {
|
||||
return WonkyAnimSetting(
|
||||
type: 'fv',
|
||||
property: 'opsz',
|
||||
fromTo: RangeDbl(from: from, to: to),
|
||||
startAt: startAt,
|
||||
endAt: endAt,
|
||||
curve: curve,
|
||||
);
|
||||
}
|
||||
|
||||
static WonkyAnimSetting weight({
|
||||
double from = 100,
|
||||
double to = 1000,
|
||||
double startAt = 0,
|
||||
double endAt = 1,
|
||||
Curve curve = defaultCurve,
|
||||
}) {
|
||||
return WonkyAnimSetting(
|
||||
type: 'fv',
|
||||
property: 'wght',
|
||||
fromTo: RangeDbl(from: from, to: to),
|
||||
startAt: startAt,
|
||||
endAt: endAt,
|
||||
curve: curve,
|
||||
);
|
||||
}
|
||||
|
||||
static WonkyAnimSetting grade({
|
||||
double from = -300,
|
||||
double to = 500,
|
||||
double startAt = 0,
|
||||
double endAt = 1,
|
||||
Curve curve = defaultCurve,
|
||||
}) {
|
||||
return WonkyAnimSetting(
|
||||
type: 'fv',
|
||||
property: 'GRAD',
|
||||
fromTo: RangeDbl(from: from, to: to),
|
||||
startAt: startAt,
|
||||
endAt: endAt,
|
||||
curve: curve,
|
||||
);
|
||||
}
|
||||
|
||||
static WonkyAnimSetting slant({
|
||||
double from = -10,
|
||||
double to = 0,
|
||||
double startAt = 0,
|
||||
double endAt = 1,
|
||||
Curve curve = defaultCurve,
|
||||
}) {
|
||||
return WonkyAnimSetting(
|
||||
type: 'fv',
|
||||
property: 'slnt',
|
||||
fromTo: RangeDbl(from: from, to: to),
|
||||
startAt: startAt,
|
||||
endAt: endAt,
|
||||
curve: curve,
|
||||
);
|
||||
}
|
||||
|
||||
static WonkyAnimSetting width({
|
||||
double from = 50,
|
||||
double to = 125,
|
||||
double startAt = 0,
|
||||
double endAt = 1,
|
||||
Curve curve = defaultCurve,
|
||||
}) {
|
||||
return WonkyAnimSetting(
|
||||
type: 'fv',
|
||||
property: 'wdth',
|
||||
fromTo: RangeDbl(from: from, to: to),
|
||||
startAt: startAt,
|
||||
endAt: endAt,
|
||||
curve: curve,
|
||||
);
|
||||
}
|
||||
|
||||
static WonkyAnimSetting thickStroke({
|
||||
double from = 18,
|
||||
double to = 263,
|
||||
double startAt = 0,
|
||||
double endAt = 1,
|
||||
Curve curve = defaultCurve,
|
||||
}) {
|
||||
return WonkyAnimSetting(
|
||||
type: 'fv',
|
||||
property: 'XOPQ',
|
||||
fromTo: RangeDbl(from: from, to: to),
|
||||
startAt: startAt,
|
||||
endAt: endAt,
|
||||
curve: curve,
|
||||
);
|
||||
}
|
||||
|
||||
static WonkyAnimSetting thinStroke({
|
||||
double from = 15,
|
||||
double to = 132,
|
||||
double startAt = 0,
|
||||
double endAt = 1,
|
||||
Curve curve = defaultCurve,
|
||||
}) {
|
||||
return WonkyAnimSetting(
|
||||
type: 'fv',
|
||||
property: 'YOPQ',
|
||||
fromTo: RangeDbl(from: from, to: to),
|
||||
startAt: startAt,
|
||||
endAt: endAt,
|
||||
curve: curve,
|
||||
);
|
||||
}
|
||||
|
||||
static WonkyAnimSetting counterWd({
|
||||
double from = 324,
|
||||
double to = 640,
|
||||
double startAt = 0,
|
||||
double endAt = 1,
|
||||
Curve curve = defaultCurve,
|
||||
}) {
|
||||
return WonkyAnimSetting(
|
||||
type: 'fv',
|
||||
property: 'XTRA',
|
||||
fromTo: RangeDbl(from: from, to: to),
|
||||
startAt: startAt,
|
||||
endAt: endAt,
|
||||
curve: curve,
|
||||
);
|
||||
}
|
||||
|
||||
static WonkyAnimSetting upperCaseHt({
|
||||
double from = 500,
|
||||
double to = 1000,
|
||||
double startAt = 0,
|
||||
double endAt = 1,
|
||||
Curve curve = defaultCurve,
|
||||
}) {
|
||||
return WonkyAnimSetting(
|
||||
type: 'fv',
|
||||
property: 'YTUC',
|
||||
fromTo: RangeDbl(from: from, to: to),
|
||||
startAt: startAt,
|
||||
endAt: endAt,
|
||||
curve: curve,
|
||||
);
|
||||
}
|
||||
|
||||
static WonkyAnimSetting lowerCaseHt({
|
||||
double from = 420,
|
||||
double to = 570,
|
||||
double startAt = 0,
|
||||
double endAt = 1,
|
||||
Curve curve = defaultCurve,
|
||||
}) {
|
||||
return WonkyAnimSetting(
|
||||
type: 'fv',
|
||||
property: 'YTLC',
|
||||
fromTo: RangeDbl(from: from, to: to),
|
||||
startAt: startAt,
|
||||
endAt: endAt,
|
||||
curve: curve,
|
||||
);
|
||||
}
|
||||
|
||||
static WonkyAnimSetting ascenderHt({
|
||||
double from = 500,
|
||||
double to = 983,
|
||||
double startAt = 0,
|
||||
double endAt = 1,
|
||||
Curve curve = defaultCurve,
|
||||
}) {
|
||||
return WonkyAnimSetting(
|
||||
type: 'fv',
|
||||
property: 'YTAS',
|
||||
fromTo: RangeDbl(from: from, to: to),
|
||||
startAt: startAt,
|
||||
endAt: endAt,
|
||||
curve: curve,
|
||||
);
|
||||
}
|
||||
|
||||
static WonkyAnimSetting descenderDepth({
|
||||
double from = -500,
|
||||
double to = -138,
|
||||
double startAt = 0,
|
||||
double endAt = 1,
|
||||
Curve curve = defaultCurve,
|
||||
}) {
|
||||
return WonkyAnimSetting(
|
||||
type: 'fv',
|
||||
property: 'YTDE',
|
||||
fromTo: RangeDbl(from: from, to: to),
|
||||
startAt: startAt,
|
||||
endAt: endAt,
|
||||
curve: curve,
|
||||
);
|
||||
}
|
||||
|
||||
static WonkyAnimSetting figureHt({
|
||||
double from = 425,
|
||||
double to = 1000,
|
||||
double startAt = 0,
|
||||
double endAt = 1,
|
||||
Curve curve = defaultCurve,
|
||||
}) {
|
||||
return WonkyAnimSetting(
|
||||
type: 'fv',
|
||||
property: 'YTFI',
|
||||
fromTo: RangeDbl(from: from, to: to),
|
||||
startAt: startAt,
|
||||
endAt: endAt,
|
||||
curve: curve,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
// 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 'package:flutter/material.dart';
|
||||
import 'dart:ui';
|
||||
import 'package:flutter/foundation.dart' show kDebugMode;
|
||||
|
||||
class WonkyChar extends StatefulWidget {
|
||||
final String text;
|
||||
final double size;
|
||||
final double baseRotation;
|
||||
final int animDurationMillis;
|
||||
final List<WonkyAnimSetting> animationSettings;
|
||||
const WonkyChar({
|
||||
Key? key,
|
||||
required this.text,
|
||||
required this.size,
|
||||
this.baseRotation = 0,
|
||||
this.animDurationMillis = 1000,
|
||||
this.animationSettings = const <WonkyAnimSetting>[],
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<WonkyChar> createState() => WonkyCharState();
|
||||
}
|
||||
|
||||
class WonkyCharState extends State<WonkyChar>
|
||||
with SingleTickerProviderStateMixin {
|
||||
bool loopingAnimation = true;
|
||||
late AnimationController _animController;
|
||||
final List<Animation<double>> _curves = [];
|
||||
late final List<Animation> _fvAnimations = [];
|
||||
final List<String> _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<double>(begin: 1, end: 1).animate(defaultCurve);
|
||||
late Animation _offsetXAnimation =
|
||||
Tween<double>(begin: 0, end: 0).animate(defaultCurve);
|
||||
late Animation _offsetYAnimation =
|
||||
Tween<double>(begin: 0, end: 0).animate(defaultCurve);
|
||||
late Animation _rotationAnimation =
|
||||
Tween<double>(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<FontVariation> fontVariations = [];
|
||||
for (int i = 0; i < _fvAxes.length; i++) {
|
||||
fontVariations.add(FontVariation(_fvAxes[i], _fvAnimations[i].value));
|
||||
}
|
||||
return Transform(
|
||||
alignment: Alignment.center,
|
||||
transform: Matrix4.translationValues(
|
||||
_offsetXAnimation.value, _offsetYAnimation.value, 0)
|
||||
..scale(_scaleAnimation.value)
|
||||
..rotateZ(widget.baseRotation + _rotationAnimation.value),
|
||||
child: IgnorePointer(
|
||||
child: Text(
|
||||
widget.text,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: _colorAnimation.value,
|
||||
fontFamily: 'Amstelvar',
|
||||
fontSize: widget.size,
|
||||
fontVariations: fontVariations,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void initAnimations(List<WonkyAnimSetting> 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(), end: s.fromTo.toValue())
|
||||
.animate(curve);
|
||||
} else {
|
||||
animation =
|
||||
Tween<double>(begin: s.fromTo.fromValue(), end: s.fromTo.toValue())
|
||||
.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;
|
||||
}
|
||||
break;
|
||||
case 'rotation':
|
||||
{
|
||||
_rotationAnimation = animation;
|
||||
}
|
||||
break;
|
||||
case 'offsetX':
|
||||
{
|
||||
_offsetXAnimation = animation;
|
||||
}
|
||||
break;
|
||||
case 'offsetY':
|
||||
{
|
||||
_offsetYAnimation = animation;
|
||||
}
|
||||
break;
|
||||
case 'color':
|
||||
{
|
||||
_colorAnimation = animation;
|
||||
}
|
||||
break;
|
||||
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();
|
||||
fromValue() {}
|
||||
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,
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user