Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add flare_flutter.dart entrypoint, add FlareSpammerActor, FlareProgressController #154

Open
wants to merge 5 commits into
base: stable
Choose a base branch
from
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
15 changes: 15 additions & 0 deletions flare_flutter/lib/flare_flutter.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/// Flare offers powerful realtime vector design and animation for app
/// and game designers alike. The primary goal of Flare is to allow designers
/// to work directly with assets that run in their final product, eliminating
/// the need to redo that work in code.

library flare_flutter;

export 'package:flare_dart/math/mat2d.dart';

export 'flare.dart';
export 'flare_actor.dart';
export 'flare_controller.dart';
export 'flare_controls.dart';
export 'flare_progress_controller.dart';
export 'flare_spammer_actor.dart';
43 changes: 43 additions & 0 deletions flare_flutter/lib/flare_progress_controller.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import 'package:flare_dart/math/mat2d.dart';

import 'flare.dart';
import 'flare_controller.dart';

/// A naiive controller that allows you to scrub through a single animation.
/// Useful for wiring up progress bars or scroll based animations.
class FlareProgressController extends FlareController {
FlareProgressController(this.animation);

final String animation;

FlutterActorArtboard _artboard;
ActorAnimation _animation;

@override
bool advance(FlutterActorArtboard artboard, double elapsed) {
return false;
}

@override
void initialize(FlutterActorArtboard artboard) {
_artboard = artboard;
if (_artboard != null) {
_animation = artboard.getAnimation(animation);

if (_animation != null) {
_animation.apply(0.0, _artboard, 1.0);
}
}
}

/// Updates the animation progress and triggers a render
void update(double t) {
if (_animation != null) {
final time = _animation.duration * t;
_animation.apply(time, _artboard, 1.0);
}
}

@override
void setViewTransform(Mat2D viewTransform) {}
}
232 changes: 232 additions & 0 deletions flare_flutter/lib/flare_spammer_actor.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
import 'package:flare_flutter/flare.dart';
import 'package:flare_flutter/flare_render_box.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flare_dart/math/mat2d.dart';
import 'package:flare_dart/math/aabb.dart';

/// Triggers a [FlareSpammerActor] to fire an animation
class FlareSpammerController extends ChangeNotifier {
void trigger() {
notifyListeners();
}
}

/// Allows spamming of multiple animations simultaneously. Useful for
/// firing multiple like animations simultaneously similar to hearts
/// in Facebook live streams.
class FlareSpammerActor extends LeafRenderObjectWidget {
const FlareSpammerActor(
this.filename, {
@required this.animationBuilder,
@required this.controller,
this.fit = BoxFit.contain,
this.alignment = Alignment.center,
this.snapToEnd = false,
this.artboard,
});

/// Name of the Flare file to be loaded from the AssetBundle.
final String filename;

/// The name of the artboard to display.
final String artboard;

/// The name of the animation to play. Provides the number of currently
/// playing animations on screen.
final String Function(int count) animationBuilder;

/// When true, the animation will be applied at the end of its duration.
final bool snapToEnd;

/// The BoxFit strategy used to scale the Flare content into the
/// bounds of this widget.
final BoxFit fit;

/// The alignment that will be applied in conjuction to the [fit] to align
/// the Flare content within the bounds of this widget.
final Alignment alignment;

final FlareSpammerController controller;

@override
RenderObject createRenderObject(BuildContext context) {
return _FlareSpammerRenderObject(animationBuilder)
..assetBundle = DefaultAssetBundle.of(context)
..filename = filename
..fit = fit
..alignment = alignment
..controller = controller;
}

@override
void updateRenderObject(
BuildContext context, covariant _FlareSpammerRenderObject renderObject) {
renderObject
..assetBundle = DefaultAssetBundle.of(context)
..filename = filename
..fit = fit
..alignment = alignment
..controller = controller;
}

@override
void didUnmountRenderObject(
covariant _FlareSpammerRenderObject renderObject) {
renderObject.dispose();
}
}

class _RepaintAnimation {
final ActorAnimation animation;
double time = 0.0;
void apply(FlutterActorArtboard artboard) {
animation.apply(time, artboard, 1.0);
}

double get duration => animation.duration;
bool get isDone => time >= animation.duration;
bool get hasAnimation => animation != null;

_RepaintAnimation(this.animation);
}

/// Does the heavy lifting
class _FlareSpammerRenderObject extends FlareRenderBox {
_FlareSpammerRenderObject(this.animationBuilder);

final String Function(int) animationBuilder;

String _filename;
FlutterActor _actor;
FlareSpammerController _controller;

FlareSpammerController get controller => _controller;
set controller(FlareSpammerController value) {
if (_controller == value) {
return;
}
_controller = value;
_controller.addListener(fireHeart);
}

void fireHeart() {
final newAnimation = _RepaintAnimation(
_artboard?.getAnimation(
animationBuilder(_repaintAnimations.length),
),
);

if (newAnimation.hasAnimation) {
_repaintAnimations.add(newAnimation);
}
}

final List<_RepaintAnimation> _repaintAnimations = [];

FlutterActorArtboard _artboard;
AABB _setupAABB;

void updateBounds() {
if (_artboard != null) {
_setupAABB = _artboard.artboardAABB();
}
}

/// We're playing if we're not paused and our controller is active (or
/// there's no controller) or there are animations running.
@override
bool get isPlaying => _repaintAnimations.isNotEmpty;

@override
void onUnload() {
_repaintAnimations.clear();
controller?.removeListener(fireHeart);
}

String get filename => _filename;
set filename(String value) {
if (value == _filename) {
return;
}
_filename = value;

if (_filename == null) {
markNeedsPaint();
}
// file will change, let's clear out old animations.
_repaintAnimations.clear();
load();
}

bool _instanceArtboard() {
if (_actor == null || _actor.artboard == null) {
return false;
}
FlutterActorArtboard artboard =
_actor.artboard.makeInstance() as FlutterActorArtboard;
artboard.initializeGraphics();
_artboard = artboard;
_artboard.advance(0.0);
updateBounds();
markNeedsPaint();
return true;
}

@override
Future<void> load() async {
if (_filename == null) {
return;
}
_actor = await loadFlare(_filename);
if (_actor == null || _actor.artboard == null) {
return;
}
_instanceArtboard();
}

@override
void advance(double elapsedSeconds) {
// advance just moves every animation forward
if (isPlaying) {
for (final _RepaintAnimation animation in _repaintAnimations) {
animation.time += elapsedSeconds;
}
}

if (_artboard != null) {
_artboard.advance(elapsedSeconds);
}
}

@override
AABB get aabb => _setupAABB;

@override
void prePaint(Canvas canvas, Offset offset) {
// disable clipping for now
// canvas.clipRect(offset & size);
}

@override
void paintFlare(Canvas canvas, Mat2D viewTransform) {
if (_artboard == null) {
return;
}

// Apply, paint, and prune.
List<_RepaintAnimation> prune = [];
for (final _RepaintAnimation animation in _repaintAnimations) {
animation.apply(_artboard);
// Don't have a sense of elapsed time here, so just pass 0 for time.
_artboard.advance(0);
_artboard.draw(canvas);
if (animation.isDone) {
prune.add(animation);
}
}
for (final _RepaintAnimation animation in prune) {
_repaintAnimations.remove(animation);
}
}
}