Skip to content

Commit

Permalink
Flutter Runtime API for Nested Inputs
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
philter and philter committed May 30, 2024
1 parent c136a25 commit ec3564f
Show file tree
Hide file tree
Showing 8 changed files with 246 additions and 1 deletion.
2 changes: 1 addition & 1 deletion .rive_head
Original file line number Diff line number Diff line change
@@ -1 +1 @@
7474216b457d7d2b29cac2209644f9f3c7821652
c8a151ebb3a99fe53af068db3da71ced90d21ea2
Binary file added example/assets/runtime_nested_inputs.riv
Binary file not shown.
96 changes: 96 additions & 0 deletions example/lib/artboard_nested_inputs.dart
Original file line number Diff line number Diff line change
@@ -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;
}
},
),
],
),
),
],
),
),
);
}
}
2 changes: 2 additions & 0 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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()),
Expand Down
72 changes: 72 additions & 0 deletions lib/src/runtime_artboard.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
10 changes: 10 additions & 0 deletions lib/src/runtime_nested_artboard.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions test/assets/runtime_nested_inputs.riv
Git LFS file not shown
62 changes: 62 additions & 0 deletions test/nested_input_test.dart
Original file line number Diff line number Diff line change
@@ -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);
});
}

0 comments on commit ec3564f

Please sign in to comment.