chore(runtime): expose data bind runtime methods (#13018) eb0c54ee20
* chore(runtime): add artboardName to ViewModelInstanceArtboardRuntime

* feat(runtime): add runtime wrapper for symbol list index

Co-authored-by: hernan <hernan@rive.app>
diff --git a/.rive_head b/.rive_head
index 0d35efd..d460901 100644
--- a/.rive_head
+++ b/.rive_head
@@ -1 +1 @@
-c4ea1784715990dd1711c8cab3f79d6b59710581
+eb0c54ee20880b2529a7c86253a32991cacb7b9a
diff --git a/include/rive/viewmodel/runtime/viewmodel_instance_artboard_runtime.hpp b/include/rive/viewmodel/runtime/viewmodel_instance_artboard_runtime.hpp
index 57bb901..5182515 100644
--- a/include/rive/viewmodel/runtime/viewmodel_instance_artboard_runtime.hpp
+++ b/include/rive/viewmodel/runtime/viewmodel_instance_artboard_runtime.hpp
@@ -21,6 +21,7 @@
     void value(rcp<BindableArtboard> bindableArtboard);
     void viewModelInstance(rcp<ViewModelInstance> viewModelInstance);
     const DataType dataType() override { return DataType::artboard; }
+    std::string artboardName() const;
 
 #ifdef TESTING
     rcp<BindableArtboard> testing_value();
diff --git a/include/rive/viewmodel/runtime/viewmodel_instance_list_index_runtime.hpp b/include/rive/viewmodel/runtime/viewmodel_instance_list_index_runtime.hpp
new file mode 100644
index 0000000..f764627
--- /dev/null
+++ b/include/rive/viewmodel/runtime/viewmodel_instance_list_index_runtime.hpp
@@ -0,0 +1,24 @@
+#ifndef _RIVE_VIEW_MODEL_INSTANCE_LIST_INDEX_RUNTIME_HPP_
+#define _RIVE_VIEW_MODEL_INSTANCE_LIST_INDEX_RUNTIME_HPP_
+
+#include <string>
+#include <stdint.h>
+#include "rive/viewmodel/runtime/viewmodel_instance_value_runtime.hpp"
+#include "rive/viewmodel/viewmodel_instance_symbol_list_index.hpp"
+
+namespace rive
+{
+
+class ViewModelInstanceListIndexRuntime : public ViewModelInstanceValueRuntime
+{
+
+public:
+    ViewModelInstanceListIndexRuntime(
+        ViewModelInstanceSymbolListIndex* viewModelInstance) :
+        ViewModelInstanceValueRuntime(viewModelInstance)
+    {}
+    uint32_t value() const;
+    const DataType dataType() override { return DataType::symbolListIndex; }
+};
+} // namespace rive
+#endif
diff --git a/include/rive/viewmodel/runtime/viewmodel_instance_runtime.hpp b/include/rive/viewmodel/runtime/viewmodel_instance_runtime.hpp
index 209d78e..65ced02 100644
--- a/include/rive/viewmodel/runtime/viewmodel_instance_runtime.hpp
+++ b/include/rive/viewmodel/runtime/viewmodel_instance_runtime.hpp
@@ -16,6 +16,7 @@
 #include "rive/viewmodel/runtime/viewmodel_instance_list_runtime.hpp"
 #include "rive/viewmodel/runtime/viewmodel_instance_asset_image_runtime.hpp"
 #include "rive/viewmodel/runtime/viewmodel_instance_artboard_runtime.hpp"
+#include "rive/viewmodel/runtime/viewmodel_instance_list_index_runtime.hpp"
 #include "rive/refcnt.hpp"
 
 namespace rive
@@ -44,6 +45,8 @@
     ViewModelInstanceTriggerRuntime* propertyTrigger(
         const std::string& path) const;
     ViewModelInstanceListRuntime* propertyList(const std::string& path) const;
+    ViewModelInstanceListIndexRuntime* propertyListIndex(
+        const std::string& path) const;
     rcp<ViewModelInstanceRuntime> propertyViewModel(
         const std::string& path) const;
     ViewModelInstanceAssetImageRuntime* propertyImage(
diff --git a/src/viewmodel/runtime/viewmodel_instance_artboard_runtime.cpp b/src/viewmodel/runtime/viewmodel_instance_artboard_runtime.cpp
index 8061a19..a797cd2 100644
--- a/src/viewmodel/runtime/viewmodel_instance_artboard_runtime.cpp
+++ b/src/viewmodel/runtime/viewmodel_instance_artboard_runtime.cpp
@@ -23,6 +23,17 @@
         ->boundViewModelInstance(viewModelInstance);
 }
 
+std::string ViewModelInstanceArtboardRuntime::artboardName() const
+{
+    auto bindableArtboard =
+        m_viewModelInstanceValue->as<ViewModelInstanceArtboard>()->asset();
+    if (bindableArtboard == nullptr || bindableArtboard->artboard() == nullptr)
+    {
+        return "";
+    }
+    return bindableArtboard->artboard()->name();
+}
+
 #ifdef TESTING
 rcp<BindableArtboard> ViewModelInstanceArtboardRuntime::testing_value()
 {
diff --git a/src/viewmodel/runtime/viewmodel_instance_list_index_runtime.cpp b/src/viewmodel/runtime/viewmodel_instance_list_index_runtime.cpp
new file mode 100644
index 0000000..911d96b
--- /dev/null
+++ b/src/viewmodel/runtime/viewmodel_instance_list_index_runtime.cpp
@@ -0,0 +1,11 @@
+
+#include "rive/viewmodel/runtime/viewmodel_instance_list_index_runtime.hpp"
+
+// Default namespace for Rive Cpp code
+using namespace rive;
+
+uint32_t ViewModelInstanceListIndexRuntime::value() const
+{
+    return m_viewModelInstanceValue->as<ViewModelInstanceSymbolListIndex>()
+        ->propertyValue();
+}
diff --git a/src/viewmodel/runtime/viewmodel_instance_runtime.cpp b/src/viewmodel/runtime/viewmodel_instance_runtime.cpp
index 55e23e8..62c01d6 100644
--- a/src/viewmodel/runtime/viewmodel_instance_runtime.cpp
+++ b/src/viewmodel/runtime/viewmodel_instance_runtime.cpp
@@ -10,6 +10,8 @@
 #include "rive/viewmodel/runtime/viewmodel_instance_enum_runtime.hpp"
 #include "rive/viewmodel/runtime/viewmodel_instance_trigger_runtime.hpp"
 #include "rive/viewmodel/runtime/viewmodel_instance_list_runtime.hpp"
+#include "rive/viewmodel/runtime/viewmodel_instance_list_index_runtime.hpp"
+#include "rive/viewmodel/viewmodel_instance_symbol_list_index.hpp"
 #include "rive/viewmodel/viewmodel_property_string.hpp"
 #include "rive/viewmodel/viewmodel_property_number.hpp"
 #include "rive/viewmodel/viewmodel_property_boolean.hpp"
@@ -127,6 +129,9 @@
                     case DataType::trigger:
                         return viewModelInstanceRuntime->propertyTrigger(
                             propertyName);
+                    case DataType::symbolListIndex:
+                        return viewModelInstanceRuntime->propertyListIndex(
+                            propertyName);
                     default:
                         break;
                 }
@@ -269,6 +274,22 @@
     return nullptr;
 }
 
+ViewModelInstanceListIndexRuntime* ViewModelInstanceRuntime::propertyListIndex(
+    const std::string& path) const
+{
+    const auto propertyName = getPropertyNameFromPath(path);
+    auto viewModelInstance = viewModelInstanceFromFullPath(path);
+    if (viewModelInstance != nullptr)
+    {
+
+        return viewModelInstance
+            ->getPropertyInstance<ViewModelInstanceSymbolListIndex,
+                                  ViewModelInstanceListIndexRuntime>(
+                propertyName);
+    }
+    return nullptr;
+}
+
 rcp<ViewModelInstance> ViewModelInstanceRuntime::viewModelInstanceProperty(
     const std::string& name) const
 {
diff --git a/tests/unit_tests/runtime/data_binding_artboards_test.cpp b/tests/unit_tests/runtime/data_binding_artboards_test.cpp
index 0a0ca82..395ffbf 100644
--- a/tests/unit_tests/runtime/data_binding_artboards_test.cpp
+++ b/tests/unit_tests/runtime/data_binding_artboards_test.cpp
@@ -235,6 +235,41 @@
     CHECK(vmiArtboard->boundViewModelInstance() == nullptr);
 }
 
+TEST_CASE("Runtime artboard property exposes the bound artboard name",
+          "[data binding]")
+{
+    auto file = ReadRiveFile("assets/data_binding_artboards_test.riv");
+    auto artboard = file->artboardDefault();
+    REQUIRE(artboard != nullptr);
+
+    int viewModelId = artboard.get()->viewModelId();
+    auto vmi = viewModelId == -1
+                   ? file->createViewModelInstance(artboard.get())
+                   : file->createViewModelInstance(viewModelId, 0);
+    REQUIRE(vmi != nullptr);
+
+    auto runtimeVmi = make_rcp<ViewModelInstanceRuntime>(vmi);
+    auto runtimeArtboard = runtimeVmi->propertyArtboard("ab");
+    REQUIRE(runtimeArtboard != nullptr);
+
+    auto sourceA = file->bindableArtboardNamed("ch1");
+    auto sourceB = file->bindableArtboardNamed("ch2");
+    REQUIRE(sourceA != nullptr);
+    REQUIRE(sourceB != nullptr);
+
+    // The name reflects the currently bound bindable artboard.
+    runtimeArtboard->value(sourceA);
+    CHECK(runtimeArtboard->artboardName() == "ch1");
+
+    // Rebinding updates the reported name.
+    runtimeArtboard->value(sourceB);
+    CHECK(runtimeArtboard->artboardName() == "ch2");
+
+    // Clearing the source reports an empty name rather than dangling.
+    runtimeArtboard->value(nullptr);
+    CHECK(runtimeArtboard->artboardName().empty());
+}
+
 TEST_CASE("Test default data binding artboard from different source",
           "[data binding]")
 {
diff --git a/tests/unit_tests/runtime/viewmodel_instance_list_index_runtime_test.cpp b/tests/unit_tests/runtime/viewmodel_instance_list_index_runtime_test.cpp
new file mode 100644
index 0000000..9c3afdb
--- /dev/null
+++ b/tests/unit_tests/runtime/viewmodel_instance_list_index_runtime_test.cpp
@@ -0,0 +1,56 @@
+#include <rive/viewmodel/viewmodel_instance_symbol_list_index.hpp>
+#include <rive/viewmodel/runtime/viewmodel_instance_list_index_runtime.hpp>
+#include <rive/data_bind/data_values/data_type.hpp>
+#include <rive/refcnt.hpp>
+#include <catch.hpp>
+
+using namespace rive;
+
+// The runtime wrapper reports the data type of the underlying symbol list index
+// and reads through to its value. It is a read-only view (no setter).
+TEST_CASE("list index runtime reports type and reads value", "[data binding]")
+{
+    // Own the initial reference here; the runtime takes its own ref on
+    // construction and releases it on destruction. Reverse-order destruction
+    // (runtime before `index`) then drops the refcount to zero without leaking.
+    auto index = make_rcp<ViewModelInstanceSymbolListIndex>();
+    index->propertyValue(3);
+
+    ViewModelInstanceListIndexRuntime runtime(index.get());
+
+    CHECK(runtime.dataType() == DataType::symbolListIndex);
+    CHECK(runtime.value() == 3);
+
+    index->propertyValue(7);
+    CHECK(runtime.value() == 7);
+}
+
+// Changing the underlying value dirties the wrapper so
+// hasChanged()/flushChanges report it, matching the behavior of the other value
+// runtimes.
+TEST_CASE("list index runtime reports value changes", "[data binding]")
+{
+    auto index = make_rcp<ViewModelInstanceSymbolListIndex>();
+    ViewModelInstanceListIndexRuntime runtime(index.get());
+
+    // No change yet.
+    CHECK_FALSE(runtime.hasChanged());
+    CHECK_FALSE(runtime.flushChanges());
+
+    // A new value marks the wrapper dirty.
+    index->propertyValue(1);
+    CHECK(runtime.hasChanged());
+
+    // flushChanges consumes the change and clears the flag.
+    CHECK(runtime.flushChanges());
+    CHECK_FALSE(runtime.hasChanged());
+    CHECK_FALSE(runtime.flushChanges());
+
+    // Setting the same value is a no-op and does not report a change.
+    index->propertyValue(1);
+    CHECK_FALSE(runtime.hasChanged());
+
+    // A different value reports again.
+    index->propertyValue(2);
+    CHECK(runtime.hasChanged());
+}