| #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; |
| } |