mirror of
https://github.com/rive-app/rive-flutter
synced 2025-07-05 21:55:58 +00:00
Flutter Runtime API for Nested Inputs
https://github.com/rive-app/rive/assets/186340/00b71832-1293-45fa-b3da-f9abcaa34eb7 Diffs= c8a151ebb Flutter Runtime API for Nested Inputs (#7325) e0a786c90 Runtime API for Nested Inputs (#7316) 01d20e026 Use unique_ptr in import stack. (#7307) 5ad13845d Fail early with bad blend modes. (#7302) Co-authored-by: Philip Chung <philterdesign@gmail.com>
This commit is contained in:
@ -1 +1 @@
|
||||
7474216b457d7d2b29cac2209644f9f3c7821652
|
||||
c8a151ebb3a99fe53af068db3da71ced90d21ea2
|
||||
|
BIN
example/assets/runtime_nested_inputs.riv
Normal file
BIN
example/assets/runtime_nested_inputs.riv
Normal file
Binary file not shown.
96
example/lib/artboard_nested_inputs.dart
Normal file
96
example/lib/artboard_nested_inputs.dart
Normal file
@ -0,0 +1,96 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:rive/rive.dart';
|
||||
|
||||
/// An example showing how to drive a StateMachine via one numeric input.
|
||||
class ArtboardNestedInputs extends StatefulWidget {
|
||||
const ArtboardNestedInputs({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<ArtboardNestedInputs> createState() => _ArtboardNestedInputsState();
|
||||
}
|
||||
|
||||
class _ArtboardNestedInputsState extends State<ArtboardNestedInputs> {
|
||||
Artboard? _riveArtboard;
|
||||
SMIBool? _circleOuterState;
|
||||
SMIBool? _circleInnerState;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_loadRiveFile();
|
||||
}
|
||||
|
||||
Future<void> _loadRiveFile() async {
|
||||
final file = await RiveFile.asset('assets/runtime_nested_inputs.riv');
|
||||
|
||||
// The artboard is the root of the animation and gets drawn in the
|
||||
// Rive widget.
|
||||
final artboard = file.artboardByName("MainArtboard")!.instance();
|
||||
var controller =
|
||||
StateMachineController.fromArtboard(artboard, 'MainStateMachine');
|
||||
// Get the nested input CircleOuterState in the nested artboard CircleOuter
|
||||
_circleOuterState =
|
||||
artboard.getBoolInput("CircleOuterState", "CircleOuter");
|
||||
// Get the nested input CircleInnerState at the nested artboard path
|
||||
// -> CircleOuter
|
||||
// -> CircleInner
|
||||
_circleInnerState =
|
||||
artboard.getBoolInput("CircleInnerState", "CircleOuter/CircleInner");
|
||||
if (controller != null) {
|
||||
artboard.addController(controller);
|
||||
}
|
||||
setState(() => _riveArtboard = artboard);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Nested Inputs'),
|
||||
),
|
||||
body: Center(
|
||||
child: _riveArtboard == null
|
||||
? const SizedBox()
|
||||
: Stack(
|
||||
children: [
|
||||
Positioned.fill(
|
||||
child: Rive(
|
||||
artboard: _riveArtboard!,
|
||||
fit: BoxFit.fitWidth,
|
||||
),
|
||||
),
|
||||
Positioned.fill(
|
||||
bottom: 32,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
ElevatedButton(
|
||||
child: const Text('Outer Circle'),
|
||||
onPressed: () {
|
||||
if (_circleOuterState != null) {
|
||||
_circleOuterState!.value =
|
||||
!_circleOuterState!.value;
|
||||
}
|
||||
},
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
ElevatedButton(
|
||||
child: const Text('Inner Circle'),
|
||||
onPressed: () {
|
||||
if (_circleInnerState != null) {
|
||||
_circleInnerState!.value =
|
||||
!_circleInnerState!.value;
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@ import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:rive/rive.dart';
|
||||
|
||||
import 'package:rive_example/artboard_nested_inputs.dart';
|
||||
import 'package:rive_example/custom_asset_loading.dart';
|
||||
import 'package:rive_example/custom_cached_asset_loading.dart';
|
||||
import 'package:rive_example/carousel.dart';
|
||||
@ -65,6 +66,7 @@ class _RiveExampleAppState extends State<RiveExampleApp> {
|
||||
const _Page('Button State Machine', ExampleStateMachine()),
|
||||
const _Page('Skills Machine', StateMachineSkills()),
|
||||
const _Page('Little Machine', LittleMachine()),
|
||||
const _Page('Nested Inputs', ArtboardNestedInputs()),
|
||||
const _Page('Liquid Download', LiquidDownload()),
|
||||
const _Page('Custom Controller - Speed', SpeedyAnimation()),
|
||||
const _Page('Simple State Machine', SimpleStateMachine()),
|
||||
|
@ -15,6 +15,78 @@ extension RuntimeArtboardGetters on RuntimeArtboard {
|
||||
animations.whereType<StateMachine>();
|
||||
}
|
||||
|
||||
extension ArtboardRuntimeExtensions on Artboard {
|
||||
NestedArtboard? nestedArtboard(String name) {
|
||||
for (final artboard in activeNestedArtboards) {
|
||||
if (artboard.name == name) {
|
||||
return artboard;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
NestedArtboard? nestedArtboardAtPath(String path) {
|
||||
const delimiter = '/';
|
||||
final dIndex = path.indexOf(delimiter);
|
||||
final artboardName = dIndex == -1 ? path : path.substring(0, dIndex);
|
||||
final restOfPath =
|
||||
dIndex == -1 ? '' : path.substring(dIndex + 1, path.length);
|
||||
if (artboardName.isNotEmpty) {
|
||||
final nested = nestedArtboard(artboardName);
|
||||
if (nested != null) {
|
||||
if (restOfPath.isEmpty) {
|
||||
return nested;
|
||||
} else {
|
||||
return nested.nestedArtboardAtPath(restOfPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
T? findSMI<T>(String name, String path) {
|
||||
final nested = nestedArtboardAtPath(path);
|
||||
if (nested != null) {
|
||||
if (nested.mountedArtboard is RuntimeMountedArtboard) {
|
||||
final runtimeMountedArtboard =
|
||||
nested.mountedArtboard as RuntimeMountedArtboard;
|
||||
final controller = runtimeMountedArtboard.controller;
|
||||
if (controller != null) {
|
||||
for (final input in controller.inputs) {
|
||||
if (input is T && input.name == name) {
|
||||
return input as T;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Find a boolean input with a given name on a nested artboard at path.
|
||||
SMIBool? getBoolInput(String name, String path) =>
|
||||
findSMI<SMIBool>(name, path);
|
||||
|
||||
/// Find a trigger input with a given name on a nested artboard at path.
|
||||
SMITrigger? getTriggerInput(String name, String path) =>
|
||||
findSMI<SMITrigger>(name, path);
|
||||
|
||||
/// Find a number input with a given name on a nested artboard at path.
|
||||
///
|
||||
/// See [triggerInput] to directly fire a trigger by its name.
|
||||
SMINumber? getNumberInput(String name, String path) =>
|
||||
findSMI<SMINumber>(name, path);
|
||||
|
||||
/// Convenience method for firing a trigger input with a given name
|
||||
/// on a nested artboard at path.
|
||||
///
|
||||
/// Also see [getTriggerInput] to get a reference to the trigger input. If the
|
||||
/// trigger happens frequently, it's more efficient to get a reference to the
|
||||
/// trigger input and call `trigger.fire()` directly.
|
||||
void triggerInput(String name, String path) =>
|
||||
getTriggerInput(name, path)?.fire();
|
||||
}
|
||||
|
||||
/// This artboard type is purely for use by the runtime system and should not be
|
||||
/// directly referenced. Use the Artboard type for any direct interactions with
|
||||
/// an artboard, and use extension methods to add functionality to Artboard.
|
||||
|
@ -11,6 +11,16 @@ import 'package:rive/src/rive_core/state_machine_controller.dart'
|
||||
import 'package:rive/src/runtime_mounted_artboard.dart';
|
||||
import 'package:rive_common/math.dart';
|
||||
|
||||
extension NestedArtboardRuntimeExtension on NestedArtboard {
|
||||
NestedArtboard? nestedArtboardAtPath(String path) {
|
||||
if (mountedArtboard is RuntimeMountedArtboard) {
|
||||
final runtimeMountedArtboard = mountedArtboard as RuntimeMountedArtboard;
|
||||
return runtimeMountedArtboard.artboardInstance.nestedArtboardAtPath(path);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class RuntimeNestedArtboard extends NestedArtboard {
|
||||
Artboard? sourceArtboard;
|
||||
@override
|
||||
|
3
test/assets/runtime_nested_inputs.riv
Normal file
3
test/assets/runtime_nested_inputs.riv
Normal file
@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:61a14d72c5b9694b675d729e756fd4e3d0a075bf76ed545d07b62c301af962a0
|
||||
size 947
|
62
test/nested_input_test.dart
Normal file
62
test/nested_input_test.dart
Normal file
@ -0,0 +1,62 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:rive/rive.dart';
|
||||
|
||||
import 'src/utils.dart';
|
||||
|
||||
void main() {
|
||||
late RiveFile riveFile;
|
||||
|
||||
setUp(() {
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
final riveBytes = loadFile('assets/runtime_nested_inputs.riv');
|
||||
riveFile = RiveFile.import(riveBytes);
|
||||
});
|
||||
|
||||
test('Nested boolean input can be get/set', () async {
|
||||
final artboard = riveFile.artboards.first.instance();
|
||||
expect(artboard, isNotNull);
|
||||
final artboardInstance = artboard.instance();
|
||||
expect(artboardInstance, isNotNull);
|
||||
final bool = artboard.getBoolInput("CircleOuterState", "CircleOuter");
|
||||
expect(bool, isNotNull);
|
||||
expect(bool!.value, false);
|
||||
bool.value = true;
|
||||
expect(bool.value, true);
|
||||
});
|
||||
|
||||
test('Nested number input can be get/set', () async {
|
||||
final artboard = riveFile.artboards.first.instance();
|
||||
expect(artboard, isNotNull);
|
||||
final artboardInstance = artboard.instance();
|
||||
expect(artboardInstance, isNotNull);
|
||||
final num = artboard.getNumberInput("CircleOuterNumber", "CircleOuter");
|
||||
expect(num, isNotNull);
|
||||
expect(num!.value, 0);
|
||||
num.value = 99;
|
||||
expect(num.value, 99);
|
||||
});
|
||||
|
||||
test('Nested trigger can be get/fired', () async {
|
||||
final artboard = riveFile.artboards.first.instance();
|
||||
expect(artboard, isNotNull);
|
||||
final artboardInstance = artboard.instance();
|
||||
expect(artboardInstance, isNotNull);
|
||||
final trigger =
|
||||
artboard.getTriggerInput("CircleOuterTrigger", "CircleOuter");
|
||||
expect(trigger, isNotNull);
|
||||
trigger!.fire();
|
||||
});
|
||||
|
||||
test('Nested boolean input can be get/set multiple levels deep', () async {
|
||||
final artboard = riveFile.artboards.first.instance();
|
||||
expect(artboard, isNotNull);
|
||||
final artboardInstance = artboard.instance();
|
||||
expect(artboardInstance, isNotNull);
|
||||
final bool =
|
||||
artboard.getBoolInput("CircleInnerState", "CircleOuter/CircleInner");
|
||||
expect(bool, isNotNull);
|
||||
expect(bool!.value, false);
|
||||
bool.value = true;
|
||||
expect(bool.value, true);
|
||||
});
|
||||
}
|
Reference in New Issue
Block a user