feature: add support for relative view model data bind paths for nested artboards (#11344) 923b32059d feature: add support for relative view model data bind paths Co-authored-by: hernan <hernan@rive.app>
diff --git a/.rive_head b/.rive_head index a74c1ee..c5efcd5 100644 --- a/.rive_head +++ b/.rive_head
@@ -1 +1 @@ -699b891b79a7af1220616e0900a3d4d5d1c40448 +923b32059d2ea012fdb47ae141b304f57095a48b
diff --git a/dev/defs/data_bind/data_bind_path.json b/dev/defs/data_bind/data_bind_path.json new file mode 100644 index 0000000..52fab4b --- /dev/null +++ b/dev/defs/data_bind/data_bind_path.json
@@ -0,0 +1,29 @@ +{ + "name": "DataBindPath", + "key": { + "int": 643, + "string": "databindpath" + }, + "properties": { + "path": { + "type": "List<Id>", + "typeRuntime": "Bytes", + "encoded": true, + "initialValue": "[]", + "key": { + "int": 920, + "string": "path" + }, + "description": "Path to the selected property" + }, + "isRelative": { + "type": "bool", + "initialValue": "false", + "key": { + "int": 921, + "string": "isrelative" + }, + "description": "Whether the data bind path is relative" + } + } +} \ No newline at end of file
diff --git a/dev/defs/nested_artboard.json b/dev/defs/nested_artboard.json index 11dc6eb..4910497 100644 --- a/dev/defs/nested_artboard.json +++ b/dev/defs/nested_artboard.json
@@ -29,6 +29,16 @@ }, "description": "Path to the selected property." }, + "isDataBindPathRelative": { + "type": "bool", + "initialValue": "false", + "key": { + "int": 916, + "string": "isdatabindpathrelative" + }, + "description": "Whether the data bind path is relative", + "runtime": false + }, "isPaused": { "type": "bool", "initialValue": "false",
diff --git a/include/rive/artboard_host.hpp b/include/rive/artboard_host.hpp index 93cb158..32657a6 100644 --- a/include/rive/artboard_host.hpp +++ b/include/rive/artboard_host.hpp
@@ -2,6 +2,7 @@ #define _RIVE_ARTBOARD_HOST_HPP_ #include "rive/refcnt.hpp" #include "rive/file.hpp" +#include "rive/data_bind_path_referencer.hpp" #include <stdio.h> namespace rive { @@ -10,12 +11,11 @@ class DataContext; class ViewModelInstance; -class ArtboardHost +class ArtboardHost : public DataBindPathReferencer { public: virtual size_t artboardCount() = 0; virtual ArtboardInstance* artboardInstance(int index = 0) = 0; - virtual std::vector<uint32_t> dataBindPathIds() { return {}; } virtual void internalDataContext(DataContext* dataContext) = 0; virtual void bindViewModelInstance(rcp<ViewModelInstance> viewModelInstance, DataContext* parent) = 0;
diff --git a/include/rive/assets/manifest_asset.hpp b/include/rive/assets/manifest_asset.hpp index 59c4802..2489dc2 100644 --- a/include/rive/assets/manifest_asset.hpp +++ b/include/rive/assets/manifest_asset.hpp
@@ -1,17 +1,21 @@ #ifndef _RIVE_MANIFEST_ASSET_HPP_ #define _RIVE_MANIFEST_ASSET_HPP_ #include "rive/generated/assets/manifest_asset_base.hpp" -#include "rive/name_resolver.hpp" +#include "rive/data_resolver.hpp" #include <stdio.h> #include <unordered_map> #include <string> namespace rive { -class ManifestAsset : public ManifestAssetBase, public NameResolver +class ManifestAsset : public ManifestAssetBase, public DataResolver { private: std::unordered_map<int, std::string> m_names; + std::unordered_map<int, std::vector<uint32_t>> m_paths; static const std::string empty; + static const std::vector<uint32_t> emptyIntVector; + bool decodeNames(BinaryReader& reader); + bool decodePaths(BinaryReader& reader); protected: bool addsToBackboard() override { return false; } @@ -20,6 +24,7 @@ bool decode(SimpleArray<uint8_t>&, Factory*) override; std::string fileExtension() const override; const std::string& resolveName(int id) override; + const std::vector<uint32_t>& resolvePath(int id) override; }; } // namespace rive
diff --git a/include/rive/data_bind/data_bind_context.hpp b/include/rive/data_bind/data_bind_context.hpp index 45b069e..818f8e7 100644 --- a/include/rive/data_bind/data_bind_context.hpp +++ b/include/rive/data_bind/data_bind_context.hpp
@@ -17,6 +17,10 @@ void decodeSourcePathIds(Span<const uint8_t> value) override; void copySourcePathIds(const DataBindContextBase& object) override; void bindFromContext(DataContext* dataContext); + +private: + void resolvePath(); + bool m_isPathResolved = false; }; } // namespace rive
diff --git a/include/rive/data_bind/data_bind_path.hpp b/include/rive/data_bind/data_bind_path.hpp new file mode 100644 index 0000000..589d291 --- /dev/null +++ b/include/rive/data_bind/data_bind_path.hpp
@@ -0,0 +1,29 @@ +#ifndef _RIVE_DATA_BIND_PATH_HPP_ +#define _RIVE_DATA_BIND_PATH_HPP_ +#include "rive/generated/data_bind/data_bind_path_base.hpp" +#include <stdio.h> +namespace rive +{ +class File; +class DataBindPath : public DataBindPathBase +{ +public: + void decodePath(Span<const uint8_t> value) override; + void copyPath(const DataBindPathBase& object) override; + StatusCode import(ImportStack& importStack) override; + std::vector<uint32_t>& path() { return m_pathBuffer; } + const std::vector<uint32_t>& resolvedPath(); + void file(File* value); + File* file() { return m_file; }; + void resolved(bool value) { m_resolved = value; } + +protected: + std::vector<uint32_t> m_pathBuffer; + +private: + File* m_file = nullptr; + bool m_resolved = false; +}; +} // namespace rive + +#endif \ No newline at end of file
diff --git a/include/rive/data_bind/data_context.hpp b/include/rive/data_bind/data_context.hpp index 9919eeb..12b7a1e 100644 --- a/include/rive/data_bind/data_context.hpp +++ b/include/rive/data_bind/data_context.hpp
@@ -2,11 +2,12 @@ #define _RIVE_DATA_CONTEXT_HPP_ #include "rive/viewmodel/viewmodel_instance_value.hpp" #include "rive/viewmodel/viewmodel_instance.hpp" -#include "rive/name_resolver.hpp" +#include "rive/data_resolver.hpp" #include "rive/refcnt.hpp" namespace rive { +class DataBindPath; class DataContext { private: @@ -22,9 +23,13 @@ const std::vector<uint32_t> path) const; ViewModelInstanceValue* getRelativeViewModelProperty( const std::vector<uint32_t> path, - NameResolver* resolver) const; + DataResolver* resolver) const; rcp<ViewModelInstance> getViewModelInstance( const std::vector<uint32_t> path) const; + rcp<ViewModelInstance> getViewModelInstance(DataBindPath*) const; + rcp<ViewModelInstance> getRelativeViewModelInstance( + const std::vector<uint32_t> path, + DataResolver* resolver) const; void viewModelInstance(rcp<ViewModelInstance> value); void advanced(); rcp<ViewModelInstance> viewModelInstance() { return m_ViewModelInstance; };
diff --git a/include/rive/data_bind_path_referencer.hpp b/include/rive/data_bind_path_referencer.hpp new file mode 100644 index 0000000..429a61f --- /dev/null +++ b/include/rive/data_bind_path_referencer.hpp
@@ -0,0 +1,23 @@ +#ifndef _RIVE_DATA_BIND_PATH_REFERENCER_HPP_ +#define _RIVE_DATA_BIND_PATH_REFERENCER_HPP_ +#include <stdio.h> +#include "rive/data_bind/data_bind_path.hpp" +namespace rive +{ +class File; +class DataBindPathReferencer +{ +public: + ~DataBindPathReferencer(); + DataBindPath* dataBindPath() const { return m_dataBindPath; } + + void copyDataBindPath(DataBindPath* dataBindPath); + void importDataBindPath(ImportStack& importStack); + void decodeDataBindPath(Span<const uint8_t>& value); + +protected: + DataBindPath* m_dataBindPath = nullptr; +}; +} // namespace rive + +#endif \ No newline at end of file
diff --git a/include/rive/data_resolver.hpp b/include/rive/data_resolver.hpp new file mode 100644 index 0000000..75d4764 --- /dev/null +++ b/include/rive/data_resolver.hpp
@@ -0,0 +1,15 @@ +#ifndef _RIVE_DATA_RESOLVER_HPP_ +#define _RIVE_DATA_RESOLVER_HPP_ +#include <stdio.h> +#include <string> +namespace rive +{ +class DataResolver +{ +public: + virtual const std::string& resolveName(int id) = 0; + virtual const std::vector<uint32_t>& resolvePath(int id) = 0; +}; +} // namespace rive + +#endif \ No newline at end of file
diff --git a/include/rive/file.hpp b/include/rive/file.hpp index 4ecfb79..93215f8 100644 --- a/include/rive/file.hpp +++ b/include/rive/file.hpp
@@ -16,7 +16,7 @@ #include "rive/animation/keyframe_interpolator.hpp" #include "rive/data_bind/converters/data_converter.hpp" #include "rive/refcnt.hpp" -#include "rive/name_resolver.hpp" +#include "rive/data_resolver.hpp" #include <vector> #include <set> #include <unordered_map> @@ -194,7 +194,7 @@ } #endif - NameResolver* nameResolver() + DataResolver* dataResolver() { if (m_manifest) {
diff --git a/include/rive/generated/core_registry.hpp b/include/rive/generated/core_registry.hpp index aa07807..78acbdb 100644 --- a/include/rive/generated/core_registry.hpp +++ b/include/rive/generated/core_registry.hpp
@@ -181,6 +181,7 @@ #include "rive/data_bind/converters/formula/formula_token_value.hpp" #include "rive/data_bind/data_bind.hpp" #include "rive/data_bind/data_bind_context.hpp" +#include "rive/data_bind/data_bind_path.hpp" #include "rive/draw_rules.hpp" #include "rive/draw_target.hpp" #include "rive/drawable.hpp" @@ -666,6 +667,8 @@ return new ScriptInputString(); case BindablePropertyArtboardBase::typeKey: return new BindablePropertyArtboard(); + case DataBindPathBase::typeKey: + return new DataBindPath(); case BindablePropertyIntegerBase::typeKey: return new BindablePropertyInteger(); case BindablePropertyTriggerBase::typeKey: @@ -1740,6 +1743,9 @@ case LayoutComponentBase::clipPropertyKey: object->as<LayoutComponentBase>()->clip(value); break; + case DataBindPathBase::isRelativePropertyKey: + object->as<DataBindPathBase>()->isRelative(value); + break; case BindablePropertyBooleanBase::propertyValuePropertyKey: object->as<BindablePropertyBooleanBase>()->propertyValue(value); break; @@ -3114,6 +3120,8 @@ return object->as<CustomPropertyBooleanBase>()->propertyValue(); case LayoutComponentBase::clipPropertyKey: return object->as<LayoutComponentBase>()->clip(); + case DataBindPathBase::isRelativePropertyKey: + return object->as<DataBindPathBase>()->isRelative(); case BindablePropertyBooleanBase::propertyValuePropertyKey: return object->as<BindablePropertyBooleanBase>() ->propertyValue(); @@ -3880,6 +3888,7 @@ case ClippingShapeBase::isVisiblePropertyKey: case CustomPropertyBooleanBase::propertyValuePropertyKey: case LayoutComponentBase::clipPropertyKey: + case DataBindPathBase::isRelativePropertyKey: case BindablePropertyBooleanBase::propertyValuePropertyKey: case TextModifierRangeBase::clampPropertyKey: case TextFollowPathModifierBase::radialPropertyKey: @@ -4112,6 +4121,7 @@ case StateMachineFireTriggerBase::viewModelPathIdsPropertyKey: case StateMachineListenerBase::viewModelPathIdsPropertyKey: case MeshBase::triangleIndexBytesPropertyKey: + case DataBindPathBase::pathPropertyKey: case DataConverterOperationViewModelBase::sourcePathIdsPropertyKey: case DataBindContextBase::sourcePathIdsPropertyKey: case FileAssetBase::cdnUuidPropertyKey: @@ -4700,6 +4710,8 @@ return object->is<CustomPropertyBooleanBase>(); case LayoutComponentBase::clipPropertyKey: return object->is<LayoutComponentBase>(); + case DataBindPathBase::isRelativePropertyKey: + return object->is<DataBindPathBase>(); case BindablePropertyBooleanBase::propertyValuePropertyKey: return object->is<BindablePropertyBooleanBase>(); case TextModifierRangeBase::clampPropertyKey:
diff --git a/include/rive/generated/data_bind/data_bind_path_base.hpp b/include/rive/generated/data_bind/data_bind_path_base.hpp new file mode 100644 index 0000000..03627df --- /dev/null +++ b/include/rive/generated/data_bind/data_bind_path_base.hpp
@@ -0,0 +1,80 @@ +#ifndef _RIVE_DATA_BIND_PATH_BASE_HPP_ +#define _RIVE_DATA_BIND_PATH_BASE_HPP_ +#include "rive/core.hpp" +#include "rive/core/field_types/core_bool_type.hpp" +#include "rive/core/field_types/core_bytes_type.hpp" +#include "rive/span.hpp" +namespace rive +{ +class DataBindPathBase : public Core +{ +protected: + typedef Core Super; + +public: + static const uint16_t typeKey = 643; + + /// Helper to quickly determine if a core object extends another without + /// RTTI at runtime. + bool isTypeOf(uint16_t typeKey) const override + { + switch (typeKey) + { + case DataBindPathBase::typeKey: + return true; + default: + return false; + } + } + + uint16_t coreType() const override { return typeKey; } + + static const uint16_t pathPropertyKey = 920; + static const uint16_t isRelativePropertyKey = 921; + +protected: + bool m_IsRelative = false; + +public: + virtual void decodePath(Span<const uint8_t> value) = 0; + virtual void copyPath(const DataBindPathBase& object) = 0; + + inline bool isRelative() const { return m_IsRelative; } + void isRelative(bool value) + { + if (m_IsRelative == value) + { + return; + } + m_IsRelative = value; + isRelativeChanged(); + } + + Core* clone() const override; + void copy(const DataBindPathBase& object) + { + copyPath(object); + m_IsRelative = object.m_IsRelative; + } + + bool deserialize(uint16_t propertyKey, BinaryReader& reader) override + { + switch (propertyKey) + { + case pathPropertyKey: + decodePath(CoreBytesType::deserialize(reader)); + return true; + case isRelativePropertyKey: + m_IsRelative = CoreBoolType::deserialize(reader); + return true; + } + return false; + } + +protected: + virtual void pathChanged() {} + virtual void isRelativeChanged() {} +}; +} // namespace rive + +#endif \ No newline at end of file
diff --git a/include/rive/importers/data_bind_path_importer.hpp b/include/rive/importers/data_bind_path_importer.hpp new file mode 100644 index 0000000..9902e20 --- /dev/null +++ b/include/rive/importers/data_bind_path_importer.hpp
@@ -0,0 +1,20 @@ +#ifndef _RIVE_DATA_BIND_PATH_IMPORTER_HPP_ +#define _RIVE_DATA_BIND_PATH_IMPORTER_HPP_ + +#include "rive/importers/import_stack.hpp" + +namespace rive +{ +class Core; +class DataBindPath; +class DataBindPathImporter : public ImportStackObject +{ +private: + DataBindPath* m_dataBindPath; + +public: + DataBindPathImporter(DataBindPath* object); + DataBindPath* claim(); +}; +} // namespace rive +#endif
diff --git a/include/rive/manifest_sections.hpp b/include/rive/manifest_sections.hpp index fb73b57..3160681 100644 --- a/include/rive/manifest_sections.hpp +++ b/include/rive/manifest_sections.hpp
@@ -6,6 +6,7 @@ enum class ManifestSections : unsigned char { names = 0, + paths = 1, }; } #endif
diff --git a/include/rive/name_resolver.hpp b/include/rive/name_resolver.hpp deleted file mode 100644 index 24ea0cf..0000000 --- a/include/rive/name_resolver.hpp +++ /dev/null
@@ -1,14 +0,0 @@ -#ifndef _RIVE_NAME_RESOLVER_HPP_ -#define _RIVE_NAME_RESOLVER_HPP_ -#include <stdio.h> -#include <string> -namespace rive -{ -class NameResolver -{ -public: - virtual const std::string& resolveName(int id) = 0; -}; -} // namespace rive - -#endif \ No newline at end of file
diff --git a/include/rive/nested_artboard.hpp b/include/rive/nested_artboard.hpp index 828b58c..715ada7 100644 --- a/include/rive/nested_artboard.hpp +++ b/include/rive/nested_artboard.hpp
@@ -3,6 +3,7 @@ #include "rive/generated/nested_artboard_base.hpp" #include "rive/artboard_host.hpp" +#include "rive/data_bind_path_referencer.hpp" #include "rive/data_bind/data_context.hpp" #include "rive/viewmodel/viewmodel_instance_value.hpp" #include "rive/hit_info.hpp" @@ -38,8 +39,6 @@ DataContext* m_dataContext = nullptr; protected: - std::vector<uint32_t> m_DataBindPathIdsBuffer; - private: Artboard* findArtboard( ViewModelInstanceArtboard* viewModelInstanceArtboard); @@ -92,10 +91,6 @@ bool worldToLocal(Vec2D world, Vec2D* local); void decodeDataBindPathIds(Span<const uint8_t> value) override; void copyDataBindPathIds(const NestedArtboardBase& object) override; - std::vector<uint32_t> dataBindPathIds() override - { - return m_DataBindPathIdsBuffer; - }; void bindViewModelInstance(rcp<ViewModelInstance> viewModelInstance, DataContext* parent) override; void internalDataContext(DataContext* dataContext) override;
diff --git a/src/artboard.cpp b/src/artboard.cpp index e1b6962..7af0b97 100644 --- a/src/artboard.cpp +++ b/src/artboard.cpp
@@ -1799,8 +1799,8 @@ m_DataContext = value; for (auto artboardHost : m_ArtboardHosts) { - auto value = m_DataContext->getViewModelInstance( - artboardHost->dataBindPathIds()); + auto value = + m_DataContext->getViewModelInstance(artboardHost->dataBindPath()); if (value != nullptr && value->is<ViewModelInstance>()) { artboardHost->bindViewModelInstance(value, m_DataContext);
diff --git a/src/assets/manifest_asset.cpp b/src/assets/manifest_asset.cpp index a99fc7c..4df0220 100644 --- a/src/assets/manifest_asset.cpp +++ b/src/assets/manifest_asset.cpp
@@ -7,6 +7,73 @@ using namespace rive; const std::string ManifestAsset::empty; +const std::vector<uint32_t> ManifestAsset::emptyIntVector; + +bool ManifestAsset::decodeNames(BinaryReader& reader) +{ + // Read count of names + uint64_t count = reader.readVarUint64(); + if (reader.hasError()) + { + return false; + } + + // Read all name entries + for (uint64_t i = 0; i < count; i++) + { + int id = static_cast<int>(reader.readVarUint64()); + if (reader.hasError()) + { + return false; + } + + std::string value = reader.readString(); + if (reader.hasError()) + { + return false; + } + + m_names[id] = value; + } + return true; +} + +bool ManifestAsset::decodePaths(BinaryReader& reader) +{ + // Read count of paths + uint64_t count = reader.readVarUint64(); + if (reader.hasError()) + { + return false; + } + + // Read all path entries + for (uint64_t i = 0; i < count; i++) + { + int id = static_cast<int>(reader.readVarUint64()); + if (reader.hasError()) + { + return false; + } + int pathLength = static_cast<int>(reader.readVarUint64()); + if (reader.hasError()) + { + return false; + } + std::vector<uint32_t> path; + for (uint64_t j = 0; j < pathLength; j++) + { + int pathId = static_cast<uint32_t>(reader.readVarUint64()); + path.push_back(pathId); + } + m_paths[id] = path; + if (reader.hasError()) + { + return false; + } + } + return true; +} bool ManifestAsset::decode(SimpleArray<uint8_t>& bytes, Factory* factory) { @@ -39,29 +106,17 @@ if (static_cast<ManifestSections>(sectionValue) == ManifestSections::names) { - // Read count of names - uint64_t count = reader.readVarUint64(); - if (reader.hasError()) + if (!decodeNames(reader)) { return false; } - - // Read all name entries - for (uint64_t i = 0; i < count; i++) + } + else if (static_cast<ManifestSections>(sectionValue) == + ManifestSections::paths) + { + if (!decodePaths(reader)) { - int id = static_cast<int>(reader.readVarUint64()); - if (reader.hasError()) - { - return false; - } - - std::string value = reader.readString(); - if (reader.hasError()) - { - return false; - } - - m_names[id] = value; + return false; } } else @@ -96,4 +151,14 @@ return it->second; } return empty; +} + +const std::vector<uint32_t>& ManifestAsset::resolvePath(int id) +{ + auto it = m_paths.find(id); + if (it != m_paths.end()) + { + return it->second; + } + return emptyIntVector; } \ No newline at end of file
diff --git a/src/data_bind/data_bind_context.cpp b/src/data_bind/data_bind_context.cpp index 63737c2..2c8540f 100644 --- a/src/data_bind/data_bind_context.cpp +++ b/src/data_bind/data_bind_context.cpp
@@ -25,17 +25,40 @@ void DataBindContext::copySourcePathIds(const DataBindContextBase& object) { m_SourcePathIdsBuffer = object.as<DataBindContext>()->m_SourcePathIdsBuffer; + m_isPathResolved = object.as<DataBindContext>()->m_isPathResolved; +} + +void DataBindContext::resolvePath() +{ + if (!isNameBased() || m_isPathResolved) + { + return; + } + m_isPathResolved = true; + if (m_SourcePathIdsBuffer.size() > 0) + { + auto pathId = m_SourcePathIdsBuffer[0]; + if (m_file) + { + auto dataResolver = m_file->dataResolver(); + if (dataResolver) + { + m_SourcePathIdsBuffer = dataResolver->resolvePath(pathId); + } + } + } } void DataBindContext::bindFromContext(DataContext* dataContext) { if (dataContext != nullptr) { + resolvePath(); auto vmSource = isNameBased() && file() ? dataContext->getRelativeViewModelProperty( m_SourcePathIdsBuffer, - file()->nameResolver()) + file()->dataResolver()) : dataContext->getViewModelProperty(m_SourcePathIdsBuffer); if (vmSource != m_Source) {
diff --git a/src/data_bind/data_bind_path.cpp b/src/data_bind/data_bind_path.cpp new file mode 100644 index 0000000..0241b80 --- /dev/null +++ b/src/data_bind/data_bind_path.cpp
@@ -0,0 +1,55 @@ +#include "rive/data_bind/data_bind_path.hpp" +#include "rive/importers/backboard_importer.hpp" + +using namespace rive; + +void DataBindPath::decodePath(Span<const uint8_t> value) +{ + BinaryReader reader(value); + while (!reader.reachedEnd()) + { + auto val = reader.readVarUintAs<uint32_t>(); + m_pathBuffer.push_back(val); + } +} + +void DataBindPath::copyPath(const DataBindPathBase& object) +{ + m_pathBuffer = object.as<DataBindPath>()->m_pathBuffer; + m_resolved = object.as<DataBindPath>()->m_resolved; +} + +StatusCode DataBindPath::import(ImportStack& importStack) +{ + auto backboardImporter = + importStack.latest<BackboardImporter>(Backboard::typeKey); + if (backboardImporter == nullptr) + { + return StatusCode::MissingObject; + } + auto file = backboardImporter->file(); + m_file = file; + + return Super::import(importStack); +} + +const std::vector<uint32_t>& DataBindPath::resolvedPath() +{ + if (!m_resolved) + { + if (!m_file) + { + return m_pathBuffer; + } + auto dataResolver = m_file->dataResolver(); + if (dataResolver) + { + auto pathId = m_pathBuffer[0]; + m_pathBuffer = dataResolver->resolvePath(pathId); + } + m_resolved = true; + } + return m_pathBuffer; +} + +void DataBindPath::file(File* file) { m_file = file; } \ No newline at end of file
diff --git a/src/data_bind/data_context.cpp b/src/data_bind/data_context.cpp index f7dc797..7299ad4 100644 --- a/src/data_bind/data_context.cpp +++ b/src/data_bind/data_context.cpp
@@ -1,4 +1,8 @@ #include "rive/data_bind/data_context.hpp" +#include "rive/data_bind/data_bind_path.hpp" +#include "rive/file.hpp" +#include "rive/viewmodel/viewmodel_instance.hpp" +#include "rive/viewmodel/viewmodel.hpp" #include "rive/viewmodel/viewmodel_instance_viewmodel.hpp" using namespace rive; @@ -54,7 +58,7 @@ ViewModelInstanceValue* DataContext::getRelativeViewModelProperty( const std::vector<uint32_t> path, - NameResolver* resolver) const + DataResolver* resolver) const { std::vector<uint32_t>::const_iterator it; if (path.size() == 0 || !resolver) @@ -97,11 +101,11 @@ rcp<ViewModelInstance> DataContext::getViewModelInstance( const std::vector<uint32_t> path) const { - std::vector<uint32_t>::const_iterator it; if (path.size() == 0) { return nullptr; } + std::vector<uint32_t>::const_iterator it; if (m_ViewModelInstance != nullptr && m_ViewModelInstance->viewModelId() == path[0]) { @@ -133,4 +137,74 @@ return m_Parent->getViewModelInstance(path); } return nullptr; +} + +rcp<ViewModelInstance> DataContext::getRelativeViewModelInstance( + const std::vector<uint32_t> path, + DataResolver* resolver) const +{ + if (path.size() == 0 || !resolver) + { + return nullptr; + } + std::vector<uint32_t>::const_iterator it; + if (m_ViewModelInstance != nullptr) + { + rcp<ViewModelInstance> instance = m_ViewModelInstance; + for (it = path.begin(); it != path.end(); it++) + { + auto viewModelInstanceValue = + instance->propertyValue(resolver->resolveName(*it)); + if (viewModelInstanceValue != nullptr && + viewModelInstanceValue->is<ViewModelInstanceViewModel>()) + { + instance = + viewModelInstanceValue->as<ViewModelInstanceViewModel>() + ->referenceViewModelInstance(); + } + else + { + instance = nullptr; + } + if (instance == nullptr) + { + goto skip_path_relative; + } + } + return instance; + } +skip_path_relative: + if (m_Parent != nullptr) + { + return m_Parent->getRelativeViewModelInstance(path, resolver); + } + return nullptr; +} + +rcp<ViewModelInstance> DataContext::getViewModelInstance( + DataBindPath* dataBindPath) const +{ + if (!dataBindPath) + { + return nullptr; + } + if (dataBindPath->isRelative()) + { + auto file = dataBindPath->file(); + if (file) + { + auto resolver = file->dataResolver(); + if (resolver) + { + return getRelativeViewModelInstance( + dataBindPath->resolvedPath(), + resolver); + } + } + return nullptr; + } + else + { + return getViewModelInstance(dataBindPath->resolvedPath()); + } } \ No newline at end of file
diff --git a/src/data_bind_path_referencer.cpp b/src/data_bind_path_referencer.cpp new file mode 100644 index 0000000..718ff1d --- /dev/null +++ b/src/data_bind_path_referencer.cpp
@@ -0,0 +1,46 @@ +#include "rive/data_bind_path_referencer.hpp" +#include "rive/importers/data_bind_path_importer.hpp" +#include "rive/file.hpp" + +using namespace rive; + +DataBindPathReferencer::~DataBindPathReferencer() +{ + if (m_dataBindPath) + { + delete m_dataBindPath; + } +} + +void DataBindPathReferencer::copyDataBindPath(DataBindPath* dataBindPath) +{ + if (dataBindPath) + { + m_dataBindPath = dataBindPath->clone()->as<DataBindPath>(); + m_dataBindPath->file(dataBindPath->file()); + } +} + +void DataBindPathReferencer::importDataBindPath(ImportStack& importStack) +{ + auto dataBindPathImporter = + importStack.latest<DataBindPathImporter>(DataBindPathBase::typeKey); + if (dataBindPathImporter) + { + auto dataBindPath = dataBindPathImporter->claim(); + if (dataBindPath != nullptr) + { + // If a DataBindPath is claimed, the core object shouldn't have a + // path already created + assert(m_dataBindPath == nullptr); + m_dataBindPath = dataBindPath; + } + } +} + +void DataBindPathReferencer::decodeDataBindPath(Span<const uint8_t>& value) +{ + m_dataBindPath = new DataBindPath(); + m_dataBindPath->decodePath(value); + m_dataBindPath->resolved(true); +} \ No newline at end of file
diff --git a/src/file.cpp b/src/file.cpp index a20be45..7e2f2da 100644 --- a/src/file.cpp +++ b/src/file.cpp
@@ -34,6 +34,7 @@ #include "rive/importers/viewmodel_importer.hpp" #include "rive/importers/viewmodel_instance_importer.hpp" #include "rive/importers/viewmodel_instance_list_importer.hpp" +#include "rive/importers/data_bind_path_importer.hpp" #include "rive/animation/blend_state_transition.hpp" #include "rive/animation/any_state.hpp" #include "rive/animation/entry_state.hpp" @@ -45,6 +46,7 @@ #include "rive/animation/transition_property_viewmodel_comparator.hpp" #include "rive/constraints/scrolling/scroll_physics.hpp" #include "rive/data_bind/data_bind.hpp" +#include "rive/data_bind/data_bind_path.hpp" #include "rive/data_bind/bindable_property.hpp" #include "rive/data_bind/bindable_property_artboard.hpp" #include "rive/data_bind/bindable_property_asset.hpp" @@ -538,6 +540,7 @@ case ScriptedDrawable::typeKey: case ScriptedLayout::typeKey: case ScriptedPathEffect::typeKey: + { auto scriptedObject = ScriptedObject::from(object); if (scriptedObject != nullptr) { @@ -546,6 +549,12 @@ stackType = ScriptedDrawable::typeKey; } break; + } + case DataBindPathBase::typeKey: + stackObject = rivestd::make_unique<DataBindPathImporter>( + object->as<DataBindPath>()); + stackType = DataBindPathBase::typeKey; + break; } if (importStack.makeLatest(stackType, std::move(stackObject)) != StatusCode::Ok)
diff --git a/src/generated/data_bind/data_bind_path_base.cpp b/src/generated/data_bind/data_bind_path_base.cpp new file mode 100644 index 0000000..4dcb383 --- /dev/null +++ b/src/generated/data_bind/data_bind_path_base.cpp
@@ -0,0 +1,11 @@ +#include "rive/generated/data_bind/data_bind_path_base.hpp" +#include "rive/data_bind/data_bind_path.hpp" + +using namespace rive; + +Core* DataBindPathBase::clone() const +{ + auto cloned = new DataBindPath(); + cloned->copy(*this); + return cloned; +}
diff --git a/src/importers/data_bind_path_importer.cpp b/src/importers/data_bind_path_importer.cpp new file mode 100644 index 0000000..9808ea8 --- /dev/null +++ b/src/importers/data_bind_path_importer.cpp
@@ -0,0 +1,15 @@ +#include "rive/data_bind/data_bind_path.hpp" +#include "rive/importers/data_bind_path_importer.hpp" + +using namespace rive; + +DataBindPathImporter::DataBindPathImporter(DataBindPath* obj) : + m_dataBindPath(obj) +{} + +DataBindPath* DataBindPathImporter::claim() +{ + auto path = m_dataBindPath; + m_dataBindPath = nullptr; + return path; +} \ No newline at end of file
diff --git a/src/nested_artboard.cpp b/src/nested_artboard.cpp index 14d907d..0759935 100644 --- a/src/nested_artboard.cpp +++ b/src/nested_artboard.cpp
@@ -6,6 +6,7 @@ #include "rive/importers/backboard_importer.hpp" #include "rive/nested_animation.hpp" #include "rive/animation/nested_state_machine.hpp" +#include "rive/data_bind/data_bind_path.hpp" #include "rive/clip_result.hpp" #include <limits> #include <cassert> @@ -209,6 +210,7 @@ StatusCode NestedArtboard::import(ImportStack& importStack) { + importDataBindPath(importStack); auto backboardImporter = importStack.latest<BackboardImporter>(Backboard::typeKey); if (backboardImporter == nullptr) @@ -378,18 +380,12 @@ void NestedArtboard::decodeDataBindPathIds(Span<const uint8_t> value) { - BinaryReader reader(value); - while (!reader.reachedEnd()) - { - auto val = reader.readVarUintAs<uint32_t>(); - m_DataBindPathIdsBuffer.push_back(val); - } + decodeDataBindPath(value); } void NestedArtboard::copyDataBindPathIds(const NestedArtboardBase& object) { - m_DataBindPathIdsBuffer = - object.as<NestedArtboard>()->m_DataBindPathIdsBuffer; + copyDataBindPath(object.as<NestedArtboard>()->dataBindPath()); } void NestedArtboard::internalDataContext(DataContext* value)
diff --git a/tests/unit_tests/assets/relative_data_bind_path.riv b/tests/unit_tests/assets/relative_data_bind_path.riv new file mode 100644 index 0000000..a0b93d8 --- /dev/null +++ b/tests/unit_tests/assets/relative_data_bind_path.riv Binary files differ
diff --git a/tests/unit_tests/assets/relative_data_binding.riv b/tests/unit_tests/assets/relative_data_binding.riv index 1979a96..038da78 100644 --- a/tests/unit_tests/assets/relative_data_binding.riv +++ b/tests/unit_tests/assets/relative_data_binding.riv Binary files differ
diff --git a/tests/unit_tests/runtime/data_binding_test.cpp b/tests/unit_tests/runtime/data_binding_test.cpp index 9add394..857acb3 100644 --- a/tests/unit_tests/runtime/data_binding_test.cpp +++ b/tests/unit_tests/runtime/data_binding_test.cpp
@@ -2001,6 +2001,51 @@ CHECK(silver.matches("relative_data_binding")); } +TEST_CASE("Relative data binding view model path", "[silver]") +{ + SerializingFactory silver; + auto file = ReadRiveFile("assets/relative_data_bind_path.riv", &silver); + + auto artboard = file->artboardDefault(); + REQUIRE(artboard != nullptr); + + silver.frameSize(artboard->width(), artboard->height()); + + auto stateMachine = artboard->stateMachineAt(0); + auto renderer = silver.makeRenderer(); + // First use the default view model instance that is attached to the + // artboard + { + auto vmi = + file->createViewModelInstance((int)artboard.get()->viewModelId(), + 0); + + stateMachine->bindViewModelInstance(vmi); + stateMachine->advanceAndApply(0.1f); + artboard->draw(renderer.get()); + silver.addFrame(); + } + // Next bind it to a different view model type that matches the expected + // view model shape + { + auto vm = file->viewModel("ViewModel1"); + auto vmi = file->createDefaultViewModelInstance(vm); + stateMachine->bindViewModelInstance(vmi); + stateMachine->advanceAndApply(0.1f); + artboard->draw(renderer.get()); + silver.addFrame(); + } + // Next bind it to a different view model with the wrong shape + { + auto vm = file->viewModel("ViewModel2"); + auto vmi = file->createDefaultViewModelInstance(vm); + stateMachine->bindViewModelInstance(vmi); + stateMachine->advanceAndApply(0.1f); + artboard->draw(renderer.get()); + } + + CHECK(silver.matches("relative_data_bind_path")); +} TEST_CASE("Listen to view model value changes in state machines", "[silver]") {
diff --git a/tests/unit_tests/silvers/relative_data_bind_path.sriv b/tests/unit_tests/silvers/relative_data_bind_path.sriv new file mode 100644 index 0000000..6b30c73 --- /dev/null +++ b/tests/unit_tests/silvers/relative_data_bind_path.sriv Binary files differ