| #include <sstream> | |
| #include <iomanip> | |
| #include <array> | |
| #include <algorithm> | |
| #include "rive/artboard.hpp" | |
| #include "rive/viewmodel/viewmodel_instance.hpp" | |
| #include "rive/viewmodel/viewmodel_instance_value.hpp" | |
| #include "rive/viewmodel/viewmodel_property_symbol_list_index.hpp" | |
| #include "rive/importers/artboard_importer.hpp" | |
| #include "rive/importers/viewmodel_instance_importer.hpp" | |
| #include "rive/data_bind/data_bind.hpp" | |
| #include "rive/refcnt.hpp" | |
| using namespace rive; | |
| StatusCode ViewModelInstanceValue::onAddedDirty(CoreContext* context) | |
| { | |
| StatusCode result = Super::onAddedDirty(context); | |
| if (result != StatusCode::Ok) | |
| { | |
| return result; | |
| } | |
| // If parent is a ViewModelInstance, register with it. | |
| // This handles stateful ViewModelInstances (children of NestedArtboard) | |
| // where values are cloned separately during artboard cloning. | |
| if (parent() != nullptr && parent()->is<ViewModelInstance>()) | |
| { | |
| parent()->as<ViewModelInstance>()->addValue(this); | |
| } | |
| return StatusCode::Ok; | |
| } | |
| StatusCode ViewModelInstanceValue::import(ImportStack& importStack) | |
| { | |
| auto viewModelInstanceImporter = | |
| importStack.latest<ViewModelInstanceImporter>( | |
| ViewModelInstance::typeKey); | |
| if (viewModelInstanceImporter == nullptr) | |
| { | |
| return StatusCode::MissingObject; | |
| } | |
| viewModelInstanceImporter->addValue(this); | |
| // If we're inside an artboard context, import through Component so we're | |
| // added to the artboard's object list and can be resolved by ID. | |
| auto artboardImporter = | |
| importStack.latest<ArtboardImporter>(ArtboardBase::typeKey); | |
| if (artboardImporter != nullptr) | |
| { | |
| return Super::import(importStack); | |
| } | |
| return Core::import(importStack); | |
| } | |
| bool ViewModelInstanceValue::hasChanged() | |
| { | |
| return enums::is_flag_set(m_changeFlags, ValueFlags::valueChanged); | |
| } | |
| void ViewModelInstanceValue::registerSymbol() | |
| { | |
| if (m_ViewModelProperty != nullptr && m_viewModelInstance != nullptr) | |
| { | |
| if (m_ViewModelProperty->is<ViewModelPropertySymbolListIndex>()) | |
| { | |
| m_viewModelInstance->propertyValue(SymbolType::itemIndex, this); | |
| } | |
| else if ((SymbolType)m_ViewModelProperty->symbolTypeValue() != | |
| SymbolType::none) | |
| { | |
| m_viewModelInstance->propertyValue( | |
| (SymbolType)m_ViewModelProperty->symbolTypeValue(), | |
| this); | |
| } | |
| } | |
| } | |
| void ViewModelInstanceValue::viewModelProperty(ViewModelProperty* value) | |
| { | |
| m_ViewModelProperty = value; | |
| registerSymbol(); | |
| } | |
| ViewModelProperty* ViewModelInstanceValue::viewModelProperty() | |
| { | |
| return m_ViewModelProperty; | |
| } | |
| void ViewModelInstanceValue::addDependent(ViewModelValueDependent* value) | |
| { | |
| m_DependencyHelper.addDependent(value); | |
| } | |
| void ViewModelInstanceValue::removeDependent(ViewModelValueDependent* value) | |
| { | |
| m_DependencyHelper.removeDependent(value); | |
| } | |
| void ViewModelInstanceValue::addDirt(ComponentDirt value) | |
| { | |
| m_DependencyHelper.addDirtToDependents(value); | |
| } | |
| void ViewModelInstanceValue::setRoot(rcp<ViewModelInstance> viewModelInstance) | |
| { | |
| // No-op. Previously stored &viewModelInstance (address of a stack-local | |
| // parameter) into DependencyHelper::m_dependecyRoot. The stored pointer | |
| // dangled the moment this function returned but was never dereferenced | |
| // because onComponentDirty is never called on a ViewModelInstanceValue's | |
| // dependents — so the latent bug never manifested. The CRTP refactor | |
| // dropped the field entirely; the method stays for ABI compatibility | |
| // with overrides in subclasses. | |
| (void)viewModelInstance; | |
| } | |
| std::string ViewModelInstanceValue::defaultName = ""; | |
| const std::string& ViewModelInstanceValue::name() const | |
| { | |
| if (m_ViewModelProperty != nullptr) | |
| { | |
| return m_ViewModelProperty->constName(); | |
| }; | |
| return defaultName; | |
| } | |
| void ViewModelInstanceValue::advanced() | |
| { | |
| m_usedLayers.clear(); | |
| m_changeFlags &= ~ValueFlags::valueChanged; | |
| } | |
| void ViewModelInstanceValue::viewModelInstance(ViewModelInstance* value) | |
| { | |
| m_viewModelInstance = value; | |
| registerSymbol(); | |
| } | |
| void ViewModelInstanceValue::addDelegate( | |
| ViewModelInstanceValueDelegate* delegate) | |
| { | |
| m_delegates.push_back(delegate); | |
| m_changeFlags |= ValueFlags::delegatesChanged; | |
| } | |
| void ViewModelInstanceValue::removeDelegate( | |
| ViewModelInstanceValueDelegate* delegate) | |
| { | |
| auto sizeBefore = m_delegates.size(); | |
| m_delegates.eraseAll(delegate); | |
| if (m_delegates.size() != sizeBefore) | |
| { | |
| m_changeFlags |= ValueFlags::delegatesChanged; | |
| } | |
| } | |
| bool ViewModelInstanceValue::suppressDelegation() | |
| { | |
| if (enums::is_flag_set(m_changeFlags, ValueFlags::delegating)) | |
| { | |
| return false; | |
| } | |
| m_changeFlags |= ValueFlags::delegating; | |
| return true; | |
| } | |
| void ViewModelInstanceValue::restoreDelegation() | |
| { | |
| m_changeFlags &= ~ValueFlags::delegating; | |
| } | |
| void ViewModelInstanceValue::onValueChanged() | |
| { | |
| m_changeFlags |= ValueFlags::valueChanged; | |
| // Common case: no delegates ever registered. Skip the snapshot+iterate | |
| // logic entirely. | |
| if (m_delegates.empty()) | |
| { | |
| return; | |
| } | |
| if (enums::is_flag_set(m_changeFlags, ValueFlags::delegatesChanged)) | |
| { | |
| m_delegatesCopy.clear(); | |
| for (auto* d : m_delegates) | |
| { | |
| m_delegatesCopy.push_back(d); | |
| } | |
| m_changeFlags &= ~ValueFlags::delegatesChanged; | |
| } | |
| if (enums::is_flag_set(m_changeFlags, ValueFlags::delegating)) | |
| { | |
| // We're already calling delegates for this value change, changing the | |
| // value in a delegate will not report the change. | |
| return; | |
| } | |
| // Flag to guard against calling this recursively. | |
| m_changeFlags |= ValueFlags::delegating; | |
| // Iterate over a copy of the delegates in case delegates get changed in | |
| // valueChanged callbacks. | |
| for (auto* delegate : m_delegatesCopy) | |
| { | |
| delegate->valueChanged(); | |
| } | |
| // Clear guard flag. | |
| m_changeFlags &= ~ValueFlags::delegating; | |
| } |