mirror of
https://github.com/rive-app/rive-flutter.git
synced 2025-11-01 20:19:39 +08:00
feat(flutter): add shared texture through rive panel (#10451) 7359e8b824
* feat(flutter): add shared texture through rive panel * feat: updates to rive panel * chore: revert pub path change * docs: update api level docs * feat: expose drawOrder feat: Add fallback AtlasTypes that don't need float color buffers (#10475) 5e6f683b9e Floating point color buffers are only supported via extensions in GL. Previously, the feather atlas would just break when this functionality wasn't present. This PR adds support for multiple different AtlasTypes that make use of various GL extensions to render the atlas. As a final resort, if none of the other extensions are available, it can split coverage up into rgba8 compoments. This mode works on unextended GL at the cost of quality. Co-authored-by: Gordon <pggordonhayes@gmail.com>
This commit is contained in:
@ -1 +1 @@
|
||||
f7f795e53015b92c38abe175259caa7b5032cb76
|
||||
7359e8b824801f268a0506ae948ab5845f610d8c
|
||||
|
||||
BIN
example/assets/centaur_v2.riv
Normal file
BIN
example/assets/centaur_v2.riv
Normal file
Binary file not shown.
BIN
example/assets/perf/rivs/Tom_Morello_2.riv
Normal file
BIN
example/assets/perf/rivs/Tom_Morello_2.riv
Normal file
Binary file not shown.
BIN
example/assets/perf/rivs/Zombie_Character.riv
Normal file
BIN
example/assets/perf/rivs/Zombie_Character.riv
Normal file
Binary file not shown.
BIN
example/assets/perf/rivs/adventuretime_marceline_pb.riv
Normal file
BIN
example/assets/perf/rivs/adventuretime_marceline_pb.riv
Normal file
Binary file not shown.
BIN
example/assets/perf/rivs/skull_404.riv
Normal file
BIN
example/assets/perf/rivs/skull_404.riv
Normal file
Binary file not shown.
BIN
example/assets/perf/rivs/towersDemo.riv
Normal file
BIN
example/assets/perf/rivs/towersDemo.riv
Normal file
Binary file not shown.
BIN
example/assets/perf/rivs/travel_icons.riv
Normal file
BIN
example/assets/perf/rivs/travel_icons.riv
Normal file
Binary file not shown.
BIN
example/assets/perf/rivs/walking.riv
Normal file
BIN
example/assets/perf/rivs/walking.riv
Normal file
Binary file not shown.
1
example/lib/advanced/advanced.dart
Normal file
1
example/lib/advanced/advanced.dart
Normal file
@ -0,0 +1 @@
|
||||
export 'centaur_example/game_widget.dart';
|
||||
368
example/lib/advanced/centaur_example/centaur_game.dart
Normal file
368
example/lib/advanced/centaur_example/centaur_game.dart
Normal file
@ -0,0 +1,368 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:rive/rive.dart' as rive;
|
||||
|
||||
class Arrow {
|
||||
static const double appleRadius = 50;
|
||||
static const appleRadiusSquared = appleRadius * appleRadius;
|
||||
|
||||
final rive.Artboard artboard;
|
||||
final rive.Vec2D heading;
|
||||
rive.Vec2D translation;
|
||||
double time = 0;
|
||||
|
||||
Arrow({
|
||||
required this.artboard,
|
||||
required this.translation,
|
||||
required this.heading,
|
||||
});
|
||||
|
||||
void dispose() => artboard.dispose();
|
||||
|
||||
bool get draws => time >= 0.1;
|
||||
bool get isDead => time > 2;
|
||||
|
||||
void advance(double elapsedSeconds, Set<Apple> apples) {
|
||||
time += elapsedSeconds;
|
||||
if (!draws) {
|
||||
// Arrow is still leaving the bow (fire animation is playing on
|
||||
// centaur).
|
||||
return;
|
||||
} else if (isDead) {
|
||||
return;
|
||||
}
|
||||
// You'd likely use a scene tree to ray cast against here, but this is a
|
||||
// simple example.
|
||||
for (var apple in apples) {
|
||||
// const { explodeTrigger, x, y } = appleInstance;
|
||||
if ((apple.translation - translation).squaredLength() <
|
||||
appleRadiusSquared) {
|
||||
apple.damage();
|
||||
}
|
||||
}
|
||||
translation += heading * elapsedSeconds * 3000;
|
||||
heading.y += elapsedSeconds;
|
||||
// Normalize heading.
|
||||
heading.norm();
|
||||
}
|
||||
|
||||
void draw(rive.Renderer renderer) {
|
||||
if (!draws) {
|
||||
return;
|
||||
}
|
||||
renderer.save();
|
||||
var arrowTransform = rive.Mat2D.fromTranslation(
|
||||
translation,
|
||||
).mul(rive.Mat2D.fromRotation(rive.Mat2D(), atan2(heading.y, heading.x)));
|
||||
renderer.transform(arrowTransform);
|
||||
artboard.draw(renderer);
|
||||
renderer.restore();
|
||||
}
|
||||
}
|
||||
|
||||
class Apple {
|
||||
final rive.Artboard artboard;
|
||||
final rive.StateMachine machine;
|
||||
final rive.TriggerInput explode;
|
||||
final rive.AABB bounds;
|
||||
rive.Vec2D translation;
|
||||
bool _isDead = false;
|
||||
|
||||
bool get isDead => _deadTime > 1;
|
||||
double _deadTime = 0;
|
||||
|
||||
Apple({
|
||||
required this.artboard,
|
||||
required this.machine,
|
||||
required this.explode,
|
||||
required this.translation,
|
||||
}) : bounds = artboard.bounds {
|
||||
var center = bounds.center();
|
||||
artboard.renderTransform = rive.Mat2D.fromTranslation(translation - center);
|
||||
}
|
||||
|
||||
void damage() {
|
||||
if (_isDead) {
|
||||
return;
|
||||
}
|
||||
explode.fire();
|
||||
_isDead = true;
|
||||
}
|
||||
|
||||
void advance(double elapsedSeconds) {
|
||||
// We don't advance the state machine here as we do all the apples in a
|
||||
// single batch call.
|
||||
if (_isDead) {
|
||||
_deadTime += elapsedSeconds;
|
||||
}
|
||||
}
|
||||
|
||||
void draw(rive.Renderer renderer) {
|
||||
renderer.save();
|
||||
var center = bounds.center();
|
||||
renderer.translate(translation.x - center.x, translation.y - center.y);
|
||||
artboard.draw(renderer);
|
||||
renderer.restore();
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
artboard.dispose();
|
||||
machine.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class GhostApple {
|
||||
final Apple apple;
|
||||
rive.Vec2D translation;
|
||||
|
||||
GhostApple(this.apple, this.translation);
|
||||
|
||||
void draw(rive.Renderer renderer) {
|
||||
renderer.save();
|
||||
var center = apple.bounds.center();
|
||||
renderer.translate(translation.x - center.x, translation.y - center.y);
|
||||
apple.artboard.draw(renderer);
|
||||
renderer.restore();
|
||||
}
|
||||
}
|
||||
|
||||
base class CentaurGame extends rive.RenderTexturePainter {
|
||||
final rive.File riveFile;
|
||||
|
||||
final rive.Artboard character;
|
||||
late rive.StateMachine characterMachine;
|
||||
final rive.Artboard backgroundTile;
|
||||
final Set<Arrow> _arrows = {};
|
||||
final Set<Apple> _apples = {};
|
||||
late rive.Component target;
|
||||
rive.Component? _characterRoot;
|
||||
rive.Component? _arrowLocation;
|
||||
rive.NumberInput? _moveInput;
|
||||
rive.TriggerInput? _fireInput;
|
||||
double _characterX = 0;
|
||||
double _characterDirection = 1;
|
||||
double move = 0;
|
||||
double _currentMoveSpeed = 0;
|
||||
CentaurGame(this.riveFile)
|
||||
: character = riveFile.artboard('Character')!,
|
||||
backgroundTile = riveFile.artboard('Background_tile')! {
|
||||
characterMachine = character.defaultStateMachine()!;
|
||||
character.frameOrigin = false;
|
||||
backgroundTile.frameOrigin = false;
|
||||
target = character.component('Look')!;
|
||||
_characterRoot = character.component('Character');
|
||||
_arrowLocation = character.component('ArrowLocation');
|
||||
_moveInput = characterMachine.number('Move');
|
||||
_fireInput = characterMachine.trigger('Fire');
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
character.dispose();
|
||||
backgroundTile.dispose();
|
||||
riveFile.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
rive.Vec2D localCursor = rive.Vec2D();
|
||||
|
||||
void aimAt(Offset localPosition) {
|
||||
localCursor = rive.Vec2D.fromOffset(localPosition);
|
||||
}
|
||||
|
||||
void pointerDown(PointerDownEvent event) {
|
||||
_fireInput?.fire();
|
||||
var transform = _arrowLocation?.worldTransform ?? rive.Mat2D();
|
||||
var artboard = riveFile.artboard('Arrow');
|
||||
if (artboard == null) {
|
||||
return;
|
||||
}
|
||||
artboard.frameOrigin = false;
|
||||
var arrowInstance = Arrow(
|
||||
artboard: artboard,
|
||||
translation:
|
||||
transform.translation + rive.Vec2D.fromValues(_characterX, 0.0),
|
||||
heading: transform.xDirection,
|
||||
);
|
||||
|
||||
_arrows.add(arrowInstance);
|
||||
}
|
||||
|
||||
static const int minApples = 30;
|
||||
static const int maxApples = 100;
|
||||
final Stopwatch _appleCooloff = Stopwatch()..start();
|
||||
void spawnApples(int maxSpawnIterations) {
|
||||
if (_appleCooloff.elapsedMilliseconds < 10) {
|
||||
return;
|
||||
}
|
||||
_appleCooloff.reset();
|
||||
_appleCooloff.start();
|
||||
var rand = Random();
|
||||
var count = rand.nextInt(maxApples - minApples) + minApples;
|
||||
var bounds = spawnAppleBounds;
|
||||
var range = bounds.maximum - bounds.minimum;
|
||||
|
||||
int spawnCount = 0;
|
||||
while (_apples.length < count) {
|
||||
var artboard = riveFile.artboard('Apple');
|
||||
if (artboard == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var stateMachine = artboard.defaultStateMachine();
|
||||
if (stateMachine == null) {
|
||||
artboard.dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
var explode = stateMachine.trigger('Explode');
|
||||
if (explode == null) {
|
||||
artboard.dispose();
|
||||
stateMachine.dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
var apple = Apple(
|
||||
artboard: artboard,
|
||||
machine: stateMachine,
|
||||
explode: explode,
|
||||
translation: rive.Vec2D.fromValues(
|
||||
bounds.minimum.x + range.x * rand.nextDouble(),
|
||||
bounds.minimum.y + range.y * rand.nextDouble(),
|
||||
),
|
||||
// translation: bounds.minimum,
|
||||
);
|
||||
_apples.add(apple);
|
||||
spawnCount++;
|
||||
if (spawnCount > maxSpawnIterations) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rive.AABB get sceneBounds {
|
||||
final bounds = character.bounds;
|
||||
final characterWidth = bounds.width;
|
||||
return bounds.inset(-characterWidth * 5, 0);
|
||||
}
|
||||
|
||||
rive.AABB get spawnAppleBounds {
|
||||
return rive.AABB.fromMinMax(
|
||||
sceneBounds.minimum - rive.Vec2D.fromValues(0, 3000),
|
||||
sceneBounds.maximum - rive.Vec2D.fromValues(0, 600),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool paint(
|
||||
rive.RenderTexture texture,
|
||||
double devicePixelRatio,
|
||||
Size size,
|
||||
double elapsedSeconds,
|
||||
) {
|
||||
var renderer = texture.renderer;
|
||||
|
||||
var viewTransform = rive.Renderer.computeAlignment(
|
||||
rive.Fit.contain,
|
||||
Alignment.bottomCenter,
|
||||
rive.AABB.fromValues(0, 0, size.width, size.height),
|
||||
sceneBounds,
|
||||
devicePixelRatio,
|
||||
);
|
||||
|
||||
// Compute cursor in world space.
|
||||
final inverseViewTransform = rive.Mat2D();
|
||||
var worldCursor = rive.Vec2D();
|
||||
if (rive.Mat2D.invert(inverseViewTransform, viewTransform)) {
|
||||
worldCursor = inverseViewTransform * localCursor;
|
||||
// Check if we should invert the character's direction by comparing
|
||||
// the world location of the cursor to the world location of the
|
||||
// character (need to compensate by character movement, characterX).
|
||||
_characterDirection = _characterX < worldCursor.x ? 1 : -1;
|
||||
_characterRoot?.scaleX = _characterDirection;
|
||||
}
|
||||
|
||||
target.worldTransform = rive.Mat2D.fromTranslation(
|
||||
worldCursor - rive.Vec2D.fromValues(_characterX, 0),
|
||||
);
|
||||
|
||||
const moveSpeed = 100;
|
||||
var targetMoveSpeed = move * moveSpeed;
|
||||
_moveInput?.value = move * _characterDirection.sign;
|
||||
|
||||
_currentMoveSpeed +=
|
||||
(targetMoveSpeed - _currentMoveSpeed) * min(1, elapsedSeconds * 10);
|
||||
_characterX += elapsedSeconds * _currentMoveSpeed;
|
||||
|
||||
characterMachine.advanceAndApply(elapsedSeconds);
|
||||
renderer.save();
|
||||
renderer.transform(viewTransform);
|
||||
|
||||
double backgroundScale = 3;
|
||||
for (int b = -2; b <= 2; b++) {
|
||||
renderer.save();
|
||||
var xform = rive.Mat2D.fromScale(backgroundScale, backgroundScale);
|
||||
xform[4] = backgroundScale * b * backgroundTile.bounds.width;
|
||||
renderer.transform(xform);
|
||||
backgroundTile.draw(renderer);
|
||||
renderer.restore();
|
||||
}
|
||||
|
||||
renderer.save();
|
||||
renderer.translate(_characterX, 0);
|
||||
character.draw(renderer);
|
||||
renderer.restore();
|
||||
|
||||
var deadArrows = <Arrow>{};
|
||||
for (final arrow in _arrows) {
|
||||
if (arrow.isDead) {
|
||||
deadArrows.add(arrow);
|
||||
arrow.dispose();
|
||||
continue;
|
||||
}
|
||||
arrow.draw(renderer);
|
||||
arrow.advance(elapsedSeconds, _apples);
|
||||
}
|
||||
_arrows.removeAll(deadArrows);
|
||||
|
||||
bool batchRender = true;
|
||||
// Advance apple state machines in one multi-threaded batch.
|
||||
if (batchRender) {
|
||||
rive.Rive.batchAdvanceAndRender(
|
||||
_apples.map((apple) => apple.machine),
|
||||
elapsedSeconds,
|
||||
renderer,
|
||||
);
|
||||
// ignore: dead_code
|
||||
} else {
|
||||
rive.Rive.batchAdvance(
|
||||
_apples.map((apple) => apple.machine),
|
||||
elapsedSeconds,
|
||||
);
|
||||
}
|
||||
|
||||
var deadApples = <Apple>{};
|
||||
for (final apple in _apples) {
|
||||
if (apple.isDead) {
|
||||
deadApples.add(apple);
|
||||
apple.dispose();
|
||||
continue;
|
||||
}
|
||||
apple.advance(elapsedSeconds);
|
||||
// ignore: dead_code
|
||||
if (!batchRender) {
|
||||
apple.draw(renderer);
|
||||
}
|
||||
}
|
||||
_apples.removeAll(deadApples);
|
||||
spawnApples(5);
|
||||
|
||||
renderer.restore();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
Color get background => const Color(0xFF6F8C9B);
|
||||
}
|
||||
96
example/lib/advanced/centaur_example/game_widget.dart
Normal file
96
example/lib/advanced/centaur_example/game_widget.dart
Normal file
@ -0,0 +1,96 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:rive/rive.dart' as rive;
|
||||
import 'package:rive_example/advanced/centaur_example/centaur_game.dart';
|
||||
|
||||
class CentaurGameWidget extends StatefulWidget {
|
||||
const CentaurGameWidget({super.key});
|
||||
|
||||
@override
|
||||
State<CentaurGameWidget> createState() => _CentaurGameWidgetState();
|
||||
}
|
||||
|
||||
class _CentaurGameWidgetState extends State<CentaurGameWidget> {
|
||||
final rive.RenderTexture _renderTexture =
|
||||
rive.RiveNative.instance.makeRenderTexture();
|
||||
CentaurGame? _centaurPainter;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
load();
|
||||
}
|
||||
|
||||
Future<void> load() async {
|
||||
var data = await rootBundle.load('assets/centaur_v2.riv');
|
||||
var bytes = data.buffer.asUint8List();
|
||||
var file = await rive.File.decode(bytes, riveFactory: rive.Factory.rive);
|
||||
if (file != null) {
|
||||
setState(() {
|
||||
_centaurPainter = CentaurGame(file);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
_centaurPainter?.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
home: Scaffold(
|
||||
appBar: AppBar(title: const Text('Centaur Game')),
|
||||
body: ColoredBox(
|
||||
color: const Color(0xFF507FBA),
|
||||
child: Center(
|
||||
child: _centaurPainter == null
|
||||
? const SizedBox()
|
||||
: Focus(
|
||||
focusNode: FocusNode(
|
||||
canRequestFocus: true,
|
||||
onKeyEvent: (node, event) {
|
||||
if (event is KeyRepeatEvent) {
|
||||
return KeyEventResult.handled;
|
||||
}
|
||||
double speed = 0;
|
||||
if (event is KeyDownEvent) {
|
||||
speed = 1;
|
||||
} else if (event is KeyUpEvent) {
|
||||
speed = -1;
|
||||
}
|
||||
if (event.logicalKey == LogicalKeyboardKey.keyA) {
|
||||
_centaurPainter!.move -= speed;
|
||||
} else if (event.logicalKey ==
|
||||
LogicalKeyboardKey.keyD) {
|
||||
_centaurPainter!.move += speed;
|
||||
}
|
||||
return KeyEventResult.handled;
|
||||
},
|
||||
)..requestFocus(),
|
||||
child: MouseRegion(
|
||||
onHover: (event) => _centaurPainter!.aimAt(
|
||||
event.localPosition * View.of(context).devicePixelRatio,
|
||||
),
|
||||
child: Listener(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onPointerDown: _centaurPainter!.pointerDown,
|
||||
onPointerMove: (event) => _centaurPainter!.aimAt(
|
||||
event.localPosition *
|
||||
View.of(context).devicePixelRatio,
|
||||
),
|
||||
child: _renderTexture.widget(
|
||||
painter: _centaurPainter!,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -14,6 +14,7 @@ export 'responsive_layouts.dart';
|
||||
export 'rive_audio.dart';
|
||||
export 'rive_widget.dart';
|
||||
export 'rive_widget_builder.dart';
|
||||
export 'rive_panel.dart';
|
||||
export 'state_machine_painter.dart';
|
||||
export 'text_runs.dart';
|
||||
export 'ticker_mode.dart';
|
||||
|
||||
168
example/lib/examples/rive_panel.dart
Normal file
168
example/lib/examples/rive_panel.dart
Normal file
@ -0,0 +1,168 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:rive/rive.dart';
|
||||
|
||||
class ExampleRivePanel extends StatelessWidget {
|
||||
const ExampleRivePanel({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const RivePanel(
|
||||
child: ListViewExample(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class RowExample extends StatefulWidget {
|
||||
const RowExample({super.key});
|
||||
|
||||
@override
|
||||
State<RowExample> createState() => _RowExampleState();
|
||||
}
|
||||
|
||||
class _RowExampleState extends State<RowExample> {
|
||||
// Only useful when using `Factory.rive`. `Factory.flutter` draws to a single
|
||||
// render target already.
|
||||
final factory = Factory.rive;
|
||||
|
||||
late List<FileLoader> listOfFileLoaders = [
|
||||
FileLoader.fromAsset(
|
||||
'assets/rating.riv',
|
||||
riveFactory: factory,
|
||||
),
|
||||
FileLoader.fromAsset(
|
||||
'assets/vehicles.riv',
|
||||
riveFactory: factory,
|
||||
),
|
||||
FileLoader.fromAsset(
|
||||
'assets/perf/rivs/travel_icons.riv',
|
||||
riveFactory: factory,
|
||||
),
|
||||
FileLoader.fromAsset(
|
||||
'assets/coyote.riv',
|
||||
riveFactory: factory,
|
||||
),
|
||||
FileLoader.fromAsset(
|
||||
'assets/perf/rivs/Tom_Morello_2.riv',
|
||||
riveFactory: factory,
|
||||
),
|
||||
FileLoader.fromAsset(
|
||||
'assets/perf/rivs/towersDemo.riv',
|
||||
riveFactory: factory,
|
||||
),
|
||||
FileLoader.fromAsset(
|
||||
'assets/perf/rivs/walking.riv',
|
||||
riveFactory: factory,
|
||||
),
|
||||
FileLoader.fromAsset(
|
||||
'assets/perf/rivs/skull_404.riv',
|
||||
riveFactory: factory,
|
||||
),
|
||||
FileLoader.fromAsset(
|
||||
'assets/perf/rivs/adventuretime_marceline_pb.riv',
|
||||
riveFactory: factory,
|
||||
),
|
||||
FileLoader.fromAsset(
|
||||
'assets/perf/rivs/Zombie_Character.riv',
|
||||
riveFactory: factory,
|
||||
),
|
||||
];
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
for (var fileLoader in listOfFileLoaders) {
|
||||
fileLoader.dispose();
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 50.0),
|
||||
child: SingleChildScrollView(
|
||||
child: Wrap(
|
||||
children: [
|
||||
...listOfFileLoaders
|
||||
.map((fileLoader) => SizedSample(fileLoader: fileLoader)),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SizedSample extends StatelessWidget {
|
||||
const SizedSample({super.key, required this.fileLoader});
|
||||
|
||||
final FileLoader fileLoader;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
width: 200,
|
||||
height: 200,
|
||||
child: MyRiveWidget(fileLoader: fileLoader),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ListViewExample extends StatefulWidget {
|
||||
const ListViewExample({super.key});
|
||||
|
||||
@override
|
||||
State<ListViewExample> createState() => _ListViewExampleState();
|
||||
}
|
||||
|
||||
class _ListViewExampleState extends State<ListViewExample> {
|
||||
late final fileLoader = FileLoader.fromAsset(
|
||||
'assets/rating.riv',
|
||||
riveFactory: Factory.rive,
|
||||
);
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
fileLoader.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListView.builder(
|
||||
itemCount: 10,
|
||||
itemBuilder: (context, index) {
|
||||
return SizedBox(
|
||||
width: 500,
|
||||
height: 100,
|
||||
child: MyRiveWidget(fileLoader: fileLoader),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MyRiveWidget extends StatelessWidget {
|
||||
const MyRiveWidget({super.key, required this.fileLoader});
|
||||
final FileLoader fileLoader;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return RiveWidgetBuilder(
|
||||
fileLoader: fileLoader,
|
||||
builder: (context, state) => switch (state) {
|
||||
RiveLoading() => const Center(
|
||||
child: Center(child: CircularProgressIndicator()),
|
||||
),
|
||||
RiveFailed() => ErrorWidget.withDetails(
|
||||
message: state.error.toString(),
|
||||
error: FlutterError(state.error.toString()),
|
||||
),
|
||||
RiveLoaded() => RiveWidget(
|
||||
controller: state.controller,
|
||||
fit: Fit.contain,
|
||||
|
||||
/// Set this to true to draw to the nearest `RivePanel`
|
||||
useSharedTexture: true,
|
||||
)
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
// ignore_for_file: deprecated_member_use
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:rive_example/advanced/advanced.dart';
|
||||
import 'package:rive_example/colors.dart';
|
||||
import 'package:rive_example/examples/examples.dart';
|
||||
import 'package:rive/rive.dart' as rive;
|
||||
@ -67,6 +68,8 @@ class _RiveExampleAppState extends State<RiveExampleApp> {
|
||||
'Simple example usage of the Rive widget with common parameters.'),
|
||||
_Page('Rive Widget Builder', ExampleRiveWidgetBuilder(),
|
||||
'Example usage of the Rive builder widget with common parameters.'),
|
||||
_Page('Rive Panel [Shared Texture]', ExampleRivePanel(),
|
||||
'Example usage of the Shared Texture View widget.'),
|
||||
],
|
||||
),
|
||||
const _Section(
|
||||
@ -107,6 +110,7 @@ class _RiveExampleAppState extends State<RiveExampleApp> {
|
||||
'Advanced: Custom painter for state machines.'),
|
||||
_Page('Single Animation Painter', ExampleSingleAnimationPainter(),
|
||||
'Advanced: Custom painter for single animation playback.'),
|
||||
_Page('Centaur Game', CentaurGameWidget(), 'Advanced: Centaur Game.'),
|
||||
],
|
||||
),
|
||||
const _Section(
|
||||
|
||||
@ -32,5 +32,6 @@ flutter:
|
||||
assets:
|
||||
- assets/
|
||||
- assets/fonts/
|
||||
- assets/perf/rivs/
|
||||
- assets/audio/
|
||||
- assets/images/
|
||||
|
||||
@ -22,5 +22,8 @@ export 'src/models/data_bind.dart';
|
||||
export 'src/models/state_machine_selector.dart';
|
||||
export 'src/painters/widget_controller.dart';
|
||||
export 'src/rive_extensions.dart';
|
||||
export 'src/widgets/inherited_widgets.dart';
|
||||
export 'src/widgets/rive_builder.dart';
|
||||
export 'src/widgets/rive_panel.dart';
|
||||
export 'src/widgets/rive_widget.dart';
|
||||
export 'src/widgets/shared_texture_view.dart';
|
||||
|
||||
96
lib/src/widgets/inherited_widgets.dart
Normal file
96
lib/src/widgets/inherited_widgets.dart
Normal file
@ -0,0 +1,96 @@
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:rive_native/rive_native.dart' as rive;
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
/// **EXPERIMENTAL**: This API may change or be removed in a future release.
|
||||
@experimental
|
||||
abstract class SharedTexturePainter {
|
||||
int get sharedDrawOrder;
|
||||
void paintIntoSharedTexture(rive.RenderTexture texture);
|
||||
}
|
||||
|
||||
/// **EXPERIMENTAL**: This API may change or be removed in a future release.
|
||||
@experimental
|
||||
class SharedRenderTexture {
|
||||
final rive.RenderTexture texture;
|
||||
final double devicePixelRatio;
|
||||
final Color backgroundColor;
|
||||
final List<SharedTexturePainter> painters = [];
|
||||
final GlobalKey panelKey;
|
||||
|
||||
SharedRenderTexture({
|
||||
required this.texture,
|
||||
required this.devicePixelRatio,
|
||||
required this.backgroundColor,
|
||||
required this.panelKey,
|
||||
});
|
||||
|
||||
/// Paint the shared render texture.
|
||||
void _paintShared(_) {
|
||||
texture.clear(backgroundColor);
|
||||
for (final painter in painters) {
|
||||
painter.paintIntoSharedTexture(texture);
|
||||
}
|
||||
texture.flush(devicePixelRatio);
|
||||
|
||||
_scheduled = false;
|
||||
}
|
||||
|
||||
bool _scheduled = false;
|
||||
|
||||
/// Schedule a paint of the shared render texture.
|
||||
void schedulePaint() {
|
||||
if (_scheduled) {
|
||||
return;
|
||||
}
|
||||
_scheduled = true;
|
||||
SchedulerBinding.instance.addPostFrameCallback(_paintShared);
|
||||
}
|
||||
|
||||
/// Add a painter to the shared render texture.
|
||||
void addPainter(SharedTexturePainter painter) {
|
||||
painters.add(painter);
|
||||
painters.sort((a, b) => a.sharedDrawOrder.compareTo(b.sharedDrawOrder));
|
||||
}
|
||||
|
||||
/// Remove a painter from the shared render texture.
|
||||
void removePainter(SharedTexturePainter painter) {
|
||||
painters.remove(painter);
|
||||
}
|
||||
}
|
||||
|
||||
/// Inherited widget that will pass the background render texture down the tree
|
||||
///
|
||||
/// **EXPERIMENTAL**: This API may change or be removed in a future release.
|
||||
@experimental
|
||||
class RiveSharedTexture extends InheritedWidget {
|
||||
late final SharedRenderTexture? texture;
|
||||
|
||||
RiveSharedTexture({
|
||||
required super.child,
|
||||
required rive.RenderTexture? texture,
|
||||
required double devicePixelRatio,
|
||||
required Color backgroundColor,
|
||||
required GlobalKey panelKey,
|
||||
super.key,
|
||||
}) {
|
||||
this.texture = texture != null
|
||||
? SharedRenderTexture(
|
||||
texture: texture,
|
||||
devicePixelRatio: devicePixelRatio,
|
||||
backgroundColor: backgroundColor,
|
||||
panelKey: panelKey,
|
||||
)
|
||||
: null;
|
||||
}
|
||||
|
||||
static SharedRenderTexture? of(BuildContext context) =>
|
||||
context.dependOnInheritedWidgetOfExactType<RiveSharedTexture>()?.texture;
|
||||
|
||||
static SharedRenderTexture? find(BuildContext context) =>
|
||||
context.findAncestorWidgetOfExactType<RiveSharedTexture>()?.texture;
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(RiveSharedTexture old) => texture != old.texture;
|
||||
}
|
||||
152
lib/src/widgets/rive_panel.dart
Normal file
152
lib/src/widgets/rive_panel.dart
Normal file
@ -0,0 +1,152 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:rive/rive.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
/// A widget that creates a shared texture to paint multiple [RiveWidget]s to.
|
||||
///
|
||||
/// Useful when using [Factory.rive]. This won't have an effect when using
|
||||
/// [Factory.flutter], and will unnecessarily create a new render texture that
|
||||
/// won't be used.
|
||||
///
|
||||
/// Painting multiple [RiveWidget]s to the same texture can drastically
|
||||
/// improve performance under certain conditions. Drawing multiple [RiveWidget]s
|
||||
/// each to their own texture has a performance cost. The more textures you draw
|
||||
/// to, the more performance you lose. Additionally, on the web, you are
|
||||
/// limited to the number of WebGL contexts the browser allows. Drawing to a
|
||||
/// single texture avoids this limitation.
|
||||
///
|
||||
/// Wrap your [RiveWidget]s with a [RivePanel] to enable this behavior, and
|
||||
/// set `useSharedTexture` to `true` in [RiveWidget]
|
||||
///
|
||||
/// Note:
|
||||
/// - There is a memory cost in allocating a larger texture. However, under some
|
||||
/// conditions, the memory cost might be better, or the same. Benchmarking
|
||||
/// is recommended.
|
||||
/// - Drawing to the same surface will mean that you cannot interleave drawing
|
||||
/// commands that Rive performs with that of Flutter. If you need to interleave
|
||||
/// content, you will need to draw to a separate surface - [RivePanel]. Or use
|
||||
/// [Factory.flutter] - which uses the Flutter rendering pipeline to perform
|
||||
/// all rendering as a single pass. Benchmarking is recommended - what works
|
||||
/// for one use case may not work for another.
|
||||
///
|
||||
/// ### Example:
|
||||
/// ```dart
|
||||
/// class ExampleRivePanel extends StatelessWidget {
|
||||
/// const ExampleRivePanel({super.key});
|
||||
///
|
||||
/// @override
|
||||
/// Widget build(BuildContext context) {
|
||||
/// return const RivePanel(
|
||||
/// backgroundColor: Colors.red,
|
||||
/// child: ListViewExample(),
|
||||
/// );
|
||||
/// }
|
||||
///}
|
||||
///class ListViewExample extends StatefulWidget {
|
||||
/// const ListViewExample({super.key});
|
||||
///
|
||||
/// @override
|
||||
/// State<ListViewExample> createState() => _ListViewExampleState();
|
||||
///}
|
||||
///
|
||||
///class _ListViewExampleState extends State<ListViewExample> {
|
||||
/// late final fileLoader = FileLoader.fromAsset(
|
||||
/// 'assets/rating.riv',
|
||||
/// riveFactory: Factory.rive,
|
||||
/// );
|
||||
///
|
||||
/// @override
|
||||
/// void dispose() {
|
||||
/// fileLoader.dispose();
|
||||
/// super.dispose();
|
||||
/// }
|
||||
///
|
||||
/// @override
|
||||
/// Widget build(BuildContext context) {
|
||||
/// return ListView.builder(
|
||||
/// itemCount: 10,
|
||||
/// itemBuilder: (context, index) {
|
||||
/// return MyRiveWidget(fileLoader: fileLoader);
|
||||
/// },
|
||||
/// );
|
||||
/// }
|
||||
///}
|
||||
///class MyRiveWidget extends StatelessWidget {
|
||||
/// const MyRiveWidget({super.key, required this.fileLoader});
|
||||
/// final FileLoader fileLoader;
|
||||
///
|
||||
/// @override
|
||||
/// Widget build(BuildContext context) {
|
||||
/// return RiveWidgetBuilder(
|
||||
/// fileLoader: fileLoader,
|
||||
/// builder: (context, state) => switch (state) {
|
||||
/// RiveLoading() => const Center(
|
||||
/// child: Center(child: CircularProgressIndicator()),
|
||||
/// ),
|
||||
/// RiveFailed() => ErrorWidget.withDetails(
|
||||
/// message: state.error.toString(),
|
||||
/// error: FlutterError(state.error.toString()),
|
||||
/// ),
|
||||
/// RiveLoaded() => RiveWidget(
|
||||
/// controller: state.controller,
|
||||
/// fit: Fit.contain,
|
||||
///
|
||||
/// /// Set this to true to draw to the nearest `RivePanel`
|
||||
/// useSharedTexture: true,
|
||||
/// )
|
||||
/// },
|
||||
/// );
|
||||
/// }
|
||||
///}
|
||||
/// ```
|
||||
///
|
||||
/// **EXPERIMENTAL**: This API may change or be removed in a future release.
|
||||
@experimental
|
||||
class RivePanel extends StatefulWidget {
|
||||
const RivePanel({
|
||||
super.key,
|
||||
this.backgroundColor = Colors.transparent,
|
||||
required this.child,
|
||||
});
|
||||
|
||||
final Color backgroundColor;
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
State<RivePanel> createState() => _RivePanelState();
|
||||
}
|
||||
|
||||
class _RivePanelState extends State<RivePanel> {
|
||||
RenderTexture? _renderTexture;
|
||||
final GlobalKey _panelKey = GlobalKey();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_renderTexture = RiveNative.instance.makeRenderTexture();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_renderTexture?.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: _renderTexture!.widget(key: _panelKey),
|
||||
),
|
||||
RiveSharedTexture(
|
||||
panelKey: _panelKey,
|
||||
backgroundColor: widget.backgroundColor,
|
||||
devicePixelRatio: MediaQuery.devicePixelRatioOf(context),
|
||||
texture: _renderTexture,
|
||||
child: widget.child,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,15 +1,19 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:rive/rive.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
/// A widget that displays a Rive artboard.
|
||||
///
|
||||
/// - The [controller] parameter is the [RiveControlelr] that controls the
|
||||
/// - The [controller] parameter is the [RiveWidgetController] that controls the
|
||||
/// artboard and state machine. This controller builds on top of the concept
|
||||
/// of a Rive painter, but provides a more convenient API for building
|
||||
/// Rive widgets.
|
||||
/// - The [fit] parameter is the fit of the artboard.
|
||||
/// - The [alignment] parameter is the alignment of the artboard.
|
||||
/// - The [hitTestBehavior] parameter is the hit test behavior of the artboard.
|
||||
/// - The [cursor] parameter is the platform/Flutter cursor when interacting with an area that has a `hitTest` of `true`.
|
||||
/// - The [layoutScaleFactor] parameter is the layout scale factor of the artboard when using `Fit.layout`.
|
||||
/// - The [useSharedTexture] parameter is whether to use a shared texture ([RivePanel]) to draw the artboard to.
|
||||
class RiveWidget extends StatefulWidget {
|
||||
const RiveWidget({
|
||||
super.key,
|
||||
@ -19,6 +23,8 @@ class RiveWidget extends StatefulWidget {
|
||||
this.hitTestBehavior = RiveDefaults.hitTestBehaviour,
|
||||
this.cursor = RiveDefaults.mouseCursor,
|
||||
this.layoutScaleFactor = RiveDefaults.layoutScaleFactor,
|
||||
this.useSharedTexture = false,
|
||||
this.drawOrder = 1,
|
||||
});
|
||||
final RiveWidgetController controller;
|
||||
|
||||
@ -47,6 +53,21 @@ class RiveWidget extends StatefulWidget {
|
||||
/// Defaults to [RiveDefaults.layoutScaleFactor].
|
||||
final double layoutScaleFactor;
|
||||
|
||||
/// Whether to use a shared texture [(RivePanel]) to draw the artboard to.
|
||||
///
|
||||
/// Defaults to false. When set to true, it draws to nearest inherited widget
|
||||
/// of type [RivePanel].
|
||||
///
|
||||
/// **EXPERIMENTAL**: This API may change or be removed in a future release.
|
||||
@experimental
|
||||
final bool useSharedTexture;
|
||||
|
||||
/// The draw order of the artboard. This is only used when [useSharedTexture]
|
||||
/// is true when drawing to a [RivePanel], and using [Factory.rive].
|
||||
///
|
||||
/// Defaults to 1.
|
||||
final int drawOrder;
|
||||
|
||||
@override
|
||||
State<RiveWidget> createState() => _RiveWidgetState();
|
||||
}
|
||||
@ -102,11 +123,41 @@ class _RiveWidgetState extends State<RiveWidget> {
|
||||
controller.scheduleRepaint();
|
||||
}
|
||||
|
||||
late final SharedTextureArtboardWidgetPainter _painter =
|
||||
SharedTextureArtboardWidgetPainter(widget.controller);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (widget.useSharedTexture) {
|
||||
if (widget.controller.artboard.riveFactory == Factory.flutter) {
|
||||
return errorWidget(
|
||||
'useSharedTexture is only supported when using Factory.rive');
|
||||
}
|
||||
final sharedTexture = RiveSharedTexture.of(context);
|
||||
if (sharedTexture == null) {
|
||||
return errorWidget(
|
||||
'RiveWidget requires a shared texture when useSharedTexture is true.\n'
|
||||
'Make sure to wrap this widget with a RiveSharedTexture widget in the widget tree.');
|
||||
} else {
|
||||
return SharedTextureView(
|
||||
artboard: widget.controller.artboard,
|
||||
painter: _painter,
|
||||
sharedTexture: sharedTexture,
|
||||
drawOrder: widget.drawOrder,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return RiveArtboardWidget(
|
||||
artboard: widget.controller.artboard,
|
||||
painter: widget.controller,
|
||||
);
|
||||
}
|
||||
|
||||
ErrorWidget errorWidget(String message) {
|
||||
return ErrorWidget.withDetails(
|
||||
message: message,
|
||||
error: FlutterError(message),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
223
lib/src/widgets/shared_texture_view.dart
Normal file
223
lib/src/widgets/shared_texture_view.dart
Normal file
@ -0,0 +1,223 @@
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:rive/src/widgets/inherited_widgets.dart';
|
||||
import 'package:rive_native/rive_native.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
/// Renderers the [artboard] to a [sharedTexture].
|
||||
///
|
||||
/// See [RivePanel]. Only useful when using `Factory.rive`.
|
||||
///
|
||||
/// **EXPERIMENTAL**: This API may change or be removed in a future release.
|
||||
@experimental
|
||||
class SharedTextureView extends StatefulWidget {
|
||||
final Artboard artboard;
|
||||
final SharedTextureArtboardWidgetPainter painter;
|
||||
final SharedRenderTexture sharedTexture;
|
||||
final int drawOrder;
|
||||
const SharedTextureView({
|
||||
required this.artboard,
|
||||
required this.painter,
|
||||
required this.sharedTexture,
|
||||
required this.drawOrder,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<SharedTextureView> createState() => _SharedTextureViewState();
|
||||
}
|
||||
|
||||
class _SharedTextureViewState extends State<SharedTextureView> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
widget.painter.artboardChanged(widget.artboard);
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant SharedTextureView oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.artboard != widget.artboard) {
|
||||
widget.painter.artboardChanged(widget.artboard);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SharedTextureViewRenderer(
|
||||
renderTexturePainter: widget.painter,
|
||||
sharedTexture: widget.sharedTexture,
|
||||
devicePixelRatio: MediaQuery.devicePixelRatioOf(context),
|
||||
drawOrder: widget.drawOrder,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SharedTextureViewRenderer extends LeafRenderObjectWidget {
|
||||
final RenderTexturePainter renderTexturePainter;
|
||||
final SharedRenderTexture sharedTexture;
|
||||
final double devicePixelRatio;
|
||||
final int drawOrder;
|
||||
|
||||
const SharedTextureViewRenderer({
|
||||
super.key,
|
||||
required this.renderTexturePainter,
|
||||
required this.sharedTexture,
|
||||
required this.devicePixelRatio,
|
||||
required this.drawOrder,
|
||||
});
|
||||
|
||||
@override
|
||||
RenderObject createRenderObject(BuildContext context) {
|
||||
return SharedTextureViewRenderObject(sharedTexture)
|
||||
..painter = renderTexturePainter
|
||||
..scrollPosition = Scrollable.maybeOf(context)?.position
|
||||
..devicePixelRatio = devicePixelRatio
|
||||
..drawOrder = drawOrder;
|
||||
}
|
||||
|
||||
@override
|
||||
void updateRenderObject(
|
||||
BuildContext context,
|
||||
covariant SharedTextureViewRenderObject renderObject,
|
||||
) {
|
||||
renderObject
|
||||
..shared = sharedTexture
|
||||
..painter = renderTexturePainter
|
||||
..scrollPosition = Scrollable.maybeOf(context)?.position
|
||||
..devicePixelRatio = devicePixelRatio
|
||||
..drawOrder = drawOrder;
|
||||
}
|
||||
|
||||
@override
|
||||
void didUnmountRenderObject(
|
||||
covariant SharedTextureViewRenderObject renderObject,
|
||||
) {}
|
||||
}
|
||||
|
||||
class SharedTextureViewRenderObject
|
||||
extends RiveNativeRenderBox<RenderTexturePainter>
|
||||
implements SharedTexturePainter {
|
||||
SharedRenderTexture _shared;
|
||||
|
||||
SharedTextureViewRenderObject(this._shared) {
|
||||
_shared.texture.onTextureChanged = _onRiveTextureChanged;
|
||||
}
|
||||
|
||||
int drawOrder = 1;
|
||||
|
||||
SharedRenderTexture get shared => _shared;
|
||||
set shared(SharedRenderTexture value) {
|
||||
if (_shared == value) {
|
||||
return;
|
||||
}
|
||||
_shared.texture.onTextureChanged = null;
|
||||
_shared.removePainter(this);
|
||||
_shared = value;
|
||||
_shared.texture.onTextureChanged = _onRiveTextureChanged;
|
||||
_shared.addPainter(this);
|
||||
markNeedsPaint();
|
||||
}
|
||||
|
||||
bool _shouldAdvance = true;
|
||||
|
||||
@override
|
||||
bool get shouldAdvance => _shouldAdvance;
|
||||
|
||||
// Repaint when the texture is created/changed. This reduces the flicker when
|
||||
// resizing the widget. This flicker is caused by recreating the underlying
|
||||
// texture Rive draws to.
|
||||
void _onRiveTextureChanged() => markNeedsLayout();
|
||||
|
||||
@override
|
||||
bool get sizedByParent => true;
|
||||
|
||||
@override
|
||||
Size computeDryLayout(BoxConstraints constraints) => constraints.smallest;
|
||||
|
||||
@override
|
||||
void paint(PaintingContext context, Offset offset) => _shared.schedulePaint();
|
||||
|
||||
ScrollPosition? _scrollPosition;
|
||||
set scrollPosition(ScrollPosition? v) {
|
||||
if (identical(v, _scrollPosition)) return;
|
||||
_unsubscribe();
|
||||
_scrollPosition = v;
|
||||
_subscribe();
|
||||
_scheduleCheck();
|
||||
}
|
||||
|
||||
void _subscribe() => _scrollPosition?.addListener(_scheduleCheck);
|
||||
void _unsubscribe() => _scrollPosition?.removeListener(_scheduleCheck);
|
||||
|
||||
void _scheduleCheck() {
|
||||
if (!attached) return;
|
||||
markNeedsPaint();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_shared.removePainter(this);
|
||||
_shared.texture.onTextureChanged = null;
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void paintIntoSharedTexture(RenderTexture texture) {
|
||||
// TODO (Gordon): could move out this logic to calculate the position only under certain conditions.
|
||||
final panelKeyContext = shared.panelKey.currentContext;
|
||||
if (panelKeyContext == null) {
|
||||
return;
|
||||
}
|
||||
RenderBox renderBox = panelKeyContext.findRenderObject() as RenderBox;
|
||||
Offset panelPosition = renderBox.localToGlobal(Offset.zero);
|
||||
Offset globalPosition = localToGlobal(Offset.zero) - panelPosition;
|
||||
|
||||
final renderer = texture.renderer;
|
||||
|
||||
renderer.save();
|
||||
renderer.translate(
|
||||
globalPosition.dx * devicePixelRatio,
|
||||
globalPosition.dy * devicePixelRatio,
|
||||
);
|
||||
final scaledSize = size * devicePixelRatio;
|
||||
final needsAdvance = rivePainter?.paint(
|
||||
texture, devicePixelRatio, scaledSize, elapsedSeconds) ??
|
||||
false;
|
||||
|
||||
_shouldAdvance = elapsedSeconds == 0 ? true : needsAdvance;
|
||||
|
||||
renderer.restore();
|
||||
}
|
||||
|
||||
@override
|
||||
int get sharedDrawOrder => drawOrder;
|
||||
|
||||
@override
|
||||
void attach(PipelineOwner owner) {
|
||||
super.attach(owner);
|
||||
markNeedsLayout();
|
||||
_shared.addPainter(this);
|
||||
}
|
||||
|
||||
@override
|
||||
void frameCallback(Duration duration) {
|
||||
super.frameCallback(duration);
|
||||
_shared.schedulePaint();
|
||||
}
|
||||
|
||||
@override
|
||||
void detach() {
|
||||
_unsubscribe();
|
||||
_scrollPosition = null;
|
||||
_shared.removePainter(this);
|
||||
super.detach();
|
||||
}
|
||||
}
|
||||
|
||||
base class SharedTextureArtboardWidgetPainter
|
||||
extends ArtboardWidgetPainter<ArtboardPainter> {
|
||||
SharedTextureArtboardWidgetPainter(ArtboardPainter super.painter);
|
||||
|
||||
void artboardChanged(Artboard artboard) => painter?.artboardChanged(artboard);
|
||||
}
|
||||
@ -19,7 +19,9 @@ dependencies:
|
||||
sdk: flutter
|
||||
flutter_web_plugins:
|
||||
sdk: flutter
|
||||
rive_native: 0.0.9
|
||||
meta: ^1.9.0
|
||||
rive_native:
|
||||
path: ../rive_native
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
Reference in New Issue
Block a user