Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions kotlin/src/main/cpp/src/bindings/bindings_command_queue.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1698,6 +1698,29 @@ extern "C"
artboardHandle);
}

JNIEXPORT void JNICALL
Java_app_rive_core_CommandQueueJNIBridge_cppSetViewModelInstanceProperty(
JNIEnv* env,
jobject,
jlong ref,
jlong jViewModelInstanceHandle,
jstring jPropertyPath,
jlong jValueHandle)
{
auto commandQueue = reinterpret_cast<rive::CommandQueue*>(ref);
auto viewModelInstanceHandle =
handleFromLong<rive::ViewModelInstanceHandle>(
jViewModelInstanceHandle);
auto propertyPath = JStringToString(env, jPropertyPath);
auto valueHandle =
handleFromLong<rive::ViewModelInstanceHandle>(jValueHandle);

commandQueue->setViewModelInstanceNestedViewModel(
viewModelInstanceHandle,
propertyPath,
valueHandle);
}

JNIEXPORT void JNICALL
Java_app_rive_core_CommandQueueJNIBridge_cppGetListSize(
JNIEnv* env,
Expand Down
17 changes: 17 additions & 0 deletions kotlin/src/main/kotlin/app/rive/ViewModelInstance.kt
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,23 @@ class ViewModelInstance internal constructor(
setProperty(propertyPath, artboard.artboardHandle, riveWorker::setArtboardProperty)
}

/**
* Replaces a nested view model instance property with another instance.
*
* After replacement, both the original and the replacement refer to the same underlying
* instance — changing one changes the other.
*
* ℹ️ Changes to bound Rive elements will not be reflected until the next state machine advance.
*
* @param propertyPath The path to the view model property from this view model instance. Slash
* delimited to refer to nested properties.
* @param instance The view model instance to assign to the property.
*/
fun setViewModelInstance(propertyPath: String, instance: ViewModelInstance) {
RiveLog.d(VM_INSTANCE_TAG) { "Assigning $instance to $propertyPath (${fileHandle})" }
setProperty(propertyPath, instance.instanceHandle, riveWorker::setViewModelInstanceProperty)
}

suspend fun getListSize(propertyPath: String): Int =
riveWorker.getListSize(instanceHandle, propertyPath)

Expand Down
21 changes: 21 additions & 0 deletions kotlin/src/main/kotlin/app/rive/core/CommandQueue.kt
Original file line number Diff line number Diff line change
Expand Up @@ -1540,6 +1540,27 @@ class CommandQueue(
artboardHandle.handle
)

/**
* Assign a view model instance to a nested view model property on the view model instance.
*
* @param viewModelInstanceHandle The handle of the view model instance that the property
* belongs to.
* @param propertyPath The path to the property that should be assigned to. Slash delimited.
* @param valueHandle The handle of the view model instance to assign.
* @throws IllegalStateException If the CommandQueue has been released.
*/
@Throws(IllegalStateException::class)
fun setViewModelInstanceProperty(
viewModelInstanceHandle: ViewModelInstanceHandle,
propertyPath: String,
valueHandle: ViewModelInstanceHandle
) = bridge.cppSetViewModelInstanceProperty(
cppPointer.pointer,
viewModelInstanceHandle.handle,
propertyPath,
valueHandle.handle
)

/**
* Gets the size of a list property on the view model instance.
*
Expand Down
14 changes: 14 additions & 0 deletions kotlin/src/main/kotlin/app/rive/core/CommandQueueBridge.kt
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,13 @@ interface CommandQueueBridge {
artboardHandle: Long
)

fun cppSetViewModelInstanceProperty(
pointer: Long,
viewModelInstanceHandle: Long,
propertyPath: String,
valueHandle: Long
)

fun cppGetListSize(
pointer: Long,
requestID: Long,
Expand Down Expand Up @@ -715,6 +722,13 @@ internal class CommandQueueJNIBridge : CommandQueueBridge {
artboardHandle: Long
)

external override fun cppSetViewModelInstanceProperty(
pointer: Long,
viewModelInstanceHandle: Long,
propertyPath: String,
valueHandle: Long
)

external override fun cppGetListSize(
pointer: Long,
requestID: Long,
Expand Down
42 changes: 42 additions & 0 deletions kotlin/src/test/kotlin/app/rive/CommandQueueUnitTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import app.rive.core.DefaultViewModelInfo
import app.rive.core.FileHandle
import app.rive.core.Listeners
import app.rive.core.RenderContext
import app.rive.core.ViewModelInstanceHandle
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.comparables.shouldBeLessThan
Expand All @@ -29,6 +30,7 @@ const val COMMAND_QUEUE_ADDR = 1L
const val RENDER_CONTEXT_ADDR = 2L
const val HANDLE_NUM = 123L
const val ARTBOARD_HANDLE_NUM = 456L
const val VALUE_HANDLE_NUM = 789L
val FILE_BYTES = byteArrayOf(0, 1, 2)

@OptIn(ExperimentalCoroutinesApi::class, ExperimentalStdlibApi::class)
Expand Down Expand Up @@ -228,6 +230,46 @@ class CommandQueueUnitTest : FunSpec({
}
}

test("Set view model instance property invokes native") {
val commandQueue = CommandQueue(renderContextMock, commandQueueBridgeMock)
val instanceHandle = ViewModelInstanceHandle(HANDLE_NUM)
val valueHandle = ViewModelInstanceHandle(VALUE_HANDLE_NUM)
val propertyPath = "nested/path"

every {
commandQueueBridgeMock.cppSetViewModelInstanceProperty(
COMMAND_QUEUE_ADDR,
HANDLE_NUM,
propertyPath,
VALUE_HANDLE_NUM
)
} just runs

commandQueue.setViewModelInstanceProperty(instanceHandle, propertyPath, valueHandle)

verify(exactly = 1) {
commandQueueBridgeMock.cppSetViewModelInstanceProperty(
COMMAND_QUEUE_ADDR,
HANDLE_NUM,
propertyPath,
VALUE_HANDLE_NUM
)
}
}

test("Set view model instance property throws when released") {
val commandQueue = CommandQueue(renderContextMock, commandQueueBridgeMock)
commandQueue.release("")

shouldThrow<IllegalStateException> {
commandQueue.setViewModelInstanceProperty(
ViewModelInstanceHandle(HANDLE_NUM),
"path",
ViewModelInstanceHandle(VALUE_HANDLE_NUM)
)
}
}

test("Delete file invokes native") {
val commandQueue = CommandQueue(renderContextMock, commandQueueBridgeMock)
val requestID = slot<Long>()
Expand Down