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()); +}