| #ifdef WITH_RIVE_SCRIPTING |
| #include "rive/lua/rive_lua_libs.hpp" |
| #endif |
| #include "rive/assets/script_asset.hpp" |
| #include "rive/component_dirt.hpp" |
| #include "rive/data_bind/data_bind.hpp" |
| #include "rive/data_bind/data_bind_context.hpp" |
| #include "rive/data_bind/data_values/data_type.hpp" |
| #include "rive/data_bind/data_values/data_value.hpp" |
| #include "rive/data_bind/data_values/data_value_boolean.hpp" |
| #include "rive/data_bind/data_values/data_value_color.hpp" |
| #include "rive/data_bind/data_values/data_value_number.hpp" |
| #include "rive/data_bind/data_values/data_value_string.hpp" |
| #include "rive/scripted/scripted_data_converter.hpp" |
| |
| using namespace rive; |
| |
| ScriptedDataConverter::~ScriptedDataConverter() |
| { |
| disposeScriptInputs(); |
| if (m_dataValue) |
| { |
| delete m_dataValue; |
| } |
| } |
| |
| void ScriptedDataConverter::disposeScriptInputs() |
| { |
| auto props = m_customProperties; |
| ScriptedObject::disposeScriptInputs(); |
| for (auto prop : props) |
| { |
| auto scriptInput = ScriptInput::from(prop); |
| if (scriptInput != nullptr) |
| { |
| // ScriptedDataConverters need to delete their own inputs |
| // because they are not components |
| delete scriptInput; |
| } |
| } |
| } |
| |
| #ifdef WITH_RIVE_SCRIPTING |
| |
| void ScriptedDataConverter::didHydrateScriptInputs() |
| { |
| addScriptedDirt(ComponentDirt::Bindings); |
| } |
| |
| bool ScriptedDataConverter::pushDataValue(DataValue* value) |
| { |
| lua_State* L = state(); |
| // Stack: [self, field, self] |
| if (value->is<DataValueNumber>()) |
| { |
| lua_newrive<ScriptedDataValueNumber>( |
| L, |
| L, |
| value->as<DataValueNumber>()->value()); |
| } |
| else if (value->is<DataValueString>()) |
| { |
| lua_newrive<ScriptedDataValueString>( |
| L, |
| L, |
| value->as<DataValueString>()->value()); |
| } |
| else if (value->is<DataValueBoolean>()) |
| { |
| lua_newrive<ScriptedDataValueBoolean>( |
| L, |
| L, |
| value->as<DataValueBoolean>()->value()); |
| } |
| else if (value->is<DataValueColor>()) |
| { |
| lua_newrive<ScriptedDataValueColor>( |
| L, |
| L, |
| value->as<DataValueColor>()->value()); |
| } |
| else |
| { |
| return false; |
| } |
| return true; |
| } |
| |
| DataValue* ScriptedDataConverter::applyConversion(DataValue* value, |
| const std::string& method) |
| { |
| lua_State* L = state(); |
| if (L == nullptr) |
| { |
| return value; |
| } |
| // Stack: [] |
| rive_lua_pushRef(L, m_self); |
| // Stack: [self] |
| if (static_cast<lua_Type>(lua_getfield(L, -1, method.c_str())) != |
| LUA_TFUNCTION) |
| { |
| // Assumed for legacy files but not implemented; pass the value through |
| // unchanged (same as !dataConverts()). |
| rive_lua_pop(L, 2); // non-function field + self |
| return value; |
| } |
| // Stack: [self, field] |
| lua_pushvalue(L, -2); |
| // Stack: [self, field, self] |
| if (pushDataValue(value)) |
| { |
| // Stack: [self, field, self, ScriptedData] |
| if (static_cast<lua_Status>( |
| rive_lua_pcall_with_context(L, this, 2, 1)) == LUA_OK) |
| { |
| auto result = (ScriptedDataValue*)lua_touserdata(L, -1); |
| if (result->isNumber()) |
| { |
| storeData<DataValueNumber>(result->dataValue()); |
| } |
| else if (result->isString()) |
| { |
| storeData<DataValueString>(result->dataValue()); |
| } |
| else if (result->isBoolean()) |
| { |
| storeData<DataValueBoolean>(result->dataValue()); |
| } |
| else if (result->isColor()) |
| { |
| storeData<DataValueColor>(result->dataValue()); |
| } |
| } |
| // Stack: [self, status] or [self, result] |
| rive_lua_pop(L, 2); |
| } |
| else |
| { |
| // Stack: [self, field, self] |
| rive_lua_pop(L, 3); |
| } |
| if (!m_dataValue) |
| { |
| m_dataValue = new DataValue(); |
| } |
| return m_dataValue; |
| } |
| |
| DataValue* ScriptedDataConverter::convert(DataValue* value, DataBind* dataBind) |
| { |
| if (dataConverts()) |
| { |
| return applyConversion(value, "convert"); |
| } |
| return value; |
| } |
| |
| DataValue* ScriptedDataConverter::reverseConvert(DataValue* value, |
| DataBind* dataBind) |
| { |
| if (dataReverseConverts()) |
| { |
| return applyConversion(value, "reverseConvert"); |
| } |
| return value; |
| } |
| #endif |
| |
| void ScriptedDataConverter::bindFromContext(DataContext* dataContext, |
| DataBind* dataBind) |
| { |
| m_dataContext = rcp<DataContext>(safe_ref(dataContext)); |
| Super::bindFromContext(dataContext, dataBind); |
| reinit(); |
| for (auto prop : m_customProperties) |
| { |
| auto input = ScriptInput::from(prop); |
| if (input != nullptr) |
| { |
| if (input->dataBind() != nullptr) |
| { |
| input->dataBind()->as<DataBindContext>()->bindFromContext( |
| dataContext); |
| } |
| } |
| } |
| } |
| |
| bool ScriptedDataConverter::advanceComponent(float elapsedSeconds, |
| AdvanceFlags flags) |
| { |
| if (!enums::is_flag_set(flags, AdvanceFlags::AdvanceNested)) |
| { |
| elapsedSeconds = 0; |
| } |
| return advance(elapsedSeconds); |
| } |
| |
| bool ScriptedDataConverter::advance(float elapsedSeconds) |
| { |
| |
| if (elapsedSeconds == 0) |
| { |
| return false; |
| } |
| auto needsAdvance = scriptAdvance(elapsedSeconds); |
| if (needsAdvance) |
| { |
| markConverterDirty(); |
| } |
| return needsAdvance; |
| } |
| |
| void ScriptedDataConverter::addProperty(CustomProperty* prop) |
| { |
| auto scriptInput = ScriptInput::from(prop); |
| if (scriptInput != nullptr) |
| { |
| scriptInput->scriptedObject(this); |
| } |
| CustomPropertyContainer::addProperty(prop); |
| } |
| |
| StatusCode ScriptedDataConverter::import(ImportStack& importStack) |
| { |
| auto result = registerReferencer(importStack); |
| if (result != StatusCode::Ok) |
| { |
| return result; |
| } |
| return Super::import(importStack); |
| } |
| |
| Core* ScriptedDataConverter::clone() const |
| { |
| ScriptedDataConverter* twin = |
| ScriptedDataConverterBase::clone()->as<ScriptedDataConverter>(); |
| if (m_fileAsset != nullptr) |
| { |
| twin->setAsset(m_fileAsset); |
| } |
| for (auto prop : m_customProperties) |
| { |
| auto clonedValue = prop->clone()->as<CustomProperty>(); |
| twin->addProperty(clonedValue); |
| auto scriptedInputClone = ScriptInput::from(clonedValue); |
| auto scriptedInputSource = ScriptInput::from(prop); |
| auto thisDataBinds = dataBinds(); |
| auto twinDataBinds = twin->dataBinds(); |
| if (scriptedInputClone && scriptedInputSource) |
| { |
| if (scriptedInputSource->dataBind()) |
| { |
| int index = 0; |
| // Data binds are cloned in the data converters, and assigned to |
| // the right target here |
| for (auto& dataBind : thisDataBinds) |
| { |
| if (dataBind->target() == prop) |
| { |
| if (index < twinDataBinds.size()) |
| { |
| twinDataBinds[index]->target(clonedValue); |
| scriptedInputClone->dataBind(twinDataBinds[index]); |
| } |
| } |
| index++; |
| } |
| } |
| } |
| } |
| return twin; |
| } |
| |
| bool ScriptedDataConverter::addDataBindFromScriptedObject(DataBind* dataBind) |
| { |
| addDataBind(dataBind); |
| return true; |
| } |