mirror of
https://github.com/rive-app/rive-flutter.git
synced 2025-07-15 06:30:53 +08:00
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)   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:  And one when hovered over the run:  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  after:  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):  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:
@ -1 +1 @@
|
||||
f99c5665ceb9de1780e91483c082b190d49839f6
|
||||
b21adb45646d34fd7ea1850e5802ce63afc937aa
|
||||
|
40
CHANGELOG.md
40
CHANGELOG.md
@ -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
104
README.md
@ -7,9 +7,9 @@
|
||||
|
||||

|
||||
|
||||
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)
|
||||
|
@ -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
BIN
example/assets/button.riv
Normal file
Binary file not shown.
BIN
example/assets/databinding_images.riv
Normal file
BIN
example/assets/databinding_images.riv
Normal file
Binary file not shown.
BIN
example/assets/electrified_button_nested_text.riv
Normal file
BIN
example/assets/electrified_button_nested_text.riv
Normal file
Binary file not shown.
BIN
example/assets/fonts/JetBrains Mono.ttf
Normal file
BIN
example/assets/fonts/JetBrains Mono.ttf
Normal file
Binary file not shown.
BIN
example/assets/images/databound_image_1.jpg
Normal file
BIN
example/assets/images/databound_image_1.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
BIN
example/assets/images/databound_image_2.jpg
Normal file
BIN
example/assets/images/databound_image_2.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
BIN
example/assets/layout_test.riv
Normal file
BIN
example/assets/layout_test.riv
Normal file
Binary file not shown.
BIN
example/assets/rewards.riv
Normal file
BIN
example/assets/rewards.riv
Normal file
Binary file not shown.
@ -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;
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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 [],
|
||||
});
|
||||
}
|
@ -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),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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],
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
59
example/lib/examples/animation_painter.dart
Normal file
59
example/lib/examples/animation_painter.dart
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
105
example/lib/examples/databinding.dart
Normal file
105
example/lib/examples/databinding.dart
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
106
example/lib/examples/databinding_images.dart
Normal file
106
example/lib/examples/databinding_images.dart
Normal 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'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
81
example/lib/examples/events.dart
Normal file
81
example/lib/examples/events.dart
Normal 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),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
17
example/lib/examples/examples.dart
Normal file
17
example/lib/examples/examples.dart
Normal 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';
|
168
example/lib/examples/hit_test_behaviour.dart
Normal file
168
example/lib/examples/hit_test_behaviour.dart
Normal 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'),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
93
example/lib/examples/inputs.dart
Normal file
93
example/lib/examples/inputs.dart
Normal 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,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
41
example/lib/examples/network_asset.dart
Normal file
41
example/lib/examples/network_asset.dart
Normal 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)
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
232
example/lib/examples/out_of_band_assets.dart
Normal file
232
example/lib/examples/out_of_band_assets.dart
Normal 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),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -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'),
|
47
example/lib/examples/responsive_layouts.dart
Normal file
47
example/lib/examples/responsive_layouts.dart
Normal 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
|
||||
)
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
46
example/lib/examples/rive_audio.dart
Normal file
46
example/lib/examples/rive_audio.dart
Normal 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)
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
56
example/lib/examples/rive_widget.dart
Normal file
56
example/lib/examples/rive_widget.dart
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
59
example/lib/examples/rive_widget_builder.dart
Normal file
59
example/lib/examples/rive_widget_builder.dart
Normal 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,
|
||||
)
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
67
example/lib/examples/state_machine_painter.dart
Normal file
67
example/lib/examples/state_machine_painter.dart
Normal 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,
|
||||
);
|
||||
}
|
||||
}
|
88
example/lib/examples/text_runs.dart
Normal file
88
example/lib/examples/text_runs.dart
Normal 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!),
|
||||
);
|
||||
}
|
||||
}
|
67
example/lib/examples/ticker_mode.dart
Normal file
67
example/lib/examples/ticker_mode.dart
Normal 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'),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
43
example/lib/examples/time_dilation.dart
Normal file
43
example/lib/examples/time_dilation.dart
Normal 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)
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
12
example/lib/examples/todo.dart
Normal file
12
example/lib/examples/todo.dart
Normal 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'),
|
||||
);
|
||||
}
|
||||
}
|
@ -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!,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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'],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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()),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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'],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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)
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
28
example/macos/Podfile.lock
Normal file
28
example/macos/Podfile.lock
Normal 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
|
@ -59,6 +59,7 @@
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
enableGPUValidationMode = "1"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
|
@ -6,4 +6,8 @@ class AppDelegate: FlutterAppDelegate {
|
||||
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@ -8,5 +8,7 @@
|
||||
<true/>
|
||||
<key>com.apple.security.network.server</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
@ -4,5 +4,7 @@
|
||||
<dict>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
@ -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/
|
||||
|
@ -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';
|
@ -1 +0,0 @@
|
||||
export 'package:rive_common/math.dart';
|
@ -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';
|
||||
|
@ -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) {}
|
||||
}
|
@ -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;
|
||||
}
|
@ -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,
|
||||
}
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
@ -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),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
@ -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();
|
||||
}
|
@ -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();
|
||||
}
|
@ -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);
|
||||
}
|
@ -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();
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
@ -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
Reference in New Issue
Block a user