-
Notifications
You must be signed in to change notification settings - Fork 2
feat: add data binding artboards support #95
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+2,098
−255
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
a94b560
chore: update copyright year in generated files
mfazekas 12972a3
feat: add data binding artboards support
mfazekas 84f3605
chore: disable ktlint no-empty-first-line-in-class-body rule
mfazekas 020861c
feat: add dispose() to HybridBindableArtboard
mfazekas 49e931c
fix: make bindableArtboard internal for cross-class access
mfazekas 8d25eb1
fix: improve dispose handling in HybridBindableArtboard
mfazekas ef29979
refactor: remove listener API from artboard property
mfazekas fc7cca9
chore: update copyright year in generated files
mfazekas File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
23 changes: 23 additions & 0 deletions
23
android/src/main/java/com/margelo/nitro/rive/HybridBindableArtboard.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| package com.margelo.nitro.rive | ||
|
|
||
| import androidx.annotation.Keep | ||
| import app.rive.runtime.kotlin.core.BindableArtboard | ||
| import com.facebook.proguard.annotations.DoNotStrip | ||
|
|
||
| @Keep | ||
| @DoNotStrip | ||
| class HybridBindableArtboard(internal var bindableArtboard: BindableArtboard?) : HybridBindableArtboardSpec() { | ||
|
|
||
| override val artboardName: String | ||
| get() = bindableArtboard?.name | ||
| ?: throw IllegalStateException("BindableArtboard has been disposed") | ||
|
|
||
| override fun dispose() { | ||
| bindableArtboard?.release() | ||
| bindableArtboard = null | ||
| } | ||
|
|
||
| protected fun finalize() { | ||
| dispose() | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
16 changes: 16 additions & 0 deletions
16
android/src/main/java/com/margelo/nitro/rive/HybridViewModelArtboardProperty.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| package com.margelo.nitro.rive | ||
|
|
||
| import androidx.annotation.Keep | ||
| import app.rive.runtime.kotlin.core.ViewModelArtboardProperty | ||
| import com.facebook.proguard.annotations.DoNotStrip | ||
|
|
||
| @Keep | ||
| @DoNotStrip | ||
| class HybridViewModelArtboardProperty(private val property: ViewModelArtboardProperty) : | ||
| HybridViewModelArtboardPropertySpec() { | ||
|
|
||
| override fun set(artboard: HybridBindableArtboardSpec?) { | ||
| val bindable = (artboard as? HybridBindableArtboard)?.bindableArtboard | ||
| property.set(bindable) | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file not shown.
Binary file not shown.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,280 @@ | ||
| import { | ||
| View, | ||
| Text, | ||
| StyleSheet, | ||
| ActivityIndicator, | ||
| Pressable, | ||
| } from 'react-native'; | ||
| import { useState, useMemo, useEffect, useRef } from 'react'; | ||
| import { | ||
| Fit, | ||
| RiveView, | ||
| useRiveFile, | ||
| type RiveFile, | ||
| type BindableArtboard, | ||
| } from '@rive-app/react-native'; | ||
| import { type Metadata } from '../helpers/metadata'; | ||
|
|
||
| /** | ||
| * Data Binding Artboards Example | ||
| * | ||
| * Demonstrates swapping artboards at runtime using data binding. | ||
| * Based on: https://rive.app/docs/runtimes/data-binding#artboards | ||
| * | ||
| * The main Rive file includes a view model with a property of type `Artboard` | ||
| * called "CharacterArtboard". This property can be set to any artboard from | ||
| * either the main file or an external file. | ||
| * | ||
| * Rive source files: | ||
| * - Main: https://rive.app/marketplace/24641-46042-data-binding-artboards/ | ||
| * - Assets: https://rive.app/marketplace/24642-47536-data-binding-artboards/ | ||
| */ | ||
|
|
||
| export default function DataBindingArtboardsExample() { | ||
| // Main scene file - contains the Card view model with CharacterArtboard property | ||
| const { | ||
| riveFile: mainFile, | ||
| isLoading: mainLoading, | ||
| error: mainError, | ||
| } = useRiveFile(require('../../assets/swap_character_main.riv')); | ||
|
|
||
| // Assets file - contains "Character 1" and "Character 2" artboards | ||
| const { | ||
| riveFile: assetsFile, | ||
| isLoading: assetsLoading, | ||
| error: assetsError, | ||
| } = useRiveFile(require('../../assets/swap_character_assets.riv')); | ||
|
|
||
| const isLoading = mainLoading || assetsLoading; | ||
| const error = mainError || assetsError; | ||
|
|
||
| if (isLoading) { | ||
| return ( | ||
| <View style={styles.container}> | ||
| <ActivityIndicator size="large" color="#0000ff" /> | ||
| <Text style={styles.loadingText}>Loading Rive files...</Text> | ||
| </View> | ||
| ); | ||
| } | ||
|
|
||
| if (error || !mainFile || !assetsFile) { | ||
| return ( | ||
| <View style={styles.container}> | ||
| <Text style={styles.errorText}> | ||
| {error || 'Failed to load Rive files'} | ||
| </Text> | ||
| </View> | ||
| ); | ||
| } | ||
|
|
||
| return <ArtboardSwapper mainFile={mainFile} assetsFile={assetsFile} />; | ||
| } | ||
|
|
||
| function ArtboardSwapper({ | ||
| mainFile, | ||
| assetsFile, | ||
| }: { | ||
| mainFile: RiveFile; | ||
| assetsFile: RiveFile; | ||
| }) { | ||
| // Get the view model from the "Main" artboard and create an instance | ||
| // IMPORTANT: Must memoize to prevent creating new instance on every render | ||
| const viewModel = useMemo( | ||
| () => mainFile.defaultArtboardViewModel(), | ||
| [mainFile] | ||
| ); | ||
| const instance = useMemo( | ||
| () => viewModel?.createDefaultInstance(), | ||
| [viewModel] | ||
| ); | ||
| const [currentArtboard, setCurrentArtboard] = useState<string>('Dragon'); | ||
| const initializedRef = useRef(false); | ||
|
|
||
| // Set initial artboard on mount | ||
| useEffect(() => { | ||
| if (initializedRef.current || !instance) return; | ||
| initializedRef.current = true; | ||
|
|
||
| const artboardProp = instance.artboardProperty('CharacterArtboard'); | ||
| if (artboardProp) { | ||
| try { | ||
| const bindable = assetsFile.getBindableArtboard('Character 1'); | ||
| artboardProp.set(bindable); | ||
| } catch (e) { | ||
| console.error(`Failed to set initial artboard: ${e}`); | ||
| } | ||
| } | ||
| }, [instance, assetsFile]); | ||
|
|
||
| // Map display names to actual artboard names | ||
| const artboardOptions = [ | ||
| { label: 'Dragon', artboard: 'Character 1', fromAssets: true }, | ||
| { label: 'Gator', artboard: 'Character 2', fromAssets: true }, | ||
| { label: 'Placeholder', artboard: 'Placeholder', fromAssets: false }, | ||
| ]; | ||
|
|
||
| const swapArtboard = (option: (typeof artboardOptions)[number]) => { | ||
| if (!instance) return; | ||
|
|
||
| const artboardProp = instance.artboardProperty('CharacterArtboard'); | ||
| if (!artboardProp) { | ||
| console.error('Artboard property "CharacterArtboard" not found'); | ||
| return; | ||
| } | ||
|
|
||
| try { | ||
| const sourceFile = option.fromAssets ? assetsFile : mainFile; | ||
| const bindable: BindableArtboard = sourceFile.getBindableArtboard( | ||
| option.artboard | ||
| ); | ||
| artboardProp.set(bindable); | ||
| setCurrentArtboard(option.label); | ||
| } catch (e) { | ||
| console.error(`Failed to swap artboard: ${e}`); | ||
| } | ||
| }; | ||
|
|
||
| if (!instance || !viewModel) { | ||
| return ( | ||
| <View style={styles.container}> | ||
| <Text style={styles.errorText}> | ||
| {!viewModel | ||
| ? 'No view model found in main file' | ||
| : 'Failed to create instance'} | ||
| </Text> | ||
| </View> | ||
| ); | ||
| } | ||
|
|
||
| return ( | ||
| <View style={styles.container}> | ||
| <Text style={styles.title}>Data Binding Artboards</Text> | ||
| <Text style={styles.subtitle}> | ||
| Swap artboards at runtime from different files | ||
| </Text> | ||
|
|
||
| <View style={styles.riveContainer}> | ||
| <RiveView | ||
| style={styles.rive} | ||
| autoPlay={true} | ||
| dataBind={instance} | ||
| fit={Fit.Layout} | ||
| layoutScaleFactor={1.0} | ||
| file={mainFile} | ||
| artboardName="Main" | ||
| stateMachineName="State Machine 1" | ||
| /> | ||
| </View> | ||
|
|
||
| <View style={styles.infoContainer}> | ||
| <Text style={styles.infoText}>Current: {currentArtboard}</Text> | ||
| </View> | ||
|
|
||
| <View style={styles.buttonContainer}> | ||
| {artboardOptions.map((option) => ( | ||
| <Pressable | ||
| key={option.label} | ||
| style={[ | ||
| styles.button, | ||
| !option.fromAssets && styles.secondaryButton, | ||
| currentArtboard === option.label && styles.buttonActive, | ||
| ]} | ||
| onPress={() => swapArtboard(option)} | ||
| > | ||
| <Text | ||
| style={[ | ||
| styles.buttonText, | ||
| currentArtboard === option.label && styles.buttonTextActive, | ||
| ]} | ||
| > | ||
| {option.label} | ||
| {option.fromAssets ? ' (external)' : ' (internal)'} | ||
| </Text> | ||
| </Pressable> | ||
| ))} | ||
| </View> | ||
| </View> | ||
| ); | ||
| } | ||
|
|
||
| DataBindingArtboardsExample.metadata = { | ||
| name: 'Data Binding Artboards', | ||
| description: 'Swap artboards at runtime using data binding properties', | ||
| } satisfies Metadata; | ||
|
|
||
| const styles = StyleSheet.create({ | ||
| container: { | ||
| flex: 1, | ||
| backgroundColor: '#fff', | ||
| padding: 16, | ||
| }, | ||
| title: { | ||
| fontSize: 20, | ||
| fontWeight: 'bold', | ||
| textAlign: 'center', | ||
| marginBottom: 4, | ||
| }, | ||
| subtitle: { | ||
| fontSize: 14, | ||
| color: '#666', | ||
| textAlign: 'center', | ||
| marginBottom: 16, | ||
| }, | ||
| riveContainer: { | ||
| flex: 1, | ||
| backgroundColor: '#f5f5f5', | ||
| borderRadius: 8, | ||
| overflow: 'hidden', | ||
| }, | ||
| rive: { | ||
| flex: 1, | ||
| }, | ||
| infoContainer: { | ||
| marginVertical: 12, | ||
| padding: 12, | ||
| backgroundColor: '#f0f0f0', | ||
| borderRadius: 8, | ||
| }, | ||
| infoText: { | ||
| fontSize: 14, | ||
| color: '#333', | ||
| fontWeight: '600', | ||
| }, | ||
| buttonContainer: { | ||
| flexDirection: 'row', | ||
| flexWrap: 'wrap', | ||
| gap: 8, | ||
| }, | ||
| button: { | ||
| paddingHorizontal: 16, | ||
| paddingVertical: 10, | ||
| backgroundColor: '#007AFF', | ||
| borderRadius: 8, | ||
| }, | ||
| secondaryButton: { | ||
| backgroundColor: '#5856D6', | ||
| }, | ||
| buttonActive: { | ||
| backgroundColor: '#34C759', | ||
| }, | ||
| buttonText: { | ||
| fontSize: 14, | ||
| fontWeight: '600', | ||
| color: '#fff', | ||
| }, | ||
| buttonTextActive: { | ||
| color: '#fff', | ||
| }, | ||
| loadingText: { | ||
| marginTop: 12, | ||
| textAlign: 'center', | ||
| color: '#666', | ||
| }, | ||
| errorText: { | ||
| color: 'red', | ||
| textAlign: 'center', | ||
| fontSize: 16, | ||
| fontWeight: 'bold', | ||
| marginBottom: 16, | ||
| }, | ||
| }); | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Really good explanation. We should also update the other examples to link to the marketplace!