feat(flutter): replace old implementation with rive_native for flutter (#10033) b21adb4564

* chore: hack use pub published rive version to sidestep current lottie conversion tests

* chore: purge old dart flutter code, tests, and samples

* feat!: rive_native updates for rive flutter usage

* feat: add rive_native to rive_flutter

* chore: update rive flutter example app

* docs: update CHANGELOG with migration info

* docs: update README

feature: scripting in editor (#10086) 7be3a0fe02

feat(webgpu): Finish input attachments for "subpassLoad" mode (#10115) f838e94a95

fix: release nested artboard animation resources when clearing (#10116) e3a68dff11
release nested artboard animation resources when clearing

chore(webgpu): Update Wagyu names (#10114) cc9f175c5e

feat(vulkan): MSAA (#10107) a9d49ca4e6
Implement the MSAA codepath on Vulkan. This finalizes our Vulkan support
because we now can work on core 1.0 with no extensions.

Disable timestamp with scroll physics (#10111) d8197feca5
Noticed some inconsistencies with scrolling with the pointerEvent timestamp implementation on desktop and web editor that need investigation. For now, disable using the timestamp passed down to rive native (revert to how it was working before) until I have time to investigate further.

feature: add image based conditions (#10108) 247b3b00e8
add image based conditions

reafactor(CommandQueue): added dependency map between files->artboards->statemachines (#10106) 1d45c6418d
Added dependency between state machines and the artboards they are created from.
Added dependency between artboards and the file they are made from
Made all dependencies get cleaned up correctly when owning dependency is deleted.

feat(CommandQueue): Several needed features (#10097) cf63e43d70
* added way to get default view model for a given artboard

* added request Id to errors and mouse events

* finished tests

* order matter when getting deleted apperently

runtime: get rid of the HitTextRun destructor (#10092) fc5abebaec
context: https://2dimensions.slack.com/archives/CLLCU09T6/p1751307412381219

Sometimes it causes crashes on Unity.

This destructor existed because I was worried the glyph contours could take up a lot of space and computation when the state machine is not playing. But perhaps that's better than crashing runtimes.

fix: get text fallback to use clustered unicode points too (#10078) 4d5037b42d

test: Render android/vulkan goldens to the device display (#10074) f6623687a8

chore(ci): Turn on ASAN for some golden tests (#10019) 4a81463748
chore(ci): Turn on ASAN for golden tests

chore: Simplify testing configs (#10072) b1793e755e
Rather than having an explosion of enums for all the different backend
configs, us very basic backend enums (gl, vulkan, metal, etc.), and
capture all the permutations in the separate BackendParams struct.

Nnnn data bind artboards 4 (#10038) 32f0d6d0a0
* add js runtime support

feat(gl): Use KHR_blend_equation_advanced (#10067) 308480cf12
We previously only worked with KHR_blend_equation_advanced_coherent, but
Samsung Xclipse GPUs only support the noncoherent version. Add a
codepath that makes use of the noncoherent extension so we don't have to
copy out a dst texture on these GPUs.

This improves perf on the "Rope" scene from 5.5fps -> 15.1, which is a
decent stopgap, but considering Vulkan currently runs at 108fps, we
should probably mostly just focus on switching to Vulkan.

fix(gl): Make the clip-plane ban on ANGLE drivers more comprehensive (#10066) d3e42c99b7
Detect ANGLE drivers by checking the GL_VERSION string *and*
GL_RENDERER. On some devices, GL_VERSION does not say "ANGLE" but
GL_RENDERER does.

Block both GL_ANGLE_clip_cull_distance and GL_EXT_clip_cull_distance on
ANGLE drivers.

This fixes the "disco" bug with dancing triangles on Xclipse devices.

test: More comprehensive testing for offscreen gms (#10055) 05d64b548f
Rename texture_target_gl* gms to offscreen_render_target*.

Implement offscreen render targets for Vulkan testing.

Add permutations for a "riveRenderable" flag, which disables input
attachment support on vulkan, and will disable UAV support on D3D.

fix: rive_native plugin isolation (#10062) ded9340fad
* chore: refactor globals for rive_native

* feat: flutter renderer starting to work

* chore: refactor web for multi window

* chore: add multi window example

* chore: cleanup

* fix: make raw text for multiwindow

* feat: support multiple plugin instances in windows

* feat: handle multi_window in windows

* feat: multi plugin support on iOS and Android

* chore: clang format

* chore: add get render context to linux

* chore: don’t get factory in test

* chore: remove multi-window example

fix: use reverse iterator (#10057) 4fd30da275
* chore: use reverse iterator

* chore: fix iterator

* chore: try on github macos

* chore: retry

docs: Remove Skia from the runtime readme (#10043) 092566581a

featt(CommandQueue): Added internal file asset loader (#10040) d13f8c2bf6
* started file asset implementation

* asset loader implemented

* started adding tests

* updated fonts and audio to validate before returning an object.

* audio source and font tests

* added deleted callback tests for audio source and fonts

* fixed compiler issues for non windows clang

* updated cargo checks to latest rive_hb

* msvc maudio does not support mp3. use wav instead

* Updated based on PR comments

chore: update list related classes to use RCP (#10032) 23c446feb4
* finish artboard component list rcp

featue: add artboard data bind support (#9996) be0b691d9b
* add artboard data bind support

feat(CommandQueue): Added error messages for each type of error that could happen (#10020) 9af17212ca
* started implementing error messages

* send errors to command q as well as print to cerr

* started adding tests for error messages

* started file error tests

* file error tests

* render image error tests

* state machine error tets

* PR suggestion to convert template function to helper class

* prevent mixup for 32 bit platforms

feature(CommandQueue): View Model Properties finished (#9993) 1d144e0d61
* added renderimagehandle and fileLoaded and enumsListe

* coded out lists

* added tests for list

* property subscriptions complete

* Added abililty to fire triggers

* added tests code cov vaught

* better name

* ASAN for unit tests (#9997)

chore: enable asan

* changes requested by PR comments

* addressed PR comment

* enum renames

* feat: add asan for tests

* chore: missed lua file

* chore: remove windows asan for now

* remove extra ref

* update print text

Use Flutter's PointerEvent timeStamp to compute elapsed time for scrolling (#10013) 94ee0804b8
In Flutter, due to how the pointer events are captured, using DateTime.now or Stopwatch at the time the StateMachine receives the pointer event does not provide accurate timestamps, thus the calculation of the pointer delta and elapsed time were inaccurate in the editor. For example, while dragging, the relative x/y delta between mouse move events may be steady, but the difference between DateTime.now and the previous now could vary widely, leading to inconsistent scroll behavior.

This PR updates to use the PointerEvent's timeStamp instead which is more representative of when the pointer event was dispatched. We capture the timeStamp and pass it through to both the Dart and C++ ScrollConstraints in order to correctly do the computation.

feat: add hash to rcp<T> (#10018) 22e314a8df
feat: add has to rcp<T>

Nnnn separate clear and unbind (#9888) f22462bd78
Nnnn separate clear and unbind

second batch of fixes (#10014) 3c09bb1e7f

rcp file assets (#10016) ecdf58f54b
* feat: rcp_file_asset

feature: add support for comparing with self (#9984) f0da7e9f27
* add support for comparing with self

fix: rectangles to contour heap use after free (#10005) 67811c029d

fix memory issues with data binding (#10002) ca0963da48
* fix memory issues with data binding

fix: recursively duplicate instances when creating view models from a… (#9995) 8cf21b7fee
* fix: recursively duplicate instances when creating view models from a number to list converter

Initialize pointer to nullptr (#9989) e35082ebdc

Fix scroll continued acceleration (#9994) 7e9d2b610f
Fixes a bug (#9985) where clicking after a scroll could result in continual scrolling when the mouse isn't moved.

chore(webgpu): Replace JS bindings with Wagyu (#9986) c05ef4daaa

add color custom property (#9181) de384615b2
* add color custom property

fix: Fix WebGPU sampler bindings (#9976) 6e3a82253a
Samplers broke on WebGPU because we didn't have CI testing. Fix the
samplers and set up a CI job to run through the WebGPU tests on Dawn to
ensure we don't break this again.

chore: refactor databind update cycle (#9863) 891f65794c
* advance data binds per artboard

update keys (#9949) 88befa489b
* update keys

* update file for test

Fix feathering assertion from padding double cusps (#9940) d5140c06b9

add list to length converter (#9930) 3d3767270c

test: Update fuzzer to support real GPU backends (#9931) 6734d748c8
The fuzzer used to only support our null render context. We should also
be fuzzing our graphics backends though. Update the fuzzer to use
TestingWindow and support real GPUs. Also add a null testing window so
the fuzzer can continue using a null render context if chosen.

pass unordered map by reference (#9945) c793892950

Nnnn data binding images and lists updates (#9932) 8363df8fb6
* add support for addInstanceAt in list

* fix istanceAt with index overflow

* fix: advanced triggers for lists

* enable resetting embedded images by passing null

* fix: correctly delete selected data components

feat(CommandQueue) Get / Set Basic Properties (#9929) 6d8f034217
* Added initial property set / get for view models

* added nested view models set

fix: crash with active title (#9928) 5a1a0b5637

Fix: tess renderer & nuke old viewer (#9927) c2ec689eb6

feat(CommandQueue)Server side Cursor Math (#9916) 8bf9682caa
* added math and pointer event struct to easily translate cursor events

* better comment

feat(CommandQueue)  View Models and View Model Instances (#9908) f4960cff48
* added viewmodels and started tests

* creating and destroying viewmodels and isntances

* listViewModels

* view model listeners finished

* added bindViewModelInstance

* added more tests

* addressed PR comments

* updated api to not use ViewModelHandles and have a more consice api

fix: static analysis fixes (#9918) c71be9b1a2
* fix: static analysis fixes

* Update packages/runtime/src/file.cpp

* chore: checking if glyph coverage broke test

* chore: try a different fix

Nnnn data bind images updates (#9911) 3aa5b93199
* add missing types to runtime
* add support for setting image to null

fix(webgpu): Don't allocate unnecessary textures (#9909) dde1897bb7
We were allocating textures for clip, scratch, and coverage even when
PixelLocalStorageType was EXT_shader_pixel_local_storage. This was just
a waste of memory since PLS keeps this data explicit tiled memory.

Don't allocate these textures in EXT_shader_pixel_local_storage mode!

fix: crash when text is in a solo (#9915) 747c3ea77a
* using debug

* fix: initialize m_text to nullptr

* chore: undo previous change

fix: check for empty id when exporting data binds (#9912) 7293b15461
fix: check for empty id when exporting data binds
fix: check for null for m_text

fix crash on unset listener change (#9907) 8edd84d2a3

fix: isTargetOpaque override (#9901) 7c8352ad63

library: allow passing view model instances of a nested library to a library artboard (#9878) 052a4984ef
fixes: https://github.com/rive-app/rive/issues/9781

Imagine this situation.
1. File1 -> publishes vm1, and a artboard1 that understands vm1
2. File2 -> publishes artboard2 that has a nested artboard of artboard1 (imported from library)
3. File3 -> wants to use artboard2 (from File2), and supply it with vm1's instance.

This should work, which is what this PR does. There are two main sectors of changes
1. I created a library asset manager, and uplifts all caching stuff to the 'file' level instead of on the 'libraryAsset' level. This is because ID's like libraryArtboard.viewModelId need to be able to refer to another libraryAsset. Same goes for viewModel.propertyEnum or propertyViewModel.
2. In runtime_exporter, I'm changing how we add to the fileIndexMapper for library components. Before this PR, I was doing this lazily. E.g. if a nestedArtboard refers to an artboardId that's from the library, I remap that to the actual Artboard's index at the time of write. However, this is getting a bit confusing. I thinnnnk it's safe to just map all LibraryComponents before writing anything.

I also fixed path_fiddle for databinding, which is how I've been inspecting databinding in runtimes.

I added a test for this.

fix: Scroll index not considering gap (#9889) aca08e662c
Scroll index values need to account for gap in when the convert to/from scroll offset.

data bind fixes 16 ffe3a20c95
* fix image sampler missing argument

* add support for comparing integers to floats in cpp

feat(Command Queue): Pointer Events (#9881) ab11082212
Adds pointer events to the command queue.

Also fixes an errant callback name.

refactor(CommandQueue) removed erase in `processMessage` to avoid double erase (#9887) e296e14c35
removed erase in processMessage to avoid double erase

fix(editor): add additional index checks for Lists (#9870) 89ffb92b57
Add additional index checks to prevent out of range errors in List access.

refactor(vk): Add a vkutil::Texture2D class (#9862) 5779effa92
vkutil::Texture2D wraps a VkImage AND a VkImageView so we don't have to
manage both of these objects separately when we just want a simple 2D
texture. It also subsumes TextureVulkanImpl, allowing us to simplify
things further and remove that class.

vkutil::Texture & vkutil::TextureView are renamed to vkutil::Image &
vkutil::ImageView, in order to reflect the fact that they are just very
thin wrappers around VkImage & VkImageView.

runtime list updates (#9855) f660dea549
* runtime list updates

removed generation of reqeust ids (#9859) b8bfaaf5e7
* removed generation of reqeust ids

* removed request id type

added advance state machine and settle callback (#9857) 58ece530ef
* added advance state machine and settle callback

* better tests

* address pr comments

Add gate for modifying dirty layouts set (#9856) 465d37f48e
Add a gate to prevent m_dirtyLayout from being modified while it's being iterated. Also adds some additional logging.

fix(renderer): Gracefully handle compilation failures (#9755) c09b771645
Ensure that rendering still succeeds when compilations fail (e.g., by
falling back on an uber shader or at least not crashing). Valid
compilations may fail in the real world if the device is pressed for
resources or in a bad state.

fix(CommandQueue)  (#9845) 6582d5bf02
fix command buffer example to use the correct syntax for file asset loaders

feat(CommandQueue)File asset loader (#9799) 77172ea62b
* added file asset loader

* tests for asset loader

* made asset loader rcp

* addressed pr comments

feat(CommandQueue) Added request Ids associated with jobs (#9796) 5aedcdc61f
* added request ids that are associated with callbacks for the command queue

* modified tests to check for request id

feat: TextInput - refactor shapes for runtime text input (#9836) 455374455c
* chore: updating runtime defs

* chore: refactor TextStyle into TextStylePaint

* chore: missed hpp files

* chore: missed cpp files

* chore: fix clone of text_style_paint

* feat: more text input at runtime

* chore: cleanup

* chore: add typed child iterator

* chore: missed files

* chore: running runtime input in editor

* feat: completing integration with editor

* chore: missed file

* chore: fix builds with text disabled

* chore: use child for test

* tests: adding more tests for text input

* chore: missed test file

* chore: more coverage for child iterator

* chore: using clipping on text input

* chore: test text input selected text

* chore: dry up iterator

Revert "bad driver detection for clip planes (#9714)" (#9775) c62bdb256e
Revert "fix: Make bad driver detection more specific for clip planes (#9714)"

This reverts commit 3ab91e096d9d27a5b8f4cc50737a70eef25d2558.

Sadly, the mini-test could pass on Galaxy S24 SM-S921B, only to have the
clip planes crash later. This should hoepfully go away with Vulkan.

feat(Image sampler filters) Adds Image filter options for rendering images (#9309) 930facea3f
* added filter type to draw batch

* c++ implementation of ImageSamplerOptions

* image sampler options now pushes correct for RiveRenderPaint

* wip

* OpenGL uses correct sample options

* now only one filter option that accounts for mip map

* d3d now uses sampler states options

* better way to get the filter option

* added mirror and inverse mirror options

* started using helper functions for retreiving sampler optiojnhs

* workaround for vulkn semaphore issue

* wip

* added filter type to draw batch

* c++ implementation of ImageSamplerOptions

* image sampler options now pushes correct for RiveRenderPaint

* wip

* OpenGL uses correct sample options

* now only one filter option that accounts for mip map

* d3d now uses sampler states options

* better way to get the filter option

* added mirror and inverse mirror options

* started using helper functions for retreiving sampler optiojnhs

* workaround for vulkn semaphore issue

* wip

* undo deleting empty descriptor

* properly packed image options

* removed uneeded checks

* more unit tests

* unreal image_filter_options integration, gm for image filters

* properly implement new draw functions for cgrender

* finish mege conflicts

* merge vulkan sampler options

* cherry-picked vulkan imp into branch

* initial metal

* clang-format

* d3d12 image filter options

* fixed d3d

* clang-format

* fixed mip filter for metal

* made every filter option act as similiar as possible

* addressed pr changes

* fixed tests

* empty commit to fix github

* fix: webgl renderers

* started addressing PR comments

* metal change for pr

* more pr stuff

* final PR address

* shader update per PR request

* rhi updat e

* added gm requested in pr

* made d3d12 work with more then 2000 sampler changes per flush

* pr comments

chore: add data binding set of tests (#9821) 6a3aa2cbfd
* add data binding set of tests

library: export used artboards from library files only (#9816) 127097dac4
# Description
Before this PR, we export all artboards from library files in runtime_exporter. This PR makes sure we only include what's directly or indirectly used by the host file.

# Details
While working on the runtime exporter, I'm realizing that we have a lot of arguments on the exportOneFile function that can be simplified, so I changed the api a little there too. I tried to separate out this in the first two commits.

For DataEnums, before this PR, there are 2 sources of 'filter' on what to export. One comes from 'usage', and the other comes from `requiredDataEnums` in the mappingContext. With this PR, I'm consolidating this into just using `requiredDataEnums`, so the usage of the current file is folded into this too.

For ViewModels, it's very similar. Before this PR, there's the argument `viewModels`, and the context view models coming from `requiredViewModels` in the mapping context. I've found this to be a little redundant, so I again folded the former into the latter.

For Artboards, there's already `exportArtboards` in the mapping context, but this represents what's exported by each file. The nested artboards rendering logic uses this directly. We don't have a way to communicate, for every file, what's required by other files. As such, I created a `requiredArtboards` concept, that's similar to the `requiredDataEnums` and the `requiredViewModels` above. This tracks the Artboards that every library file needs to export.

Fixes: https://github.com/rive-app/rive/issues/9434

feat(apple): add mac catalyst support (#9759) e6b052eed9

fix: add missing symbol property when creating view model instance (#9827) 3277968b6d

fix: extend unbinding to data bind dependencies (#9817) 0e1fce36c3

Nnnn enable data bind images ff early access (#9808) f91f517f32
* data bind images fixes
* enable data bind images in early access

add tests (#9807) b21cda7564

feature(CommandQeue)  Message Queue (#9704) 5b253d0b0b
* added message queue for receiving information back from server

* addressed some issues

* renamed everything to listeners, manage lifetime of listeners implcictly, modified tests to match and updated example to use listeners.

* updated to match naming convention we talked about

* addressed PR

* added delete messages for file,artboard and statemachine

* fix dumb dumb

* Update packages/runtime/include/rive/command_queue.hpp

* more pr changes

* nested listener classes

* addressed pr requests

fix: cpp modulo to match dart (#9757) 6c03dca01d
* fix cpp modulo to match dart

fix: skia loses the fill type after a rewind (#9741) 8f5a30ac7c

fix: Make bad driver detection more specific for clip planes (#9714) 3ab91e096d
Some ANGLE drivers don't support clip planes correctly. But ather than
making a blanket ban on ANGLE clip planes, compile a mini-test and see
if the failure repros before banning the extension.

fix(gl): Fix a memory leak in TextureGLImpl (#9663) 9e3db1f98f
It appears that we were never deleting the underlying GL texture for
image textures (×_×)

fix: Memory issues in TrivialObjectStream (#9731) 5661461ca8
The idea of addressing full objects inside a deque of bytes is
fundamentally broken, because std::deque does not have contiguous
storage.

To fix this, use std::copy et. al. to move the bytes in and out of the deque,
instead of trying to dereference them. Change our requirements to POD
and rename it PODStream.

fix: Find a workaround to compiler bug on Pixel 3 (#9729) 5ed9258760
Pixel 3's GLSL compiler didn't like a recent change we made. Find a
workaround.

fix: ensure data bind refs and unrefs its source when binding and unb… (#9738) 0d0f9cd7d9
fix: ensure data bind refs and unrefs its source when binding and unbinding

fix: scrollIndex on Lists (#9732) d46676d8d1
Setting/getting scrollIndex on a ScrollConstraint that contained an Artboard List was not updating the scroll offset properly.

We add a layoutBoundsForNode(index) to allow accessing proper layout bounds for every layout in a LayoutNodeProvider.

feat: List Artboard Reuse (#9691) 74188ce8cc
Modifies the List DataBinding classes to cache ViewModelInstanceListItems rather than doing a full replacement, which makes it easier to reuse Artboards that have already been mapped to a ViewModelInstanceListItem.

This PR also clears the List when the state machine is paused (when DataBinding in design mode is turned off)

refactor(CommandQueue) moved drawloop to be driven from commandQueue rather than from the server (#9692) 203b35111d
* moved drawloop to be driven from commandQue rather then from the server

* made pollMessages and waitMessages return !M_disconnect

* removed drawloop added drawkey added stopmessage

* addressed PR comments

fix(unreal):missing decoders (#9637) b46446c172
* Update premake to include decoders for Unreal builds.

fix: get rid of unused build.sh (#9710) 6be784f0dc
* fix: get rid of unused build.sh

* fix: unit tests on msvc and linux

* chore: fix again

* chore: use cirrus labs for linux unit tests

* fix: linux test

* fix gvec polyfill

* chore: fix linux

* chore: linux again

* fix: don’t let ratio go below 0

test: Add a threshold parameter to check_golds.sh (#9670) 4c2a6633c1

update riv to reflect the new core ids (#9711) e2e4f1e226

Nnnn data bind assets bkp 2 (#9705) e6782a6bfb
data bind images

library: remove references to LibraryStateMachineInput (#9701) 5f34d5e684
Note we removed creating them in ec0c2b2#diff-4ace8b8bb73f8a7f225de6e4452a88a5af27d860d807ad64c1e1b2fe40321c12

Not sure if we still wanna resolver still? I kept them in for now.

We have a runtime test that's failing, that's what got me to notice that we still have inputs... https://github.com/rive-app/rive/actions/runs/15053446609/job/42313583695 SORRY!!!!!!

fix: ViewModelInstanceListItem memory leak (#9699) e399210e17
Fixes a memory leak where we were double reffing core instances of ViewModelInstanceListItem. Unit tests now pass.

fix unit test make (#9698) 2e0c905455
merge skipping the pending tests

library: make sure host artboards always get exported first (#9686) 7c7035d035
# Description
In a .riv file that doesn't import from libraries, we expect that the first Artboard is the default one. Our runtime API's expect this. E.g. riveFile->artboard(0) is used extensively.

Before this PR, because we export files in a DFS fashion, artboards from the library actually gets exported first. This breaks the above assumption.

# Details
This PR makes sure that the assumption holds. The way I implemented this is by having files export Artboards to a different buffer, then write that buffer after the host's objects are written.

Here's how I expect the objects to get written, on a file that makes use of 2 libraries.

Host's Backboard
  Lib1's file assets
  Lib1's dataenums, viewmodels and their instances
  Lib2's file assets
  Lib2's dataenums, viewmodels and their instances
Host's file assets
Host's dataenums, viewmodels and their instances
Host's Artboards, animations
Lib1's Artboards, animations
Lib2's Artboards, animations

If Host doesn't have libraries, then it'll just be a removal of all the Lib lines, so we get,
Host's Backboard
Host's file assets
Host's dataenums, viewmodels and their instances
Host's Artboards, animations

fixes: https://github.com/rive-app/rive/issues/9537

chore: fix clang-17 compiler (#9666) 8a1f3286b9
* chore: fix clang-17 compiler

* chore: adding missing file

* fix: rive_native builds

* chore: remove no runtime linking on linux

* chore: more fixes

* chore: removing rive_common

* chore: use build_rive.sh for ios

* chore: use rive_build.sh for recorder

* chore: fix fill missing version

* chore: add rive_build.sh to path

* chore: add rive_build.sh to pr_ios_tests.yaml

* chore: add rive_build to the recorder tests

* chore: drop rive_flutter tests

* chore: fixing ios tests

* fix misspelled

* chore: cleanup

* chore: use latest zlib

* chore: premake5.lua redirects to premake5_v2

* fix: tvos and ios builds

* fix: unreal build path for miniaudio

refactor(TestHarness) Test harness stacktrace's (#9642) c0844f01b5
* added stack trace and signal handling for windows

* added stack trace unwinding for mac / linux

* refactored into seperate file

* add file

* builds for non windows

* addressed some pr comments

library: view model support (#9630) 16c30e956a
This PR allows drag+drop a ViewModel from a library asset into a host file. I tried to keep it readable via commits.

The approach is pretty similar to how DataEnums work with libraries. I've wrapped every view model property into a library version, which would also contain a componentId that contains the ID of that property in the original library file's core.

I've added LibraryArtboard.viewModelId that's similar to Artboard.viewModelId. This is inferred from interalCore when the library file loads (in LibraryAsset.decode, `_updateVms`).

The runtime exporter's context needs to keep track of the instances we want to export from the host file. This is b/c view model instances have to be exported with view models, so the host's instances need to be passed back to the library file and exported with its view models. (mapping_context, `requiredVmInstances`).

Because runtime is extremely order sensitive, I've also added support for DataEnumValue.order and ViewModelProperty.propertyIndex in this PR.

QoL improvements: I refactored the caching logic to account for Library components that are removed, and it's now more generalized (this is tested).

Testing: I also added to the import export test in the editor and C++

Demo: https://2dimensions.slack.com/archives/C07M7DQL4F2/p1746745186933889

Fixes: https://github.com/rive-app/rive/issues/9461
Fixes: https://github.com/rive-app/rive/issues/9345
Fixes: https://github.com/rive-app/rive/issues/9570
Fixes: https://github.com/rive-app/rive/issues/9588

add support for symbol index for formula and operation converters (#9655) fbbd128426

feat: Editor text input (#9644) 4aefd8c646
* feat: working on text input in the editor

* feat: adding raw_text_input to input

* feature: text input starting to work in the editor

* feat: text input

* feat: text input sizing rules

* fix: handle empty lines

* chore: fixing dupe core ids

* chore: removing old menu system

* chore: working on crash

* feat: generate text input artboard

* fix: setting source artboard hug correctly

* fix: failing test

* chore: missed popup button file

* chore: fix tests

* chore: remove no longer valid test

* chore: fix more tests

* chore: fix more tests

* chore: cleanup

* chore: fix clashing ids

feat: Support js arch in build_rive.sh (#9617) 9763df3872

refactor(webgpu): Delete write_texture and write_buffer helpers (#9640) 527fb53e7f

Number to List Converter (#9622) 3a43815502
Adds a data converter to convert a number to a set of List items.
This converter is a quick way to generate list items. It can be assigned a ViewModel, and the converter will generate ViewModelInstanceListItems using the ViewModel's default instance. The ArtboardComponentList inspector now displays ViewModelPropertyLists and ViewModelPropertyNumbers in the combobox which can be selected or pickwhipped.

refactor(tvos): fallback to libwebp if cg fails (#9534) d3b61cf628

chore: wrap layout nodes for editor (#9641) 6af68fed8b

feature: add list index symbol for view model lists (#9643) 2b1a85fa6f
* add list index symbol for view model lists

fix(gl): Fix uninitialized pixel local storage (#9638) fac712cc41
The GL_EXT_shader_pixel_local_storage extension makes a vague hint in
Issue #4 that one could clear PLS to 0 by issuing a glClear with a
zero clear value:

  "This makes the value effectively undefined unless the framebuffer has been
   cleared to zero."

However, Issue #5 may go back on the suggestion from #4 and require
applications to always initialize PLS with a fullscreen draw.

https://registry.khronos.org/OpenGL/extensions/EXT/EXT_shader_pixel_local_storage.txt

Either way, we are observing on some older ARM Mali devices that the glClear
approach does not always initialize PLS, resulting in a rainbow-colored
QR-code-like pattern of uninitialized tile memory. Always initializing
PLS with a draw instead of glClear fixes the issue.

Fixes https://github.com/rive-app/rive-react-native/issues/279

Fix Artboard List crash when layout siblings come before it (#9633) 18c9402bc3
A crash was reported with Artboard Lists which occurred when Artboard List had any sibling layouts and starting and stopping the state machine a number of times. Additionally it only occurred if the Artboard List wasn't the first layout child.

feat: First draft of a CommandQueue (#9620) 18dc7c390b
* feat: First draft of a RiveCommandBuffer

RiveCommandBuffer hopes to eventually become the basic high-level Rive
API. It's an asynchronous command queue that hides the data behind
handles and executes on a separate thread.

This initial example is just something to get started. We will evolve
the API as we feel out what is needed in the various backends.

* More testing

* AutoLockAndNotify

* CommandQueue

* Fix linux build

fix(webgl): Work around a crash on Chrome 136 (#9623) 82c66ee251
Chrome 136 crashes when trying to run Rive because it attempts to enable
blending on the tessellation texture, which is invalid for an integer
render target. The workaround is to use a floating-point tessellation
texture.

https://issues.chromium.org/issues/416294709

Remove artboardId & useLinkedArtboard props from List Items (#9605) 99534c2749
In order to make ViewModelInstanceListItems more generic in preparation for supporting other types of "Lists" in the future, we are removing Artboard specific properties from this core object. Instead, we infer the Artboard based on the ViewModel applied to the List Item (we use the first Artboard we find that has a ViewModel bound to it that matches the List Item's ViewModel)

fix: do not clone converters at resolve time (#9616) ade06e3c79
fix: do not clone converters on import

fix(renderer): Gracefully handle null image textures (#9600) 7dd9f91b4a
Core problem: The android runtime updates out-of-band assets on the main
thread, while rendering and texture updates happen on the worker thread.
This led to a race condition where the RenderImage could still have a
pending texture update while drawing a frame, meaning its current
texture was null.

Short term fix: Just don't draw RiveRenderImages if their texture is
null.

NOTE: This doesn't fix the general race condition where data is
simultaneously being read and written by different threads, but as long
as we cross our fingers and hope that pointer writes are atomic (an
assumption that must also be true for other types of OOB assets being
written), the null check will help us limp by until we rearchitect the
threading model, which is now a very high priority item.

editor: DataEnum in library (#9603) 3df92e66a4
Please review commit-by-commit :)

This PR adds DataEnum as a publishable component. Then in the host file, you can drag the DataEnum into the stage. This DataEnum will appear in the Data sidebar, but will be read-only (no re-ordering or adding new values)
![image](https://github.com/user-attachments/assets/09007ca1-0ed4-46de-a87f-88010b0895bc)
![image](https://github.com/user-attachments/assets/2eda4c1e-8e41-46d1-9ddb-c01562bcc1bc)

Then, in a view model in the host, you can use this DataEnum as if it's defined locally.

Some notes
1. A LibraryDataEnum extends  DataEnum. The only difference is that it has an asset ID and a component ID. I think this approach makes it such that existing UI's will just work.
2. The runtime exporter now has a concept of what DataEnums are required from each file. This makes sure we always include the DataEnum's needed by the host file.
3. I changed the ID resolving logic into the runtime exporter's bindable hooks. Going forward, this is a unified place to remap ID's.

Tests:
1. I added some tests, and also manually verified that transitions understands library data enums. Here's a video: https://2dimensions.slack.com/archives/C07M7DQL4F2/p1746486246058399

Component list refactor pt1 (#9595) 126c53a0f5
Some reworking of ArtboardComponentList and related vm/db classes:

- Refactors ArtboardComponentList to accept ViewModelInstanceListItems rather than ContextValueListItem
- Adds DataValueList.
- Consolidates update and apply functions into a single ContextValue.apply
- Fixes a couple of crashes

fix(scripts)Fixes down stream dirs (#9601) 343f00ae33
now works with downstream dirs

feature: support bindable position units (#9598) 0cadb00a87
support bindable position units

test(unreal) added tests and build scripts for unreal static build anylysis (#9553) 7a9da23d19
* added tests and build scripts for unreal static build anylysis

* added helpers scripts for building the plugin and platform binaries

* now build plugin as well as static analyze it

* now builds gm/golden app in CI

* build fix for unreal

* wip

* script fixes

Fix clamped scroll drag accumulation (#9596) a596ba0966
When using the clamped scroll constraint in the runtimes it was noticed that when dragging, the scroll offset would continue to increase when you scroll beyond the start or end. Visually the scroll was constrained properly, however, this resulted in having to drag in the opposite direction by the amount that was "overdragged" before it would start moving again in that direction. This behavior was not seen in Dart (nor with the elastic scroll)

fix(rive-runtime)moved scripts to be in downstream (#9593) e9fce7bba1
moved scripts to be in downstream

Component list js runtime ca744236fb
Component list js runtime

feat: Move the alpha portion of MSAA blend modes to the blend unit (#9576) 4f9625183f
Move the alpha portion of MSAA blend modes to the blend unit

The advanced blend modes can be split algebraically into two distinct
operations: color and alpha.

The color portion has all the custom logic that needs to run in the
shader.

The alpha portion is run-of-the-mill source-over blending.

For the MSAA backend, move the alpha portion of the operation into the
hardware blend coefficients instead of doing it in the shader. This
gives smoother antialiasing because the blend unit does the blend at
each sample separately, whereas the shader runs once per fragment
against a single (resolved) destination color.

refactor(build scripts) updated setup_windows_dev.bat to act like the ps1 variant (#9577) 7051d2685f
updated setup_windows_dev.bat to act like the ps1 variant

fix: downstream rive-cpp tests (#9575) 32041efdc7
* fix: downstream cpp

* chore: skip input tests when there’s no rive text

fix: do not draw fully transparent shape paints (#9573) 6082e56f65

feat: RawTextInput in rive_native (#9564) 5adbd3311d
* fix: wrong return type

* adding make raw text input

* feat: adding some rawtextinput bindings

* feat: more raw text input apis

* finish bindings

* chore: cleanup

* feat: expose cursor visual position

* chore: allow drag passed bottom

* chore: add comments

* chore: fixing cast

* fix: wrong return types in rive_binding

* chore: missed tests checkin

Expose Transforms for DataBinding (#9538) cc09ee1983
Exposing transform values of Rive objects to databinding has been highly requested. This exposes values such as x, y, width, height as read only bindable fields.

Add Layout scale type to System Enums (#9560) 4ba64bae05
This PR adds the Layout scale type options (Fixed, Fill, Hug) as system enums for data binding. The scale type options enums are necessary for enabling ViewModels to bind to the scale type options. This is particularly important when it comes to Component Lists where Artboards are instantiated dynamically so scale type can't be set at edit time.

feature(Dx12 backend) (#9520) b4a317b254
* path_fiddle d3d12 and start of d3d12 imple

* converted to precompiled hlsl for tess, grad and atlas. Fixed sampler issue in d3d11

* started implementing resource managment

* working state with grad,tess and atlas draws coded. GPUResource mangement also in

* draws in with another set of validation fixes

* atomic mode working

* textures now upload correctly, atomics now have barriers and index/vertex buffers are properly bound

* typo fix

* made work with gms, uniforms updated correctly now, coelesed and resolve now works

* passing golds?

* proper que dependencies

* added some comments around passing command lists for d3d12, if no copy is given, use direct for both

* rename

* added golden pr

* refactor pulling out all shader compiling to a shared D3DPipelineManager class

* pre comments

* fixed workflow syntax error

fix: pass correct data context to state machine (#9545) 68262f2f3f
* fix: pass correct data context to state machine

* remove unused var

RawTextInput (#9540) 85e8a5681f
* starting to add cursor tests

* working on cursor positions

* two ways to compute location the cursor

* working on adding a fully_shaped_text abstraction

* improving cursor movement with bidi

* adding selection

* use rectangles to contour

* cursor movement and tests

* adding selection caching

* adding getting/setting text

* test for setting text

* adding home/end

* feature: word + subword movement and tests

* Adding word selection

* feat: undo/redo for text input

* removing time from text input

* separate selection text

* chore: remove print statement

* chore: clean up

* chore: remove printf

* chore: do not compile cursor when rive text is disabled

* chore: do not use fully shaped text when not compiling in text

* chore: strip text selection path when not using rive text

* chore: remove unused variables

* chore: add changed goldens due to different transform method

* chore: rebaseline iphone se

* chore: remove unused vars

* chore: more unused variables removed

feat(vulkan_unity): adding vulkan support to unity (#9544) 38d5ae8571
* starting to add vulkan support

* Vulkan works!

* use build_rive.sh

* fix premake and remove unused var

* get lunar sdk

* install glslang

* all components

* Use new sync method for vulkan!

* getting vulkan rendering working when color buffer pointer is 0

* frame delay coroutine

* fix: crash swapping native texture pointer

* feat: linux unity support

* chore: adding missed meta file.

* chore: adding build downstream for linux unity

* install glslang

* chore: remove unused code

* chore: addressing PR feedback!

* chore: add SPIR-V tools back

feat(vulkan): Support all render target formats (#9527) 1014fd7fa4
Refactor the Vulkan backend to support render passes of any format,
rather than hard-coding RGBA8 or BGRA8.

To test this, add a "vksrgb" config just for fun. Shaders aren't
generating linear color (yet), but the SRGB attachment works.

Fix layout alignment space between (#9526) 59b55d281f
When moving between row & column, if alignment was set to Expanded (start, center or end), it would not render at the correct position. This was masked at edit time because when you set the alignment using the grid UX, it switches to an non-Expanded alignment (left/center/right and top/middle/bottom), which causes the alignment to fix itself before clicking again to set it to Expanded.

fix(vulkan): Fix input attachmens on AMD (#9513) e5dc0b989c
Feedback loops are valid in Vulkan as long as you don't issue
overlapping geometry, but we were issuing overlaps and using atomics to
disdcard all but one. This seemed valid, but didn't work on AMD.
However, if we emit color values of "0" and instead rely on blending to
make sure the color buffer doesn't change, it works on AMD ¯\_(ツ)_/¯.

refactor(vulkan): Move shaders to a different cpp file (#9504) 598ad6c62e
RenderContextVulkanImpl is getting so big that it's hard to navigate and
my editor is starting to get bogged down. Move the SPIRV array
definitions and shader-building logic to its own cpp file.

feat(vulkan): Implement coalesced resolve (#9497) ea1b51f970
Not all swapchains can be bound as input attachments, and Unity doesn't
appear to have a way to request VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT for
its textures either. Adding the target texture to the render pass and
doing a coalesced resolve should be almost as fast as if the color
attachment had VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT to begin with.

fix: update data binds when events from children are notified (#9499) a21d95735d
# Description
when a nested artboard notifies events, we need to update data bound
objects because those events might trigger changes on values that need
to be synced

Component List in CPP (#9408) db11b46e96
# Description
This PR implements Component Lists in the CPP runtime.

# Details
Mirrors the Component List Dart functionality in C++. Allows generation
of Artboard Instances at runtime through ViewModelInstanceLists. This PR
does not implement virtualization.

feat(vulkan): Use VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL (#9495) 4ddcd7f9c2
When there is no advanced blend, we can use
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL for the color attachment
instead of VK_IMAGE_LAYOUT_GENERAL, which is theoretically slightly more
performant.

refactor(vulkan): Make RenderTargetVulkan more generic (#9494) 21820c5a7a
Rather than just setting & getting low-level information about a
texture, add methods to RenderTargetVulkanImpl that explicitly access
the target and offscreen textures. These methods require specific
layouts and access flags, and will perform barriers if necessary.

Also split out a virtual RenderTargetVulkan class that Unity can
override and provide its own access mechanism for its internal VkImage
objects.

In addition to helping with the Unity effort, this greatly simplifies
some of the barrier work going on and makes the code more robust.

fix: 32-bit Vulkan builds on Android (#9491) f9adbe2746

fix: Fix release build (#9437) 459688c69c
Also add two CI workflows to build release on mac & windows to make sure
we don't break it again.

refactor: Put blend state in PipelineState (#9482) 2d03eba653
Move more pipeline logic up to the higher level and introduce some
static pipeline states for the basic draws inside flush.

library: allow loading library that uses another library (#9480) bcdfcda330
This PR lazily decodes libraries referred to by other libraries.
Fixes: https://github.com/rive-app/rive/issues/9457

Before this PR, a host file will only load the FileAssets referred in
itself. If that FileAsset is a LibraryAsset, we don't go look for other
libraries it refers. This PR changes that to lazily loading whichever
LibraryAssets are needed in the export process.

This is manually verified and I also wrote a test for it.

refactor: Work out low level barriers at the RenderContext level (#9474) 0ebca777f4
Determine all the barriers at a higher level so that the individual
backends don't have to duplicate this work.

This also prepares us for another refactor that will move the blend
state into PipelineState, and to start supporting "blend barriers" that
we can use with non-coherent advanced blend extensions.

fix: data converter range mapper argument order (#9467) e5fc8db9e0
# Description
data converter range mapper in dart was using the arguments in the wrong
order

# Details
This PR also includes an optimization to avoid a bidirectional data bind
to add dirt to itself when it changes the view model value by
suppressing dirt while it modifies the view model value

fix: access enums data (#9460) 9a843dc27a
# Description
some fixes related to view models and enums

# Details
two main fixes:
- export system enum names
- make sure that viewmodels and enums can outlive the file as long as a
view model instance is alive

fix: stroke effects not updating with text modifiers (#9463) 2c52089bde
# Description
fix: stroke effects not updating with text modifiers

# Details
when text modifiers add paint dirt to the text, we have to invalidate
stroke effects so they get redrawn

refactor(renderer): pulled out decodeImage from context helper and made it platform decode (#9448) e7b0480e4e
# Description
made decode Image implantation in `RenderContextHelperImpl` now defined
in `RenderContext`

# Details
We were copy pasting the code for decoding into several places. Moving
it to `RenderContext` allows us to have it in one spot and just be used
by default.

fix: contour bugs with new RectanglesToContour (#9450) 53131428a6
# Description
- I caught a bug with multiple contours due to the inclusion setter
always using the first rect, so it would fail on rects that would create
multiple contours.
- Also fixes an issue with the unique point sorter not using the stored
vectors. I messed something up when rebasing that previous pr.

Adds a test for the multiple contour issue.

fix crash if operation stack is 0 in a malformed formula (#9435) b4066558a1
# Description
fix crash if operation stack is 0 in a malformed formula

Make RectanglesToContour more memory efficient (particularly for re-runs). (#9438) 5c4e83502f
# Description
In the text input branch I realized I needed to re-use
RectanglesToContour and that it would be called often when moving
selection ranged cursors. The existing implementation closely matches
our Dart one which is not very memory efficient, mostly due to it just
being Dart.

# Details
- Try to store re-used containers on the RectanglesToContour class so
allocation can happen less frequently when re-using the
RectanglesToContour.
- Try to avoid any forced heap allocation (although some is still bound
to happen).
- We had a lot of Vectors of Vectors. In almost all cases these got
simplified to linear vectors or vectors of structs/classes. Because each
vector does its own heap allocation, vector<vector<vector<...>>> can
cause a lot of memory re-alloc when adding/removing elements.
- The boolean inclusion vector got converted to a bitset which is also
stored on the class so it can be re-used.
- Nuke Rect as we have the same functionality in AABB.
- Massaged the API a bit to have names match the updated API a little
more closely.

I'll add more comments inline below...

adds a text listener silver (#9444) 922b445f21
# Description
Adds a silver for text run listeners.

Creates two frames, one at the start:
![CleanShot 2025-04-14 at 10 51
41@2x](https://github.com/user-attachments/assets/8499a054-f7d0-4071-b1b1-398bfc12380a)

And one when hovered over the run:
![CleanShot 2025-04-14 at 10 51
49@2x](https://github.com/user-attachments/assets/12ab939a-78d5-4680-bb21-697fd8a49e40)

fix coverage reports (#9446) c07021b43b
# Description
Re-enables building coverage data. This got nuked when different
argument handling was added to test.sh

libraries: serialize library artboards as local artboards (#9419) e75974b896
Fixes
https://github.com/orgs/rive-app/projects/15/views/1?pane=issue&itemId=104718681&issue=rive-app%7Crive%7C9356

Fixes https://github.com/rive-app/rive/issues/9324#issue-2966381868
### Overview
Currently we do not export LibraryArtboards to runtime, as C++ won't
understand them. As such, currently, if you have ArtboardX that contains
NestedArtboard_ofALibraryArtboard, a NestedArtboard that points to
ArtboardX won't work.

This PR makes it such that a NestedArtboard to ArtboardX would show.
Moreover, its statemachine will play too.

### There are a few main changes
1. LibraryAsset: cached library artboards in LibraryAsset are referenced
by ID instead of by name. This ID is the original artboard's ID in the
library asset file, and is persisted in our schema as
LibraryArtboard.artboardId. Same same with caching statemachines and
linear animations. I've added LibraryStateMachine.animationId and
LibraryLinearAnimation.animationId.
2. NestedArtboard: before this PR, there was logic that checks whether
the source artboard was a LibraryArtboard or just an Artboard. Now, it's
just Artboard, because the runtime exporter only exports Artboards.
3. Runtime exporter: I've created the idea of MappedContext, which maps
a library identifier to its various remaps, like globalIndexMapper,
nestedIds, eventIds, etc.

### The mapping
NestedArtboard.artboardId -> LibraryArtboard.id
LibraryArtboard.artboardId -> LibraryAsset's rev file's Artboard.id

NestedStateMachine.animationId -> LibraryStateMachine.id
LibraryStateMachine.animationId -> LibraryAsset's rev file's State
Machine.id

### Order of export
Currently, when we export we only go over the host file, and the order
looks like
Backboard_ofHost
FileAssets_ofHost
DataModels_ofHost
Artboard1_ofHost
Artboard1_components_ofHost (e.g. paints, nested artboards, state
machines, etc.)

Now, I'm recursively also exporting the library files. So the order
looks like
Backboard_ofHost
[XXX]
  **--FileAssets_ofLibrary
  --DataModels_ofLibrary
  --Artboard1_ofLibrary
  --Artboard1_components_ofLibrary**
FileAssets_ofHost
DataModels_ofHost
Artboard1_ofHost
Artboard1_components_ofHost

Basically, we insert the indented section, which represents the library
file. And if Library uses other library assets, it should go into the
[XXX] section. Tho we currently don't support this, as we don't decode
library assets used from a library asset.
If we aggregate all the artboards, [0, total count) is the range of
valid artboard indices in C++.

### Tests
I'll fix the TODO's in the tests

refactor(renderer): Consolidate MSAA depth/stencil settings (#9436) 689a455e67

log error with missing components (#9431) 099d761edd
# Description
print errors at the cpp level so they are surfaced on our higher level
runtimes

# Details
In order to provide users with more information about errors, we're
relying on the fprintf method from cpp.
This is a temporary solution until we build a more robust error message
model.

chore: flush rive runtime change (#9428) 59d5d4d658

Add silvers without lfs (#9425) f2968887ec
# Description
re-add silvers, no lfs to keep ios/spm happy

chore: temporarily remove silvers (#9424) eabc82a4c2
# Description
get rid of silvers for now, to be added back in outside of lfs

refactor(vulkan): Lift lifecycle management (#9410) 6cce6709cb
Refactor Vulkan lifecycle management into more organized, non-templated
classes. Lift them to the renderer level, where D3D12 and Metal can also
start taking advantage of the 'safeFrameNumber' paradigm.

refactor(renderer): Send map sizes to unmap functions as well (#9407) 7f5e51f4b4
Vulkan was having to track the mapping size from the map() function in
order to know how much to flush in the unmap() function.

Update the higher level code to just pass the map size to unmap() as
well.

This also found a bug where we were mapping the entire size of the
buffer, rather than just the amount we were going to write. This will
most likely make no difference on most platforms, where this is all
coherent anyway, but it's good to fix.

# Description
<!-- Short, digestible description of what this adds or fixes -->

# Details
<!-- Rationale, history, Slack discussions, and technical details -->

<!--
Questions before merging:
- If this is a feature, is it sufficiently unit tested?
- If this fixes a bug, is there a unit test to ensure no regression?
- Does this concern Marketing, and could we make an announcement about
it?
  - If so, now is the time to notify them
- Are the commits in [Conventional
Commit](https://www.conventionalcommits.org/en/v1.0.0/) format?
  - This will help with automated release notes
-->

Metal fiddle context assert fix (#9393) 43d6ac25e6
# Description
Fixed Fiddle context drawableSize

# Details
Now properly set the correct `drawableSize` so that `CAMetalLayer` gives
us the correct size

Fix GL buffer race condition (#9387) 93d4fda5a2

Fix rendering corruption from nested clipIDs (#9383) 373238831d

add support for joystick time based dependents (#9272) 038bd34aee
# Description
fix for joystick controlled timelines not updating the time

# Details
If a state machine has a timeline controlling a nested remapped
animation through keyframes,
and that timeline is itself controlled by a joystick
our update cycle would not apply the time updated by the joystick
because the advance cycle has passed.
This PR looks for joystick controlled animations, and stores which
objects are nested remapped animations to advance them while they are
being applied.

Switch image textures to premultiplied alpha (#9377) a70f0f84dd
The renderer intentionally chose unmultiplied alpha for gradients, in
order to not lose color information with transparent stops. But for this
very same reason, we need *premultiplied* alpha for image textures.
Otherwise, the (transparent) background color can bleed into edges.
Switch image textures to premultiplied, while keeping gradients
unmultiplied.

rive-runtime pull silver rivs (#9384) 9d516a9ece
# Description
rive-runtime is building, but now its failing on the silvers. I
**think** it could be because we need to git pull lfs the srivs so
trying that.

Fix glfw working path (#9382) bd512ec1d9
# Description
Previous PR to fix rive-runtime test had the path relative to mono, not
relative to rive-runtime.

Fix Rive-runtime tests (#9381) 92a4d0a287
# Description
Mac unit tests for the downstream rive-runtime have been failing for
some time. Looks like glfw wasn't being built in the downstream
workflow.

# Details
This PR adds the Build glfw step that we do in our mono repo into the
rive-runtime test workflow. Ran it locally, was able to repro the test
error and this fixes the issue locally.

```
../../../../renderer/path_fiddle/fiddle_context_gl.cpp:23:10: fatal error: 'GLFW/glfw3.h' file not found
hb-ot-map.cc
#include "GLFW/glfw3.h"
         ^~~~~~~~~~~~~~
```

Nnnn conditionnally export images and artboards (#9376) c4dcac3232
# Description
add support for three features:
- set images as guide layers
- select which additional artboards are exported with the riv file
- select which additional images are exported with the riv file

Component Lists (Dart) (#9278) 55918df404
# Description
This is a first pass at the Dart implementation of Component Lists
(dynamically generated lists). In this iteration, the Component List
items can be built up using ViewModel list items together with
Artboards.

# Details
ArtboardComponentList is a new type of Drawable that builds up and
renders MountedArtboards in a similar way to how NestedArtboards work.
To use it with DataBinding, perform the following steps:
1. Create a ViewModel to contain your list
2. Create another ViewModel representing your List item. You can also
add properties to this viewmodel that are bound to properties in an
artboard to be used as a list item.
3. In the ViewModel created in step 1, add a List property from the
popout
4. Select the List property and in the inspector, add List items. These
items should be tied to instances of the ViewModel created in step 2 and
the Artboard to be used as your list items.
5. Bind the ViewModel from step 1 to the Artboard where you are adding
the Component List (ie Main Artboard)
6. On the main artboard, select either the Artboard itself or create a
layout and select the layout. In the inspector, click **Add Artboard
List**. The Component List will be added as a child of the selected
Artboard/Layout.
7. Select the Component List, then bind the ViewModel's list property to
the Source field (either via pickwhip or right click)
8. Play the state machine

Notes:
- (UPDATE: workarounds have been removed and it now works with design
mode DB)There are a couple of workarounds to get this to work because
the ViewModels don't get "applied" until the State machine starts
playback (this will change soon with Design time DataBinding). Those are
pointed out in the comments.
- (UPDATE: crash has been resolved)There is also a temp workaround for a
crash I was seeing. Need to investigate further.
- Editor UX still needs work
- CPP implementation to follow in a seperate PR

check for msbuild so we know if we need to load the vs environment (#9374) 762485768a
# Description
Fixes build_rive.ps1 so that it no longer eventually breaks PATH

# Details
added a check that only loads the VS environment when `msbuild` is not
found

<!--
Questions before merging:
- If this is a feature, is it sufficiently unit tested?
- If this fixes a bug, is there a unit test to ensure no regression?
- Does this concern Marketing, and could we make an announcement about
it?
  - If so, now is the time to notify them
- Are the commits in [Conventional
Commit](https://www.conventionalcommits.org/en/v1.0.0/) format?
  - This will help with automated release notes
-->

Update ANGLE_shader_pixel_local_storage usage (#9367) 85cdb31022
Update our bindings, definitions, and usage to reflect the latest
updates to the extension. Notably, we can now compile programs while PLS
is active, so no need for the duplicated logic inside and outside of PLS
being active.

Libraries (#9255) 9d8a7e81fa
feature: core definitions for libraries
feature: basic editor ui for libraries
feature: basic library backend api integration

feat: return string reference from vm instance runtime name (#9366) f359c918e0
Changed `ViewModelInstanceRuntime::name()` to return a const reference
instead of a string copy, making Unity marshalling a bit easier.

Listener Silver and How to make a Silver (#9321) 8bdedb6b19
# Description
Small example tutorial for how to make a silver, also adds a silver in
the process.

# Details
Details/tutorial here:
https://www.notion.so/rive-app/Make-a-new-Silver-1c95da0b06d9808e9fbbe6bdf6219df4?pvs=4

Revisit numeric stability for colordodge and colorburn (#9313) 1e42beac24
A previously-solved issue had crept back in, where unmultiplying "color
== alpha" did not yield precisely 1.0. Fix the issue (again) and set up
full blown unit tests for the glsl code to ensure this doesn't regress
again.

Nnnn viewmodel runtime updates (#9300) 5dba17ac5d
# Description
- add support for binding an artboard through a state machine
- implements callbacks per view model instance instead of using the root
instance
- fixes some memory issues
- adds a first set of tests

Nnnn data bind fixes 9 (#9314) 8bb6aa6311
# Description
Four small fixes:
- reset playbackValues when syncing view models
- rename Boolean Negate to Boolean Toggle
- pad hex values in to string converter
- expose js viewModelCount getter

Silvers (#9281) b329a96f26
# Description
This adds the ability to serialize draw commands to .sriv file via a
SerializingFactory which can be used by the unit tests in place of the
NoOpFactory.

# Details
You can use the SerializingFactory to initialize your .riv and annotate
the serialization with things like frame size and new frame/frame begin.
This makes it possible to drop the generated .sriv into the Flutter
viewer which will perform the serialized rendering commands to show
exactly what the runtime is doing, high level rendering-wise. It can
also diff two .sriv files if they are dropped in one drag/drop op.

When adding new "Silver" tests make sure to rebaseline the silvers by
doing:
```
cd rive/packages/runtime/tests/unit_tests
./test.sh rebaseline
```

You can now modify the runtime and re-run the tests to make sure the
silvers still match:
```
./test.sh
```

The important function to call at the end of your test is ::matches on
the SerializingFactory. This will either save or check for equality of
the existing silver.
```
TEST_CASE("juice silver", "[file]")
{
    SerializingFactory silver;

    // .. actually use the factory

    CHECK(silver.matches("juice"));
}
```

Remove a spurious print from WebGPU mipmap generation (#9306) f346d7b933

Make elastic scroll properties bindable (#9304) 1673c55a6c
# Description
Making elastic physics properties Friction, Speed Multiplier and Elastic
factor bindable.

# Details
Also added a few unit tests for ScrollConstraint. Will add more.

More WebGPU fixups (#9187) ea9f89e682
Use an empty bind group instead of a null bind group when an index is
unused.

Make the base instance uniform more flexible.

Nnnn data bind mode design (#9279) 305920643b
# Description
Adds support for data binding in design mode.

Implement mipmaps in WebGPU (#9302) de583e7000
Mipmaps had not been implemented yet because WebGPU doesn't provide an
easy mechanism to generate them. Generate mipmaps by literally rendering
higher levels into lower levels.

Fix for layoutstyle dirt continually being added in nested artboards (#9298) 05d87692bb
# Description
Noticed when using scrollbar constraints in a nested artboard that
LayoutStyle can continue to be dirtied.

# Details
Continual LayoutStyle dirt was the result of a recent change related to
Layout Direction. With layout direction, we needed the cascade of
direction information to result in calculateLayout and
updateLayoutBounds, so we forced that to happen in nested artboards.
However, this caused a side effect with continual dirt. The fix now
checks to see if the cascade resulted in any changes, in which case we
call calculateLayout and updateLayoutBounds.

Improve numeric stability of blend modes (#9290) 2ed28d20e6
Turns out it was possible for src and dst to fall outside the range
0..1. The blend math relies strictly on these values both being within
said range, so clamp them.

Rearrange the COLOR_DODGE and COLOR_BURN formulae to no longer rely on
receiving appropriately signed IEEE Inf values from dividing by zero.
The spec mandates this behavior, but it's too dangerous to rely on it.

Fix Layout issue with image when N-slice applied (#9283) c118df3ccb
# Description
Images were not respecting their parent layout's size when an N-slice
was was applied as a mesh (runtime only).

# Details
This bug was introduced as part of another fix
(https://github.com/rive-app/rive/pull/8840) where the correct value of
type was not being applied for n-slice meshes.

Accept a raw VkImageView in the Vulkan renderTarget (#9286) f103ee589e
Now that the client is fully in charge of synchronization, we don't need
to bridge awkwardly between the client's data and Rive's internal
resource management (i.e., VulkanContext::makeExternalTextureView()).

The client can just give us a VkImageView and make sure they don't
delete it until they've waited for that same frame's fence.

Only call gms_build_registry() once (#9289) 0122594822
Looks like these were accidentally being run twice each. We only need
one run.

Overhaul Vulkan synchronization (#9280) 335ed61d42
The Vulkan backend has been requiring the client to pass fences along
from queue submissions, and then it waits on these fences on its own
timeline. This is hard to work with, bug prone, not optimal for
performance, and just isn't very Vulkany.

This change removes all CPU-GPU synchronization from Rive's backend.
Instead, the client is responsible for waiting on its own fences (which,
realistically, it was already doing anyway). Instead of passing in
fences, it just passes in two lifecycle counters currentFrameNumber and
safeFrameNumber. Resources used during the flush belong to
'currentFrameNumber'. Resources last used on or before 'safeFrameNumber'
are safe for Rive to be release or recycle.

Begin rework of Vulkan synchronization (#9275) 051f5a7411
Final vision: Client takes full responsibility for their app's Vulkan
synchronization; Rive never touches a fence or semaphore.

In this PR: Update just the testing backends to synchronize on their own
fences instead of passing them along to Rive. Factor out a
rive_vkb::Swapchain class that encapsulates the common functionality
from all the Vulkan testing backends.

This update alone fixes the Vulkan crashes we were seeing on Android, so
also turn Vulkan CI testing back on!

...And also fix some bugs to get us running on ARM GPUs.

More cleanups for Vulkan bootstrapping (#9270) 0d2bfd8f72
* Pass around fewer pointers to vkGetDeviceProcAddr.
* Use more modern Vulkan instance versions in non-vkcore configs.

Reduce the # of arguments required for Vulkan context creation (#9265) d1602f07cd
Don't ask for things we can query ourselves. Make it easier on the
client calling us.

Supporting binding of Layout enums (#9247) f5975ce890
# Description
Adds support for Layout enums as system enums to allow DataBinding.

# Details
Adds binding support for the following Layout enums:
- Flex Direction
- Wrap
- Direction (LTR/RTL)
- Alignment
- Show/hide
- Absolute/relative

Also updates some of the core widgets to display the DB border more
consistently. This includes ComboBox, MultiToggle and custom alignment
widget.

editor: fix using feather with opacity in a text modifier (#9233) ae422c57fb
When using a text modifier with opacity settings, we go through a
different route for rendering.
In the render function for the shape paint, we apply an override render
paint to it that copies over the color of the shape's original render
paint, but with opacity overrides (this is the `applyTo` call). We
currently do not apply the original render paint's feather attributes.
This PR does that.

Some details:
1. Previously, linear gradients didn't account for the text modifier's
opacity overrides. This PR accounts for it.
2. Inner feathers depend on a text style's _renderPath for its bounds
calculations, so we need to make sure this is updated even if it's not
used for rendering as-is.
3. I added a golden test

before in editor+nested artboard

![image](https://github.com/user-attachments/assets/f6a681e4-f900-4280-b22a-7e245de6ce40)

after:

![image](https://github.com/user-attachments/assets/18b91586-cdef-492f-978c-670a8226fc1e)

Nnnn data bind fixes 8 (#9239) 8d15312404
# Description
change default view model color to 0xFF000000
use checkbox for view model booleans during playback

More Build Options (#9240) ff4d47326e
# Description
Added Build and Platform Options

define rive::math::pi to be the same as m_pi (#9241) a201088666
# Description
make `rive::math::pi` the same number as `M_PI` so that `M_PI` is no
longer needed.

fix lists and viewmodel instances (#9236) 0bd3aad9ec
# Description
fix view model instances destroyed
fix view model instances not being reused

# Details
When lists were created, we were wrapping them unnecessarily in an rcp
which would cause the instance to be destroyed when it went out of
scope.
This PR also fixes a scenario where, if the original view model instance
tree had multiple branches pointing to the same view model instance, the
copy would create new copies for each branch instead of reusing the same
cloned one.

Nnnn data bind tests (#9180) c8441a6f67
first batch of data bind tests
it covers converterts, binding properties and state machine conditions
also fix some deletion of instances surfaced by tests

add support for replacing view model instances and improve memory man… (#9206) 70bb6bd0f7
add support for replacing view model instances and improve memory
management

# Description
Adds support for replacing view model instances in a view model instance
sub tree

# Details
We have heard requests to replace or reuse view model instances across
multiple files.
This PR addresses both cases by exposing an appropriate API and handles
memory as needed.

fix: include artboard data bindings when cloning an artboard instance (#9215) 1833d63ebb
# Description
data bindings targeting artboard properties were not being cloned when
generating an artboard instance

# Details
since artboards are cloned separately, cloning their data bindings was
missing

Fix layout isDisplayHidden check in CPP (#9223) cf1026cb7f
# Description
A file containing 50 levels of nested layouts would hang when opening.

# Details
There was a recursion bug discovered in the CPP LayoutComponent causing
the freeze. The fix was tested with layouts 100 levels deep on the main
artboard and nested artboards and performed without any problems. Added
a couple of additional optimizations after chatting with @bodymovin.

Renderer - move draw limit check to run before draw resource allocation occurs (#9200) e97d77938d
Renderer - There is an issue where resource allocation for draws (which currently
refers to only coverage buffer and feather atlas allocations) from
within allocateResourcesAndSubpasses() can succeed, but be followed by a
failed check for hitting an internal draw limit. This causes the draw
that was attempted to be submitted/pushed to reattempt to push a second
time, and then make a second allocation. This PR rearranges the limit check and splits out the subpass determination to avoid this issue, although the code is still fragile as-is.

Vulkan - Atlas texture barrier fix  (#9212) 48b57c6062
Renderer, Vulkan backend bug fix: The barrier that occurs after the atlas texture
render pass was incorrectly performing the barrier on the tessellation vertex texture.
This barrier could cause corrupted data in the tess texture.

make slicer width and height bindable (#9209) d48e9c8513
# Description
add two missing bindable properties

# Details
allow width and height of n-slicer to be bindable

n-slicing: update path when removing n-slicing  (#9211) cecd189934
The Path needs to recompute its render path to update the positions of
its vertices on the screen.

When an NSlicedNode is becoming irrelevant to a Path, e.g. when we
ungroup it, or when we drag a shape out of the hierarchy, Path needs to
update.

However, currently it doesn't because update() happens after a core
component's onRemoved. This means, deformer from the Path's perspective
will be null.

This PR tells Path to update whenever Dirt.nslicer is propagated,
regardless of whether it's currently under the effect of one.
But it will only recalculate path on worldTransform dirt if a deformer
is valid.

I verified that when we ungroup or drag a shape out of a NSlicedNode,
the most updated path is shown.

Layout Direction Left/Right Logical Properties (#9202) 6cd55f8411
# Description
Adds support for treating left/right based layout property values as
logical values, similar to how HTML start/end logical properties work.
These include:
- Left/right Padding
- Left/right Margin
- Absolute left/right Position
- Left/right Corner Radius

# Details
The initial commit to support Layout direction only included relative
layout child position (and text), but it was pointed out that it would
be useful to also support the above property values. Adding support for
these by default, but they can also be overridden for any particular
layout if desired by setting its own Direction to either LTR or RTL
since we support direction as a cascading layout style.

We also updated the Direction combobox so that it always appears on a
layout flex inspector regardless of whether it has layout children or
not, since it may contain a text child which can also have its direction
set using this property.

Golden test included.

clear bindable property when used (#9198) e3cd39b754
# Description
fix: If a bindable property is used in multiple places it causes invalid
states and can lead to a crash

# Details
a bindable property has a single owner responsible of its lifecycle, so
we need to clear its value once it's used

runtime golden: add support for databinding (#9188) c21188ef26
This PR adds support for databinding in the runtime golden test
framework (not the flutter one).
I also added a test:
<img width="652" alt="image"
src="https://github.com/user-attachments/assets/88a658d9-d0eb-4f16-9e54-5fa308d5c335"
/>

I also added support for the skia viewer app and path fiddle, and
verified that the .riv file added in this PR renders well in both those
places.

fix warnings reported by a client (#9192) 66b36fc42e
Reported here:
https://2dimensions.slack.com/archives/C08BQSENP08/p1741376439219539

Definitely seems like this is undefined behavior:
https://en.cppreference.com/w/cpp/string/basic_string/basic_string

Fix bit rotting in WebGPU (#9183) 3eae4e45b0
Update Dawn integration and peg the build script at a specific commit.

Implement feathering.

Fix image textures.

editor+runtime: text follow path orient, strength, and trim path props (#9177) 7fe2e81969
These properties take inspiration from Rive's other features. E.g. the
creative time are used to manipulating the start+end+offset in trim
path, and the follow path constraint has strength and orient.

For trim path, I'm noticing that the stroke always wraps. However, I
thought it'd be kinda cool to intentionally not wrap if the range is
<100% on a closed path. Then you can get a cool kinda effect with
tangents (e.g. the penultimate row on the golden test below).

This is the new UI (in a popout):

![image](https://github.com/user-attachments/assets/952e0bc0-8c0e-47b0-8f6a-03ba67eb6349)

I added a golden test that looks like this:
<img width="926" alt="image"
src="https://github.com/user-attachments/assets/376b7969-ea7b-479f-8bad-d098e5914bf0"
/>

Co-authored-by: Gordon <pggordonhayes@gmail.com>
Co-authored-by: hernan <hernan@rive.app>
This commit is contained in:
HayesGordon
2025-07-07 16:37:21 +00:00
parent 2b91ad3e27
commit a6cda37523
699 changed files with 3027 additions and 46353 deletions

View File

@ -1 +1 @@
f99c5665ceb9de1780e91483c082b190d49839f6
b21adb45646d34fd7ea1850e5802ce63afc937aa

View File

@ -1,7 +1,45 @@
## 0.14.0-dev.1
This is a significant update for Rive Flutter. We've completely removed all of the Dart code that was used for the Rive runtime and replaced it with our underlying [C++ Runtime](https://github.com/rive-app/rive-runtime).
This has resulted in significant changes to the underlying API.
Please see the [migration guide](https://rive.app/docs/runtimes/flutter/migration-guide), [Rive Flutter documentation](https://rive.app/docs/runtimes/flutter/flutter), and the updated example app for more information.
### What's New in 0.14.0
This release of Rive Flutter adds:
- The [Rive Renderer](https://rive.app/renderer)
- Support for [Data Binding](https://rive.app/docs/editor/data-binding/overview)
- Support for [Layouts](https://rive.app/docs/editor/layouts/layouts-overview)
- Support for [Scrolling](https://rive.app/docs/editor/layouts/scrolling)
- Support for N-[Slicing](https://rive.app/docs/editor/layouts/n-slicing)
- Support for [Vector Feathering](https://rive.app/blog/introducing-vector-feathering)
- All other features added to Rive that did not make it to the previous versions of Rive Flutter
- Includes the latest fixes and improvements for the Rive C++ runtime
- Adds prebuilt libraries, with the ability to build manually. See the [rive_native](https://pub.dev/packages/rive_native) package for more information
- Removes the `rive_common` package and replaces it with `rive_native`
Now that Rive Flutter makes use of the core Rive C++ runtime, you can expect new Rive features to be supported sooner for Rive Flutter.
**Note:** All your Rive graphics will still look and function the same as they did before.
## Requirements
### Dart and Flutter Versions
This release bumps to these versions:
```yaml
sdk: ">=3.5.0 <4.0.0"
flutter: ">=3.3.0"
```
## 0.13.20
- Fix: Windows/Linux building. Undefined symbol `hb_style_get_value`, see issue [437](https://github.com/rive-app/rive-flutter/issues/437)
## 0.13.19
- Adds the `isTouchScrollEnabled` property to `RiveAnimation` and `Rive` widgets. When `true` allows scrolling behavior to occur on Rive widgets when a touch/drag action is performed on touch-enabled devices. Defauls to `false`, which means Rive will "absorb" the pointer down event and a scroll cannot be triggered if the touch occured within a Rive Listener area. Setting to `true` will impact Rive's capability to handle multiple gestures simultaneously.

104
README.md
View File

@ -7,9 +7,9 @@
![Rive hero image](https://cdn.rive.app/rive_logo_dark_bg.png)
Rive Flutter is a runtime library for [Rive](https://rive.app), a real-time interactive design and animation tool.
Rive Flutter is a runtime library for [Rive](https://rive.app), a real-time interactive design tool.
This library allows you to fully control Rive files with a high-level API for simple interactions and animations, as well as a low-level API for creating custom render loops for multiple artboards, animations, and state machines in a single canvas.
This library allows you to fully control Rive files in your Flutter apps and games.
## Table of contents
@ -21,50 +21,60 @@ This library allows you to fully control Rive files with a high-level API for si
- [Note on the Impeller renderer](#note-on-the-impeller-renderer)
- [Supported platforms](#supported-platforms)
- [Awesome Rive](#awesome-rive)
- [Troubleshooting](#troubleshooting)
- [Building `rive_native`](#building-rive_native)
- [Testing](#testing)
- [Contributing](#contributing)
- [Issues](#issues)
## Overview of Rive
[Rive](https://rive.app) is a powerful tool that helps teams create and run interactive animations for apps, games, and websites. Designers and developers can use the collaborative editor to create motion graphics that respond to different states and user inputs, and then use the lightweight open-source runtime libraries, like Rive Flutter, to load their animations into their projects.
[Rive](https://rive.app) combines an interactive design tool, a new stateful graphics format, a lightweight multi-platform runtime, and a blazing-fast vector renderer. This end-to-end pipeline guarantees that what you build in the Rive Editor is exactly what ships in your apps, games, and websites.
For more information, check out the following resources:
:house_with_garden: [Homepage](https://rive.app/)
:blue_book: [General help docs](https://rive.app/community/doc/introduction/docvphVOrBbl)
🛠 [Rive Forums](https://rive.app/community/forums/home)
- [Homepage](https://rive.app/)
- [General Docs](https://rive.app/docs/)
- [Flutter Docs](https://rive.app/docs/runtimes/flutter/flutter)
- [Rive Community / Support](https://community.rive.app/c/support/)
## Getting started
To get started with Rive Flutter, check out the following resources:
- [Getting Started with Rive in Flutter](https://rive.app/community/doc/flutter/docqzmYRZmvF)
- [Getting Started with Rive in Flutter](https://rive.app/docs/runtimes/flutter/flutter)
For more information, see the Runtime sections of the Rive help documentation:
- [Animation Playback](https://rive.app/community/doc/animation-playback/docDKKxsr7ko)
- [Layout](https://rive.app/community/doc/layout/docBl81zd1GB)
- [State Machines](https://rive.app/community/doc/state-machines/docxeznG7iiK)
- [Rive Text](https://rive.app/community/doc/text/docn2E6y1lXo)
- [Rive Events](https://rive.app/community/doc/rive-events/docbOnaeffgr)
- [Loading Assets](https://rive.app/community/doc/loading-assets/doct4wVHGPgC)
More advanced usage:
- [Caching a RiveFile](https://rive.app/community/doc/caching-a-rive-file/docrLMDw15AJ)
- [Alternative Widget Setup](https://rive.app/community/doc/alternative-widget-setup/docNlDD0H0rp)
- [Custom Rive RenderObject](https://rive.app/community/doc/custom-rive-renderobject/docnbX5AnjkW)
- [Custom Painter](https://rive.app/community/doc/custom-rive-renderobject/docnbX5AnjkW)
- [Artboards](https://rive.app/docs/runtimes/artboards)
- [Layout](https://rive.app/docs/runtimes/layout)
- [State Machine Playback](https://rive.app/docs/runtimes/state-machines)
- [Data Binding](https://rive.app/docs/runtimes/data-binding)
- [Loading Assets](https://rive.app/docs/runtimes/loading-assets)
- [Caching a Rive file](https://rive.app/docs/runtimes/caching-a-rive-file)
## Choosing a Renderer
For more information see: https://rive.app/community/doc/overview/docD20dU9Rod
In Rive Flutter you have the option to choose either the Rive renderer, or the renderer that is used in Flutter (Skia or Impeller).
You choose a desired renderer when creating a Rive `File` object. All graphics that are then created from this `File` instance will use the selected renderer.
```dart
final riveFile = (await File.asset(
'assets/rewards.riv',
// Choose which renderer to use
riveFactory: Factory.rive,
))!;
```
Options:
- `Factory.rive` for the Rive renderer
- `Factoy.flutter` for the Flutter renderer
For more information and additional consideration, see [Specifying a Renderer](https://rive.app/docs/runtimes/flutter/flutter#specifying-a-renderer).
### Note on the Impeller renderer
Starting in Flutter v3.10, [Impeller](https://docs.flutter.dev/perf/impeller) has replaced [Skia](https://skia.org/) to become the default renderer for apps on the iOS platform and may continue to be the default on future platforms over time. As such, there is a possibility of rendering and [performance discrepancies](https://github.com/flutter/flutter/issues/134432) when using the Rive Flutter runtime with platforms that use the Impeller renderer that may not have surfaced before. If you encounter any visual or performance errors at runtime compared to expected behavior in the Rive editor, we recommend trying the following steps to triage:
Starting in Flutter v3.10, [Impeller](https://docs.flutter.dev/perf/impeller) has replaced [Skia](https://skia.org/) to become the default renderer for apps on the iOS platform and may continue to be the default on future platforms over time. As such, there is a possibility of rendering and performance discrepencies when using the Rive Flutter runtime with platforms that use the Impeller renderer that may not have surfaced before. If you encounter any visual or performance errors at runtime compared to expected behavior in the Rive editor, we recommend trying the following steps to triage:
1. Try running the Flutter app with the `--no-enable-impeller` flag to use the Skia renderer. If the visual discrepancy does not show when using Skia, it may be a rendering bug on Impeller. However, before raising a bug with the Flutter team, try the second point below👇
```bash
@ -75,16 +85,58 @@ flutter run --no-enable-impeller
## Supported platforms
| Platform | Flutter Renderer | Rive Renderer |
|----------|------------------|---------------|
| iOS | ✅ | ✅ |
| Android | ✅ | ✅ |
| macOS | ✅ | ✅ |
| Windows | ✅ | ✅ |
| Linux | ❌ | ❌ |
| Web | ✅ | ✅ |
Be sure to read the [platform specific considerations](platform_considerations.md) for the Rive Flutter package.
## Awesome Rive
For even more examples and resources on using Rive at runtime or in other tools, checkout the [awesome-rive](https://github.com/rive-app/awesome-rive) repo.
## Troubleshooting
The required native libraries should be automatically downloaded during the build step (`flutter run` or `flutter build`). If you encounter issues, try the following:
1. Run `flutter clean`
2. Run `flutter pub get`
3. Run `flutter run`
Alternatively, you can manually run the `rive_native` setup script. In the root of your Flutter app, execute:
```bash
dart run rive_native:setup --verbose --clean --platform macos
```
This will clean the `rive_native` setup and download the platform-specific libraries specified with the `--platform` flag. Refer to the **Platform Support** section above for details.
## Building `rive_native`
By default, prebuilt native libraries are downloaded and used. If you prefer to build the libraries yourself, use the `--build` flag with the setup script:
```bash
flutter clean # Important
dart run rive_native:setup --verbose --clean --build --platform macos
```
> **Note**: Building the libraries requires specific tooling on your machine. Additional documentation will be provided soon.
## Testing
Shared libraries are included in the download/build process. If you encounter issues using `rive_native` in your tests, please reach out to us for assistance.
## Contributing
We love contributions and all are welcome! 💙
## Issues
Have an issue with using the runtime, or want to suggest a feature/API to help make your development life better? Log an issue in our [issues](https://github.com/rive-app/flutter/issues) tab! You can also browse older issues and discussion threads there to see solutions that may have worked for common problems.
- Reach out to us on our [Community](https://community.rive.app/feed)
- File an issue on the [Rive Flutter repository](https://github.com/rive-app/rive-flutter/issues)

View File

@ -1,117 +1,4 @@
analyzer:
errors:
unused_import: error
exclude:
- ios
- macos
include: package:flutter_lints/flutter.yaml
linter:
rules:
- always_put_required_named_parameters_first
- annotate_overrides
# - avoid_annotating_with_dynamic
- avoid_bool_literals_in_conditional_expressions
- avoid_catches_without_on_clauses
- avoid_catching_errors
- avoid_classes_with_only_static_members
- avoid_double_and_int_checks
- avoid_empty_else
- avoid_field_initializers_in_const_classes
- avoid_implementing_value_types
- avoid_init_to_null
- avoid_js_rounded_ints
- avoid_null_checks_in_equality_operators
- avoid_relative_lib_imports
- avoid_return_types_on_setters
- avoid_returning_null_for_void
- avoid_returning_this
- avoid_setters_without_getters
- avoid_shadowing_type_parameters
- avoid_single_cascade_in_expression_statements
- avoid_slow_async_io
- avoid_types_as_parameter_names
- avoid_unused_constructor_parameters
- avoid_void_async
- await_only_futures
- camel_case_types
- cancel_subscriptions
- close_sinks
- constant_identifier_names
- control_flow_in_finally
- curly_braces_in_flow_control_structures
- directives_ordering
- empty_catches
- empty_constructor_bodies
- empty_statements
- file_names
- hash_and_equals
- implementation_imports
# - invariant_booleans # too many false positives: https://github.com/dart-lang/linter/issues/811
- collection_methods_unrelated_type
- join_return_with_assignment
- library_names
- library_prefixes
- lines_longer_than_80_chars
# - literal_only_boolean_expressions # too many false positives: https://github.com/dart-lang/sdk/issues/34181
- no_adjacent_strings_in_list
- no_duplicate_case_values
- non_constant_identifier_names
- null_closures
- one_member_abstracts
- only_throw_errors
- overridden_fields
- package_api_docs
- package_names
- package_prefixed_library_names
- parameter_assignments
- prefer_adjacent_string_concatenation
- prefer_asserts_in_initializer_lists
- prefer_collection_literals
- prefer_conditional_assignment
- prefer_const_constructors
- prefer_const_constructors_in_immutables
- prefer_const_declarations
- prefer_const_literals_to_create_immutables
- prefer_constructors_over_static_methods
- prefer_contains
- prefer_final_fields
- prefer_final_in_for_each
- prefer_foreach
- prefer_function_declarations_over_variables
- prefer_initializing_formals
- prefer_is_empty
- prefer_is_not_empty
- prefer_iterable_whereType
# - prefer_mixin
- prefer_null_aware_operators
- prefer_typing_uninitialized_variables
- prefer_void_to_null
- recursive_getters
- slash_for_doc_comments
- sort_pub_dependencies
- sort_unnamed_constructors_first
- test_types_in_equals
- throw_in_finally
- type_annotate_public_apis
- type_init_formals
- unawaited_futures
- unnecessary_await_in_return
- unnecessary_brace_in_string_interps
- unnecessary_const
- unnecessary_getters_setters
- unnecessary_lambdas
- unnecessary_new
- unnecessary_null_aware_assignments
- unnecessary_null_in_if_null_operators
- unnecessary_overrides
- unnecessary_parenthesis
- unnecessary_statements
- unnecessary_this
- unrelated_type_equality_checks
- use_full_hex_values_for_flutter_colors
- use_rethrow_when_possible
- use_setters_to_change_properties
- use_string_buffers
- use_to_and_as_if_applicable
- valid_regexps
- void_checks
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

BIN
example/assets/button.riv Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

BIN
example/assets/rewards.riv Normal file

Binary file not shown.

View File

@ -1,96 +0,0 @@
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;
}
},
),
],
),
),
],
),
),
);
}
}

View File

@ -1,22 +0,0 @@
import 'package:flutter/material.dart';
import 'package:rive/rive.dart';
/// Basic example playing a Rive animation from a packaged asset.
class BasicText extends StatelessWidget {
const BasicText({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Basic Text'),
),
body: const Center(
child: RiveAnimation.asset(
'assets/text_flutter.riv',
fit: BoxFit.cover,
),
),
);
}
}

View File

@ -1,99 +0,0 @@
import 'package:flutter/material.dart';
import 'package:rive/rive.dart';
/// An example showing how to drive a StateMachine via one numeric input.
class AnimationCarousel extends StatefulWidget {
const AnimationCarousel({Key? key}) : super(key: key);
@override
State<AnimationCarousel> createState() => _AnimationCarouselState();
}
class _AnimationCarouselState extends State<AnimationCarousel> {
final riveAnimations = [
const RiveCustomAnimationData(
name: 'assets/liquid_download.riv',
),
const RiveCustomAnimationData(
name: 'assets/little_machine.riv',
stateMachines: ['State Machine 1'],
),
const RiveCustomAnimationData(
name: 'assets/off_road_car.riv',
),
const RiveCustomAnimationData(
name: 'assets/rocket.riv',
stateMachines: ['Button'],
animations: ['Roll_over'],
),
const RiveCustomAnimationData(
name: 'assets/skills.riv',
stateMachines: ['Designer\'s Test'],
),
// not a pretty out of the box example
const RiveCustomAnimationData(
name: 'assets/light_switch.riv',
stateMachines: ['Switch'],
),
// v6.0 file,
// 'assets/teeny_tiny.riv',
// const RiveCustomAnimationData(name: 'assets/teeny_tiny.riv'),
];
var _index = 0;
void next() {
setState(() {
_index += 1;
});
}
void previous() {
setState(() {
_index -= 1;
});
}
@override
Widget build(BuildContext context) {
final indexToShow = _index % riveAnimations.length;
return Scaffold(
appBar: AppBar(
title: const Text('Animation Carousel'),
),
body: Center(
child: Row(
children: [
GestureDetector(
onTap: previous,
child: const Icon(Icons.arrow_back),
),
Expanded(
child: RiveAnimation.asset(
riveAnimations[indexToShow].name,
animations: riveAnimations[indexToShow].animations,
stateMachines: riveAnimations[indexToShow].stateMachines,
),
),
GestureDetector(
onTap: next,
child: const Icon(Icons.arrow_forward),
),
],
),
),
);
}
}
@immutable
class RiveCustomAnimationData {
final String name;
final List<String> animations;
final List<String> stateMachines;
const RiveCustomAnimationData({
required this.name,
this.animations = const [],
this.stateMachines = const [],
});
}

View File

@ -1,208 +0,0 @@
import 'dart:math';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:rive/rive.dart';
import 'package:http/http.dart' as http;
/// An example showing how to load image or font assets dynamically.
///
/// In this example you'll note that there is a delay in the assets
/// loading/refreshing when you tap the back/forward buttons.
/// This is because the assets are being loaded asynchronously.
/// If you want to avoid this delay you can cache the assets in memory
/// and provide them instantly.
///
/// See `custom_cached_asset_loading.dart` for an example of this.
///
/// See: https://rive.app/community/doc/loading-assets/doct4wVHGPgC
class CustomAssetLoading extends StatefulWidget {
const CustomAssetLoading({Key? key}) : super(key: key);
@override
State<CustomAssetLoading> createState() => _CustomAssetLoadingState();
}
class _CustomAssetLoadingState extends State<CustomAssetLoading> {
var _index = 0;
void next() => setState(() {
_index += 1;
});
void previous() => setState(() {
_index -= 1;
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Custom Asset Loading'),
),
body: Center(
child: Row(
children: [
GestureDetector(
onTap: previous,
child: const Icon(Icons.arrow_back),
),
Expanded(
child: (_index % 2 == 0)
? const _RiveRandomImage()
: const _RiveRandomFont(),
),
GestureDetector(
onTap: next,
child: const Icon(Icons.arrow_forward),
),
],
),
),
);
}
}
/// Loads a random image as an asset.
class _RiveRandomImage extends StatefulWidget {
const _RiveRandomImage();
@override
State<_RiveRandomImage> createState() => _RiveRandomImageState();
}
class _RiveRandomImageState extends State<_RiveRandomImage> {
@override
void initState() {
super.initState();
_loadFiles();
}
RiveFile? _riveImageSampleFile;
Future<void> _loadFiles() async {
final imageFile = await RiveFile.asset(
'assets/image_out_of_band.riv',
assetLoader: CallbackAssetLoader(
(asset, bytes) async {
// Replace image assets that are not embedded in the rive file
if (asset is ImageAsset && bytes == null) {
final res =
await http.get(Uri.parse('https://picsum.photos/500/500'));
await asset.decode(Uint8List.view(res.bodyBytes.buffer));
return true;
} else {
return false; // use default asset loading
}
},
),
);
setState(() {
_riveImageSampleFile = imageFile;
});
}
@override
Widget build(BuildContext context) {
if (_riveImageSampleFile == null) {
return const Center(child: CircularProgressIndicator());
}
return Stack(
children: [
RiveAnimation.direct(
_riveImageSampleFile!,
stateMachines: const ['State Machine 1'],
fit: BoxFit.cover,
),
const Positioned(
child: Padding(
padding: EdgeInsets.all(8.0),
child: Text(
'This example loads a random image dynamically and asynchronously.\n\nHover to zoom.',
style: TextStyle(color: Colors.black),
),
),
)
],
);
}
}
/// Loads a random font as an asset.
class _RiveRandomFont extends StatefulWidget {
const _RiveRandomFont();
@override
State<_RiveRandomFont> createState() => _RiveRandomFontState();
}
class _RiveRandomFontState extends State<_RiveRandomFont> {
@override
void initState() {
super.initState();
_loadFiles();
}
RiveFile? _riveFontSampleFile;
Future<void> _loadFiles() async {
final fontFile = await RiveFile.asset(
'assets/acqua_text_out_of_band.riv',
assetLoader: CallbackAssetLoader(
(asset, bytes) async {
// Replace font assets that are not embedded in the rive file
if (asset is FontAsset && bytes == null) {
final urls = [
'https://cdn.rive.app/runtime/flutter/IndieFlower-Regular.ttf',
'https://cdn.rive.app/runtime/flutter/comic-neue.ttf',
'https://cdn.rive.app/runtime/flutter/inter.ttf',
'https://cdn.rive.app/runtime/flutter/inter-tight.ttf',
'https://cdn.rive.app/runtime/flutter/josefin-sans.ttf',
'https://cdn.rive.app/runtime/flutter/send-flowers.ttf',
];
final res = await http.get(
// pick a random url from the list of fonts
Uri.parse(urls[Random().nextInt(urls.length)]),
);
await asset.decode(Uint8List.view(res.bodyBytes.buffer));
return true;
} else {
return false; // use default asset loading
}
},
),
);
setState(() {
_riveFontSampleFile = fontFile;
});
}
@override
Widget build(BuildContext context) {
if (_riveFontSampleFile == null) {
return const Center(child: CircularProgressIndicator());
}
return Stack(
children: [
RiveAnimation.direct(
_riveFontSampleFile!,
stateMachines: const ['State Machine 1'],
fit: BoxFit.cover,
),
const Positioned(
child: Padding(
padding: EdgeInsets.all(8.0),
child: Text(
'This example loads a random font dynamically and asynchronously.\n\nClick to change drink.',
style: TextStyle(color: Colors.black),
),
),
)
],
);
}
}

View File

@ -1,46 +0,0 @@
/// Demonstrates how to create a custom controller to change the speed of an
/// animation
import 'package:flutter/material.dart';
import 'package:rive/rive.dart';
class SpeedyAnimation extends StatelessWidget {
const SpeedyAnimation({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Custom Controller - Speed'),
),
body: Center(
child: RiveAnimation.asset(
'assets/vehicles.riv',
fit: BoxFit.cover,
animations: const ['idle'],
controllers: [SpeedController('curves', speedMultiplier: 3)],
),
),
);
}
}
class SpeedController extends SimpleAnimation {
final double speedMultiplier;
SpeedController(
String animationName, {
double mix = 1,
this.speedMultiplier = 1,
}) : super(animationName, mix: mix);
@override
void apply(RuntimeArtboard artboard, double elapsedSeconds) {
if (instance == null || !instance!.keepGoing) {
isActive = false;
}
instance!
..animation.apply(instance!.time, coreContext: artboard, mix: mix)
..advance(elapsedSeconds * speedMultiplier);
}
}

View File

@ -1,70 +0,0 @@
import 'package:flutter/material.dart';
import 'package:rive/rive.dart';
import 'package:url_launcher/url_launcher.dart';
/// This example demonstrates how to open a URL from a Rive [RiveOpenUrlEvent]..
class EventOpenUrlButton extends StatefulWidget {
const EventOpenUrlButton({super.key});
@override
State<EventOpenUrlButton> createState() => _EventOpenUrlButtonState();
}
class _EventOpenUrlButtonState extends State<EventOpenUrlButton> {
late StateMachineController _controller;
@override
void initState() {
super.initState();
}
void onInit(Artboard artboard) async {
_controller = StateMachineController.fromArtboard(artboard, 'button')!;
artboard.addController(_controller);
_controller.addEventListener(onRiveEvent);
}
void onRiveEvent(RiveEvent event) {
if (event is RiveOpenURLEvent) {
try {
final Uri url = Uri.parse(event.url);
launchUrl(url);
} on Exception catch (e) {
debugPrint(e.toString());
}
}
}
@override
void dispose() {
_controller.removeEventListener(onRiveEvent);
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Event Open URL'),
),
body: Column(
children: [
Expanded(
child: RiveAnimation.asset(
'assets/url_event_button.riv',
onInit: onInit,
),
),
const Center(
child: Padding(
padding: EdgeInsets.all(8.0),
child: Text('Open URL: https://rive.app'),
),
),
],
),
);
}
}

View File

@ -1,75 +0,0 @@
import 'package:flutter/material.dart';
import 'package:rive/rive.dart';
/// This example demonstrates how to retrieve custom properties set on a Rive
/// event, and update the UI accordingly.
class EventStarRating extends StatefulWidget {
const EventStarRating({super.key});
@override
State<EventStarRating> createState() => _EventStarRatingState();
}
class _EventStarRatingState extends State<EventStarRating> {
late StateMachineController _controller;
@override
void initState() {
super.initState();
}
String ratingValue = 'Rating: 0';
void onInit(Artboard artboard) async {
_controller =
StateMachineController.fromArtboard(artboard, 'State Machine 1')!;
artboard.addController(_controller);
_controller.addEventListener(onRiveEvent);
}
void onRiveEvent(RiveEvent event) {
// Access custom properties defined on the event
var rating = event.properties['rating'] as double;
// Schedule the setState for the next frame, as an event can be
// triggered during a current frame update
WidgetsBinding.instance.addPostFrameCallback((_) {
setState(() {
ratingValue = 'Rating: $rating';
});
});
}
@override
void dispose() {
_controller.removeEventListener(onRiveEvent);
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Event Star Rating'),
),
body: Column(
children: [
Expanded(
child: RiveAnimation.asset(
'assets/rating_animation.riv',
onInit: onInit,
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
ratingValue,
style: const TextStyle(fontSize: 22, fontWeight: FontWeight.w600),
),
)
],
),
);
}
}

View File

@ -1,79 +0,0 @@
import 'package:flutter/material.dart';
import 'package:rive/rive.dart';
/// An example showing how to drive two boolean state machine inputs.
class ExampleStateMachine extends StatefulWidget {
const ExampleStateMachine({Key? key}) : super(key: key);
@override
State<ExampleStateMachine> createState() => _ExampleStateMachineState();
}
class _ExampleStateMachineState extends State<ExampleStateMachine> {
Artboard? _riveArtboard;
SMIBool? _hoverInput;
SMIBool? _pressInput;
@override
void initState() {
super.initState();
_loadRiveFile();
}
Future<void> _loadRiveFile() async {
// Load the animation file from the bundle.
final riveFile = await RiveFile.asset('assets/rocket.riv');
// The artboard is the root of the animation and gets drawn in the
// Rive widget.
final artboard = riveFile.mainArtboard.instance();
var controller = StateMachineController.fromArtboard(artboard, 'Button');
if (controller != null) {
artboard.addController(controller);
_hoverInput = controller.getBoolInput('Hover');
_pressInput = controller.getBoolInput('Press');
}
setState(() => _riveArtboard = artboard);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Button State Machine')),
body: _riveArtboard == null
? const SizedBox()
: MouseRegion(
onEnter: (_) => _hoverInput?.value = true,
onExit: (_) => _hoverInput?.value = false,
child: GestureDetector(
onTapDown: (_) => _pressInput?.value = true,
onTapCancel: () => _pressInput?.value = false,
onTapUp: (_) => _pressInput?.value = false,
child: Stack(
children: [
Rive(
fit: BoxFit.cover,
artboard: _riveArtboard!,
),
Align(
alignment: Alignment.bottomCenter,
child: Padding(
padding: const EdgeInsets.all(18.0),
child: Text(
'Try pressing the button...',
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
color: Colors.grey[800],
),
),
),
)
],
),
),
),
);
}
}

View File

@ -0,0 +1,59 @@
import 'package:flutter/material.dart';
import 'package:rive/rive.dart';
import 'package:rive_example/main.dart' show RiveExampleApp;
/// This is an alternative controller (painter) to use instead of the
/// [RiveWidgetController].
///
/// This painter is used to paint/advance a state machine. Functionally it's
/// very similar to the [RiveWidgetController], which we recommend using for
/// most use cases.
class ExampleSingleAnimationPainter extends StatefulWidget {
const ExampleSingleAnimationPainter({super.key});
@override
State<ExampleSingleAnimationPainter> createState() =>
_ExampleSingleAnimationPainterState();
}
class _ExampleSingleAnimationPainterState
extends State<ExampleSingleAnimationPainter> {
late File file;
Artboard? artboard;
late SingleAnimationPainter painter;
@override
void initState() {
super.initState();
init();
}
void init() async {
file = (await File.asset(
'assets/off_road_car.riv',
riveFactory: RiveExampleApp.getCurrentFactory,
))!;
painter = SingleAnimationPainter('idle');
artboard = file.defaultArtboard();
setState(() {});
}
@override
void dispose() {
painter.dispose();
artboard?.dispose();
file.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
if (artboard == null) {
return const Center(child: CircularProgressIndicator());
}
return RiveArtboardWidget(
artboard: artboard!,
painter: painter,
);
}
}

View File

@ -0,0 +1,105 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:rive/rive.dart';
import 'package:rive_example/main.dart' show RiveExampleApp;
/// Example using Rive data binding at runtime.
///
/// See: https://rive.app/docs/dart/data-binding
class ExampleDataBinding extends StatefulWidget {
const ExampleDataBinding({super.key});
@override
State<ExampleDataBinding> createState() => _ExampleBasicState();
}
class _ExampleBasicState extends State<ExampleDataBinding> {
File? file;
RiveWidgetController? controller;
late ViewModelInstance viewModelInstance;
late ViewModelInstance coinItemVM;
late ViewModelInstance gemItemVM;
late ViewModelInstanceNumber coinValue;
late ViewModelInstanceNumber gemValue;
@override
void initState() {
super.initState();
_init();
}
Future<void> _init() async {
file = await File.asset(
'assets/rewards.riv',
riveFactory: RiveExampleApp.getCurrentFactory,
);
controller = RiveWidgetController(file!);
_initViewModel();
setState(() {});
}
void _initViewModel() {
viewModelInstance = controller!.dataBind(DataBind.auto());
_selectRandomToken();
// Print the view model instance properties
print(viewModelInstance.properties);
// Get the rewards view model
coinItemVM = viewModelInstance.viewModel('Coin')!;
gemItemVM = viewModelInstance.viewModel('Gem')!;
print(coinItemVM); // Print the view model instance properties
coinValue = coinItemVM.number('Item_Value')!;
gemValue = gemItemVM.number('Item_Value')!;
// Listen to the changes on the Item_Value input
coinValue.addListener(_onCoinValueChange);
coinValue.value = 1000; // set the initial coin value to 1000
gemValue.addListener(_onGemValueChange);
gemValue.value = 4000; // set the initial gem value to 4000
}
void _selectRandomToken() {
final random = Random.secure().nextBool() ? 'Coin' : 'Gem';
// We randomly select to reward either coins or gems
viewModelInstance
.viewModel('Item_Selection')!
.enumerator('Item_Selection')!
.value = random;
}
void _onCoinValueChange(double value) {
print('New coin value: $value');
}
void _onGemValueChange(double value) {
print('New gem value: $value');
}
@override
void dispose() {
coinValue.removeListener(_onCoinValueChange);
gemValue.removeListener(_onGemValueChange);
coinValue.dispose();
gemValue.dispose();
coinItemVM.dispose();
gemItemVM.dispose();
controller?.dispose();
file?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final controller = this.controller;
if (controller == null) {
return const Center(child: CircularProgressIndicator());
}
return RiveWidget(
controller: controller,
fit: Fit.layout, // for responsive layouts
layoutScaleFactor: 1 / 2.0,
);
}
}

View File

@ -0,0 +1,106 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:rive/rive.dart';
import 'package:rive_example/main.dart' show RiveExampleApp;
/// Example using Rive data binding images at runtime.
///
/// See: https://rive.app/docs/dart/data-binding
class ExampleDataBindingImages extends StatefulWidget {
const ExampleDataBindingImages({super.key});
@override
State<ExampleDataBindingImages> createState() => _ExampleBasicState();
}
class _ExampleBasicState extends State<ExampleDataBindingImages> {
late ViewModelInstance viewModelInstance;
FileLoader fileLoader = FileLoader.fromAsset(
'assets/databinding_images.riv',
riveFactory: RiveExampleApp.getCurrentFactory,
);
Future<Uint8List> loadBundleAsset(int index) async {
final ByteData data =
await rootBundle.load('assets/images/databound_image_$index.jpg');
return data.buffer.asUint8List();
}
Future<void> _clearImage() async {
final imageProperty = viewModelInstance.image('bound_image')!;
imageProperty.value = null;
setState(() {});
}
Future<void> _swapImage(int index) async {
final imageProperty = viewModelInstance.image('bound_image')!;
final bytes = await loadBundleAsset(index);
final renderImage =
await RiveExampleApp.getCurrentFactory.decodeImage(bytes);
if (renderImage != null) {
imageProperty.value = renderImage;
}
setState(() {});
}
@override
void dispose() {
fileLoader.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Expanded(
child: RiveWidgetBuilder(
fileLoader: fileLoader,
dataBind: DataBind.auto(),
onLoaded: (state) {
viewModelInstance = state.viewModelInstance!;
},
builder: (context, state) => switch (state) {
RiveLoading() => const Center(child: CircularProgressIndicator()),
RiveFailed() => ErrorWidget.withDetails(
message: state.error.toString(),
error: FlutterError(state.error.toString()),
),
RiveLoaded() => RiveWidget(controller: state.controller),
},
),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.all(16.0),
child: ElevatedButton(
onPressed: _clearImage,
child: const Text('Clear image'),
),
),
Padding(
padding: const EdgeInsets.all(16.0),
child: ElevatedButton(
onPressed: () {
_swapImage(1);
},
child: const Text('Swap image 1'),
),
),
Padding(
padding: const EdgeInsets.all(16.0),
child: ElevatedButton(
onPressed: () {
_swapImage(2);
},
child: const Text('Swap image 2'),
),
),
],
),
],
);
}
}

View File

@ -0,0 +1,81 @@
// ignore_for_file: avoid_print
import 'package:flutter/material.dart';
import 'package:rive/rive.dart';
/// We strongly recommend using Data Binding instead of Rive Events for better
/// runtime control if you need to do more advanced logic than simple events.
///
/// See: https://rive.app/docs/runtimes/data-binding
///
/// This example demonstrates how to retrieve custom properties set on a Rive
/// event, and update the UI accordingly.
///
/// See: https://rive.app/docs/runtimes/events
class ExampleEvents extends StatefulWidget {
const ExampleEvents({super.key});
@override
State<ExampleEvents> createState() => _ExampleEventsState();
}
class _ExampleEventsState extends State<ExampleEvents> {
File? _riveFile;
RiveWidgetController? _controller;
@override
void initState() {
super.initState();
_init();
}
Future<void> _init() async {
_riveFile = await File.asset(
'assets/rating.riv',
riveFactory: Factory.rive,
);
_controller = RiveWidgetController(_riveFile!);
_controller?.stateMachine.addEventListener(_onRiveEvent);
setState(() {});
}
String ratingValue = 'Rating: 0';
void _onRiveEvent(Event event) {
// Access custom properties defined on the event
print(event);
var rating = event.numberProperty('rating')?.value ?? 0;
setState(() {
ratingValue = 'Rating: $rating';
});
}
@override
void dispose() {
_controller?.stateMachine.removeEventListener(_onRiveEvent);
_controller?.stateMachine.dispose();
_riveFile?.dispose();
_controller?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Expanded(
child: _riveFile == null
? const SizedBox()
: RiveWidget(controller: _controller!),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
ratingValue,
style: const TextStyle(fontSize: 22, fontWeight: FontWeight.w600),
),
)
],
);
}
}

View File

@ -0,0 +1,17 @@
export 'animation_painter.dart';
export 'databinding.dart';
export 'events.dart';
export 'hit_test_behaviour.dart';
export 'inputs.dart';
export 'network_asset.dart';
export 'out_of_band_assets.dart';
export 'out_of_band_assets_cached.dart';
export 'responsive_layouts.dart';
export 'rive_audio.dart';
export 'rive_widget.dart';
export 'rive_widget_builder.dart';
export 'state_machine_painter.dart';
export 'text_runs.dart';
export 'ticker_mode.dart';
export 'time_dilation.dart';
export 'todo.dart';

View File

@ -0,0 +1,168 @@
import 'package:flutter/material.dart';
import 'package:rive/rive.dart';
import 'package:rive_example/main.dart';
class ExampleHitTestBehaviour extends StatefulWidget {
const ExampleHitTestBehaviour({super.key});
@override
State<ExampleHitTestBehaviour> createState() =>
_ExampleHitTestBehaviourState();
}
class _ExampleHitTestBehaviourState extends State<ExampleHitTestBehaviour> {
FileLoader fileLoader = FileLoader.fromAsset(
'assets/button.riv',
riveFactory: RiveExampleApp.getCurrentFactory,
);
int count = 0;
RiveHitTestBehavior hitTestBehavior = RiveHitTestBehavior.opaque;
MouseCursor cursor = MouseCursor.defer;
@override
void dispose() {
fileLoader.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Expanded(
child: Stack(
children: [
Center(
child: MouseRegion(
cursor: SystemMouseCursors.copy,
child: GestureDetector(
onTap: () {
setState(() {
count++;
});
},
child: Container(
width: 300,
height: 300,
color: Colors.red,
),
),
),
),
Center(
child: MouseRegion(
opaque: true,
cursor: SystemMouseCursors.click,
hitTestBehavior: HitTestBehavior.deferToChild,
child: 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,
hitTestBehavior: hitTestBehavior,
cursor: cursor,
)
},
),
),
),
],
),
),
Text('Underlying Flutter container tapped: $count'),
const SizedBox(
height: 16,
),
Text('Current hit test behaviour: $hitTestBehavior'),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
setState(() {
hitTestBehavior = RiveHitTestBehavior.none;
});
},
child: const Text('none'),
),
ElevatedButton(
onPressed: () {
setState(() {
hitTestBehavior = RiveHitTestBehavior.opaque;
});
},
child: const Text('opaque'),
),
ElevatedButton(
onPressed: () {
setState(() {
hitTestBehavior = RiveHitTestBehavior.translucent;
});
},
child: const Text('translucent'),
),
ElevatedButton(
onPressed: () {
setState(() {
hitTestBehavior = RiveHitTestBehavior.transparent;
});
},
child: const Text('transparent'),
)
],
),
),
Text('Current mouse cursor: $cursor'),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
setState(() {
cursor = MouseCursor.defer;
});
},
child: const Text('defer'),
),
ElevatedButton(
onPressed: () {
setState(() {
cursor = MouseCursor.uncontrolled;
});
},
child: const Text('unconrolled'),
),
ElevatedButton(
onPressed: () {
setState(() {
cursor = SystemMouseCursors.contextMenu;
});
},
child: const Text('context menu'),
),
ElevatedButton(
onPressed: () {
setState(() {
cursor = SystemMouseCursors.text;
});
},
child: const Text('text'),
),
],
),
)
],
);
}
}

View File

@ -0,0 +1,93 @@
import 'package:flutter/material.dart';
import 'package:rive/rive.dart';
/// We strongly recommend using Data Binding instead of Rive Inputs for better
/// runtime control. See: https://rive.app/docs/runtimes/data-binding
///
/// An example showing how to drive a StateMachine via one numeric input.
/// Triggers and boolean inputs can be driven in a similar way.
///
/// See: https://rive.app/docs/runtimes/inputs
class ExampleInputs extends StatefulWidget {
const ExampleInputs({Key? key}) : super(key: key);
@override
State<ExampleInputs> createState() => _ExampleInputsState();
}
class _ExampleInputsState extends State<ExampleInputs> {
File? _riveFile;
RiveWidgetController? _controller;
NumberInput? _levelInput;
Future<void> _loadRiveFile() async {
_riveFile = await File.asset(
'assets/skills.riv',
riveFactory: Factory.rive,
);
// You can access nested inputs by providing an optional path to the input
_controller = RiveWidgetController(_riveFile!);
_levelInput = _controller?.stateMachine.number('Level', path: null);
/* EXAMPLE BOOLEAN INPUT API */
// var boolInput = _controller?.stateMachine.boolean('some_bool');
// boolInput?.value = true;
/* EXAMPLE TRIGGER INPUT API */
// var triggerInput = _controller?.stateMachine.trigger('some_trigger');
// triggerInput?.fire();
setState(() {});
}
@override
void initState() {
super.initState();
_loadRiveFile();
}
@override
void dispose() {
_levelInput?.dispose();
_controller?.dispose();
_riveFile?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Center(
child: _riveFile == null
? const SizedBox()
: Stack(
children: [
Positioned.fill(
child: RiveWidget(
controller: _controller!,
),
),
Positioned.fill(
bottom: 32,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
ElevatedButton(
child: const Text('Beginner'),
onPressed: () => _levelInput?.value = 0,
),
const SizedBox(width: 10),
ElevatedButton(
child: const Text('Intermediate'),
onPressed: () => _levelInput?.value = 1,
),
const SizedBox(width: 10),
ElevatedButton(
child: const Text('Expert'),
onPressed: () => _levelInput?.value = 2,
),
],
),
),
],
),
);
}
}

View File

@ -0,0 +1,41 @@
import 'package:flutter/material.dart';
import 'package:rive/rive.dart';
import 'package:rive_example/main.dart' show RiveExampleApp;
class ExampleNetworkAsset extends StatefulWidget {
const ExampleNetworkAsset({super.key});
@override
State<ExampleNetworkAsset> createState() => _ExampleNetworkAssetState();
}
class _ExampleNetworkAssetState extends State<ExampleNetworkAsset> {
late final fileLoader = FileLoader.fromUrl(
'https://cdn.rive.app/animations/vehicles.riv',
riveFactory: RiveExampleApp.getCurrentFactory,
);
@override
void dispose() {
// This widget state owns the file loader, dispose it.
fileLoader.dispose();
super.dispose();
}
@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)
},
);
}
}

View File

@ -0,0 +1,232 @@
import 'dart:math';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:rive/rive.dart';
import 'package:http/http.dart' as http;
/// An example showing how to load image or font assets dynamically.
///
/// In this example you'll note that there is a delay in the assets
/// loading/refreshing when you tap the back/forward buttons.
/// This is because the assets are being loaded asynchronously.
/// If you want to avoid this delay you can cache the assets in memory
/// and provide them instantly.
///
/// See `out_of_band_assets_cached.dart` for an example of this.
///
/// See: https://rive.app/docs/runtimes/loading-assets
class ExampleOutOfBandAssetLoading extends StatefulWidget {
const ExampleOutOfBandAssetLoading({Key? key}) : super(key: key);
@override
State<ExampleOutOfBandAssetLoading> createState() =>
_ExampleOutOfBandAssetLoadingState();
}
class _ExampleOutOfBandAssetLoadingState
extends State<ExampleOutOfBandAssetLoading> {
var _index = 0;
void next() => setState(() {
_index += 1;
});
void previous() => setState(() {
_index -= 1;
});
@override
Widget build(BuildContext context) {
return Center(
child: Row(
children: [
GestureDetector(
onTap: previous,
child: const Icon(Icons.arrow_back),
),
Expanded(
child: (_index % 2 == 0)
? const _RiveRandomImage()
: const _RiveRandomFont(),
),
GestureDetector(
onTap: next,
child: const Icon(Icons.arrow_forward),
),
],
),
);
}
}
/// Loads a random image as an asset.
class _RiveRandomImage extends StatefulWidget {
const _RiveRandomImage();
@override
State<_RiveRandomImage> createState() => _RiveRandomImageState();
}
class _RiveRandomImageState extends State<_RiveRandomImage> {
@override
void initState() {
super.initState();
_loadFiles();
}
File? _riveImageSampleFile;
RiveWidgetController? _controller;
Future<void> _loadFiles() async {
final imageFile = await File.asset(
'assets/image_out_of_band.riv',
riveFactory: Factory.rive,
assetLoader: (asset, bytes) {
if (asset is ImageAsset && bytes == null) {
http.get(Uri.parse('https://picsum.photos/500/500')).then((res) {
if (mounted) {
asset.decode(Uint8List.view(res.bodyBytes.buffer));
setState(() {
// force rebuild in case the Rive graphic is no longer advancing
});
}
});
return true; // Tell the runtime not to load the asset automatically
} else {
// Tell the runtime to proceed with loading the asset if it exists
return false;
}
},
);
setState(() {
_riveImageSampleFile = imageFile;
_controller = RiveWidgetController(_riveImageSampleFile!);
});
}
@override
void dispose() {
_controller?.dispose();
_riveImageSampleFile?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
if (_riveImageSampleFile == null) {
return const Center(child: CircularProgressIndicator());
}
return Stack(
children: [
_controller == null
? const SizedBox()
: RiveWidget(controller: _controller!),
const Positioned(
child: Padding(
padding: EdgeInsets.all(8.0),
child: Text(
'This example loads a random image dynamically and asynchronously.\n\nHover to zoom.',
style: TextStyle(color: Colors.black),
),
),
)
],
);
}
}
/// Loads a random font as an asset.
class _RiveRandomFont extends StatefulWidget {
const _RiveRandomFont();
@override
State<_RiveRandomFont> createState() => _RiveRandomFontState();
}
class _RiveRandomFontState extends State<_RiveRandomFont> {
@override
void initState() {
super.initState();
_loadFiles();
}
File? _riveFontSampleFile;
RiveWidgetController? _controller;
Future<void> _loadFiles() async {
final fontFile = await File.asset(
'assets/acqua_text_out_of_band.riv',
riveFactory: Factory.rive,
assetLoader: (asset, bytes) {
// Replace font assets that are not embedded in the rive file
if (asset is FontAsset && bytes == null) {
final urls = [
'https://cdn.rive.app/runtime/flutter/IndieFlower-Regular.ttf',
'https://cdn.rive.app/runtime/flutter/comic-neue.ttf',
'https://cdn.rive.app/runtime/flutter/inter.ttf',
'https://cdn.rive.app/runtime/flutter/inter-tight.ttf',
'https://cdn.rive.app/runtime/flutter/josefin-sans.ttf',
'https://cdn.rive.app/runtime/flutter/send-flowers.ttf',
];
// pick a random url from the list of fonts
http.get(Uri.parse(urls[Random().nextInt(urls.length)])).then((res) {
if (mounted) {
asset.decode(
Uint8List.view(res.bodyBytes.buffer),
);
setState(() {
// force rebuild in case the Rive graphic is no longer advancing
});
}
});
return true; // Tell the runtime not to load the asset automatically
} else {
// Tell the runtime to proceed with loading the asset if it exists
return false;
}
},
);
setState(() {
_riveFontSampleFile = fontFile;
_controller = RiveWidgetController(_riveFontSampleFile!);
});
}
@override
void dispose() {
_controller?.dispose();
_riveFontSampleFile?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
if (_controller == null) {
return const Center(child: CircularProgressIndicator());
}
return Stack(
children: [
_riveFontSampleFile == null
? const SizedBox()
: RiveWidget(
controller: _controller!,
fit: Fit.cover,
),
const Positioned(
child: Padding(
padding: EdgeInsets.all(8.0),
child: Text(
'This example loads a random font dynamically and asynchronously.\n\nClick to change drink.',
style: TextStyle(color: Colors.black),
),
),
)
],
);
}
}

View File

@ -1,12 +1,11 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
import 'package:flutter/material.dart';
import 'package:rive/rive.dart';
import 'package:rive_example/main.dart' show RiveExampleApp;
import 'dart:math';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:rive/rive.dart';
/// An example showing how to load image or font assets dynamically.
///
/// In this example there is no delay in the assets loading, as they are
@ -15,20 +14,26 @@ import 'package:rive/rive.dart';
/// The example also shows how to swap out the assets multiple times by
/// keeping a reference to the asset and swapping it out.
///
/// See: https://rive.app/community/doc/loading-assets/doct4wVHGPgC
class CustomCachedAssetLoading extends StatefulWidget {
const CustomCachedAssetLoading({Key? key}) : super(key: key);
/// Note that this swaps out the image/font on the Rive File instance. All
/// artboards created from this file will use the same asset reference. Meaning
/// that if you swap out the image/font on the Rive File instance, all
/// artboards created from this file will use the new asset reference.
///
/// See: https://rive.app/docs/runtimes/loading-assets
class ExampleOutOfBandCachedAssetLoading extends StatefulWidget {
const ExampleOutOfBandCachedAssetLoading({Key? key}) : super(key: key);
@override
State<CustomCachedAssetLoading> createState() =>
_CustomCachedAssetLoadingState();
State<ExampleOutOfBandCachedAssetLoading> createState() =>
_ExampleOutOfBandCachedAssetLoadingState();
}
class _CustomCachedAssetLoadingState extends State<CustomCachedAssetLoading> {
class _ExampleOutOfBandCachedAssetLoadingState
extends State<ExampleOutOfBandCachedAssetLoading> {
var _index = 0;
var _ready = false;
final _imageCache = [];
final _fontCache = [];
final _imageCache = <RenderImage>[];
final _fontCache = <Font>[];
@override
void initState() {
@ -42,7 +47,7 @@ class _CustomCachedAssetLoadingState extends State<CustomCachedAssetLoading> {
loadImage() async {
final res = await http.get(Uri.parse('https://picsum.photos/500/500'));
final body = Uint8List.view(res.bodyBytes.buffer);
final image = await ImageAsset.parseBytes(body);
final image = await RiveExampleApp.getCurrentFactory.decodeImage(body);
if (image != null) {
_imageCache.add(image);
}
@ -51,7 +56,7 @@ class _CustomCachedAssetLoadingState extends State<CustomCachedAssetLoading> {
loadFont(url) async {
final res = await http.get(Uri.parse(url));
final body = Uint8List.view(res.bodyBytes.buffer);
final font = await FontAsset.parseBytes(body);
final font = await RiveExampleApp.getCurrentFactory.decodeFont(body);
if (font != null) {
_fontCache.add(font);
@ -75,7 +80,9 @@ class _CustomCachedAssetLoadingState extends State<CustomCachedAssetLoading> {
await Future.wait(futures);
setState(() => _ready = true);
if (mounted) {
setState(() => _ready = true);
}
}
void next() {
@ -86,49 +93,53 @@ class _CustomCachedAssetLoadingState extends State<CustomCachedAssetLoading> {
setState(() => _index -= 1);
}
@override
void dispose() {
for (var image in _imageCache) {
image.dispose();
}
for (var font in _fontCache) {
font.dispose();
}
super.dispose();
}
@override
Widget build(BuildContext context) {
if (!_ready) {
return const Scaffold(
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: EdgeInsets.all(8.0),
child: Text('Warming up cache. Loading files from network...'),
),
CircularProgressIndicator(
strokeWidth: 2,
),
],
),
return const Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: EdgeInsets.all(8.0),
child: Text('Warming up cache. Loading files from network...'),
),
CircularProgressIndicator(
strokeWidth: 2,
),
],
),
);
}
return Scaffold(
appBar: AppBar(
title: const Text('Custom Cached Asset Loading'),
),
body: Center(
child: Row(
children: [
GestureDetector(
onTap: previous,
child: const Icon(Icons.arrow_back),
),
Expanded(
child: (_index % 2 == 0)
? _RiveRandomCachedImage(imageCache: _imageCache)
: _RiveRandomCachedFont(fontCache: _fontCache),
),
GestureDetector(
onTap: next,
child: const Icon(Icons.arrow_forward),
),
],
),
return Center(
child: Row(
children: [
GestureDetector(
onTap: previous,
child: const Icon(Icons.arrow_back),
),
Expanded(
child: (_index % 2 == 0)
? _RiveRandomCachedImage(imageCache: _imageCache)
: _RiveRandomCachedFont(fontCache: _fontCache),
),
GestureDetector(
onTap: next,
child: const Icon(Icons.arrow_forward),
),
],
),
);
}
@ -155,34 +166,46 @@ class __RiveRandomCachedImageState extends State<_RiveRandomCachedImage> {
_loadRiveFile();
}
RiveFile? _riveImageSampleFile;
File? _riveImageSampleFile;
RiveWidgetController? _controller;
// A reference to the Rive image. Can be use to swap out the image at any
// point.
ImageAsset? _imageAsset;
Future<void> _loadRiveFile() async {
final imageFile = await RiveFile.asset(
final imageFile = await File.asset(
'assets/image_out_of_band.riv',
assetLoader: CallbackAssetLoader(
(asset, bytes) async {
if (asset is ImageAsset) {
asset.image = _imageCache[Random().nextInt(_imageCache.length)];
// Maintain a reference to the image asset
// so we can swap it out later instantly.
_imageAsset = asset;
return true;
}
return false;
},
),
riveFactory: RiveExampleApp.getCurrentFactory,
assetLoader: (asset, bytes) {
if (asset is ImageAsset) {
asset.renderImage(_imageCache[Random().nextInt(_imageCache.length)]);
// Maintain a reference to the image asset
// so we can swap it out later instantly.
_imageAsset = asset;
return true;
}
return false;
},
);
setState(() => _riveImageSampleFile = imageFile);
if (mounted) {
setState(() {
_riveImageSampleFile = imageFile;
_controller = RiveWidgetController(_riveImageSampleFile!);
});
}
}
@override
void dispose() {
_riveImageSampleFile?.dispose();
_controller?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
if (_riveImageSampleFile == null) {
if (_controller == null) {
return const Center(child: CircularProgressIndicator());
}
@ -191,10 +214,9 @@ class __RiveRandomCachedImageState extends State<_RiveRandomCachedImage> {
Expanded(
child: Stack(
children: [
RiveAnimation.direct(
_riveImageSampleFile!,
stateMachines: const ['State Machine 1'],
fit: BoxFit.cover,
RiveWidget(
controller: _controller!,
fit: Fit.cover,
),
const Positioned(
child: Padding(
@ -212,8 +234,11 @@ class __RiveRandomCachedImageState extends State<_RiveRandomCachedImage> {
padding: const EdgeInsets.all(8.0),
child: ElevatedButton(
onPressed: () {
_imageAsset?.image =
_imageCache[Random().nextInt(_imageCache.length)];
_imageAsset?.renderImage(
_imageCache[Random().nextInt(_imageCache.length)]);
setState(() {
// force rebuild for Rive widget to update the image
});
},
child: const Text('Random image'),
),
@ -244,32 +269,42 @@ class __RiveRandomCachedFontState extends State<_RiveRandomCachedFont> {
_loadRiveFile();
}
RiveFile? _riveFontSampleFile;
File? _riveFontSampleFile;
RiveWidgetController? _controller;
final List<FontAsset?> _fontAssets = [];
Future<void> _loadRiveFile() async {
final fontFile = await RiveFile.asset(
final fontFile = await File.asset(
'assets/acqua_text_out_of_band.riv',
assetLoader: CallbackAssetLoader(
(asset, bytes) async {
if (asset is FontAsset) {
asset.font = _fontCache[Random().nextInt(_fontCache.length)];
_fontAssets.add(asset);
return true;
}
return false;
},
),
riveFactory: RiveExampleApp.getCurrentFactory,
assetLoader: (asset, bytes) {
if (asset is FontAsset) {
asset.font(_fontCache[Random().nextInt(_fontCache.length)]);
_fontAssets.add(asset);
return true;
}
return false;
},
);
setState(() {
_riveFontSampleFile = fontFile;
});
if (mounted) {
setState(() {
_riveFontSampleFile = fontFile;
_controller = RiveWidgetController(_riveFontSampleFile!);
});
}
}
@override
void dispose() {
_riveFontSampleFile?.dispose();
_controller?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
if (_riveFontSampleFile == null) {
if (_controller == null) {
return const Center(child: CircularProgressIndicator());
}
@ -278,10 +313,9 @@ class __RiveRandomCachedFontState extends State<_RiveRandomCachedFont> {
Expanded(
child: Stack(
children: [
RiveAnimation.direct(
_riveFontSampleFile!,
stateMachines: const ['State Machine 1'],
fit: BoxFit.cover,
RiveWidget(
controller: _controller!,
fit: Fit.cover,
),
const Positioned(
child: Padding(
@ -300,7 +334,10 @@ class __RiveRandomCachedFontState extends State<_RiveRandomCachedFont> {
child: ElevatedButton(
onPressed: () {
for (var element in _fontAssets) {
element?.font = _fontCache[Random().nextInt(_fontCache.length)];
element?.font(_fontCache[Random().nextInt(_fontCache.length)]);
setState(() {
// force rebuild for Rive widget to update the image
});
}
},
child: const Text('Random font'),

View File

@ -0,0 +1,47 @@
import 'package:flutter/material.dart';
import 'package:rive/rive.dart';
import 'package:rive_example/main.dart' show RiveExampleApp;
class ExampleResponsiveLayouts extends StatefulWidget {
const ExampleResponsiveLayouts({super.key});
@override
State<ExampleResponsiveLayouts> createState() =>
_ExampleResponsiveLayoutsState();
}
class _ExampleResponsiveLayoutsState extends State<ExampleResponsiveLayouts> {
late final fileLoader = FileLoader.fromAsset(
'assets/layout_test.riv',
// Choose which renderer to use
riveFactory: RiveExampleApp.getCurrentFactory,
);
@override
void dispose() {
// This widget state owns the file loader, dispose it.
fileLoader.dispose();
super.dispose();
}
@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.layout, // pass Fit.layout to use Rive's layout system
// layoutScaleFactor: 1 / 2, // Optionally: scale the layout
)
},
);
}
}

View File

@ -0,0 +1,46 @@
import 'package:flutter/material.dart';
import 'package:rive/rive.dart';
import 'package:rive_example/main.dart';
/// An example showing Rive Audio working with the Rive Widget.
///
/// There is nothing special that needs to be done to get audio working at
/// runtime.
///
/// See: https://rive.app/docs/editor/events/audio-events
class ExampleRiveAudio extends StatefulWidget {
const ExampleRiveAudio({super.key});
@override
State<ExampleRiveAudio> createState() => _ExampleRiveAudioState();
}
class _ExampleRiveAudioState extends State<ExampleRiveAudio> {
late final fileLoader = FileLoader.fromAsset('assets/lip-sync.riv',
riveFactory: RiveExampleApp.getCurrentFactory);
@override
void dispose() {
// This widget state owns the file loader, dispose it.
fileLoader.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return RiveWidgetBuilder(
fileLoader: fileLoader,
artboardSelector: ArtboardSelector.byName('Lip_sync_2'),
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)
},
);
}
}

View File

@ -0,0 +1,56 @@
import 'package:flutter/material.dart';
import 'package:rive/rive.dart';
import 'package:rive_example/main.dart' show RiveExampleApp;
class ExampleRiveWidget extends StatefulWidget {
const ExampleRiveWidget({super.key});
@override
State<ExampleRiveWidget> createState() => _ExampleRiveWidgetState();
}
class _ExampleRiveWidgetState extends State<ExampleRiveWidget> {
late File file;
late RiveWidgetController controller;
late ViewModelInstance viewModelInstance;
bool isInitialized = false;
@override
void initState() {
super.initState();
initRive();
}
void initRive() async {
file = (await File.asset(
'assets/rewards.riv',
// Choose which renderer to use
riveFactory: RiveExampleApp.getCurrentFactory,
))!;
controller = RiveWidgetController(file);
viewModelInstance = controller.dataBind(DataBind.auto());
setState(() => isInitialized = true);
}
@override
void dispose() {
// This widget state created the file, controller, and view model instance.
// Dispose them once they are no longer needed.
file.dispose();
controller.dispose();
viewModelInstance.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
if (!isInitialized) {
return const Center(child: CircularProgressIndicator());
}
return RiveWidget(
controller: controller,
fit: Fit.layout,
layoutScaleFactor: 1 / 3,
);
}
}

View File

@ -0,0 +1,59 @@
import 'package:flutter/material.dart';
import 'package:rive/rive.dart';
import 'package:rive_example/main.dart' show RiveExampleApp;
class ExampleRiveWidgetBuilder extends StatefulWidget {
const ExampleRiveWidgetBuilder({super.key});
@override
State<ExampleRiveWidgetBuilder> createState() =>
_ExampleRiveWidgetBuilderState();
}
class _ExampleRiveWidgetBuilderState extends State<ExampleRiveWidgetBuilder> {
late final fileLoader = FileLoader.fromAsset(
'assets/rewards.riv',
// Choose which renderer to use
riveFactory: RiveExampleApp.getCurrentFactory,
);
@override
void dispose() {
// This widget state owns the file loader, dispose it.
fileLoader.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return RiveWidgetBuilder(
fileLoader: fileLoader,
dataBind: DataBind.auto(),
// Optional `onFailed` callback to handle loading errors
onFailed: (error, stackTrace) {
print(error);
print(stackTrace);
},
// Optional `onLoaded` callback to access the loaded state
onLoaded: (state) {
print('Rive loaded');
},
// Optionally specify the controller to create
// controller: (file) => RiveWidgetController(file),
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.layout,
layoutScaleFactor: 1 / 3,
)
},
);
}
}

View File

@ -0,0 +1,67 @@
import 'package:flutter/material.dart';
import 'package:rive/rive.dart' as rive;
import 'package:rive_example/main.dart' show RiveExampleApp;
/// This is an alternative controller (painter) to use instead of the
/// [RiveWidgetController].
///
/// This painter is used to paint/advance a state machine. Functionally it's
/// very similar to the [RiveWidgetController], which we recommend using for
/// most use cases.
class ExampleStateMachinePainter extends StatefulWidget {
const ExampleStateMachinePainter({super.key});
@override
State<ExampleStateMachinePainter> createState() =>
_ExampleStateMachinePainterState();
}
class _ExampleStateMachinePainterState
extends State<ExampleStateMachinePainter> {
late rive.File file;
rive.Artboard? artboard;
rive.ViewModelInstance? viewModelInstance;
late rive.StateMachinePainter painter;
@override
void initState() {
super.initState();
init();
}
void _withStateMachine(rive.StateMachine stateMachine) {
stateMachine.bindViewModelInstance(viewModelInstance!);
}
void init() async {
file = (await rive.File.asset(
'assets/rewards.riv',
riveFactory: RiveExampleApp.getCurrentFactory,
))!;
painter = rive.StateMachinePainter(withStateMachine: _withStateMachine);
artboard = file.defaultArtboard();
viewModelInstance =
file.defaultArtboardViewModel(artboard!)!.createDefaultInstance();
setState(() {});
}
@override
void dispose() {
painter.dispose();
artboard?.dispose();
viewModelInstance?.dispose();
file.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
if (artboard == null) {
return const Center(child: CircularProgressIndicator());
}
return rive.RiveArtboardWidget(
artboard: artboard!,
painter: painter,
);
}
}

View File

@ -0,0 +1,88 @@
import 'package:flutter/services.dart';
import 'package:flutter/material.dart';
import 'package:rive/rive.dart';
import 'package:rive_example/main.dart' show RiveExampleApp;
/// We strongly recommend using Data Binding instead of updating text runs
/// manually. See: https://rive.app/docs/runtimes/data-binding
///
/// An example showing how to read and update text runs at runtime.
/// See: https://rive.app/docs/runtimes/text
class ExampleTextRuns extends StatefulWidget {
const ExampleTextRuns({super.key});
@override
State<ExampleTextRuns> createState() => _ExampleTextRunsState();
}
class _ExampleTextRunsState extends State<ExampleTextRuns> {
File? _riveFile;
RiveWidgetController? _controller;
Future<void> _init() async {
_riveFile = await File.asset(
'assets/electrified_button_nested_text.riv',
riveFactory: RiveExampleApp.getCurrentFactory,
assetLoader: (asset, bytes) {
if (asset is FontAsset) {
_loadFont(asset);
return true;
}
return false;
},
);
_controller = RiveWidgetController(
_riveFile!,
artboardSelector: ArtboardSelector.byName('Button'),
stateMachineSelector: StateMachineSelector.byName('button'),
);
// Read/update text runs
// You can access nested text runs by providing an optional path
final initialText =
_controller?.artboard.getText('button_text', path: null);
print('Initial text: $initialText');
_controller?.artboard.setText('button_text', 'Hello, world!');
final updatedText = _controller?.artboard.getText('button_text');
print('Updated text: $updatedText');
setState(() {});
}
@override
void initState() {
super.initState();
_init();
}
@override
void dispose() {
_controller?.dispose();
_riveFile?.dispose();
super.dispose();
}
// This example has the font asset external from the Rive file, and we're
// loading it manually. If you embedded the font in the Rive file, you can
// skip this step.
//
// See: https://rive.app/docs/runtimes/loading-assets
Future<void> _loadFont(FontAsset asset) async {
final bytes = await rootBundle.load('assets/fonts/Inter-Regular.ttf');
final font = await RiveExampleApp.getCurrentFactory
.decodeFont(bytes.buffer.asUint8List());
if (font != null && mounted) {
asset.font(font);
font.dispose();
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: _riveFile == null
? const SizedBox()
: RiveWidget(controller: _controller!),
);
}
}

View File

@ -0,0 +1,67 @@
import 'package:flutter/material.dart';
import 'package:rive/rive.dart';
import 'package:rive_example/main.dart' show RiveExampleApp;
class ExampleTickerMode extends StatefulWidget {
const ExampleTickerMode({super.key});
@override
State<ExampleTickerMode> createState() => _ExampleTickerModeState();
}
class _ExampleTickerModeState extends State<ExampleTickerMode> {
FileLoader fileLoader = FileLoader.fromAsset(
'assets/little_machine.riv',
riveFactory: RiveExampleApp.getCurrentFactory,
);
var tickerMode = false;
@override
void dispose() {
fileLoader.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Expanded(
child: TickerMode(
enabled: tickerMode,
child: 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)
},
),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: tickerMode
? const Text('Ticker mode enabled')
: const Text('Ticker mode disabled')),
Padding(
padding: const EdgeInsets.all(8.0),
child: ElevatedButton(
onPressed: () {
setState(() {
tickerMode = !tickerMode;
});
},
child: const Text('Toggle ticker mode'),
),
)
],
);
}
}

View File

@ -0,0 +1,43 @@
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:rive/rive.dart';
import 'package:rive_example/main.dart' show RiveExampleApp;
class ExampleTimeDilation extends StatefulWidget {
const ExampleTimeDilation({super.key});
@override
State<ExampleTimeDilation> createState() => _ExampleTimeDilationState();
}
class _ExampleTimeDilationState extends State<ExampleTimeDilation> {
FileLoader fileLoader = FileLoader.fromAsset(
'assets/little_machine.riv',
riveFactory: RiveExampleApp.getCurrentFactory,
);
@override
void dispose() {
fileLoader.dispose();
timeDilation = 1; // reset time dilation to normal
super.dispose();
}
@override
Widget build(BuildContext context) {
timeDilation = 5; // 5x slower than normal
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)
},
);
}
}

View File

@ -0,0 +1,12 @@
import 'package:flutter/material.dart';
class Todo extends StatelessWidget {
const Todo({super.key});
@override
Widget build(BuildContext context) {
return const Center(
child: Text('Todo - coming soon'),
);
}
}

View File

@ -1,79 +0,0 @@
import 'package:flutter/material.dart';
import 'package:rive/rive.dart';
/// An example showing how to drive a StateMachine via a trigger and number
/// input.
class LiquidDownload extends StatefulWidget {
const LiquidDownload({Key? key}) : super(key: key);
@override
State<LiquidDownload> createState() => _LiquidDownloadState();
}
class _LiquidDownloadState extends State<LiquidDownload> {
Artboard? _riveArtboard;
SMITrigger? _start;
SMINumber? _progress;
@override
void initState() {
super.initState();
_loadRiveFile();
}
Future<void> _loadRiveFile() async {
final file = await RiveFile.asset('assets/liquid_download.riv');
// The artboard is the root of the animation and gets drawn in the
// Rive widget.
final artboard = file.mainArtboard.instance();
var controller = StateMachineController.fromArtboard(artboard, 'Download');
if (controller != null) {
artboard.addController(controller);
_start = controller.getTriggerInput('Download');
_progress = controller.getNumberInput('Progress');
}
setState(() => _riveArtboard = artboard);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Liquid Download'),
),
body: Center(
child: _riveArtboard == null
? const SizedBox()
: GestureDetector(
onTapDown: (_) => _start?.value = true,
child: Column(
children: [
const SizedBox(height: 10),
const Text(
'Press to activate, slide for progress...',
style: TextStyle(
fontSize: 18,
),
),
Slider(
value: _progress!.value,
min: 0,
max: 100,
label: _progress!.value.round().toString(),
onChanged: (double value) => setState(() {
_progress!.value = value;
}),
),
const SizedBox(height: 10),
Expanded(
child: Rive(
artboard: _riveArtboard!,
),
),
],
),
),
),
);
}
}

View File

@ -1,89 +0,0 @@
import 'package:flutter/material.dart';
import 'package:rive/rive.dart';
/// An example showing how to drive a StateMachine via a trigger input.
class LittleMachine extends StatefulWidget {
const LittleMachine({Key? key}) : super(key: key);
@override
State<LittleMachine> createState() => _LittleMachineState();
}
class _LittleMachineState extends State<LittleMachine> {
/// Message that displays when state has changed
String stateChangeMessage = '';
Artboard? _riveArtboard;
SMITrigger? _trigger;
@override
void initState() {
super.initState();
_loadRiveFile();
}
Future<void> _loadRiveFile() async {
final file = await RiveFile.asset('assets/little_machine.riv');
// The artboard is the root of the animation and gets drawn in the
// Rive widget.
final artboard = file.mainArtboard.instance();
var controller = StateMachineController.fromArtboard(
artboard,
'State Machine 1',
onStateChange: _onStateChange,
);
if (controller != null) {
artboard.addController(controller);
_trigger = controller.getTriggerInput('Trigger 1');
}
setState(() => _riveArtboard = artboard);
}
/// Do something when the state machine changes state
void _onStateChange(String stateMachineName, String stateName) => setState(
() => stateChangeMessage =
'State Changed in $stateMachineName to $stateName',
);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Little Machine')),
body: Stack(
children: [
_riveArtboard == null
? const SizedBox()
: GestureDetector(
onTapDown: (_) => _trigger?.fire(),
child: Rive(
artboard: _riveArtboard!,
fit: BoxFit.cover,
),
),
const Align(
alignment: Alignment.topCenter,
child: Padding(
padding: EdgeInsets.all(16.0),
child: Text(
'Press to activate!',
style: TextStyle(fontSize: 18, color: Colors.black),
),
),
),
Align(
alignment: Alignment.bottomCenter,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
stateChangeMessage,
style: const TextStyle(
color: Colors.black, fontWeight: FontWeight.bold),
),
),
)
],
),
);
}
}

View File

@ -1,139 +1,434 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:rive/rive.dart';
import 'package:rive_example/examples/databinding_images.dart';
import 'package:rive_example/examples/examples.dart';
import 'package:rive/rive.dart' as rive;
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';
import 'package:rive_example/custom_controller.dart';
import 'package:rive_example/event_open_url_button.dart';
import 'package:rive_example/event_star_rating.dart';
import 'package:rive_example/example_state_machine.dart';
import 'package:rive_example/liquid_download.dart';
import 'package:rive_example/little_machine.dart';
import 'package:rive_example/play_one_shot_animation.dart';
import 'package:rive_example/play_pause_animation.dart';
import 'package:rive_example/rive_audio.dart';
import 'package:rive_example/rive_audio_out_of_band.dart';
import 'package:rive_example/simple_animation.dart';
import 'package:rive_example/simple_animation_network.dart';
import 'package:rive_example/simple_machine_listener.dart';
import 'package:rive_example/simple_state_machine.dart';
import 'package:rive_example/skinning_demo.dart';
import 'package:rive_example/state_machine_skills.dart';
import 'package:rive_example/basic_text.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await rive.RiveNative.init();
void main() {
/// Initialize Rive's text, audio, and layout engines.
/// This will automatically be called the first time a RiveFile is loaded if
/// it has not been initialized. And does not need to be called.
///
/// However, calling it early here makes the first
/// visible Rive graphic load faster.
unawaited(RiveFile.initialize());
runApp(
MaterialApp(
title: 'Rive Example',
home: const RiveExampleApp(),
darkTheme: ThemeData.dark().copyWith(
darkTheme: ThemeData(
fontFamily: 'JetBrainsMono',
brightness: Brightness.dark,
scaffoldBackgroundColor: _backgroundColor,
appBarTheme: const AppBarTheme(backgroundColor: _appBarColor),
colorScheme: ColorScheme.fromSwatch().copyWith(primary: _primaryColor),
colorScheme: ColorScheme.fromSwatch(brightness: Brightness.dark)
.copyWith(primary: _primaryColor),
),
themeMode: ThemeMode.dark,
),
);
}
/// Determines which factory/renderer to use for the Rive examples.
///
/// In your app you can combine the usage of the Rive Renderer and the Flutter
/// Renderer. For this example app we have a static variable to determine which
/// factory to use app wide.
///
/// - `rive` uses the Rive Renderer
/// - `flutter` uses the Flutter Renderer (Skia / Impeller)
enum RiveFactoryToUse {
rive,
flutter,
}
/// An example application demoing Rive.
class RiveExampleApp extends StatefulWidget {
const RiveExampleApp({Key? key}) : super(key: key);
static RiveFactoryToUse factoryToUse = RiveFactoryToUse.rive;
static rive.Factory get getCurrentFactory => switch (factoryToUse) {
RiveFactoryToUse.rive => rive.Factory.rive,
RiveFactoryToUse.flutter => rive.Factory.flutter,
};
@override
State<RiveExampleApp> createState() => _RiveExampleAppState();
}
class _RiveExampleAppState extends State<RiveExampleApp> {
// Examples
final _pages = [
const _Page('Simple Animation - Asset', SimpleAssetAnimation()),
const _Page('Simple Animation - Network', SimpleNetworkAnimation()),
const _Page('Play/Pause Animation', PlayPauseAnimation()),
const _Page('Play One-Shot Animation', PlayOneShotAnimation()),
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()),
const _Page('State Machine with Listener', StateMachineListener()),
const _Page('Skinning Demo', SkinningDemo()),
const _Page('Animation Carousel', AnimationCarousel()),
const _Page('Basic Text', BasicText()),
const _Page('Asset Loading', CustomAssetLoading()),
const _Page('Cached Asset Loading', CustomCachedAssetLoading()),
const _Page('Event Open URL Button', EventOpenUrlButton()),
const _Page('Event Star Rating', EventStarRating()),
const _Page('Rive Audio', RiveAudioExample()),
const _Page('Rive Audio [Out-of-Band]', RiveAudioOutOfBandExample()),
// ScrollController for the CustomScrollView
final ScrollController _scrollController = ScrollController();
// Examples organized into sections
final _sections = [
const _Section(
'Getting Started',
[
_Page('Rive Widget', ExampleRiveWidget(),
'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.'),
],
),
const _Section(
'Rive Features',
[
_Page('Data Binding', ExampleDataBinding(),
'Example using Rive data binding at runtime.'),
_Page('Data Binding - Images', ExampleDataBindingImages(),
'Example using Rive data binding images at runtime.'),
_Page('Data Binding - Lists', Todo(),
'Example using Rive data binding lists at runtime.'),
_Page('Responsive Layouts', ExampleResponsiveLayouts(),
'Create responsive Rive graphics that adapt to screen size.'),
_Page('Events', ExampleEvents(), 'Handle Rive events.'),
_Page('Audio', ExampleRiveAudio(), 'Example Rive file with audio.'),
],
),
const _Section(
'Asset Loading',
[
_Page('Network .riv Asset', ExampleNetworkAsset(),
'Load and display Rive graphics from network URLs.'),
_Page('Out-of-band Assets', ExampleOutOfBandAssetLoading(),
'Load Rive files with external assets (images, audio) separately.'),
_Page(
'Out-of-band Assets - Cached',
ExampleOutOfBandCachedAssetLoading(),
'Load Rive files with cached external assets for better immediate availability.',
),
],
),
const _Section(
'Painters [Advanced]',
[
_Page('State Machine Painter', ExampleStateMachinePainter(),
'Advanced: Custom painter for state machines.'),
_Page('Single Animation Painter', ExampleSingleAnimationPainter(),
'Advanced: Custom painter for single animation playback.'),
],
),
const _Section(
'Flutter Concepts/Integration',
[
// _Page('Flutter Lists', Todo(),
// 'Integrate Rive graphics with Flutter list widgets.'),
_Page('Flutter Hit Test + Cursor Behaviour', ExampleHitTestBehaviour(),
'Specifying hit test and cursor behaviour.'),
_Page('Flutter Ticker Mode', ExampleTickerMode(),
'Rive graphics respect Flutter ticker mode.'),
_Page('Flutter Time Dilation', ExampleTimeDilation(),
'Rive graphics respect Flutter time dilation.'),
// _Page('Flutter Hero Transitions', Todo(),
// 'Create smooth transitions between pages with Rive graphics.'),
// _Page('Flutter State Management', Todo(),
// 'Manage Rive state with Flutter state management.'),
// _Page('Flutter Localization', Todo(),
// 'Localize Rive graphics for different languages.'),
// _Page('Flutter Internationalization', Todo(),
// 'Internationalize Rive graphics with Flutter i18n.'),
],
),
const _Section(
'Legacy Features [Use data binding instead]',
[
_Page('Inputs [Nested]', ExampleInputs(),
'Legacy: Handle input [nested] controls in Rive graphics.'),
_Page('Text Runs [Nested]', ExampleTextRuns(),
'Legacy: Handle text runs [nested] components in Rive graphics.'),
],
),
];
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Rive Examples')),
body: Center(
child: ListView.separated(
shrinkWrap: true,
itemBuilder: (context, index) => _NavButton(page: _pages[index]),
separatorBuilder: (context, index) => const SizedBox(height: 16),
itemCount: _pages.length,
body: Column(children: [
Expanded(
child: Scrollbar(
controller: _scrollController,
child: CustomScrollView(
controller: _scrollController,
slivers: [
SliverPadding(
padding: const EdgeInsets.all(8.0),
sliver: SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
// Calculate which section and item we're at
int itemIndex = index;
for (int i = 0; i < _sections.length; i++) {
if (itemIndex == 0) {
// This is a section header
return _SectionHeader(_sections[i].title);
}
itemIndex--;
if (itemIndex < _sections[i].pages.length) {
// This is a page within the current section
return Padding(
padding: const EdgeInsets.only(bottom: 16.0),
child: _NavButton(
page: _sections[i].pages[itemIndex]),
);
}
itemIndex -= _sections[i].pages.length;
}
return null;
},
childCount: _getTotalItemCount(),
),
),
),
],
),
),
),
),
const SizedBox(height: 16),
ColoredBox(
color: Colors.black,
child: Column(
children: [
const SizedBox(height: 16),
const Text('Factory to use:',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
const SizedBox(height: 16),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
children: [
Radio<RiveFactoryToUse>(
value: RiveFactoryToUse.rive,
groupValue: RiveExampleApp.factoryToUse,
onChanged: (value) {
setState(() {
RiveExampleApp.factoryToUse =
value as RiveFactoryToUse;
});
},
),
const Text('Rive Renderer',
style: TextStyle(fontSize: 14)),
],
),
const SizedBox(width: 32),
Row(
children: [
Radio<RiveFactoryToUse>(
value: RiveFactoryToUse.flutter,
groupValue: RiveExampleApp.factoryToUse,
onChanged: (value) {
setState(() {
RiveExampleApp.factoryToUse =
value as RiveFactoryToUse;
});
},
),
const Text('Flutter Renderer',
style: TextStyle(fontSize: 14)),
],
),
],
),
),
],
),
),
]),
);
}
int _getTotalItemCount() {
int count = 0;
for (final section in _sections) {
count += 1; // Section header
count += section.pages.length; // Pages in section
}
return count;
}
}
/// Class used to organize demo sections.
class _Section {
final String title;
final List<_Page> pages;
const _Section(this.title, this.pages);
}
/// Class used to organize demo pages.
class _Page {
final String name;
final Widget page;
final String description;
const _Page(this.name, this.page);
const _Page(this.name, this.page, this.description);
}
/// Button to navigate to demo pages.
class _NavButton extends StatelessWidget {
/// Section header widget with divider.
class _SectionHeader extends StatelessWidget {
const _SectionHeader(this.title);
final String title;
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 16),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: Text(
title,
style: Theme.of(context).textTheme.labelLarge?.copyWith(
color: _primaryColor,
),
),
),
const Divider(
color: _primaryColor,
thickness: 0.5,
height: 1,
),
const SizedBox(height: 16),
],
);
}
}
/// Button to navigate to demo pages with hover overlay.
class _NavButton extends StatefulWidget {
const _NavButton({required this.page});
final _Page page;
@override
Widget build(BuildContext context) {
return Center(
child: ElevatedButton(
child: SizedBox(
width: 250,
child: Center(
State<_NavButton> createState() => _NavButtonState();
}
class _NavButtonState extends State<_NavButton> {
bool _isHovered = false;
OverlayEntry? _overlayEntry;
@override
void dispose() {
_removeOverlay();
super.dispose();
}
void _removeOverlay() {
_overlayEntry?.remove();
_overlayEntry = null;
}
void _showOverlay() {
_removeOverlay();
final RenderBox renderBox = context.findRenderObject() as RenderBox;
final position = renderBox.localToGlobal(Offset.zero);
final size = renderBox.size;
_overlayEntry = OverlayEntry(
builder: (context) => Positioned(
top: position.dy - 80, // Position above the button
left: position.dx + (size.width / 2) - 150, // Center horizontally
child: Material(
color: Colors.transparent,
child: Container(
width: 300,
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.9),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: _primaryColor, width: 1),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.3),
blurRadius: 8,
offset: const Offset(0, 4),
),
],
),
child: Text(
page.name,
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
widget.page.description,
style: const TextStyle(
color: Colors.white,
fontSize: 12,
height: 1.4,
),
textAlign: TextAlign.center,
),
),
),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute<void>(
builder: (context) => page.page,
),
);
},
),
);
Overlay.of(context).insert(_overlayEntry!);
}
@override
Widget build(BuildContext context) {
return MouseRegion(
onEnter: (_) {
setState(() => _isHovered = true);
_showOverlay();
},
onExit: (_) {
setState(() => _isHovered = false);
_removeOverlay();
},
child: Center(
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: _isHovered ? _primaryColor.withOpacity(0.1) : null,
elevation: _isHovered ? 8 : 2,
),
child: SizedBox(
width: 300,
child: Center(
child: Text(
widget.page.name,
style: Theme.of(context).textTheme.labelLarge,
),
),
),
onPressed: () {
_removeOverlay();
Navigator.push(
context,
MaterialPageRoute<void>(
builder: (context) => _WrappedPage(page: widget.page),
),
);
},
),
),
);
}
}
/// Scaffold wrapper for the page.
class _WrappedPage extends StatelessWidget {
const _WrappedPage({required this.page});
final _Page page;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(page.name)),
body: page.page,
);
}
}

View File

@ -1,52 +0,0 @@
import 'package:flutter/material.dart';
import 'package:rive/rive.dart';
/// Demonstrates playing a one-shot animation on demand
class PlayOneShotAnimation extends StatefulWidget {
const PlayOneShotAnimation({Key? key}) : super(key: key);
@override
State<PlayOneShotAnimation> createState() => _PlayOneShotAnimationState();
}
class _PlayOneShotAnimationState extends State<PlayOneShotAnimation> {
/// Controller for playback
late RiveAnimationController _controller;
/// Is the animation currently playing?
bool _isPlaying = false;
@override
void initState() {
super.initState();
_controller = OneShotAnimation(
'bounce',
autoplay: false,
onStop: () => setState(() => _isPlaying = false),
onStart: () => setState(() => _isPlaying = true),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('One-Shot Example'),
),
body: Center(
child: RiveAnimation.asset(
'assets/vehicles.riv',
animations: const ['idle', 'curves'],
fit: BoxFit.cover,
controllers: [_controller],
),
),
floatingActionButton: FloatingActionButton(
// disable the button while playing the animation
onPressed: () => _isPlaying ? null : _controller.isActive = true,
tooltip: 'Bounce',
child: const Icon(Icons.arrow_upward),
),
);
}
}

View File

@ -1,54 +0,0 @@
/// Demonstrates how to play and pause a looping animation
import 'package:flutter/material.dart';
import 'package:rive/rive.dart';
class PlayPauseAnimation extends StatefulWidget {
const PlayPauseAnimation({Key? key}) : super(key: key);
@override
State<PlayPauseAnimation> createState() => _PlayPauseAnimationState();
}
class _PlayPauseAnimationState extends State<PlayPauseAnimation> {
Artboard? _artboard;
bool get isPlaying => _artboard?.isPlaying ?? true;
/// Toggles between play and pause on the artboard
void _togglePlay() {
if (isPlaying) {
_artboard?.pause();
} else {
_artboard?.play();
}
// We call set state to update the Play/Pause Icon. This isn't needed
// to update Rive.
setState(() {});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Animation Example'),
),
body: RiveAnimation.asset(
'assets/off_road_car.riv',
animations: const ["idle"],
fit: BoxFit.cover,
onInit: (artboard) {
_artboard = artboard;
},
),
floatingActionButton: FloatingActionButton(
onPressed: _togglePlay,
tooltip: isPlaying ? 'Pause' : 'Play',
child: Icon(
isPlaying ? Icons.pause : Icons.play_arrow,
),
),
);
}
}

View File

@ -1,20 +0,0 @@
import 'package:flutter/material.dart';
import 'package:rive/rive.dart';
class RiveAudioExample extends StatelessWidget {
const RiveAudioExample({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Rive Audio'),
),
body: const RiveAnimation.asset(
"assets/lip-sync.riv",
artboard: 'Lip_sync_2',
stateMachines: ['State Machine 1'],
),
);
}
}

View File

@ -1,48 +0,0 @@
import 'package:flutter/material.dart';
import 'package:rive/rive.dart';
class RiveAudioOutOfBandExample extends StatefulWidget {
const RiveAudioOutOfBandExample({super.key});
@override
State<RiveAudioOutOfBandExample> createState() =>
_RiveAudioOutOfBandExampleState();
}
class _RiveAudioOutOfBandExampleState extends State<RiveAudioOutOfBandExample> {
@override
void initState() {
super.initState();
_loadRiveFile();
}
RiveFile? _riveAudioAssetFile;
Future<void> _loadRiveFile() async {
final riveFile = await RiveFile.asset(
'assets/ping_pong_audio_demo.riv',
assetLoader: LocalAssetLoader(audioPath: 'assets/audio'),
);
setState(() {
_riveAudioAssetFile = riveFile;
});
}
@override
Widget build(BuildContext context) {
if (_riveAudioAssetFile == null) {
return const Center(child: CircularProgressIndicator());
}
return Scaffold(
appBar: AppBar(
title: const Text('Rive Audio [Out-of-Band]'),
),
body: RiveAnimation.direct(
_riveAudioAssetFile!,
stateMachines: const ['State Machine 1'],
fit: BoxFit.cover,
),
);
}
}

View File

@ -1,22 +0,0 @@
import 'package:flutter/material.dart';
import 'package:rive/rive.dart';
/// Basic example playing a Rive animation from a packaged asset.
class SimpleAssetAnimation extends StatelessWidget {
const SimpleAssetAnimation({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Simple Animation'),
),
body: const Center(
child: RiveAnimation.asset(
'assets/off_road_car.riv',
fit: BoxFit.cover,
),
),
);
}
}

View File

@ -1,23 +0,0 @@
import 'package:flutter/material.dart';
import 'package:rive/rive.dart';
/// Basic example playing a Rive animation from a network asset.
class SimpleNetworkAnimation extends StatelessWidget {
const SimpleNetworkAnimation({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Simple Animation'),
),
body: const Center(
child: RiveAnimation.network(
'https://cdn.rive.app/animations/vehicles.riv',
fit: BoxFit.cover,
placeHolder: Center(child: CircularProgressIndicator()),
),
),
);
}
}

View File

@ -1,27 +0,0 @@
import 'package:flutter/material.dart';
import 'package:rive/rive.dart';
class StateMachineListener extends StatefulWidget {
const StateMachineListener({Key? key}) : super(key: key);
@override
State<StateMachineListener> createState() => _StateMachineListenerState();
}
class _StateMachineListenerState extends State<StateMachineListener> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Light Switch'),
),
body: const Center(
child: RiveAnimation.asset(
'assets/light_switch.riv',
fit: BoxFit.contain,
stateMachines: ['Switch'],
),
),
);
}
}

View File

@ -1,57 +0,0 @@
import 'package:flutter/material.dart';
import 'package:rive/rive.dart';
/// An example demonstrating a simple state machine.
///
/// The "bumpy" state machine will be activated on tap of the vehicle.
class SimpleStateMachine extends StatefulWidget {
const SimpleStateMachine({Key? key}) : super(key: key);
@override
State<SimpleStateMachine> createState() => _SimpleStateMachineState();
}
class _SimpleStateMachineState extends State<SimpleStateMachine> {
SMITrigger? _bump;
void _onRiveInit(Artboard artboard) {
final controller = StateMachineController.fromArtboard(artboard, 'bumpy');
artboard.addController(controller!);
_bump = controller.getTriggerInput('bump');
}
void _hitBump() => _bump?.fire();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Simple State Machine'),
),
body: Stack(
children: [
Center(
child: GestureDetector(
onTap: _hitBump,
child: RiveAnimation.asset(
'assets/vehicles.riv',
fit: BoxFit.cover,
onInit: _onRiveInit,
),
),
),
const Align(
alignment: Alignment.topCenter,
child: Padding(
padding: EdgeInsets.all(16.0),
child: Text(
'Bump the van!',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
),
),
],
),
);
}
}

View File

@ -1,105 +0,0 @@
import 'package:flutter/material.dart';
import 'package:rive/rive.dart';
/// Basic skinning example. The skinning states is set in the Rive editor and
/// triggers are used to change the value.
class SkinningDemo extends StatefulWidget {
const SkinningDemo({Key? key}) : super(key: key);
@override
State<SkinningDemo> createState() => _SkinningDemoState();
}
class _SkinningDemoState extends State<SkinningDemo> {
static const _skinMapping = {
"Skin_0": "Plain",
"Skin_1": "Summer",
"Skin_2": "Elvis",
"Skin_3": "Superhero",
"Skin_4": "Astronaut"
};
String _currentState = 'Plain';
SMITrigger? _skin;
void _onRiveInit(Artboard artboard) {
final controller = StateMachineController.fromArtboard(
artboard,
'Motion',
onStateChange: _onStateChange,
);
artboard.addController(controller!);
_skin = controller.getTriggerInput('Skin');
}
void _onStateChange(String stateMachineName, String stateName) {
if (stateName.contains("Skin_")) {
setState(() {
_currentState = _skinMapping[stateName] ?? 'Plain';
});
}
}
void _swapSkin() {
_skin?.fire();
}
@override
Widget build(BuildContext context) {
const textColor = Color(0xFFefcb7d);
return Scaffold(
appBar: AppBar(
title: const Text('Skinning Demo'),
),
body: Stack(
children: [
Center(
child: RiveAnimation.asset(
'assets/skins_demo.riv',
fit: BoxFit.cover,
onInit: _onRiveInit,
),
),
Align(
alignment: Alignment.topCenter,
child: Column(
children: [
const Padding(
padding: EdgeInsets.all(24.0),
child: Text(
'Choose an Avatar',
style: TextStyle(
fontSize: 48,
fontWeight: FontWeight.bold,
color: textColor,
),
),
),
FilledButton(
onPressed: _swapSkin,
style: const ButtonStyle(
backgroundColor: WidgetStatePropertyAll(Color(0xFF7d99ef)),
),
child: const Text('Swap Skin'),
),
const Spacer(),
Text(
'Skin: $_currentState',
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: textColor,
),
),
const SizedBox(height: 48)
],
),
)
],
),
);
}
}

View File

@ -1,83 +0,0 @@
import 'package:flutter/material.dart';
import 'package:rive/rive.dart';
/// An example showing how to drive a StateMachine via one numeric input.
class StateMachineSkills extends StatefulWidget {
const StateMachineSkills({Key? key}) : super(key: key);
@override
State<StateMachineSkills> createState() => _StateMachineSkillsState();
}
class _StateMachineSkillsState extends State<StateMachineSkills> {
Artboard? _riveArtboard;
SMINumber? _levelInput;
@override
void initState() {
super.initState();
_loadRiveFile();
}
Future<void> _loadRiveFile() async {
final file = await RiveFile.asset('assets/skills.riv');
// The artboard is the root of the animation and gets drawn in the
// Rive widget.
final artboard = file.mainArtboard.instance();
var controller =
StateMachineController.fromArtboard(artboard, 'Designer\'s Test');
if (controller != null) {
artboard.addController(controller);
_levelInput = controller.getNumberInput('Level');
}
setState(() => _riveArtboard = artboard);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Skills Machine'),
),
body: Center(
child: _riveArtboard == null
? const SizedBox()
: Stack(
children: [
Positioned.fill(
child: Rive(
artboard: _riveArtboard!,
fit: BoxFit.cover,
),
),
Positioned.fill(
bottom: 32,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
ElevatedButton(
child: const Text('Beginner'),
onPressed: () => _levelInput?.value = 0,
),
const SizedBox(width: 10),
ElevatedButton(
child: const Text('Intermediate'),
onPressed: () => _levelInput?.value = 1,
),
const SizedBox(width: 10),
ElevatedButton(
child: const Text('Expert'),
onPressed: () => _levelInput?.value = 2,
),
],
),
),
],
),
),
);
}
}

View File

@ -0,0 +1,28 @@
PODS:
- FlutterMacOS (1.0.0)
- rive_native (0.0.1):
- FlutterMacOS
- url_launcher_macos (0.0.1):
- FlutterMacOS
DEPENDENCIES:
- FlutterMacOS (from `Flutter/ephemeral`)
- rive_native (from `Flutter/ephemeral/.symlinks/plugins/rive_native/macos`)
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
EXTERNAL SOURCES:
FlutterMacOS:
:path: Flutter/ephemeral
rive_native:
:path: Flutter/ephemeral/.symlinks/plugins/rive_native/macos
url_launcher_macos:
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
SPEC CHECKSUMS:
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
rive_native: 9930efc341917c512f451cfe1522d8addf54d569
url_launcher_macos: 0fba8ddabfc33ce0a9afe7c5fef5aab3d8d2d673
PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367
COCOAPODS: 1.16.2

View File

@ -59,6 +59,7 @@
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
enableGPUValidationMode = "1"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">

View File

@ -6,4 +6,8 @@ class AppDelegate: FlutterAppDelegate {
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true
}
override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
return true
}
}

View File

@ -8,5 +8,7 @@
<true/>
<key>com.apple.security.network.server</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
</dict>
</plist>

View File

@ -4,5 +4,7 @@
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
</dict>
</plist>

View File

@ -6,7 +6,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
version: 1.0.0+1
environment:
sdk: ">=2.17.0 <4.0.0"
sdk: ">=3.5.0 <4.0.0"
dependencies:
flutter:
@ -24,7 +24,13 @@ dev_dependencies:
flutter:
uses-material-design: true
fonts:
- family: JetBrainsMono
fonts:
- asset: assets/fonts/JetBrains Mono.ttf
assets:
- assets/
- assets/fonts/
- assets/audio/
- assets/images/

View File

@ -1,3 +0,0 @@
export 'package:rive/src/rive_core/node.dart';
export 'package:rive/src/rive_core/transform_component.dart';
export 'package:rive/src/rive_core/world_transform_component.dart';

View File

@ -1 +0,0 @@
export 'package:rive_common/math.dart';

View File

@ -1,51 +1,25 @@
library rive;
export 'package:rive_native/rive_native.dart'
hide
InternalViewModelInstanceBoolean,
InternalViewModelInstanceEnum,
InternalViewModelInstanceTrigger,
InternalViewModelInstanceSymbolListIndex,
InternalViewModelInstanceNumber,
InternalViewModelInstanceValue,
InternalDataContext,
InternalViewModelInstanceColor,
InternalViewModelInstanceList,
InternalViewModelInstanceString,
InternalViewModelInstance,
InternalViewModelInstanceViewModel,
InternalDataBind;
export 'package:rive/src/asset.dart';
export 'package:rive/src/asset_loader.dart';
export 'package:rive/src/controllers/linear_animation_controller.dart';
export 'package:rive/src/controllers/one_shot_controller.dart';
export 'package:rive/src/controllers/simple_controller.dart';
export 'package:rive/src/controllers/state_machine_controller.dart';
export 'package:rive/src/extensions.dart';
export 'package:rive/src/rive.dart';
export 'package:rive/src/rive_core/animation/linear_animation.dart';
export 'package:rive/src/rive_core/animation/loop.dart';
export 'package:rive/src/rive_core/animation/state_machine.dart';
export 'package:rive/src/rive_core/artboard.dart';
export 'package:rive/src/rive_core/assets/audio_asset.dart';
export 'package:rive/src/rive_core/assets/font_asset.dart';
export 'package:rive/src/rive_core/assets/image_asset.dart';
export 'package:rive/src/rive_core/data_bind/data_bind.dart';
export 'package:rive/src/rive_core/data_bind/data_bind_context.dart';
export 'package:rive/src/rive_core/data_bind/data_context.dart';
export 'package:rive/src/rive_core/event.dart';
export 'package:rive/src/rive_core/nested_artboard.dart';
export 'package:rive/src/rive_core/open_url_target.dart';
export 'package:rive/src/rive_core/rive_animation_controller.dart';
export 'package:rive/src/rive_core/runtime/exceptions/rive_format_error_exception.dart';
export 'package:rive/src/rive_core/runtime/runtime_header.dart'
show riveVersion;
export 'package:rive/src/rive_core/shapes/image.dart';
export 'package:rive/src/rive_core/shapes/paint/fill.dart';
export 'package:rive/src/rive_core/shapes/paint/linear_gradient.dart';
export 'package:rive/src/rive_core/shapes/paint/radial_gradient.dart';
export 'package:rive/src/rive_core/shapes/paint/solid_color.dart';
export 'package:rive/src/rive_core/shapes/paint/stroke.dart';
export 'package:rive/src/rive_core/shapes/shape.dart';
export 'package:rive/src/rive_core/text/text_value_run.dart';
export 'package:rive/src/rive_core/viewmodel/viewmodel_instance.dart';
export 'package:rive/src/rive_core/viewmodel/viewmodel_instance_color.dart';
export 'package:rive/src/rive_core/viewmodel/viewmodel_instance_number.dart';
export 'package:rive/src/rive_core/viewmodel/viewmodel_instance_string.dart';
export 'package:rive/src/rive_core/viewmodel/viewmodel_instance_value.dart';
export 'package:rive/src/rive_core/viewmodel/viewmodel_instance_viewmodel.dart';
export 'package:rive/src/rive_file.dart';
export 'package:rive/src/rive_scene.dart';
export 'package:rive/src/runtime_artboard.dart';
export 'package:rive/src/runtime_event.dart';
export 'package:rive/src/runtime_mounted_artboard.dart';
export 'package:rive/src/runtime_nested_artboard.dart';
export 'package:rive/src/widgets/rive_animation.dart';
export 'src/dynamic_library_helper/dynamic_library_helper_stub.dart'
if (dart.library.js_interop) 'src/dynamic_library_helper/dynamic_library_helper_web.dart'
if (dart.library.ffi) 'src/dynamic_library_helper/dynamic_library_helper_ffi.dart';
export 'src/errors.dart';
export 'src/file_loader.dart';
export 'src/models/artboard_selector.dart';
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/rive_builder.dart';
export 'src/widgets/rive_widget.dart';

View File

@ -1,16 +0,0 @@
// In order to *not* need this ignore, consider extracting the "web" version
// of your plugin as a separate package, instead of inlining it in the same
// package as the core of your plugin.
// ignore: avoid_web_libraries_in_flutter
// ignore_for_file: avoid_classes_with_only_static_members
import 'package:flutter_web_plugins/flutter_web_plugins.dart';
/// A web implementation of the RivePlatform of the Rive plugin.
class RivePlugin {
/// Constructs a RiveWeb
RivePlugin();
static void registerWith(Registrar registrar) {}
}

View File

@ -1,20 +0,0 @@
import 'dart:collection';
import 'package:rive/src/rive_core/animation/animation.dart';
class AnimationList extends ListBase<Animation> {
// Lame way to do this due to how ListBase needs to expand a nullable list.
final List<Animation?> _values = [];
List<Animation> get values => _values.cast<Animation>();
@override
int get length => _values.length;
@override
set length(int value) => _values.length = value;
@override
Animation operator [](int index) => _values[index]!;
@override
void operator []=(int index, Animation value) => _values[index] = value;
}

View File

@ -1,75 +0,0 @@
// ignore_for_file: deprecated_member_use_from_same_package
import 'package:rive/src/rive_core/assets/file_asset.dart';
export 'package:rive/src/generated/artboard_base.dart';
const _deprecationExtensionMessage =
'''This Extension is no longer maintained. Similar behaviour can
be re-created with a custom extension.
Example: https://gist.github.com/HayesGordon/5d37d3fb26f54b2c231760c2c8685963
''';
const _deprecationEnumMessage =
'''This Enum is no longer maintained. Similar behaviour can
be re-created with a custom Enum.
Example: https://gist.github.com/HayesGordon/5d37d3fb26f54b2c231760c2c8685963
''';
@Deprecated(_deprecationExtensionMessage)
extension FileAssetExtension on FileAsset {
@Deprecated(_deprecationExtensionMessage)
Extension get extension => _getExtension(fileExtension);
@Deprecated(_deprecationExtensionMessage)
Type get type => _getType(fileExtension);
}
Extension _getExtension(String ext) {
switch (ext) {
case 'png':
return Extension.png;
case 'jpeg':
return Extension.jpeg;
case 'webp':
return Extension.webp;
case 'otf':
return Extension.otf;
case 'ttf':
return Extension.ttf;
}
return Extension.unknown;
}
Type _getType(String ext) {
switch (ext) {
case 'png':
case 'jpeg':
case 'webp':
return Type.image;
case 'otf':
case 'ttf':
return Type.font;
}
return Type.unknown;
}
@Deprecated(_deprecationEnumMessage)
enum Extension {
otf,
ttf,
jpeg,
png,
webp,
unknown,
}
@Deprecated(_deprecationEnumMessage)
enum Type {
font,
image,
unknown,
}

View File

@ -1,21 +0,0 @@
import 'dart:collection';
import 'package:rive/src/rive_core/assets/asset.dart';
// List of assets used by the backboard.
class AssetList extends ListBase<Asset> {
final List<Asset?> _values = [];
List<Asset> get values => _values.cast<Asset>();
@override
int get length => _values.length;
@override
set length(int value) => _values.length = value;
@override
Asset operator [](int index) => _values[index]!;
@override
void operator []=(int index, Asset value) => _values[index] = value;
}

View File

@ -1,212 +0,0 @@
import 'package:flutter/services.dart';
import 'package:http/http.dart' as http;
import 'package:rive/rive.dart';
import 'package:rive/src/debug.dart';
import 'package:rive/src/rive_core/assets/file_asset.dart';
import 'package:rive/src/utilities/utilities.dart';
/// Base class for resolving out of band Rive assets, such as images and fonts.
///
/// See [CallbackAssetLoader] and [LocalAssetLoader] for an example of how to
/// use this.
// ignore: one_member_abstracts
abstract class FileAssetLoader {
Future<bool> load(FileAsset asset, Uint8List? embeddedBytes);
}
/// Loads assets from Rive's CDN.
///
/// This is used internally by Rive to load assets from the CDN. It is not
/// intended to be used by end users. Instead extend [FileAssetLoader] for
/// custom asset loading, or use [CallbackAssetLoader].
class CDNAssetLoader extends FileAssetLoader {
CDNAssetLoader();
@override
Future<bool> load(FileAsset asset, Uint8List? embeddedBytes) async {
// if the asset is embedded, or does not have a cdn uuid, do not attempt
// to load it
if (embeddedBytes != null || asset.cdnUuid.isEmpty) {
return false;
}
// TODO (Max): Do we have a URL builder?
// TODO (Max): We should aim to get loading errors exposed where
// possible, this includes failed network requests but also
// failed asset.decode
var url = asset.cdnBaseUrl;
if (!url.endsWith('/')) {
url += '/';
}
url += formatUuid(
uuidVariant2(asset.cdnUuid),
);
final res = await http.get(Uri.parse(url));
if ((res.statusCode / 100).floor() == 2) {
try {
await asset.decode(
Uint8List.view(res.bodyBytes.buffer),
);
} on Exception catch (e) {
printDebugMessage(
'''Unable to parse response ${asset.runtimeType}.
- Url: $url
- Exception: $e''',
);
return false;
}
return true;
} else {
return false;
}
}
}
/// Convenience class for loading assets from the local file system.
///
/// Specify the [fontPath], [imagePath], and [audioPath] to load assets from the
/// asset bundle for a specific asset type. Or use [path] as a general
/// fallback instead. [path] will only be used for an asset type if the
/// corresponding asset path is null.
///
/// For example, to provide an audio asset path:
/// ```dart
/// final riveFile = await RiveFile.asset(
/// 'assets/ping_pong_audio_demo.riv',
/// assetLoader: LocalAssetLoader(
/// audioPath: 'assets/some/path',
/// // path: 'assets/some/path', // or provide fallback/general
/// ),
/// );
/// ```
///
/// Be sure to provide the correct path where the file is located.
///
/// If more control is desired, extend [FileAssetLoader] and override [load].
class LocalAssetLoader extends FileAssetLoader {
final String? audioPath;
final String? fontPath;
final String? imagePath;
final String? path;
final AssetBundle _assetBundle;
LocalAssetLoader({
this.audioPath,
this.fontPath,
this.imagePath,
this.path,
AssetBundle? assetBundle,
}) : _assetBundle = assetBundle ?? rootBundle;
@override
Future<bool> load(FileAsset asset, Uint8List? embeddedBytes) async {
// do not load embedded assets.
if (embeddedBytes != null) {
return false;
}
String? assetPath;
String filePath;
switch (asset.runtimeType) {
case AudioAsset:
assert(audioPath != null || path != null,
'''Audio asset not found. Be sure to provide either `audioPath` or `path` in `LocalAssetLoader`.''');
if (audioPath == null && path == null) return false;
filePath = audioPath ?? path!;
break;
case FontAsset:
assert(fontPath != null || path != null,
'''Font asset not found. Be sure to provide either `fontPath` or `path` in `LocalAssetLoader`.''');
if (fontPath == null && path == null) return false;
filePath = fontPath ?? path!;
break;
case ImageAsset:
assert(imagePath != null || path != null,
'''Image asset not found. Be sure to provide either `imagePath` or `path` in `LocalAssetLoader`.''');
if (imagePath == null && path == null) return false;
filePath = imagePath ?? path!;
break;
default:
return false;
}
filePath = filePath.endsWith("/") ? filePath : "$filePath/";
assetPath = filePath + asset.uniqueFilename;
final bytes = await _assetBundle.load(assetPath);
await asset.decode(Uint8List.view(bytes.buffer));
return true;
}
}
/// Convenience class that extends [FileAssetLoader] and allows you to
/// register a callback for loading Rive assets.
///
/// The callback will be called for each asset that needs to be loaded manually.
/// See [RiveFile] for additional options. Which assets are embedded are defined
/// within the Rive editor.
///
/// This callback will be triggered for any **referenced** assets.
///
///
/// Set [loadCdnAssets] to false to disable loading
/// assets from the Rive CDN.
///
/// ### Example usage:
/// Loading from assets. Here only assets marked as **referenced** will trigger
/// the callback.
/// ```dart
/// final riveFile = await RiveFile.asset(
/// 'assets/asset.riv',
/// loadCdnAssets: true,
/// assetLoader: CallbackAssetLoader(
/// (asset, bytes) async {
/// final res =
/// await http.get(Uri.parse('https://picsum.photos/1000/1000'));
/// await asset.decode(Uint8List.view(res.bodyBytes.buffer));
/// return true;
/// },
/// ),
/// );
/// ```
class CallbackAssetLoader extends FileAssetLoader {
Future<bool> Function(FileAsset asset, Uint8List? embeddedBytes) callback;
CallbackAssetLoader(this.callback);
@override
Future<bool> load(FileAsset asset, Uint8List? embeddedBytes) async {
return callback(asset, embeddedBytes);
}
}
/// Convenience class that extends [FileAssetLoader] and allows you to
/// register fallbacks for loading Rive assets, such as images and fonts.
///
/// For example, you can use this to load assets from the CDN, and if that
/// fails, load them from the asset bundle.
///
/// Alternatively, extend [FileAssetLoader] and override [load] for more
/// custom control in how assets are resolved.
class FallbackAssetLoader extends FileAssetLoader {
final List<FileAssetLoader> fileAssetLoaders;
FallbackAssetLoader(this.fileAssetLoaders);
@override
Future<bool> load(FileAsset asset, Uint8List? embeddedBytes) async {
for (var i = 0; i < fileAssetLoaders.length; i++) {
final resolver = fileAssetLoaders[i];
final success = await resolver.load(asset, embeddedBytes);
if (success) {
return true;
}
}
return false;
}
}

View File

@ -1,20 +0,0 @@
import 'dart:collection';
import 'package:rive/src/rive_core/animation/blend_animation.dart';
class BlendAnimations<T extends BlendAnimation> extends ListBase<T> {
final List<T?> _values = [];
List<T> get values => _values.cast<T>();
@override
int get length => _values.length;
@override
set length(int value) => _values.length = value;
@override
T operator [](int index) => _values[index]!;
@override
void operator []=(int index, T value) => _values[index] = value;
}

View File

@ -1,20 +0,0 @@
import 'dart:collection';
import 'package:rive/src/rive_core/component.dart';
// TODO: figure out how to make this cleaner.
class ContainerChildren extends ListBase<Component> {
final List<Component?> _values = [];
List<Component> get values => _values.cast<Component>();
@override
int get length => _values.length;
@override
set length(int value) => _values.length = value;
@override
Component operator [](int index) => _values[index]!;
@override
void operator []=(int index, Component value) => _values[index] = value;
}

View File

@ -1,47 +0,0 @@
import 'package:rive/src/core/core.dart';
import 'package:rive/src/rive_core/animation/keyed_object.dart';
import 'package:rive/src/rive_core/animation/linear_animation_instance.dart'
as core;
import 'package:rive/src/rive_core/event.dart';
import 'package:rive/src/runtime_mounted_artboard.dart';
export 'package:rive/src/runtime_mounted_artboard.dart';
/// An AnimationController which controls a StateMachine and provides access to
/// the inputs of the StateMachine.
class LinearAnimationInstance extends core.LinearAnimationInstance
with RuntimeEventReporter, KeyedCallbackReporter {
final _runtimeEventListeners = <OnRuntimeEvent>{};
late CoreContext? context;
LinearAnimationInstance(animation,
{double speedMultiplier = 1.0, this.context})
: super(animation, speedMultiplier: speedMultiplier);
@override
void addRuntimeEventListener(OnRuntimeEvent callback) =>
_runtimeEventListeners.add(callback);
@override
void removeRuntimeEventListener(OnRuntimeEvent callback) =>
_runtimeEventListeners.remove(callback);
@override
void reportEvent(Event event) {
_runtimeEventListeners.toList().forEach((callback) {
callback(event);
});
}
@override
void reportKeyedCallback(
int objectId, int propertyKey, double elapsedSeconds) {
var coreObject = context?.resolve(objectId);
if (coreObject != null) {
RiveCoreContext.setCallback(
coreObject,
propertyKey,
CallbackData(this, delay: elapsedSeconds),
);
}
}
}

View File

@ -1,51 +0,0 @@
import 'package:flutter/widgets.dart';
import 'package:rive/src/controllers/simple_controller.dart';
/// This allows a value of type T or T?
/// to be treated as a value of type T?.
///
/// We use this so that APIs that have become
/// non-nullable can still be used with `!` and `?`
/// to support older versions of the API as well.
T? _ambiguate<T>(T? value) => value;
/// Controller tailored for managing one-shot animations
class OneShotAnimation extends SimpleAnimation {
/// Fires when the animation stops being active
final VoidCallback? onStop;
/// Fires when the animation starts being active
final VoidCallback? onStart;
OneShotAnimation(
String animationName, {
double mix = 1,
bool autoplay = true,
this.onStop,
this.onStart,
}) : super(animationName, mix: mix, autoplay: autoplay) {
isActiveChanged.addListener(onActiveChanged);
}
/// Dispose of any callback listeners
@override
void dispose() {
isActiveChanged.removeListener(onActiveChanged);
super.dispose();
}
/// Perform tasks when the animation's active state changes
void onActiveChanged() {
// If the animation stops and it is at the end of the one-shot, reset the
// animation back to the starting time
if (!isActive) {
reset();
}
// Fire any callbacks
isActive
? onStart?.call()
// onStop can fire while widgets are still drawing
: _ambiguate(WidgetsBinding.instance)
?.addPostFrameCallback((_) => onStop?.call());
}
}

View File

@ -1,63 +0,0 @@
import 'package:rive/src/extensions.dart';
import 'package:rive/src/rive_core/animation/linear_animation_instance.dart';
import 'package:rive/src/rive_core/rive_animation_controller.dart';
import 'package:rive/src/runtime_artboard.dart';
/// A simple [RiveAnimationController] that plays back a LinearAnimation defined
/// by an artist. All playback parameters (looping, speed, keyframes) are artist
/// defined in the Rive editor. This takes a declaritive approach of using an
/// [animationName] as the only requirement for resolving the animation. When
/// the controller is added to an artboard (note that due to widget lifecycles
/// it could get re-initialized on another artboard later) it'll look for the
/// animation. Not finding the animation is a condition this example deals with
/// by simply nulling the [AnimationInstance] _instance which means it won't be
/// applied during advance cycles. Another approach would be let this throw, but
/// this one is a little more forgiving which can be desireable with files
/// dynamically loaded (downloaded even) at runtime.
class SimpleAnimation extends RiveAnimationController<RuntimeArtboard> {
LinearAnimationInstance? _instance;
/// Animation name
final String animationName;
/// Pauses the animation when it's created
final bool autoplay;
/// Mix value for the animation, value between 0 and 1
double _mix;
// Controls the level of mix for the animation, clamped between 0 and 1
SimpleAnimation(this.animationName, {double mix = 1, this.autoplay = true})
: _mix = mix.clamp(0, 1).toDouble();
LinearAnimationInstance? get instance => _instance;
double get mix => _mix;
set mix(double value) => _mix = value.clamp(0, 1).toDouble();
@override
void apply(RuntimeArtboard artboard, double elapsedSeconds) {
if (_instance == null || !_instance!.keepGoing) {
isActive = false;
}
// We apply before advancing. So we want to stop rendering only once the
// last advanced frame has been applied. This means knowing when the last
// frame is advanced, ensuring the next apply happens, and then finally
// stopping playback. We do this with keepGoing as this will be true of a
// one-shot has passed its stop time. Fixes #28 and should help with issues
// #51 and #56.
_instance!
..animation.apply(_instance!.time, coreContext: artboard, mix: mix)
..advance(elapsedSeconds);
}
@override
bool init(RuntimeArtboard artboard) {
_instance = artboard.animationByName(animationName);
isActive = autoplay;
return _instance != null;
}
/// Resets the animation back to it's starting time position
void reset() => _instance?.reset();
}

View File

@ -1,231 +0,0 @@
import 'package:rive/src/core/core.dart';
import 'package:rive/src/rive_core/animation/state_machine.dart';
import 'package:rive/src/rive_core/animation/state_machine_bool.dart';
import 'package:rive/src/rive_core/animation/state_machine_input.dart' as core;
import 'package:rive/src/rive_core/animation/state_machine_number.dart';
import 'package:rive/src/rive_core/animation/state_machine_trigger.dart';
import 'package:rive/src/rive_core/artboard.dart';
import 'package:rive/src/rive_core/state_machine_controller.dart' as core;
import 'package:rive/src/runtime_mounted_artboard.dart';
export 'package:rive/src/runtime_mounted_artboard.dart';
/// [StateMachine]s supports three input types. The StateMachine mostly
/// abstracts types by allowing the programmer to query for an input of a
/// specific Dart backing type, mapping it to the correct StateMachine type.
/// This is the most flexible API to use to check if a type with a given name
/// exists. However, if you need to iterate inputs and query their types, this
/// enum is exposed for convenience.
enum SMIType { number, boolean, trigger }
/// SMI = StateMachineInstance
///
/// This is the abstraction of an instanced input
/// from the [StateMachine]. Whenever a [StateMachineController] is created, the
/// list of inputs in the corresponding [StateMachine] is wrapped into a set of
/// [SMIInput] objects that ensure inputs are initialized to design-time values.
/// The implementation can now change these values freely as they are decoupled
/// from the backing [StateMachine] and can safely be re-instanced by another
/// controller later.
abstract class SMIInput<T> {
final core.StateMachineInput _input;
final StateMachineController controller;
final SMIType type;
SMIInput._(this._input, this.type, this.controller);
@protected
void advance() {}
/// The id of the input within the context of the [StateMachine] it belongs
/// to.
int get id => _input.id;
/// The name given to this input at design time in Rive.
String get name => _input.name;
/// Convenience method for changing the backing [SMIInput.value] of the input.
/// For [SMITrigger] it's usually preferable to use the [SMITrigger.fire]
/// method to change the input value, but calling change(true) is totally
/// valid.
bool change(T value) {
if (controller.getInputValue(id) == value) {
return false;
}
controller.setInputValue(id, value);
controller.isActive = true;
return true;
}
T get value => controller.getInputValue(id) as T;
set value(T newValue) => change(newValue);
bool _is<K>() {
return K == T;
}
}
/// A boolean StateMachine input instance. Use the [value] property to change
/// the input which will automatically re-activate the [StateMachineController]
/// if necessary.
class SMIBool extends SMIInput<bool> {
SMIBool._(StateMachineBool input, StateMachineController controller)
: super._(
input,
SMIType.boolean,
controller,
) {
controller.setInputValue(id, input.value);
}
}
/// A numeric StateMachine input instance. Use the [value] property to change
/// the input which will automatically re-activate the [StateMachineController]
/// if necessary.
class SMINumber extends SMIInput<double> {
SMINumber._(StateMachineNumber input, StateMachineController controller)
: super._(
input,
SMIType.number,
controller,
) {
controller.setInputValue(id, input.value);
}
}
/// A trigger StateMachine input instance. Use the [fire] method to change the
/// input which will automatically re-activate the [StateMachineController] if
/// necessary.
class SMITrigger extends SMIInput<bool> {
SMITrigger._(StateMachineTrigger input, StateMachineController controller)
: super._(
input,
SMIType.trigger,
controller,
) {
controller.setInputValue(id, false);
}
void fire() => change(true);
@override
void advance() => change(false);
}
/// An AnimationController which controls a StateMachine and provides access to
/// the inputs of the StateMachine.
class StateMachineController extends core.StateMachineController
with RuntimeEventReporter {
final List<SMIInput> _inputs = <SMIInput>[];
/// A list of inputs available in the StateMachine.
Iterable<SMIInput> get inputs => _inputs;
final _runtimeEventListeners = <OnRuntimeEvent>{};
StateMachineController(
StateMachine stateMachine, {
core.OnStateChange? onStateChange,
// ignore: deprecated_member_use_from_same_package
}) : super(stateMachine, onStateChange: onStateChange) {
isActive = true;
for (final input in stateMachine.inputs) {
switch (input.coreType) {
case StateMachineNumberBase.typeKey:
_inputs.add(SMINumber._(input as StateMachineNumber, this));
break;
case StateMachineBoolBase.typeKey:
_inputs.add(SMIBool._(input as StateMachineBool, this));
break;
case StateMachineTriggerBase.typeKey:
_inputs.add(SMITrigger._(input as StateMachineTrigger, this));
break;
}
}
}
/// Instance a [StateMachineController] from an [artboard] with the given
/// [stateMachineName]. Returns the [StateMachineController] or null if no
/// [StateMachine] with [stateMachineName] is found.
static StateMachineController? fromArtboard(
Artboard artboard,
String stateMachineName, {
core.OnStateChange? onStateChange,
}) {
for (final animation in artboard.animations) {
if (animation is StateMachine && animation.name == stateMachineName) {
final controller =
StateMachineController(animation, onStateChange: onStateChange);
if (artboard is RuntimeArtboard) {
artboard.addNestedEventListener(controller);
}
return controller;
}
}
return null;
}
/// Find an input with a specific backing type and a given name.
///
/// For easier to use methods, see [getBoolInput], [getTriggerInput],
/// [getNumberInput].
SMIInput<T>? findInput<T>(String name) {
for (final input in _inputs) {
if (input._is<T>() && input.name == name) {
return input as SMIInput<T>;
}
}
return null;
}
/// Find an input of specific concrete input type, with a given name.
///
/// For easier to use methods, see [getBoolInput], [getTriggerInput],
/// [getNumberInput].
T? findSMI<T>(String name) {
for (final input in _inputs) {
if (input is T && input.name == name) {
return input as T;
}
}
return null;
}
/// Find a boolean input with a given name.
SMIBool? getBoolInput(String name) => findSMI<SMIBool>(name);
/// Find a trigger input with a given name.
SMITrigger? getTriggerInput(String name) => findSMI<SMITrigger>(name);
/// Find a number input with a given name.
///
/// See [triggerInput] to directly fire a trigger by its name.
SMINumber? getNumberInput(String name) => findSMI<SMINumber>(name);
/// Convenience method for firing a trigger input with a given name.
///
/// 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) => getTriggerInput(name)?.fire();
@override
void advanceInputs() {
for (final input in _inputs) {
input.advance();
}
}
@override
void addRuntimeEventListener(OnRuntimeEvent callback) =>
_runtimeEventListeners.add(callback);
@override
void removeRuntimeEventListener(OnRuntimeEvent callback) =>
_runtimeEventListeners.remove(callback);
@override
void applyEvents() {
var events = reportedEvents.toList(growable: false);
super.applyEvents();
_runtimeEventListeners.toList().forEach(events.forEach);
}
}

View File

@ -1,177 +0,0 @@
import 'dart:collection';
import 'package:flutter/foundation.dart';
import 'package:rive/src/rive_core/runtime/exceptions/rive_format_error_exception.dart';
export 'dart:typed_data';
export 'package:flutter/foundation.dart';
export 'package:rive/src/animation_list.dart';
export 'package:rive/src/asset_list.dart';
export 'package:rive/src/blend_animations.dart';
export 'package:rive/src/container_children.dart';
export 'package:rive/src/core/field_types/core_callback_type.dart';
export 'package:rive/src/core/importers/artboard_importer.dart';
export 'package:rive/src/core/importers/backboard_importer.dart';
export 'package:rive/src/core/importers/file_asset_importer.dart';
export 'package:rive/src/core/importers/keyed_object_importer.dart';
export 'package:rive/src/core/importers/keyed_property_importer.dart';
export 'package:rive/src/core/importers/layer_state_importer.dart';
export 'package:rive/src/core/importers/linear_animation_importer.dart';
export 'package:rive/src/core/importers/nested_state_machine_importer.dart';
export 'package:rive/src/core/importers/state_machine_importer.dart';
export 'package:rive/src/core/importers/state_machine_layer_component_importer.dart';
export 'package:rive/src/core/importers/state_machine_layer_importer.dart';
export 'package:rive/src/core/importers/state_machine_listener_importer.dart';
export 'package:rive/src/core/importers/state_transition_importer.dart';
export 'package:rive/src/data_enum_values.dart';
export 'package:rive/src/event_list.dart';
export 'package:rive/src/generated/rive_core_context.dart';
export 'package:rive/src/layer_component_events.dart';
export 'package:rive/src/listener_actions.dart';
export 'package:rive/src/runtime_artboard.dart';
export 'package:rive/src/state_machine_components.dart';
export 'package:rive/src/state_transition_conditions.dart';
export 'package:rive/src/state_transitions.dart';
export 'package:rive/src/viewmodel_list_items.dart';
export 'package:rive/src/viewmodel_properties.dart';
typedef PropertyChangeCallback = void Function(dynamic from, dynamic to);
typedef BatchAddCallback = void Function();
abstract class Core<T extends CoreContext> {
static const int missingId = -1;
covariant late T context;
int get coreType;
int id = missingId;
Set<int> get coreTypes => {};
bool _hasValidated = false;
bool get hasValidated => _hasValidated;
void onAddedDirty();
void onAdded() {}
void onRemoved() {}
void remove() => context.removeObject(this);
bool import(ImportStack stack) => true;
bool validate() => true;
/// Make a duplicate of this core object, N.B. that all properties excluding
/// object id are copied.
K? clone<K extends Core>() {
var object = context.makeCoreInstance(coreType);
object?.copy(this);
return object is K ? object : null;
}
/// Copies property values, currently doesn't trigger change callbacks. It's
/// meant to be a helper for [clone].
@protected
void copy(covariant Core source) {}
}
// ignore: avoid_classes_with_only_static_members
class InternalCoreHelper {
static void markValid(Core object) {
object._hasValidated = true;
}
}
abstract class CoreContext {
static const int invalidPropertyKey = 0;
Core? makeCoreInstance(int typeKey);
T? resolve<T>(int id);
T resolveWithDefault<T>(int id, T defaultValue);
void markDependencyOrderDirty();
bool markDependenciesDirty(covariant Core rootObject);
void removeObject<T extends Core>(T object);
T? addObject<T extends Core>(T? object);
void markNeedsAdvance();
void dirty(void Function() dirt);
}
// ignore: one_member_abstracts
abstract class ImportStackObject {
final _resolveBefore = <ImportStackObject>{};
bool _resolved = false;
bool initStack(ImportStack stack) {
var type = resolvesBefore;
if (type == -1) {
return true;
}
var importer = stack.latest<ImportStackObject>(type);
if (importer == null) {
return false;
}
importer._resolveBefore.add(this);
return true;
}
int get resolvesBefore => -1;
bool _internalResolve() {
if (_resolved) {
return true;
}
_resolved = true;
if (_resolveBefore.isNotEmpty) {
for (final before in _resolveBefore) {
if (!before._internalResolve()) {
return false;
}
}
}
return resolve();
}
bool resolve() => true;
}
/// Stack to help the RiveFile locate latest ImportStackObject created of a
/// certain type.
class ImportStack {
final _latests = HashMap<int, ImportStackObject>();
T? latest<T extends ImportStackObject>(int coreType) {
var latest = _latests[coreType];
if (latest is T) {
return latest;
}
return null;
}
T requireLatest<T extends ImportStackObject>(int coreType) {
var object = latest<T>(coreType);
if (object == null) {
throw RiveFormatErrorException(
'Rive file is corrupt. Couldn\'t find expected object of type '
'$coreType in import stack.');
}
return object;
}
bool makeLatest(int coreType, ImportStackObject? importObject) {
var latest = _latests[coreType];
if (latest != null) {
if (!latest._internalResolve()) {
return false;
}
}
if (importObject != null && importObject.initStack(this)) {
_latests[coreType] = importObject;
} else {
_latests.remove(coreType);
}
return true;
}
bool resolve() {
for (final object in _latests.values) {
if (!object._internalResolve()) {
return false;
}
}
return true;
}
}

View File

@ -1,7 +0,0 @@
import 'package:rive/src/core/field_types/core_field_type.dart';
import 'package:rive_common/utilities.dart';
class CoreBoolType extends CoreFieldType<bool> {
@override
bool deserialize(BinaryReader reader) => reader.readInt8() == 1;
}

View File

@ -1,12 +0,0 @@
import 'dart:typed_data';
import 'package:rive/src/core/field_types/core_field_type.dart';
import 'package:rive_common/utilities.dart';
class CoreBytesType extends CoreFieldType<Uint8List> {
@override
Uint8List deserialize(BinaryReader reader) {
var length = reader.readVarUint();
return reader.read(length);
}
}

View File

@ -1,16 +0,0 @@
import 'package:rive/src/core/field_types/core_field_type.dart';
import 'package:rive_common/utilities.dart';
class CallbackData {
final Object? context;
final double delay;
CallbackData(
this.context, {
required this.delay,
});
}
class CoreCallbackType extends CoreFieldType<int> {
@override
int deserialize(BinaryReader reader) => 0;
}

View File

@ -1,7 +0,0 @@
import 'package:rive/src/core/field_types/core_field_type.dart';
import 'package:rive_common/utilities.dart';
class CoreColorType extends CoreFieldType<int> {
@override
int deserialize(BinaryReader reader) => reader.readUint32();
}

View File

@ -1,7 +0,0 @@
import 'package:rive/src/core/field_types/core_field_type.dart';
import 'package:rive_common/utilities.dart';
class CoreDoubleType extends CoreFieldType<double> {
@override
double deserialize(BinaryReader reader) => reader.readFloat32();
}

View File

@ -1,7 +0,0 @@
import 'package:rive_common/utilities.dart';
// ignore: one_member_abstracts
abstract class CoreFieldType<T extends Object> {
T deserialize(BinaryReader reader);
void skip(BinaryReader reader) => deserialize(reader);
}

View File

@ -1,7 +0,0 @@
import 'package:rive/src/core/field_types/core_field_type.dart';
import 'package:rive_common/utilities.dart';
class CoreIntType extends CoreFieldType<int> {
@override
int deserialize(BinaryReader reader) => reader.readVarUint();
}

View File

@ -1,14 +0,0 @@
import 'package:rive/src/core/field_types/core_field_type.dart';
import 'package:rive_common/utilities.dart';
class CoreStringType extends CoreFieldType<String> {
@override
String deserialize(BinaryReader reader) =>
reader.readString(explicitLength: true);
@override
void skip(BinaryReader reader) {
var length = reader.readVarUint();
reader.read(length);
}
}

View File

@ -1,7 +0,0 @@
import 'package:rive/src/core/field_types/core_field_type.dart';
import 'package:rive_common/utilities.dart';
class CoreUintType extends CoreFieldType<int> {
@override
int deserialize(BinaryReader reader) => reader.readVarUint();
}

View File

@ -1,8 +0,0 @@
import 'package:rive/src/core/core.dart';
import 'package:rive/src/rive_core/artboard.dart';
/// An importer that will always resolve with the artboard importer.
abstract class ArtboardImportStackObject extends ImportStackObject {
@override
int get resolvesBefore => ArtboardBase.typeKey;
}

View File

@ -1,43 +0,0 @@
import 'package:rive/rive.dart';
import 'package:rive/src/core/core.dart';
import 'package:rive/src/rive_core/animation/animation.dart';
import 'package:rive/src/rive_core/component.dart';
class ArtboardImporter extends ImportStackObject {
final RuntimeArtboard artboard;
ArtboardImporter(this.artboard);
final List<LinearAnimation> animations = [];
void addComponent(Core<CoreContext>? object) => artboard.addObject(object);
void addAnimation(Animation animation) {
if (animation is LinearAnimation) {
animations.add(animation);
}
artboard.addObject(animation);
animation.artboard = artboard;
}
void addStateMachine(StateMachine animation) => addAnimation(animation);
@override
bool resolve() {
for (final object in artboard.objects.skip(1)) {
if (object is Component &&
object.parentId == ComponentBase.parentIdInitialValue) {
object.parent = artboard;
}
object?.onAddedDirty();
}
assert(!artboard.children.contains(artboard),
'artboard should never contain itself as a child');
for (final object in artboard.objects.toList(growable: false)) {
if (object == null) {
continue;
}
object.onAdded();
}
artboard.clean();
return true;
}
}

View File

@ -1,45 +0,0 @@
import 'dart:collection';
import 'package:rive/src/core/core.dart';
import 'package:rive/src/rive_core/artboard.dart';
import 'package:rive/src/rive_core/assets/file_asset.dart';
import 'package:rive/src/rive_core/backboard.dart';
import 'package:rive/src/rive_core/nested_artboard.dart';
import 'package:rive/src/runtime_nested_artboard.dart';
class BackboardImporter extends ImportStackObject {
final Backboard backboard;
final HashMap<int, Artboard> artboardLookup;
final Set<NestedArtboard> nestedArtboards = {};
final List<FileAsset> fileAssets = [];
final Set<FileAssetReferencer> fileAssetReferencers = {};
BackboardImporter(this.artboardLookup, this.backboard);
void addArtboard(Artboard object) {}
void addNestedArtboard(NestedArtboard nestedArtboard) =>
nestedArtboards.add(nestedArtboard);
void addFileAsset(FileAsset fileAsset) => fileAssets.add(fileAsset);
void addFileAssetReferencer(FileAssetReferencer referencer) =>
fileAssetReferencers.add(referencer);
@override
bool resolve() {
for (final nestedArtboard in nestedArtboards) {
var artboard = artboardLookup[nestedArtboard.artboardId];
if (artboard is RuntimeArtboard) {
(nestedArtboard as RuntimeNestedArtboard).sourceArtboard = artboard;
}
}
for (final referencer in fileAssetReferencers) {
if (referencer.assetId >= 0 && referencer.assetId < fileAssets.length) {
referencer.asset = fileAssets[referencer.assetId];
}
}
return super.resolve();
}
}

View File

@ -1,38 +0,0 @@
import 'package:rive/src/asset_loader.dart';
import 'package:rive/src/core/core.dart';
import 'package:rive/src/debug.dart';
import 'package:rive/src/rive_core/assets/file_asset.dart';
import 'package:rive/src/rive_core/assets/file_asset_contents.dart';
class FileAssetImporter extends ImportStackObject {
final FileAssetLoader? assetLoader;
final FileAsset fileAsset;
Uint8List? embeddedBytes;
FileAssetImporter(
this.fileAsset,
this.assetLoader,
);
void resolveContents(FileAssetContents contents) {
embeddedBytes = contents.bytes;
}
@override
bool resolve() {
// allow our loader to load the file asset.
assetLoader?.load(fileAsset, embeddedBytes).then((loaded) {
if (!loaded && embeddedBytes != null) {
fileAsset.decode(embeddedBytes!);
} else if (!loaded) {
// TODO: improve error logging
printDebugMessage(
'''Rive asset (${fileAsset.name}) was not able to load:
- Unique file name: ${fileAsset.uniqueFilename}
- Asset id: ${fileAsset.id}''',
);
}
});
return super.resolve();
}
}

View File

@ -1,14 +0,0 @@
import 'package:rive/src/core/importers/artboard_import_stack_object.dart';
import 'package:rive/src/rive_core/animation/keyed_object.dart';
import 'package:rive/src/rive_core/animation/keyed_property.dart';
class KeyedObjectImporter extends ArtboardImportStackObject {
final KeyedObject keyedObject;
KeyedObjectImporter(this.keyedObject);
void addKeyedProperty(KeyedProperty property) {
keyedObject.context.addObject(property);
keyedObject.internalAddKeyedProperty(property);
}
}

View File

@ -1,17 +0,0 @@
import 'package:rive/src/core/importers/artboard_import_stack_object.dart';
import 'package:rive/src/rive_core/animation/keyed_property.dart';
import 'package:rive/src/rive_core/animation/keyframe.dart';
import 'package:rive/src/rive_core/animation/linear_animation.dart';
class KeyedPropertyImporter extends ArtboardImportStackObject {
final KeyedProperty keyedProperty;
final LinearAnimation animation;
KeyedPropertyImporter(this.keyedProperty, this.animation);
void addKeyFrame(KeyFrame keyFrame) {
keyedProperty.context.addObject(keyFrame);
keyedProperty.internalAddKeyFrame(keyFrame);
keyFrame.computeSeconds(animation);
}
}

View File

@ -1,36 +0,0 @@
import 'package:rive/src/core/importers/artboard_import_stack_object.dart';
import 'package:rive/src/rive_core/animation/blend_animation.dart';
import 'package:rive/src/rive_core/animation/blend_state.dart';
import 'package:rive/src/rive_core/animation/blend_state_transition.dart';
import 'package:rive/src/rive_core/animation/layer_state.dart';
import 'package:rive/src/rive_core/animation/state_transition.dart';
class LayerStateImporter extends ArtboardImportStackObject {
final LayerState state;
LayerStateImporter(this.state);
void addTransition(StateTransition transition) {
state.context.addObject(transition);
state.internalAddTransition(transition);
}
bool addBlendAnimation(BlendAnimation blendAnimation) {
// This works because we explicitly export our transitions before our
// animations.
if (state is BlendState) {
var blendState = state as BlendState;
blendState.internalAddAnimation(blendAnimation);
for (final transition
in state.transitions.whereType<BlendStateTransition>()) {
if (transition.exitBlendAnimationId >= 0 &&
transition.exitBlendAnimationId < blendState.animations.length) {
transition.exitBlendAnimation =
blendState.animations[transition.exitBlendAnimationId];
}
}
return true;
}
return false;
}
}

View File

@ -1,13 +0,0 @@
import 'package:rive/rive.dart';
import 'package:rive/src/core/importers/artboard_import_stack_object.dart';
import 'package:rive/src/rive_core/animation/keyed_object.dart';
class LinearAnimationImporter extends ArtboardImportStackObject {
final LinearAnimation linearAnimation;
LinearAnimationImporter(this.linearAnimation);
void addKeyedObject(KeyedObject object) {
linearAnimation.context.addObject(object);
linearAnimation.internalAddKeyedObject(object);
}
}

View File

@ -1,21 +0,0 @@
import 'package:rive/src/core/importers/artboard_import_stack_object.dart';
import 'package:rive/src/rive_core/animation/nested_input.dart';
import 'package:rive/src/rive_core/animation/nested_state_machine.dart';
class NestedStateMachineImporter extends ArtboardImportStackObject {
final NestedStateMachine stateMachine;
NestedStateMachineImporter(this.stateMachine);
final List<NestedInput> _inputs = [];
void addNestedInput(NestedInput nestedInput) {
_inputs.add(nestedInput);
}
@override
bool resolve() {
for (final input in _inputs) {
input.parent = stateMachine;
}
return super.resolve();
}
}

View File

@ -1,13 +0,0 @@
import 'package:rive/src/core/importers/artboard_import_stack_object.dart';
import 'package:rive/src/rive_core/animation/state_machine.dart';
import 'package:rive/src/rive_core/animation/state_machine_component.dart';
class StateMachineImporter extends ArtboardImportStackObject {
final StateMachine machine;
StateMachineImporter(this.machine);
void addMachineComponent(StateMachineComponent object) {
machine.context.addObject(object);
object.stateMachine = machine;
}
}

View File

@ -1,12 +0,0 @@
import 'package:rive/src/core/importers/artboard_import_stack_object.dart';
import 'package:rive/src/rive_core/animation/state_machine_fire_event.dart';
import 'package:rive/src/rive_core/animation/state_machine_layer_component.dart';
class StateMachineLayerComponentImporter extends ArtboardImportStackObject {
final StateMachineLayerComponent component;
StateMachineLayerComponentImporter(this.component);
void addFireEvent(StateMachineFireEvent event) {
component.internalAddFireEvent(event);
}
}

View File

@ -1,41 +0,0 @@
import 'package:rive/rive.dart';
import 'package:rive/src/core/core.dart';
import 'package:rive/src/rive_core/animation/layer_state.dart';
import 'package:rive/src/rive_core/animation/state_machine_layer.dart';
class StateMachineLayerImporter extends ImportStackObject {
final StateMachineLayer layer;
StateMachineLayerImporter(this.layer);
final List<LayerState> importedStates = [];
void addState(LayerState state) {
importedStates.add(state);
// Here the state gets assigned a core (artboard) id.
layer.context.addObject(state);
layer.internalAddState(state);
}
@override
int get resolvesBefore => StateMachineBase.typeKey;
bool _resolved = false;
@override
bool resolve() {
assert(!_resolved);
_resolved = true;
for (final state in importedStates) {
for (final transition in state.transitions) {
// At import time the stateToId is an index relative to the entire layer
// (which state in this layer). We can use that to find the matching
// importedState and assign back the core id that will resolve after the
// entire artboard imports.
if (transition.stateToId >= 0 &&
transition.stateToId < importedStates.length) {
transition.stateTo = importedStates[transition.stateToId];
}
}
}
return true;
}
}

View File

@ -1,12 +0,0 @@
import 'package:rive/src/core/importers/artboard_import_stack_object.dart';
import 'package:rive/src/rive_core/animation/listener_action.dart';
import 'package:rive/src/rive_core/animation/state_machine_listener.dart';
class StateMachineListenerImporter extends ArtboardImportStackObject {
final StateMachineListener listener;
StateMachineListenerImporter(this.listener);
void addAction(ListenerAction change) {
listener.internalAddAction(change);
}
}

View File

@ -1,30 +0,0 @@
import 'package:rive/src/core/core.dart';
import 'package:rive/src/core/importers/artboard_import_stack_object.dart';
import 'package:rive/src/rive_core/animation/state_transition.dart';
import 'package:rive/src/rive_core/animation/transition_condition.dart';
import 'package:rive/src/rive_core/animation/transition_input_condition.dart';
class StateTransitionImporter extends ArtboardImportStackObject {
final StateMachineImporter stateMachineImporter;
final StateTransition transition;
StateTransitionImporter(this.transition, this.stateMachineImporter);
void addCondition(TransitionCondition condition) {
transition.context.addObject(condition);
transition.internalAddCondition(condition);
}
@override
bool resolve() {
var inputs = stateMachineImporter.machine.inputs;
for (final condition in transition.conditions) {
if (condition is TransitionInputCondition) {
var inputIndex = condition.inputId;
if (inputIndex >= 0 && inputIndex < inputs.length) {
condition.inputId = inputs[inputIndex].id;
}
}
}
return true;
}
}

View File

@ -1,12 +0,0 @@
import 'package:rive/src/core/core.dart';
import 'package:rive/src/rive_core/viewmodel/viewmodel_instance.dart';
import 'package:rive/src/rive_core/viewmodel/viewmodel_instance_value.dart';
class ViewModelInstanceImporter extends ImportStackObject {
final ViewModelInstance viewModelInstance;
ViewModelInstanceImporter(this.viewModelInstance);
void addValue(ViewModelInstanceValue value) {
viewModelInstance.addPropertyValue(value);
}
}

View File

@ -1,20 +0,0 @@
import 'dart:collection';
import 'package:rive/src/rive_core/viewmodel/data_enum_value.dart';
class DataEnumValues<T extends DataEnumValue> extends ListBase<T> {
final List<T?> _values = [];
List<T> get values => _values.cast<T>();
@override
int get length => _values.length;
@override
set length(int value) => _values.length = value;
@override
T operator [](int index) => _values[index]!;
@override
void operator []=(int index, T value) => _values[index] = value;
}

View File

@ -1,9 +0,0 @@
import 'package:flutter/foundation.dart';
/// Print a message only when running in debug.
void printDebugMessage(String message) {
assert(() {
debugPrint(message);
return true;
}());
}

Some files were not shown because too many files have changed in this diff Show More