rive-app / rive-flutter

Flutter runtime for Rive
https://rive.app
MIT License
1.16k stars 180 forks source link

Accessing state machine controller / inputs for nested artboards via code #341

Closed PickleCubeSoftwareLtd closed 3 weeks ago

PickleCubeSoftwareLtd commented 10 months ago

Description

I have an artboard, with 2 nested artboards.

image

All artboards have their own state machine named 'States'

I am then rendering the cameraActions artboard in the following manner:

RiveAnimation.asset(
      'assets/rive/camera_actions.riv',
      artboard: 'cameraActions',
      onInit: _onInit,
      alignment: Alignment.bottomCenter,
    );

And then retrieving the animation controller inside _onInit like so:

final ctrl = StateMachineController.fromArtboard(art, 'States');
if (ctrl != null) {
  art.addController(ctrl);

Now... I want to access the animation controller for takePicture nested artboard. I have tried all sorts of ways, the only way so far I can get it to not crash is if I try the following:

final takePictureArt = art.activeNestedArtboards
          .singleWhere((x) => x.name == 'takePicture')
          .artboard;
      if (takePictureArt != null) {
        final takePictureCtrl =
            StateMachineController.fromArtboard(takePictureArt, 'States');
        // art here is from the param to _onInit
        art.addController(takePictureCtrl!);
      }

But the takePictureCtrl has 0 inputs populated even though I know they're their in my Rive app. Additionally, I can see, through the IDE inspector, that these inputs do exists from the original ctrl in a deeply nested property chain, but there is no public methods/properties that seem to expose them 🤔 the property chain seems to be ctrl.hitNestedArtboards[0]._animations[0]._stateMachineInstance.stateMachineController._inputs

image

Here are the inputs for the nested artboard

image

Steps To Reproduce

Steps to reproduce the behavior:

  1. Create an artboard with an input
  2. Create a new artboard
  3. Nest the artboard from step 2 into step 1
  4. In Flutter code, load the artboard and follow the steps above

Source .riv/.rev file

I will send the the files via email

Expected behavior

I would expect to be able to retrieve and manipulate animation inputs from nested artboards via code.

Device & Versions (please complete the following information)

Flutter 3.13.0 • channel stable • https://github.com/flutter/flutter.git
Framework • revision efbf63d9c6 (3 days ago) • 2023-08-15 21:05:06 -0500
Engine • revision 1ac611c64e
Tools • Dart 3.1.0 • DevTools 2.25.0
PickleCubeSoftwareLtd commented 10 months ago

I managed to get a reference to the controller! But only in a way that feels hacky 😓

This is the code:

void _onInit(Artboard art) {
    final ctrl = StateMachineController.fromArtboard(art, 'States');

    if (ctrl != null) {
      art.addController(ctrl);

      final takePictureArt = art.activeNestedArtboards.singleWhere(
        (x) => x.name == 'takePicture',
      );
      final anim = takePictureArt.animations.first;
      final instance = (anim as dynamic).stateMachineInstance;
      final animController =
          instance.stateMachineController as StateMachineController;

      isBusy = animController.findInput<bool>('isBusy') as SMIBool;

      setState(() {
        controller = ctrl;
      });

      Future.delayed(Duration(milliseconds: 400), () {
        isBusy.value = true;
        return Future.delayed(Duration(milliseconds: 5000), () {
          isBusy.value = false;
        });
      });
    }
  }

The bit to note...

final takePictureArt = art.activeNestedArtboards.singleWhere(
        (x) => x.name == 'takePicture',
      );
      final anim = takePictureArt.animations.first;
      final instance = (anim as dynamic).stateMachineInstance;
      final animController =
          instance.stateMachineController as StateMachineController;

having to cast the first animation to dynamic because the real instance NestedStateMachine doesn't seem to be exported.. it's innested_state_machine.dart

Not sure if there is a reason why this isn't exported, I did some (very basic) testing of setting a value on the nested state machine and it seems to work fine? 🤔

example

HayesGordon commented 10 months ago

Hi @PickleCubeSoftwareLtd,

You're correct in that we do not expose a way to easily manipulate nested artboard controllers. There are plans underway to improve the way that interaction occurs with nested artboards and to make the API easier to use.

There has been similar questions on nested state machines (how to respond to state changes). You can see a sample that we made here: https://github.com/mjtalbot/nested_switch

The API isn't public but you can still import the files from the src directory instead of casting to dynamic. Please note though that you're making use of the underlying code that is not public for a reason. It may be that we introduce breaking changes in that underlying code between releases that aren't documented or deprecated (as it's not public).

HayesGordon commented 10 months ago

If there are particular features you feel are missing or if there is some flow between the editor and runtime you're missing, then be sure to add it on our Discord channel as a feature request. There it can then also be visible to the rest of the community and may influence future design decisions.

PickleCubeSoftwareLtd commented 10 months ago

Hi Gordon

Thanks for sharing this, this looks exactly like what I need. I’ll be mindful that it’s not public, so will keep an eye out for this in future releases!

I did come across a discord feature request that is similar to this and I gave it a 👍.

Thanks for this great tool, it’s the first time I’ve managed to make animations this easy!

naychrist commented 10 months ago

this looks promising! All I need in my current project is to be able to get and set bool inputs. I have tried doing this modifying the onInit code in your example @HayesGordon but it never registers as the SMI value changing even though I can see the results of it changing in the rive. Am I missing something that will stop the last line here from working? I appreciate that this is an annoying question when you guys are in the process of creating this API functionality!

        if (element is NestedStateMachine) {
          if (element.stateMachineInstance
          is RuntimeNestedStateMachineInstance) {
            final cache = (element.stateMachineInstance
            as RuntimeNestedStateMachineInstance);
            element.stateMachineInstance = RuntimeNestedStateMachineInstance(
              cache.stateMachineController.artboard as RuntimeArtboard,
              StateMachineController(cache.stateMachineController.stateMachine,
                  onStateChange: callback),
            );

            String name = element.nestedArtboard!.name;//works
            _smiBool = cache.stateMachineController.findSMI('isOver');//found but value never changes
naychrist commented 10 months ago

actually refactored things a bit to troubleshoot and this is working!

    for (var nestedArtboard in artboard.activeNestedArtboards) {
      for (var animation in (nestedArtboard as RuntimeNestedArtboard).animations) {
        void callback(String stateMachineName, String stateName) =>
            _onNestedStateChange(animation.nestedArtboard?.name ?? '',
                stateMachineName, stateName);
        if (animation is NestedStateMachine) {
          if (animation.stateMachineInstance is RuntimeNestedStateMachineInstance) {
            final cache = (animation.stateMachineInstance
            as RuntimeNestedStateMachineInstance);
            final StateMachineController controller = StateMachineController(cache.stateMachineController.stateMachine,
                onStateChange: callback);
            animation.stateMachineInstance = RuntimeNestedStateMachineInstance(
              cache.stateMachineController.artboard as RuntimeArtboard,
              controller,
            );
            _smiBool = controller.findInput<bool>('isOver') as SMIBool;
naychrist commented 2 months ago

@HayesGordon any chance there is a variation of this than can be used to set the value of nested TextRuns? I have had no luck getting the renderer to show up any changes

clarkezone commented 1 month ago

Checking in on this issue, is there a solution to this for Rive WASM yet?

HayesGordon commented 3 weeks ago

This is now supported on all of the runtimes! See our docs: https://rive.app/community/doc/state-machines/docxeznG7iiK#nested-inputs