forked from firka/student-legacy
481 lines
14 KiB
Dart
481 lines
14 KiB
Dart
// MIT License
|
|
|
|
// Copyright (c) 2018 Norbert Kozsir
|
|
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to deal
|
|
// in the Software without restriction, including without limitation the rights
|
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
// copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
|
|
// The above copyright notice and this permission notice shall be included in all
|
|
// copies or substantial portions of the Software.
|
|
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
// SOFTWARE.
|
|
|
|
// ignore_for_file: invalid_use_of_protected_member
|
|
|
|
import 'dart:math';
|
|
import 'package:flutter/material.dart';
|
|
|
|
typedef ParticleBuilder = Particle Function(int index);
|
|
|
|
abstract class Particle {
|
|
void paint(Canvas canvas, Size size, double progress, int seed);
|
|
}
|
|
|
|
class FourRandomSlotParticle extends Particle {
|
|
final List<Particle> children;
|
|
|
|
final double relativeDistanceToMiddle;
|
|
|
|
FourRandomSlotParticle(
|
|
{required this.children, this.relativeDistanceToMiddle = 2.0});
|
|
|
|
@override
|
|
void paint(Canvas canvas, Size size, double progress, int seed) {
|
|
Random random = Random(seed);
|
|
int side = 0;
|
|
for (Particle particle in children) {
|
|
PositionedParticle(
|
|
position: sideToOffset(side, size, random) * relativeDistanceToMiddle,
|
|
child: particle,
|
|
).paint(canvas, size, progress, seed);
|
|
side++;
|
|
}
|
|
}
|
|
|
|
Offset sideToOffset(int side, Size size, Random random) {
|
|
if (side == 0) {
|
|
return Offset(-random.nextDouble() * (size.width / 2),
|
|
-random.nextDouble() * (size.height / 2));
|
|
} else if (side == 1) {
|
|
return Offset(random.nextDouble() * (size.width / 2),
|
|
-random.nextDouble() * (size.height / 2));
|
|
} else if (side == 2) {
|
|
return Offset(random.nextDouble() * (size.width / 2),
|
|
random.nextDouble() * (size.height / 2));
|
|
} else if (side == 3) {
|
|
return Offset(-random.nextDouble() * (size.width / 2),
|
|
random.nextDouble() * (size.height / 2));
|
|
} else {
|
|
throw Exception();
|
|
}
|
|
}
|
|
|
|
double randomOffset(Random random, int range) {
|
|
return range / 2 - random.nextInt(range);
|
|
}
|
|
}
|
|
|
|
class PoppingCircle extends Particle {
|
|
final Color color;
|
|
|
|
PoppingCircle({required this.color});
|
|
|
|
final double radius = 3.0;
|
|
|
|
@override
|
|
void paint(Canvas canvas, Size size, double progress, seed) {
|
|
if (progress < 0.5) {
|
|
canvas.drawCircle(
|
|
Offset.zero,
|
|
radius + (progress * 8),
|
|
Paint()
|
|
..color = color
|
|
..style = PaintingStyle.stroke
|
|
..strokeWidth = 5.0 - progress * 2);
|
|
} else {
|
|
CircleMirror(
|
|
numberOfParticles: 4,
|
|
child: AnimatedPositionedParticle(
|
|
begin: const Offset(0.0, 5.0),
|
|
end: const Offset(0.0, 15.0),
|
|
child: FadingRect(
|
|
color: color,
|
|
height: 7.0,
|
|
width: 2.0,
|
|
)),
|
|
initialRotation: pi / 4,
|
|
).paint(canvas, size, progress, seed);
|
|
}
|
|
}
|
|
}
|
|
|
|
class Firework extends Particle {
|
|
@override
|
|
void paint(Canvas canvas, Size size, double progress, int seed) {
|
|
FourRandomSlotParticle(children: [
|
|
IntervalParticle(
|
|
interval: const Interval(0.0, 0.5, curve: Curves.easeIn),
|
|
child: PoppingCircle(
|
|
color: Colors.deepOrangeAccent,
|
|
),
|
|
),
|
|
IntervalParticle(
|
|
interval: const Interval(0.2, 0.5, curve: Curves.easeIn),
|
|
child: PoppingCircle(
|
|
color: Colors.green,
|
|
),
|
|
),
|
|
IntervalParticle(
|
|
interval: const Interval(0.4, 0.8, curve: Curves.easeIn),
|
|
child: PoppingCircle(
|
|
color: Colors.indigo,
|
|
),
|
|
),
|
|
IntervalParticle(
|
|
interval: const Interval(0.5, 1.0, curve: Curves.easeIn),
|
|
child: PoppingCircle(
|
|
color: Colors.teal,
|
|
),
|
|
),
|
|
]).paint(canvas, size, progress, seed);
|
|
}
|
|
}
|
|
|
|
/// Mirrors a given particle around a circle.
|
|
///
|
|
/// When using the default constructor you specify one [Particle], this particle
|
|
/// is going to be used on its own, this implies that
|
|
/// all mirrored particles are identical (expect for the rotation around the circle)
|
|
class CircleMirror extends Particle {
|
|
final ParticleBuilder particleBuilder;
|
|
|
|
final double initialRotation;
|
|
|
|
final int numberOfParticles;
|
|
|
|
CircleMirror.builder(
|
|
{required this.particleBuilder,
|
|
required this.initialRotation,
|
|
required this.numberOfParticles});
|
|
|
|
CircleMirror(
|
|
{required Particle child,
|
|
required this.initialRotation,
|
|
required this.numberOfParticles})
|
|
: particleBuilder = ((index) => child);
|
|
|
|
@override
|
|
void paint(Canvas canvas, Size size, double progress, seed) {
|
|
canvas.save();
|
|
canvas.rotate(initialRotation);
|
|
for (int i = 0; i < numberOfParticles; i++) {
|
|
particleBuilder(i).paint(canvas, size, progress, seed);
|
|
canvas.rotate(pi / (numberOfParticles / 2));
|
|
}
|
|
canvas.restore();
|
|
}
|
|
}
|
|
|
|
/// Mirrors a given particle around a circle.
|
|
///
|
|
/// When using the default constructor you specify one [Particle], this particle
|
|
/// is going to be used on its own, this implies that
|
|
/// all mirrored particles are identical (expect for the rotation around the circle)
|
|
class RectangleMirror extends Particle {
|
|
final ParticleBuilder particleBuilder;
|
|
|
|
/// Position of the first particle on the rect
|
|
final double initialDistance;
|
|
|
|
final int numberOfParticles;
|
|
|
|
RectangleMirror.builder(
|
|
{required this.particleBuilder,
|
|
required this.initialDistance,
|
|
required this.numberOfParticles});
|
|
|
|
RectangleMirror(
|
|
{required Particle child,
|
|
required this.initialDistance,
|
|
required this.numberOfParticles})
|
|
: particleBuilder = ((index) => child);
|
|
|
|
@override
|
|
void paint(Canvas canvas, Size size, double progress, seed) {
|
|
canvas.save();
|
|
double totalLength = size.width * 2 + size.height * 2;
|
|
double distanceBetweenParticles = totalLength / numberOfParticles;
|
|
|
|
bool onHorizontalAxis = true;
|
|
int side = 0;
|
|
|
|
assert((distanceBetweenParticles * numberOfParticles).round() ==
|
|
totalLength.round());
|
|
|
|
canvas.translate(-size.width / 2, -size.height / 2);
|
|
|
|
double currentDistance = initialDistance;
|
|
for (int i = 0; i < numberOfParticles; i++) {
|
|
while (true) {
|
|
if (onHorizontalAxis
|
|
? currentDistance > size.width
|
|
: currentDistance > size.height) {
|
|
currentDistance -= onHorizontalAxis ? size.width : size.height;
|
|
onHorizontalAxis = !onHorizontalAxis;
|
|
side = (++side) % 4;
|
|
} else {
|
|
if (side == 0) {
|
|
assert(onHorizontalAxis);
|
|
moveTo(canvas, size, 0, currentDistance, 0.0, () {
|
|
particleBuilder(i).paint(canvas, size, progress, seed);
|
|
});
|
|
} else if (side == 1) {
|
|
assert(!onHorizontalAxis);
|
|
moveTo(canvas, size, 1, size.width, currentDistance, () {
|
|
particleBuilder(i).paint(canvas, size, progress, seed);
|
|
});
|
|
} else if (side == 2) {
|
|
assert(onHorizontalAxis);
|
|
moveTo(canvas, size, 2, size.width - currentDistance, size.height,
|
|
() {
|
|
particleBuilder(i).paint(canvas, size, progress, seed);
|
|
});
|
|
} else if (side == 3) {
|
|
assert(!onHorizontalAxis);
|
|
moveTo(canvas, size, 3, 0.0, size.height - currentDistance, () {
|
|
particleBuilder(i).paint(canvas, size, progress, seed);
|
|
});
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
currentDistance += distanceBetweenParticles;
|
|
}
|
|
|
|
canvas.restore();
|
|
}
|
|
|
|
void moveTo(Canvas canvas, Size size, int side, double x, double y,
|
|
VoidCallback painter) {
|
|
canvas.save();
|
|
canvas.translate(x, y);
|
|
canvas.rotate(-atan2(size.width / 2 - x, size.height / 2 - y));
|
|
painter();
|
|
canvas.restore();
|
|
}
|
|
}
|
|
|
|
/// Offsets a child by a given [Offset]
|
|
class PositionedParticle extends Particle {
|
|
PositionedParticle({required this.position, required this.child});
|
|
|
|
final Particle child;
|
|
|
|
final Offset position;
|
|
|
|
@override
|
|
void paint(Canvas canvas, Size size, double progress, seed) {
|
|
canvas.save();
|
|
canvas.translate(position.dx, position.dy);
|
|
child.paint(canvas, size, progress, seed);
|
|
canvas.restore();
|
|
}
|
|
}
|
|
|
|
/// Animates a childs position based on a Tween<Offset>
|
|
class AnimatedPositionedParticle extends Particle {
|
|
AnimatedPositionedParticle(
|
|
{required Offset begin, required Offset end, required this.child})
|
|
: offsetTween = Tween<Offset>(begin: begin, end: end);
|
|
|
|
final Particle child;
|
|
|
|
final Tween<Offset> offsetTween;
|
|
|
|
@override
|
|
void paint(Canvas canvas, Size size, double progress, seed) {
|
|
canvas.save();
|
|
canvas.translate(
|
|
offsetTween.lerp(progress).dx, offsetTween.lerp(progress).dy);
|
|
child.paint(canvas, size, progress, seed);
|
|
canvas.restore();
|
|
}
|
|
}
|
|
|
|
/// Specifies an [Interval] for its child.
|
|
///
|
|
/// Instead of applying a curve the the input parameters of the paint method,
|
|
/// apply it with this Particle.
|
|
///
|
|
/// If you want you child to only animate from 0.0 - 0.5 (relative), specify an [Interval] with those values.
|
|
class IntervalParticle extends Particle {
|
|
final Interval interval;
|
|
|
|
final Particle child;
|
|
|
|
IntervalParticle({required this.child, required this.interval});
|
|
|
|
@override
|
|
void paint(Canvas canvas, Size size, double progress, seed) {
|
|
if (progress < interval.begin || progress > interval.end) return;
|
|
child.paint(canvas, size, interval.transform(progress), seed);
|
|
}
|
|
}
|
|
|
|
/// Does nothing else than holding a list of particles and painting them in that order
|
|
class CompositeParticle extends Particle {
|
|
final List<Particle> children;
|
|
|
|
CompositeParticle({required this.children});
|
|
|
|
@override
|
|
void paint(Canvas canvas, Size size, double progress, seed) {
|
|
for (Particle particle in children) {
|
|
particle.paint(canvas, size, progress, seed);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A particle which rotates the child.
|
|
///
|
|
/// Does not animate.
|
|
class RotationParticle extends Particle {
|
|
final Particle child;
|
|
|
|
final double rotation;
|
|
|
|
RotationParticle({required this.child, required this.rotation});
|
|
|
|
@override
|
|
void paint(Canvas canvas, Size size, double progress, int seed) {
|
|
canvas.save();
|
|
canvas.rotate(rotation);
|
|
child.paint(canvas, size, progress, seed);
|
|
canvas.restore();
|
|
}
|
|
}
|
|
|
|
/// A particle which rotates a child along a given [Tween]
|
|
class AnimatedRotationParticle extends Particle {
|
|
final Particle child;
|
|
|
|
final Tween<double> rotation;
|
|
|
|
AnimatedRotationParticle(
|
|
{required this.child, required double begin, required double end})
|
|
: rotation = Tween<double>(begin: begin, end: end);
|
|
|
|
@override
|
|
void paint(Canvas canvas, Size size, double progress, int seed) {
|
|
canvas.save();
|
|
canvas.rotate(rotation.lerp(progress));
|
|
child.paint(canvas, size, progress, seed);
|
|
canvas.restore();
|
|
}
|
|
}
|
|
|
|
/// Geometry
|
|
///
|
|
/// These are some basic geometric classes which also fade out as time goes on.
|
|
/// Each primitive should draw itself at the origin. If the orientation matters it should be directed to the top
|
|
/// (negative y)
|
|
///
|
|
/// A rectangle which also fades out over time.
|
|
class FadingRect extends Particle {
|
|
final Color color;
|
|
final double width;
|
|
final double height;
|
|
|
|
FadingRect({required this.color, required this.width, required this.height});
|
|
|
|
@override
|
|
void paint(Canvas canvas, Size size, double progress, seed) {
|
|
canvas.drawRect(Rect.fromLTWH(0.0, 0.0, width, height),
|
|
Paint()..color = color.withValues(alpha: 1 - progress));
|
|
}
|
|
}
|
|
|
|
/// A circle which fades out over time
|
|
class FadingCircle extends Particle {
|
|
final Color color;
|
|
final double radius;
|
|
|
|
FadingCircle({required this.color, required this.radius});
|
|
|
|
@override
|
|
void paint(Canvas canvas, Size size, double progress, seed) {
|
|
canvas.drawCircle(Offset.zero, radius,
|
|
Paint()..color = color.withValues(alpha: 1 - progress));
|
|
}
|
|
}
|
|
|
|
/// A triangle which also fades out over time
|
|
class FadingTriangle extends Particle {
|
|
/// This controls the shape of the triangle.
|
|
///
|
|
/// Value between 0 and 1
|
|
final double variation;
|
|
|
|
final Color color;
|
|
|
|
/// The size of the base side of the triangle.
|
|
final double baseSize;
|
|
|
|
/// This is the factor of how much bigger then length than the width is
|
|
final double heightToBaseFactor;
|
|
|
|
FadingTriangle(
|
|
{required this.variation,
|
|
required this.color,
|
|
required this.baseSize,
|
|
required this.heightToBaseFactor});
|
|
|
|
@override
|
|
void paint(Canvas canvas, Size size, double progress, int seed) {
|
|
Path path = Path();
|
|
path.moveTo(0.0, 0.0);
|
|
path.lineTo(baseSize * variation, baseSize * heightToBaseFactor);
|
|
path.lineTo(baseSize, 0.0);
|
|
path.close();
|
|
canvas.drawPath(
|
|
path, Paint()..color = color.withValues(alpha: 1 - progress));
|
|
}
|
|
}
|
|
|
|
/// An ugly looking "snake"
|
|
///
|
|
/// See for yourself
|
|
class FadingSnake extends Particle {
|
|
final double width;
|
|
final double segmentLength;
|
|
final int segments;
|
|
final double curvyness;
|
|
|
|
final Color color;
|
|
|
|
FadingSnake(
|
|
{required this.width,
|
|
required this.segmentLength,
|
|
required this.segments,
|
|
required this.curvyness,
|
|
required this.color});
|
|
|
|
@override
|
|
void paint(Canvas canvas, Size size, double progress, int seed) {
|
|
canvas.save();
|
|
canvas.rotate(pi / 6);
|
|
Path path = Path();
|
|
for (int i = 0; i < segments; i++) {
|
|
path.quadraticBezierTo(curvyness * i, segmentLength * (i + 1),
|
|
curvyness * (i + 1), segmentLength * (i + 1));
|
|
}
|
|
for (int i = segments - 1; i >= 0; i--) {
|
|
path.quadraticBezierTo(curvyness * (i + 1), segmentLength * i - curvyness,
|
|
curvyness * i, segmentLength * i - curvyness);
|
|
}
|
|
path.close();
|
|
canvas.drawPath(path, Paint()..color = color);
|
|
canvas.restore();
|
|
}
|
|
}
|