| #ifdef WITH_RIVE_SCRIPTING | |
| #include "rive/lua/rive_lua_libs.hpp" | |
| #include "rive/viewmodel/viewmodel_property_asset_image.hpp" | |
| #include "rive/viewmodel/viewmodel_property_boolean.hpp" | |
| #include "rive/viewmodel/viewmodel_property_color.hpp" | |
| #include "rive/viewmodel/viewmodel_property_enum.hpp" | |
| #include "rive/viewmodel/viewmodel_property_number.hpp" | |
| #include "rive/viewmodel/viewmodel_property_string.hpp" | |
| #include "rive/viewmodel/viewmodel_property_trigger.hpp" | |
| #include "rive/viewmodel/viewmodel_property_list.hpp" | |
| #include "rive/viewmodel/viewmodel_property_symbol_list_index.hpp" | |
| #include "rive/viewmodel/viewmodel_property_viewmodel.hpp" | |
| #include "rive/viewmodel/viewmodel_instance_asset_image.hpp" | |
| #include "rive/viewmodel/viewmodel_property_asset_font.hpp" | |
| #include "rive/viewmodel/viewmodel_instance_asset_font.hpp" | |
| #include "rive/viewmodel/viewmodel_property_asset_blob.hpp" | |
| #include "rive/viewmodel/viewmodel_instance_asset_blob.hpp" | |
| #include "rive/viewmodel/viewmodel_instance_list.hpp" | |
| #include "rive/viewmodel/viewmodel_instance_enum.hpp" | |
| #include "rive/viewmodel/viewmodel_instance_list_item.hpp" | |
| #include "rive/viewmodel/viewmodel_instance_symbol_list_index.hpp" | |
| #include "rive/scripted/scripted_object.hpp" | |
| #include "rive/assets/image_asset.hpp" | |
| #include "rive/assets/script_asset.hpp" | |
| #include "rive/file.hpp" | |
| #ifdef WITH_RIVE_TOOLS | |
| #include "rive/viewmodel/viewmodel.hpp" | |
| #include "rive/viewmodel/viewmodel_instance.hpp" | |
| #endif | |
| #include <math.h> | |
| using namespace rive; | |
| static ScriptingContext* scriptingContext(lua_State* L) | |
| { | |
| return static_cast<ScriptingContext*>(lua_getthreaddata(L)); | |
| } | |
| static void pushViewModelInstanceValue(lua_State* L, | |
| ViewModelInstanceValue* propValue) | |
| { | |
| switch (propValue->coreType()) | |
| { | |
| case ViewModelInstanceNumberBase::typeKey: | |
| lua_newrive<ScriptedPropertyNumber>( | |
| L, | |
| L, | |
| ref_rcp(propValue->as<ViewModelInstanceNumber>())); | |
| break; | |
| case ViewModelInstanceTriggerBase::typeKey: | |
| lua_newrive<ScriptedPropertyTrigger>( | |
| L, | |
| L, | |
| ref_rcp(propValue->as<ViewModelInstanceTrigger>())); | |
| break; | |
| case ViewModelInstanceListBase::typeKey: | |
| lua_newrive<ScriptedPropertyList>( | |
| L, | |
| L, | |
| ref_rcp(propValue->as<ViewModelInstanceList>())); | |
| break; | |
| case ViewModelInstanceColorBase::typeKey: | |
| lua_newrive<ScriptedPropertyColor>( | |
| L, | |
| L, | |
| ref_rcp(propValue->as<ViewModelInstanceColor>())); | |
| break; | |
| case ViewModelInstanceStringBase::typeKey: | |
| lua_newrive<ScriptedPropertyString>( | |
| L, | |
| L, | |
| ref_rcp(propValue->as<ViewModelInstanceString>())); | |
| break; | |
| case ViewModelInstanceBooleanBase::typeKey: | |
| lua_newrive<ScriptedPropertyBoolean>( | |
| L, | |
| L, | |
| ref_rcp(propValue->as<ViewModelInstanceBoolean>())); | |
| break; | |
| case ViewModelInstanceEnumBase::typeKey: | |
| lua_newrive<ScriptedPropertyEnum>( | |
| L, | |
| L, | |
| ref_rcp(propValue->as<ViewModelInstanceEnum>())); | |
| break; | |
| case ViewModelInstanceViewModelBase::typeKey: | |
| { | |
| // The wrapper needs the referenced view model, not the owner's, so | |
| // instance() mints the nested type. | |
| auto vmValue = propValue->as<ViewModelInstanceViewModel>(); | |
| auto reference = vmValue->referenceViewModelInstance(); | |
| lua_newrive<ScriptedPropertyViewModel>( | |
| L, | |
| L, | |
| reference != nullptr ? ref_rcp(reference->viewModel()) | |
| : nullptr, | |
| ref_rcp(vmValue)); | |
| break; | |
| } | |
| case ViewModelInstanceSymbolListIndexBase::typeKey: | |
| { | |
| auto listIndexProp = | |
| propValue->as<ViewModelInstanceSymbolListIndex>(); | |
| lua_pushinteger(L, listIndexProp->propertyValue()); | |
| break; | |
| } | |
| case ViewModelInstanceAssetImageBase::typeKey: | |
| { | |
| lua_newrive<ScriptedPropertyImage>( | |
| L, | |
| L, | |
| ref_rcp(propValue->as<ViewModelInstanceAssetImage>())); | |
| break; | |
| } | |
| case ViewModelInstanceAssetFontBase::typeKey: | |
| { | |
| lua_newrive<ScriptedPropertyFont>( | |
| L, | |
| L, | |
| ref_rcp(propValue->as<ViewModelInstanceAssetFont>())); | |
| break; | |
| } | |
| case ViewModelInstanceAssetBlobBase::typeKey: | |
| { | |
| lua_newrive<ScriptedPropertyBlob>( | |
| L, | |
| L, | |
| ref_rcp(propValue->as<ViewModelInstanceAssetBlob>())); | |
| break; | |
| } | |
| default: | |
| lua_pushnil(L); | |
| break; | |
| } | |
| } | |
| // int ScriptedPropertyViewModel::pushValueOfType(const char* name, int | |
| // coreType) | |
| // { | |
| // auto vmi = m_instanceValue->referenceViewModelInstance(); | |
| // if (!vmi) | |
| // { | |
| // lua_pushnil(m_state); | |
| // } | |
| // else | |
| // { | |
| // auto propValue = vmi->propertyValue(name); | |
| // if (propValue->coreType() != coreType) | |
| // { | |
| // lua_pushnil(m_state); | |
| // } | |
| // else | |
| // { | |
| // pushViewModelInstanceValue(m_state, propValue); | |
| // } | |
| // } | |
| // return 1; | |
| // } | |
| ScriptedProperty::~ScriptedProperty() { dispose(); } | |
| ScriptedPropertyViewModel::~ScriptedPropertyViewModel() { dispose(); } | |
| void ScriptedProperty::clearCachedValueRef() | |
| { | |
| if (m_cachedValueRef != 0) | |
| { | |
| lua_unref(m_state, m_cachedValueRef); | |
| m_cachedValueRef = 0; | |
| } | |
| } | |
| void ScriptedProperty::clearListeners() | |
| { | |
| for (auto itr = m_listeners.begin(); itr != m_listeners.end(); ++itr) | |
| { | |
| const ScriptedListener& listener = *itr; | |
| lua_unref(m_state, listener.function); | |
| if (listener.userdata) | |
| { | |
| lua_unref(m_state, listener.userdata); | |
| } | |
| lua_unref(m_state, listener.propertySelfRef); | |
| } | |
| m_listeners.clear(); | |
| } | |
| ScriptedProperty::ScriptedProperty(lua_State* L, | |
| rcp<ViewModelInstanceValue> value) : | |
| m_state(L), m_instanceValue(std::move(value)) | |
| { | |
| if (m_instanceValue != nullptr) | |
| { | |
| m_instanceValue->addDelegate(this); | |
| } | |
| auto context = scriptingContext(L); | |
| if (context != nullptr) | |
| { | |
| m_owner = context->currentScriptedObject(); | |
| if (m_owner != nullptr) | |
| { | |
| m_owner->addTrackedScriptedProperty(this); | |
| } | |
| #ifdef WITH_RIVE_TOOLS | |
| else | |
| { | |
| context->trackOrphanScriptedProperty(this); | |
| m_orphanContext = context; | |
| m_orphanOwnerTag = context->orphanOwnerTag(); | |
| } | |
| #endif | |
| } | |
| } | |
| void ScriptedProperty::dispose() | |
| { | |
| if (m_disposed) | |
| { | |
| return; | |
| } | |
| m_disposed = true; | |
| if (m_owner != nullptr) | |
| { | |
| m_owner->removeTrackedScriptedProperty(this); | |
| m_owner = nullptr; | |
| } | |
| #ifdef WITH_RIVE_TOOLS | |
| if (m_orphanContext != nullptr) | |
| { | |
| m_orphanContext->untrackOrphanScriptedProperty(this); | |
| m_orphanContext = nullptr; | |
| } | |
| #endif | |
| if (m_instanceValue != nullptr) | |
| { | |
| m_instanceValue->removeDelegate(this); | |
| m_instanceValue = nullptr; | |
| } | |
| clearCachedValueRef(); | |
| clearListeners(); | |
| } | |
| void ScriptedProperty::valueChanged() | |
| { | |
| clearCachedValueRef(); | |
| if (m_listeners.empty()) | |
| { | |
| return; | |
| } | |
| if (!lua_checkstack(m_state, (int)(m_listeners.size() * 2 + LUA_MINSTACK))) | |
| { | |
| return; | |
| } | |
| // This works because we don't actually call as we go (or we could | |
| // invalidate listeners if a callback registers a new or removes a | |
| // listener). Instead, we build up the call stack and then call for each | |
| // callback on the stack. | |
| for (auto itr = m_listeners.rbegin(); itr != m_listeners.rend(); itr++) | |
| { | |
| const ScriptedListener& listener = *itr; | |
| lua_rawgeti(m_state, LUA_REGISTRYINDEX, listener.function); | |
| if (listener.userdata) | |
| { | |
| lua_rawgeti(m_state, LUA_REGISTRYINDEX, listener.userdata); | |
| } | |
| else | |
| { | |
| // push nil so we have a constant number of args per call | |
| lua_pushnil(m_state); | |
| } | |
| } | |
| size_t calls = m_listeners.size(); | |
| for (size_t i = 0; i < calls; i++) | |
| { | |
| lua_pcall(m_state, 1, 0, 0); | |
| } | |
| } | |
| int ScriptedProperty::addListener() | |
| { | |
| if (lua_isfunction(m_state, 2)) | |
| { | |
| lua_pushvalue(m_state, 1); | |
| int propertySelfRef = lua_ref(m_state, -1); | |
| lua_pop(m_state, 1); | |
| int callbackRef = lua_ref(m_state, 2); | |
| m_listeners.push_back({callbackRef, 0, propertySelfRef}); | |
| return 0; | |
| } | |
| else if (lua_isfunction(m_state, 3)) | |
| { | |
| lua_pushvalue(m_state, 1); | |
| int propertySelfRef = lua_ref(m_state, -1); | |
| lua_pop(m_state, 1); | |
| int userdataRef = lua_ref(m_state, 2); | |
| int callbackRef = lua_ref(m_state, 3); | |
| m_listeners.push_back({callbackRef, userdataRef, propertySelfRef}); | |
| return 0; | |
| } | |
| luaL_typeerrorL(m_state, 2, lua_typename(m_state, LUA_TFUNCTION)); | |
| return 0; | |
| } | |
| int ScriptedProperty::removeListener() | |
| { | |
| int checkIndex; | |
| if (lua_isfunction(m_state, 2)) | |
| { | |
| checkIndex = 2; | |
| } | |
| else if (lua_isfunction(m_state, 3)) | |
| { | |
| checkIndex = 3; | |
| } | |
| else | |
| { | |
| luaL_typeerrorL(m_state, 2, lua_typename(m_state, LUA_TFUNCTION)); | |
| } | |
| for (auto itr = m_listeners.begin(); itr != m_listeners.end();) | |
| { | |
| const ScriptedListener& listener = *itr; | |
| lua_rawgeti(m_state, LUA_REGISTRYINDEX, listener.function); | |
| if (lua_rawequal(m_state, -1, checkIndex)) | |
| { | |
| lua_unref(m_state, listener.function); | |
| if (listener.userdata) | |
| { | |
| lua_unref(m_state, listener.userdata); | |
| } | |
| lua_unref(m_state, listener.propertySelfRef); | |
| itr = m_listeners.erase(itr); | |
| } | |
| else | |
| { | |
| ++itr; | |
| } | |
| lua_pop(m_state, 1); | |
| } | |
| return 0; | |
| } | |
| ScriptedPropertyViewModel::ScriptedPropertyViewModel( | |
| lua_State* L, | |
| rcp<ViewModel> viewModel, | |
| rcp<ViewModelInstanceViewModel> value) : | |
| ScriptedProperty(L, std::move(value)), m_viewModel(std::move(viewModel)) | |
| {} | |
| void ScriptedPropertyViewModel::setValue(ScriptedViewModel* scriptedViewModel) | |
| { | |
| if (m_instanceValue != nullptr && | |
| m_instanceValue->is<ViewModelInstanceViewModel>()) | |
| { | |
| auto instanceValue = m_instanceValue->as<ViewModelInstanceViewModel>(); | |
| auto parentViewModelInstance = instanceValue->parentViewModelInstance(); | |
| auto viewModelInstance = scriptedViewModel->mutableViewModelInstance(); | |
| // replaceViewModelByProperty notifies this property's value-dependents | |
| // (including this wrapper and any siblings sharing the property) so | |
| // cached Lua-side values are invalidated. | |
| parentViewModelInstance->replaceViewModelByProperty( | |
| instanceValue, | |
| rcp<ViewModelInstance>(viewModelInstance)); | |
| } | |
| } | |
| void ScriptedPropertyViewModel::dispose() | |
| { | |
| if (m_instanceValue) | |
| { | |
| m_instanceValue->removeDependent(this); | |
| } | |
| clearRef(); | |
| ScriptedProperty::dispose(); | |
| } | |
| void ScriptedPropertyViewModel::clearRef() | |
| { | |
| if (m_valueRef != 0) | |
| { | |
| lua_unref(m_state, m_valueRef); | |
| m_valueRef = 0; | |
| } | |
| } | |
| ScriptedViewModel::ScriptedViewModel(lua_State* L, | |
| rcp<ViewModel> viewModel, | |
| rcp<ViewModelInstance> viewModelInstance) : | |
| m_state(L), m_viewModel(viewModel), m_viewModelInstance(viewModelInstance) | |
| { | |
| // Register the instance so detached ones (no parents) get advanced at the | |
| // end of each frame. Tracking is keyed to owner lifetime, so the instance | |
| // stays tracked while any owner (this wrapper, a scripted artboard) is | |
| // alive. Store the context so unregistration in the destructor does not | |
| // depend on lua_getthreaddata during Lua finalization. | |
| m_scriptingContext = scriptingContext(L); | |
| if (m_scriptingContext != nullptr) | |
| { | |
| m_scriptingContext->trackViewModelInstance(m_viewModelInstance); | |
| } | |
| } | |
| ScriptedViewModel::~ScriptedViewModel() | |
| { | |
| if (m_scriptingContext != nullptr) | |
| { | |
| m_scriptingContext->untrackViewModelInstance(m_viewModelInstance.get()); | |
| } | |
| for (auto itr : m_propertyRefs) | |
| { | |
| lua_unref(m_state, itr.second); | |
| } | |
| } | |
| int ScriptedViewModel::instance(lua_State* L) | |
| { | |
| if (m_viewModel) | |
| { | |
| int nargs = lua_gettop(L); | |
| if (nargs == 2) | |
| { | |
| if (lua_isnoneornil(L, -1) || !lua_isstring(L, -1)) | |
| { | |
| auto instance = m_viewModel->createInstance(); | |
| lua_newrive<ScriptedViewModel>(L, L, m_viewModel, instance); | |
| } | |
| else | |
| { | |
| const char* instanceName = lua_tostring(L, -1); | |
| auto instance = m_viewModel->createFromInstance(instanceName); | |
| if (!instance) | |
| { | |
| instance = m_viewModel->createInstance(); | |
| } | |
| lua_newrive<ScriptedViewModel>(L, L, m_viewModel, instance); | |
| } | |
| } | |
| else | |
| { | |
| auto instance = m_viewModel->createInstance(); | |
| lua_newrive<ScriptedViewModel>(L, L, m_viewModel, instance); | |
| } | |
| } | |
| else | |
| { | |
| lua_pushnil(L); | |
| } | |
| return 1; | |
| } | |
| #ifdef WITH_RIVE_TOOLS | |
| static ScriptedProperty* scriptedPropertyOrNull(lua_State* L, int idx) | |
| { | |
| if (!lua_isuserdata(L, idx)) | |
| { | |
| return nullptr; | |
| } | |
| switch (lua_userdatatag(L, idx)) | |
| { | |
| case ScriptedPropertyNumber::luaTag: | |
| case ScriptedPropertyTrigger::luaTag: | |
| case ScriptedPropertyList::luaTag: | |
| case ScriptedPropertyColor::luaTag: | |
| case ScriptedPropertyString::luaTag: | |
| case ScriptedPropertyBoolean::luaTag: | |
| case ScriptedPropertyEnum::luaTag: | |
| case ScriptedPropertyImage::luaTag: | |
| case ScriptedPropertyFont::luaTag: | |
| case ScriptedPropertyBlob::luaTag: | |
| return (ScriptedProperty*)lua_touserdata(L, idx); | |
| default: | |
| return nullptr; | |
| } | |
| } | |
| #endif | |
| int ScriptedViewModel::pushValue(const char* name, int coreType) | |
| { | |
| auto itr = m_propertyRefs.find(name); | |
| if (itr != m_propertyRefs.end()) | |
| { | |
| lua_rawgeti(m_state, LUA_REGISTRYINDEX, itr->second); | |
| #ifdef WITH_RIVE_TOOLS | |
| // Orphan properties (which are only tracked/swept under | |
| // WITH_RIVE_TOOLS) can be disposed out from under us when a | |
| // scripting-context regeneration (e.g. the editor's recompileAll) | |
| // sweeps them but keeps this lua_State alive — leaving the | |
| // ScriptedViewModel (cached on a long-lived artboard/self) pointing at | |
| // dead wrappers. | |
| ScriptedProperty* cached = scriptedPropertyOrNull(m_state, -1); | |
| if (cached != nullptr && cached->disposed()) | |
| { | |
| lua_pop(m_state, 1); | |
| lua_unref(m_state, itr->second); | |
| m_propertyRefs.erase(itr); | |
| } | |
| else | |
| { | |
| return 1; | |
| } | |
| #else | |
| return 1; | |
| #endif | |
| } | |
| // To be fully typesafe at runtime we should check the property in the | |
| // viewmodel and make sure the one in the value matches the same type or | |
| // return a default based on the type of the viewmodel, but this incurs | |
| // extra performance and since our type system at edit-time lets the user be | |
| // intentional, for now we runtime error if the type is un-expected and we | |
| // optimize for the best case. | |
| auto vmi = m_viewModelInstance; | |
| if (!vmi) | |
| { | |
| // Get type from viewmodel if we have one and user didn't request a type | |
| // dynamically. | |
| if (coreType != 0 || !m_viewModel) | |
| { | |
| lua_pushnil(m_state); | |
| } | |
| else | |
| { | |
| auto prop = m_viewModel->property(name); | |
| switch (prop->coreType()) | |
| { | |
| case ViewModelPropertyNumberBase::typeKey: | |
| lua_newrive<ScriptedPropertyNumber>(m_state, | |
| m_state, | |
| nullptr); | |
| break; | |
| case ViewModelPropertyTriggerBase::typeKey: | |
| lua_newrive<ScriptedPropertyTrigger>(m_state, | |
| m_state, | |
| nullptr); | |
| break; | |
| case ViewModelPropertyListBase::typeKey: | |
| lua_newrive<ScriptedPropertyList>(m_state, | |
| m_state, | |
| nullptr); | |
| break; | |
| case ViewModelPropertyColorBase::typeKey: | |
| lua_newrive<ScriptedPropertyColor>(m_state, | |
| m_state, | |
| nullptr); | |
| break; | |
| case ViewModelPropertyStringBase::typeKey: | |
| lua_newrive<ScriptedPropertyString>(m_state, | |
| m_state, | |
| nullptr); | |
| break; | |
| case ViewModelPropertyBooleanBase::typeKey: | |
| lua_newrive<ScriptedPropertyBoolean>(m_state, | |
| m_state, | |
| nullptr); | |
| break; | |
| case ViewModelPropertyEnumBase::typeKey: | |
| lua_newrive<ScriptedPropertyEnum>(m_state, | |
| m_state, | |
| nullptr); | |
| break; | |
| case ViewModelPropertyViewModelBase::typeKey: | |
| lua_newrive<ScriptedPropertyViewModel>(m_state, | |
| m_state, | |
| nullptr, | |
| nullptr); | |
| break; | |
| case ViewModelPropertyAssetImageBase::typeKey: | |
| lua_newrive<ScriptedPropertyImage>(m_state, | |
| m_state, | |
| nullptr); | |
| break; | |
| case ViewModelPropertyAssetFontBase::typeKey: | |
| lua_newrive<ScriptedPropertyFont>(m_state, | |
| m_state, | |
| nullptr); | |
| break; | |
| case ViewModelPropertyAssetBlobBase::typeKey: | |
| lua_newrive<ScriptedPropertyBlob>(m_state, | |
| m_state, | |
| nullptr); | |
| break; | |
| case ViewModelPropertySymbolListIndexBase::typeKey: | |
| lua_pushinteger(m_state, 1); | |
| break; | |
| } | |
| } | |
| } | |
| else | |
| { | |
| auto propValue = vmi->propertyValue(name); | |
| if (propValue == nullptr || | |
| (coreType != 0 && propValue->coreType() != coreType)) | |
| { | |
| lua_pushnil(m_state); | |
| } | |
| else | |
| { | |
| pushViewModelInstanceValue(m_state, propValue); | |
| } | |
| } | |
| m_propertyRefs[name] = lua_ref(m_state, -1); | |
| return 1; | |
| } | |
| int ScriptedViewModel::pushIndex() | |
| { | |
| auto prop = m_viewModelInstance->propertyValue(SymbolType::itemIndex); | |
| if (prop && prop->is<ViewModelInstanceSymbolListIndex>()) | |
| { | |
| lua_pushinteger( | |
| m_state, | |
| prop->as<ViewModelInstanceSymbolListIndex>()->propertyValue()); | |
| } | |
| else | |
| { | |
| lua_pushinteger(m_state, -1); | |
| } | |
| return 1; | |
| } | |
| int ScriptedPropertyViewModel::pushValue() | |
| { | |
| if (m_valueRef != 0) | |
| { | |
| lua_rawgeti(m_state, LUA_REGISTRYINDEX, m_valueRef); | |
| return 1; | |
| } | |
| if (m_instanceValue) | |
| { | |
| m_instanceValue->addDependent(this); | |
| // Derive the view model from the current reference; the creation-time | |
| // one goes stale when the reference binds or swaps after a relink. | |
| auto reference = m_instanceValue->as<ViewModelInstanceViewModel>() | |
| ->referenceViewModelInstance(); | |
| lua_newrive<ScriptedViewModel>(m_state, | |
| m_state, | |
| reference != nullptr | |
| ? ref_rcp(reference->viewModel()) | |
| : m_viewModel, | |
| reference); | |
| } | |
| else | |
| { | |
| // Push nil or push an empty model ref? For now empty/with def values. | |
| lua_newrive<ScriptedViewModel>(m_state, m_state, m_viewModel, nullptr); | |
| } | |
| m_valueRef = lua_ref(m_state, -1); | |
| return 1; | |
| } | |
| void ScriptedPropertyViewModel::relinkDataBind() { clearRef(); } | |
| static int property_vm_index(lua_State* L) | |
| { | |
| int atom; | |
| const char* key = lua_tostringatom(L, 2, &atom); | |
| if (!key) | |
| { | |
| luaL_typeerrorL(L, 2, lua_typename(L, LUA_TSTRING)); | |
| return 0; | |
| } | |
| auto vmProp = lua_torive<ScriptedPropertyViewModel>(L, 1); | |
| assert(vmProp->state() == L); | |
| switch (atom) | |
| { | |
| case (int)LuaAtoms::value: | |
| return vmProp->pushValue(); | |
| default: | |
| return 0; | |
| } | |
| } | |
| static int property_vm_newindex(lua_State* L) | |
| { | |
| int atom; | |
| const char* key = lua_tostringatom(L, 2, &atom); | |
| if (!key) | |
| { | |
| luaL_typeerrorL(L, 2, lua_typename(L, LUA_TSTRING)); | |
| return 0; | |
| } | |
| auto scriptedPropertyViewModel = | |
| lua_torive<ScriptedPropertyViewModel>(L, 1); | |
| switch (atom) | |
| { | |
| case (int)LuaAtoms::value: | |
| { | |
| auto vm = lua_torive<ScriptedViewModel>(L, 3); | |
| scriptedPropertyViewModel->setValue(vm); | |
| break; | |
| } | |
| default: | |
| return 0; | |
| } | |
| return 0; | |
| } | |
| static int vm_index(lua_State* L) | |
| { | |
| auto vm = lua_torive<ScriptedViewModel>(L, 1); | |
| size_t namelen = 0; | |
| const char* name = luaL_checklstring(L, 2, &namelen); | |
| assert(vm->state() == L); | |
| return vm->pushValue(name); | |
| } | |
| static int property_namecall_atom(lua_State* L, | |
| ScriptedProperty* property, | |
| int atom, | |
| bool& error) | |
| { | |
| switch (atom) | |
| { | |
| case (int)LuaAtoms::addListener: | |
| { | |
| assert(property->state() == L); | |
| return property->addListener(); | |
| } | |
| case (int)LuaAtoms::removeListener: | |
| { | |
| assert(property->state() == L); | |
| return property->removeListener(); | |
| } | |
| case (int)LuaAtoms::fire: | |
| { | |
| auto value = property->instanceValue(); | |
| if (value != nullptr && value->is<ViewModelInstanceTrigger>()) | |
| { | |
| value->as<ViewModelInstanceTrigger>()->trigger(); | |
| } | |
| return 0; | |
| } | |
| case (int)LuaAtoms::push: | |
| { | |
| auto scriptedViewModel = lua_torive<ScriptedViewModel>(L, 2); | |
| auto vmi = scriptedViewModel->viewModelInstance(); | |
| auto copy = ref_rcp(vmi.get()); | |
| auto list = property->instanceValue()->as<ViewModelInstanceList>(); | |
| auto listItem = make_rcp<ViewModelInstanceListItem>(); | |
| listItem->viewModelInstance(copy); | |
| list->addItem(listItem); | |
| return 0; | |
| } | |
| case (int)LuaAtoms::pop: | |
| { | |
| auto list = property->instanceValue()->as<ViewModelInstanceList>(); | |
| auto listItem = list->pop(); | |
| if (listItem) | |
| { | |
| auto vmi = listItem->viewModelInstance(); | |
| if (vmi) | |
| { | |
| lua_newrive<ScriptedViewModel>(L, | |
| L, | |
| ref_rcp(vmi->viewModel()), | |
| vmi); | |
| return 1; | |
| } | |
| } | |
| return 0; | |
| } | |
| case (int)LuaAtoms::swap: | |
| { | |
| auto list = property->instanceValue()->as<ViewModelInstanceList>(); | |
| auto index1 = lua_tounsigned(L, 2); | |
| auto index2 = lua_tounsigned(L, 3); | |
| list->swap(index1 - 1, index2 - 1); | |
| return 0; | |
| } | |
| case (int)LuaAtoms::shift: | |
| { | |
| auto list = property->instanceValue()->as<ViewModelInstanceList>(); | |
| auto listItem = list->shift(); | |
| if (listItem) | |
| { | |
| auto vmi = listItem->viewModelInstance(); | |
| if (vmi) | |
| { | |
| lua_newrive<ScriptedViewModel>(L, | |
| L, | |
| ref_rcp(vmi->viewModel()), | |
| vmi); | |
| return 1; | |
| } | |
| } | |
| return 0; | |
| } | |
| case (int)LuaAtoms::clear: | |
| { | |
| auto list = property->instanceValue()->as<ViewModelInstanceList>(); | |
| list->removeAllItems(); | |
| return 0; | |
| } | |
| case (int)LuaAtoms::insert: | |
| { | |
| auto scriptedViewModel = lua_torive<ScriptedViewModel>(L, 2); | |
| auto index = lua_tounsigned(L, 3); | |
| auto vmi = scriptedViewModel->viewModelInstance(); | |
| auto copy = ref_rcp(vmi.get()); | |
| auto list = property->instanceValue()->as<ViewModelInstanceList>(); | |
| auto listItem = make_rcp<ViewModelInstanceListItem>(); | |
| listItem->viewModelInstance(copy); | |
| list->addItemAt(listItem, index - 1); | |
| return 0; | |
| } | |
| case (int)LuaAtoms::remove: | |
| { | |
| auto list = property->instanceValue()->as<ViewModelInstanceList>(); | |
| auto* vmUser = static_cast<ScriptedViewModel*>( | |
| lua_touserdatatagged(L, 2, ScriptedViewModel::luaTag)); | |
| if (vmUser != nullptr) | |
| { | |
| auto vmi = vmUser->viewModelInstance(); | |
| if (vmi) | |
| { | |
| for (const auto& item : list->listItems()) | |
| { | |
| if (item->viewModelInstance() == vmi) | |
| { | |
| list->removeItem(item); | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| return 0; | |
| } | |
| case (int)LuaAtoms::removeAt: | |
| { | |
| auto list = property->instanceValue()->as<ViewModelInstanceList>(); | |
| const lua_Integer luaIndex = luaL_checkinteger(L, 2); | |
| const size_t count = list->listItems().size(); | |
| if (luaIndex < 1 || | |
| static_cast<uint64_t>(luaIndex) > static_cast<uint64_t>(count)) | |
| { | |
| luaL_error(L, "removeAt index out of range"); | |
| } | |
| list->removeItem(static_cast<int>(luaIndex - 1)); | |
| return 0; | |
| } | |
| case (int)LuaAtoms::removeAllOf: | |
| { | |
| auto list = property->instanceValue()->as<ViewModelInstanceList>(); | |
| auto* vmUser = static_cast<ScriptedViewModel*>( | |
| lua_touserdatatagged(L, 2, ScriptedViewModel::luaTag)); | |
| if (vmUser != nullptr) | |
| { | |
| auto vmi = vmUser->viewModelInstance(); | |
| if (vmi) | |
| { | |
| list->removeAllItemsWithViewModelInstance(vmi.get()); | |
| } | |
| } | |
| return 0; | |
| } | |
| case (int)LuaAtoms::values: | |
| { | |
| auto viewModelInstanceEnum = | |
| property->instanceValue()->as<ViewModelInstanceValue>(); | |
| DataEnum* dataEnum = nullptr; | |
| auto viewModelPropertyEnum = | |
| viewModelInstanceEnum->viewModelProperty() | |
| ->as<ViewModelPropertyEnum>(); | |
| if (viewModelPropertyEnum) | |
| { | |
| dataEnum = viewModelPropertyEnum->dataEnum(); | |
| } | |
| lua_newrive<ScriptedEnumValues>(L, L, dataEnum); | |
| return 1; | |
| } | |
| } | |
| error = true; | |
| return 0; | |
| } | |
| static int vm_namecall(lua_State* L) | |
| { | |
| int atom; | |
| const char* str = lua_namecallatom(L, &atom); | |
| if (str != nullptr) | |
| { | |
| auto vm = lua_torive<ScriptedViewModel>(L, 1); | |
| switch (atom) | |
| { | |
| case (int)LuaAtoms::getNumber: | |
| { | |
| size_t namelen = 0; | |
| const char* name = luaL_checklstring(L, 2, &namelen); | |
| assert(vm->state() == L); | |
| return vm->pushValue(name, | |
| ViewModelInstanceNumberBase::typeKey); | |
| } | |
| case (int)LuaAtoms::getTrigger: | |
| { | |
| size_t namelen = 0; | |
| const char* name = luaL_checklstring(L, 2, &namelen); | |
| assert(vm->state() == L); | |
| return vm->pushValue(name, | |
| ViewModelInstanceTriggerBase::typeKey); | |
| } | |
| case (int)LuaAtoms::getString: | |
| { | |
| size_t namelen = 0; | |
| const char* name = luaL_checklstring(L, 2, &namelen); | |
| assert(vm->state() == L); | |
| return vm->pushValue(name, | |
| ViewModelInstanceStringBase::typeKey); | |
| } | |
| case (int)LuaAtoms::getBoolean: | |
| { | |
| size_t namelen = 0; | |
| const char* name = luaL_checklstring(L, 2, &namelen); | |
| assert(vm->state() == L); | |
| return vm->pushValue(name, | |
| ViewModelInstanceBooleanBase::typeKey); | |
| } | |
| case (int)LuaAtoms::getColor: | |
| { | |
| size_t namelen = 0; | |
| const char* name = luaL_checklstring(L, 2, &namelen); | |
| assert(vm->state() == L); | |
| return vm->pushValue(name, ViewModelInstanceColorBase::typeKey); | |
| } | |
| case (int)LuaAtoms::getList: | |
| { | |
| size_t namelen = 0; | |
| const char* name = luaL_checklstring(L, 2, &namelen); | |
| assert(vm->state() == L); | |
| return vm->pushValue(name, ViewModelInstanceListBase::typeKey); | |
| } | |
| case (int)LuaAtoms::getViewModel: | |
| { | |
| size_t namelen = 0; | |
| const char* name = luaL_checklstring(L, 2, &namelen); | |
| assert(vm->state() == L); | |
| return vm->pushValue(name, | |
| ViewModelInstanceViewModelBase::typeKey); | |
| } | |
| case (int)LuaAtoms::getEnum: | |
| { | |
| size_t namelen = 0; | |
| const char* name = luaL_checklstring(L, 2, &namelen); | |
| assert(vm->state() == L); | |
| return vm->pushValue(name, ViewModelInstanceEnumBase::typeKey); | |
| } | |
| case (int)LuaAtoms::getIndex: | |
| { | |
| assert(vm->state() == L); | |
| return vm->pushIndex(); | |
| } | |
| case (int)LuaAtoms::getImage: | |
| { | |
| size_t namelen = 0; | |
| const char* name = luaL_checklstring(L, 2, &namelen); | |
| assert(vm->state() == L); | |
| return vm->pushValue(name, | |
| ViewModelInstanceAssetImageBase::typeKey); | |
| } | |
| case (int)LuaAtoms::getFont: | |
| { | |
| size_t namelen = 0; | |
| const char* name = luaL_checklstring(L, 2, &namelen); | |
| assert(vm->state() == L); | |
| return vm->pushValue(name, | |
| ViewModelInstanceAssetFontBase::typeKey); | |
| } | |
| case (int)LuaAtoms::getBlob: | |
| { | |
| size_t namelen = 0; | |
| const char* name = luaL_checklstring(L, 2, &namelen); | |
| assert(vm->state() == L); | |
| return vm->pushValue(name, | |
| ViewModelInstanceAssetBlobBase::typeKey); | |
| } | |
| case (int)LuaAtoms::instance: | |
| case (int)LuaAtoms::newAtom: | |
| { | |
| assert(vm->state() == L); | |
| return vm->instance(L); | |
| } | |
| default: | |
| break; | |
| } | |
| } | |
| luaL_error(L, | |
| "%s is not a valid method of %s", | |
| str, | |
| ScriptedPropertyViewModel::luaName); | |
| return 0; | |
| } | |
| static int property_vm_namecall(lua_State* L) | |
| { | |
| int atom; | |
| const char* str = lua_namecallatom(L, &atom); | |
| if (str != nullptr) | |
| { | |
| auto vm = lua_torive<ScriptedPropertyViewModel>(L, 1); | |
| bool error = false; | |
| int stackChange = property_namecall_atom(L, vm, atom, error); | |
| if (!error) | |
| { | |
| return stackChange; | |
| } | |
| } | |
| luaL_error(L, | |
| "%s is not a valid method of %s", | |
| str, | |
| ScriptedPropertyViewModel::luaName); | |
| return 0; | |
| } | |
| static int property_namecall(lua_State* L) | |
| { | |
| int atom; | |
| const char* str = lua_namecallatom(L, &atom); | |
| const char* name = "Property"; | |
| if (str != nullptr) | |
| { | |
| auto tag = lua_userdatatag(L, 1); | |
| switch (tag) | |
| { | |
| case ScriptedPropertyNumber::luaTag: | |
| name = ScriptedPropertyNumber::luaName; | |
| break; | |
| case ScriptedPropertyTrigger::luaTag: | |
| name = ScriptedPropertyTrigger::luaName; | |
| break; | |
| case ScriptedPropertyColor::luaTag: | |
| name = ScriptedPropertyColor::luaName; | |
| break; | |
| case ScriptedPropertyString::luaTag: | |
| name = ScriptedPropertyString::luaName; | |
| break; | |
| case ScriptedPropertyBoolean::luaTag: | |
| name = ScriptedPropertyBoolean::luaName; | |
| break; | |
| case ScriptedPropertyEnum::luaTag: | |
| name = ScriptedPropertyEnum::luaName; | |
| break; | |
| case ScriptedPropertyList::luaTag: | |
| name = ScriptedPropertyList::luaName; | |
| break; | |
| case ScriptedPropertyImage::luaTag: | |
| name = ScriptedPropertyImage::luaName; | |
| break; | |
| case ScriptedPropertyFont::luaTag: | |
| name = ScriptedPropertyFont::luaName; | |
| break; | |
| case ScriptedPropertyBlob::luaTag: | |
| name = ScriptedPropertyBlob::luaName; | |
| break; | |
| default: | |
| luaL_typeerror(L, 1, name); | |
| break; | |
| } | |
| auto vm = (ScriptedProperty*)lua_touserdata(L, 1); | |
| bool error = false; | |
| int stackChange = property_namecall_atom(L, vm, atom, error); | |
| if (!error) | |
| { | |
| return stackChange; | |
| } | |
| } | |
| luaL_error(L, "%s is not a valid method of %s", str, name); | |
| return 0; | |
| } | |
| ScriptedPropertyNumber::ScriptedPropertyNumber( | |
| lua_State* L, | |
| rcp<ViewModelInstanceNumber> value) : | |
| ScriptedProperty(L, std::move(value)) | |
| {} | |
| ScriptedPropertyTrigger::ScriptedPropertyTrigger( | |
| lua_State* L, | |
| rcp<ViewModelInstanceTrigger> value) : | |
| ScriptedProperty(L, std::move(value)) | |
| {} | |
| void ScriptedPropertyNumber::setValue(float value) | |
| { | |
| if (m_instanceValue) | |
| { | |
| m_instanceValue->as<ViewModelInstanceNumber>()->propertyValue(value); | |
| } | |
| } | |
| int ScriptedPropertyNumber::pushValue() | |
| { | |
| if (m_instanceValue) | |
| { | |
| lua_pushnumber( | |
| m_state, | |
| m_instanceValue->as<ViewModelInstanceNumber>()->propertyValue()); | |
| } | |
| else | |
| { | |
| lua_pushnumber(m_state, 0); | |
| } | |
| return 1; | |
| } | |
| ScriptedPropertyColor::ScriptedPropertyColor( | |
| lua_State* L, | |
| rcp<ViewModelInstanceColor> value) : | |
| ScriptedProperty(L, std::move(value)) | |
| {} | |
| void ScriptedPropertyColor::setValue(unsigned value) | |
| { | |
| if (m_instanceValue) | |
| { | |
| m_instanceValue->as<ViewModelInstanceColor>()->propertyValue( | |
| (int)value); | |
| } | |
| } | |
| int ScriptedPropertyColor::pushValue() | |
| { | |
| if (m_instanceValue) | |
| { | |
| lua_pushunsigned(m_state, | |
| (unsigned)m_instanceValue->as<ViewModelInstanceColor>() | |
| ->propertyValue()); | |
| } | |
| else | |
| { | |
| lua_pushunsigned(m_state, 0); | |
| } | |
| return 1; | |
| } | |
| ScriptedPropertyString::ScriptedPropertyString( | |
| lua_State* L, | |
| rcp<ViewModelInstanceString> value) : | |
| ScriptedProperty(L, std::move(value)) | |
| {} | |
| void ScriptedPropertyString::setValue(const std::string& value) | |
| { | |
| if (m_instanceValue) | |
| { | |
| m_instanceValue->as<ViewModelInstanceString>()->propertyValue(value); | |
| } | |
| } | |
| int ScriptedPropertyString::pushValue() | |
| { | |
| if (m_instanceValue) | |
| { | |
| lua_pushstring(m_state, | |
| m_instanceValue->as<ViewModelInstanceString>() | |
| ->propertyValue() | |
| .c_str()); | |
| } | |
| else | |
| { | |
| lua_pushstring(m_state, ""); | |
| } | |
| return 1; | |
| } | |
| ScriptedPropertyBoolean::ScriptedPropertyBoolean( | |
| lua_State* L, | |
| rcp<ViewModelInstanceBoolean> value) : | |
| ScriptedProperty(L, std::move(value)) | |
| {} | |
| void ScriptedPropertyBoolean::setValue(bool value) | |
| { | |
| if (m_instanceValue) | |
| { | |
| m_instanceValue->as<ViewModelInstanceBoolean>()->propertyValue(value); | |
| } | |
| } | |
| int ScriptedPropertyBoolean::pushValue() | |
| { | |
| if (m_instanceValue) | |
| { | |
| lua_pushboolean( | |
| m_state, | |
| m_instanceValue->as<ViewModelInstanceBoolean>()->propertyValue()); | |
| } | |
| else | |
| { | |
| lua_pushboolean(m_state, 0); | |
| } | |
| return 1; | |
| } | |
| ScriptedPropertyEnum::ScriptedPropertyEnum(lua_State* L, | |
| rcp<ViewModelInstanceEnum> value) : | |
| ScriptedProperty(L, std::move(value)) | |
| {} | |
| void ScriptedPropertyEnum::setValue(const std::string& value) | |
| { | |
| if (m_instanceValue) | |
| { | |
| m_instanceValue->as<ViewModelInstanceEnum>()->value(value); | |
| } | |
| } | |
| int ScriptedPropertyEnum::pushValue() | |
| { | |
| if (m_instanceValue) | |
| { | |
| auto vmi = m_instanceValue->as<ViewModelInstanceEnum>(); | |
| auto vmProp = vmi->viewModelProperty()->as<ViewModelPropertyEnum>(); | |
| if (vmProp != nullptr && vmProp->dataEnum() != nullptr) | |
| { | |
| auto values = vmProp->dataEnum()->values(); | |
| uint32_t index = vmi->propertyValue(); | |
| if (index < values.size()) | |
| { | |
| lua_pushstring(m_state, values[index]->key().c_str()); | |
| return 1; | |
| } | |
| } | |
| } | |
| lua_pushstring(m_state, ""); | |
| return 1; | |
| } | |
| ScriptedPropertyList::ScriptedPropertyList(lua_State* L, | |
| rcp<ViewModelInstanceList> value) : | |
| ScriptedProperty(L, std::move(value)) | |
| {} | |
| ScriptedPropertyList::~ScriptedPropertyList() | |
| { | |
| for (const auto& pair : m_propertyRefs) | |
| { | |
| lua_unref(m_state, pair.second); | |
| } | |
| } | |
| void ScriptedPropertyList::valueChanged() | |
| { | |
| m_changed = true; | |
| ScriptedProperty::valueChanged(); | |
| } | |
| int ScriptedPropertyList::pushLength() | |
| { | |
| if (m_instanceValue) | |
| { | |
| lua_pushinteger(m_state, | |
| (int)m_instanceValue->as<ViewModelInstanceList>() | |
| ->listItems() | |
| .size()); | |
| } | |
| else | |
| { | |
| lua_pushinteger(m_state, 0); | |
| } | |
| return 1; | |
| } | |
| int ScriptedPropertyList::pushValue(int index) | |
| { | |
| if (m_instanceValue) | |
| { | |
| auto items = m_instanceValue->as<ViewModelInstanceList>()->listItems(); | |
| if (m_changed) | |
| { | |
| std::unordered_map<ViewModelInstance*, int> refs; | |
| // re-validate references. | |
| for (auto& item : items) | |
| { | |
| auto vmi = item->viewModelInstance().get(); | |
| auto itr = m_propertyRefs.find(vmi); | |
| if (itr != m_propertyRefs.end()) | |
| { | |
| refs[vmi] = itr->second; | |
| m_propertyRefs.erase(itr); | |
| } | |
| } | |
| for (const auto& pair : m_propertyRefs) | |
| { | |
| lua_unref(m_state, pair.second); | |
| } | |
| m_propertyRefs = refs; | |
| m_changed = false; | |
| } | |
| if (index >= items.size()) | |
| { | |
| lua_pushnil(m_state); | |
| } | |
| else | |
| { | |
| auto listItem = items[index]; | |
| auto vmi = listItem->viewModelInstance(); | |
| auto itr = m_propertyRefs.find(vmi.get()); | |
| if (itr != m_propertyRefs.end()) | |
| { | |
| lua_rawgeti(m_state, LUA_REGISTRYINDEX, itr->second); | |
| } | |
| else | |
| { | |
| lua_newrive<ScriptedViewModel>(m_state, | |
| m_state, | |
| ref_rcp(vmi->viewModel()), | |
| vmi); | |
| m_propertyRefs[vmi.get()] = lua_ref(m_state, -1); | |
| } | |
| } | |
| } | |
| else | |
| { | |
| lua_pushnil(m_state); | |
| } | |
| return 1; | |
| } | |
| ScriptedPropertyImage::ScriptedPropertyImage( | |
| lua_State* L, | |
| rcp<ViewModelInstanceAssetImage> value) : | |
| ScriptedProperty(L, std::move(value)) | |
| {} | |
| void ScriptedPropertyImage::setValue(ScriptedImage* scriptedImage) | |
| { | |
| if (!m_instanceValue) | |
| { | |
| return; | |
| } | |
| m_instanceValue->as<ViewModelInstanceAssetImage>()->value( | |
| scriptedImage != nullptr ? scriptedImage->image.get() : nullptr); | |
| } | |
| int ScriptedPropertyImage::pushValue() | |
| { | |
| if (m_cachedValueRef != 0) | |
| { | |
| lua_rawgeti(m_state, LUA_REGISTRYINDEX, m_cachedValueRef); | |
| return 1; | |
| } | |
| if (m_instanceValue) | |
| { | |
| auto vmi = m_instanceValue->as<ViewModelInstanceAssetImage>(); | |
| RenderImage* renderImage = nullptr; | |
| if (auto asset = vmi->asset()) | |
| { | |
| renderImage = asset->renderImage(); | |
| } | |
| // Fall back to the file's asset registry when no image is embedded | |
| // on the instance — mirrors DataBindContextValueAssetImage. | |
| if (renderImage == nullptr && owner() != nullptr) | |
| { | |
| if (auto scriptAsset = owner()->scriptAsset()) | |
| { | |
| if (auto file = scriptAsset->file()) | |
| { | |
| auto fileAsset = file->asset(vmi->propertyValue()); | |
| if (fileAsset != nullptr && fileAsset->is<ImageAsset>()) | |
| { | |
| renderImage = | |
| fileAsset->as<ImageAsset>()->renderImage(); | |
| } | |
| } | |
| } | |
| } | |
| #ifdef WITH_RIVE_TOOLS | |
| // Editor/Dart path: when the property is constructed without a | |
| // ScriptedObject owner (owner() is null), reach the File through | |
| // the ViewModel instead. | |
| if (renderImage == nullptr && owner() == nullptr) | |
| { | |
| if (auto vmInstance = vmi->viewModelInstance()) | |
| { | |
| if (auto viewModel = vmInstance->viewModel()) | |
| { | |
| if (auto file = viewModel->file()) | |
| { | |
| auto fileAsset = file->asset(vmi->propertyValue()); | |
| if (fileAsset != nullptr && fileAsset->is<ImageAsset>()) | |
| { | |
| renderImage = | |
| fileAsset->as<ImageAsset>()->renderImage(); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| #endif | |
| if (renderImage != nullptr) | |
| { | |
| // Use the out-of-line `ScriptedImage::luaNew` factory rather | |
| // than `lua_newrive<ScriptedImage>` directly: when ore is | |
| // enabled, ScriptedImage holds an `rcp<ore::TextureView>` and | |
| // its destructor needs the full TextureView definition (this | |
| // TU only sees the forward decl in `rive_lua_libs.hpp`). The | |
| // factory lives in `lua_gpu.cpp` / `lua_image.cpp` where the | |
| // ore headers are visible. | |
| auto scriptedImage = ScriptedImage::luaNew(m_state); | |
| scriptedImage->image = ref_rcp(renderImage); | |
| m_cachedValueRef = lua_ref(m_state, -1); | |
| return 1; | |
| } | |
| } | |
| lua_pushnil(m_state); | |
| return 1; | |
| } | |
| ScriptedPropertyFont::ScriptedPropertyFont( | |
| lua_State* L, | |
| rcp<ViewModelInstanceAssetFont> value) : | |
| ScriptedProperty(L, std::move(value)) | |
| {} | |
| void ScriptedPropertyFont::setValue(ScriptedFont* scriptedFont) | |
| { | |
| if (!m_instanceValue) | |
| { | |
| return; | |
| } | |
| m_instanceValue->as<ViewModelInstanceAssetFont>()->value( | |
| scriptedFont != nullptr ? scriptedFont->font.get() : nullptr); | |
| } | |
| int ScriptedPropertyFont::pushValue() | |
| { | |
| if (m_cachedValueRef != 0) | |
| { | |
| lua_rawgeti(m_state, LUA_REGISTRYINDEX, m_cachedValueRef); | |
| return 1; | |
| } | |
| if (m_instanceValue) | |
| { | |
| auto vmi = m_instanceValue->as<ViewModelInstanceAssetFont>(); | |
| Font* font = nullptr; | |
| if (auto asset = vmi->asset()) | |
| { | |
| font = asset->font().get(); | |
| } | |
| // Fall back to the file's asset registry when no font is embedded | |
| // on the instance — mirrors the image property. | |
| if (font == nullptr && owner() != nullptr) | |
| { | |
| if (auto scriptAsset = owner()->scriptAsset()) | |
| { | |
| if (auto file = scriptAsset->file()) | |
| { | |
| auto fileAsset = file->asset(vmi->propertyValue()); | |
| if (fileAsset != nullptr && fileAsset->is<FontAsset>()) | |
| { | |
| font = fileAsset->as<FontAsset>()->font().get(); | |
| } | |
| } | |
| } | |
| } | |
| #ifdef WITH_RIVE_TOOLS | |
| // Editor/Dart path: when the property is constructed without a | |
| // ScriptedObject owner, reach the File through the ViewModel. | |
| if (font == nullptr && owner() == nullptr) | |
| { | |
| if (auto vmInstance = vmi->viewModelInstance()) | |
| { | |
| if (auto viewModel = vmInstance->viewModel()) | |
| { | |
| if (auto file = viewModel->file()) | |
| { | |
| auto fileAsset = file->asset(vmi->propertyValue()); | |
| if (fileAsset != nullptr && fileAsset->is<FontAsset>()) | |
| { | |
| font = fileAsset->as<FontAsset>()->font().get(); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| #endif | |
| if (font != nullptr) | |
| { | |
| auto scriptedFont = lua_newrive<ScriptedFont>(m_state); | |
| scriptedFont->font = ref_rcp(font); | |
| m_cachedValueRef = lua_ref(m_state, -1); | |
| return 1; | |
| } | |
| } | |
| lua_pushnil(m_state); | |
| return 1; | |
| } | |
| ScriptedPropertyBlob::ScriptedPropertyBlob( | |
| lua_State* L, | |
| rcp<ViewModelInstanceAssetBlob> value) : | |
| ScriptedProperty(L, std::move(value)) | |
| {} | |
| void ScriptedPropertyBlob::setValue(BlobAsset* blob) | |
| { | |
| if (!m_instanceValue) | |
| { | |
| return; | |
| } | |
| m_instanceValue->as<ViewModelInstanceAssetBlob>()->value(blob); | |
| } | |
| int ScriptedPropertyBlob::pushValue() | |
| { | |
| if (m_cachedValueRef != 0) | |
| { | |
| lua_rawgeti(m_state, LUA_REGISTRYINDEX, m_cachedValueRef); | |
| return 1; | |
| } | |
| if (m_instanceValue) | |
| { | |
| auto vmi = m_instanceValue->as<ViewModelInstanceAssetBlob>(); | |
| rcp<FileAsset> fileAsset = nullptr; | |
| // A non-null asset means the blob was set directly on the instance | |
| // (e.g. `prop.value = ...` from a script), including a legitimately | |
| // empty blob. A null asset means the value is id-bound (or unset) — | |
| // resolve it through the file registry. | |
| if (auto asset = vmi->asset()) | |
| { | |
| fileAsset = asset; | |
| } | |
| if (fileAsset == nullptr && owner() != nullptr) | |
| { | |
| if (auto scriptAsset = owner()->scriptAsset()) | |
| { | |
| if (auto file = scriptAsset->file()) | |
| { | |
| auto candidate = file->asset(vmi->propertyValue()); | |
| if (candidate != nullptr && candidate->is<BlobAsset>()) | |
| { | |
| fileAsset = candidate; | |
| } | |
| } | |
| } | |
| } | |
| #ifdef WITH_RIVE_TOOLS | |
| // Editor/Dart path: when the property is constructed without a | |
| // ScriptedObject owner, reach the File through the ViewModel. | |
| if (fileAsset == nullptr && owner() == nullptr) | |
| { | |
| if (auto vmInstance = vmi->viewModelInstance()) | |
| { | |
| if (auto viewModel = vmInstance->viewModel()) | |
| { | |
| if (auto file = viewModel->file()) | |
| { | |
| auto candidate = file->asset(vmi->propertyValue()); | |
| if (candidate != nullptr && candidate->is<BlobAsset>()) | |
| { | |
| fileAsset = candidate; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| #endif | |
| if (fileAsset != nullptr) | |
| { | |
| auto scriptedBlob = lua_newrive<ScriptedBlob>(m_state); | |
| scriptedBlob->asset = fileAsset; | |
| m_cachedValueRef = lua_ref(m_state, -1); | |
| return 1; | |
| } | |
| } | |
| lua_pushnil(m_state); | |
| return 1; | |
| } | |
| int ScriptedEnumValues::pushValue(int index) | |
| { | |
| if (m_dataEnum && m_state) | |
| { | |
| auto values = m_dataEnum->values(); | |
| if (index >= 0 && index < values.size()) | |
| { | |
| auto value = values[index]; | |
| lua_pushstring(m_state, value->key().c_str()); | |
| return 1; | |
| } | |
| } | |
| lua_pushnil(m_state); | |
| return 1; | |
| } | |
| int ScriptedEnumValues::pushLength() | |
| { | |
| if (m_dataEnum) | |
| { | |
| lua_pushinteger(m_state, (int)m_dataEnum->values().size()); | |
| } | |
| else | |
| { | |
| lua_pushinteger(m_state, 0); | |
| } | |
| return 1; | |
| } | |
| // Direct field getters: dispatched by LOP_GETTABLEKS on `prop.value` / | |
| // `list.length`. Slow paths (property_*_index) remain reachable for | |
| // computed-key access and lua_getfield from C. | |
| static void property_number_direct_value(void* udata, void* result) | |
| { | |
| auto* p = (ScriptedPropertyNumber*)udata; | |
| auto* iv = p->instanceValue(); | |
| lua_userdatadirectfield_setnumber( | |
| result, | |
| iv ? iv->as<ViewModelInstanceNumber>()->propertyValue() : 0); | |
| } | |
| static void property_color_direct_value(void* udata, void* result) | |
| { | |
| auto* p = (ScriptedPropertyColor*)udata; | |
| auto* iv = p->instanceValue(); | |
| lua_userdatadirectfield_setnumber( | |
| result, | |
| iv ? (unsigned)iv->as<ViewModelInstanceColor>()->propertyValue() : 0); | |
| } | |
| static void property_boolean_direct_value(void* udata, void* result) | |
| { | |
| auto* p = (ScriptedPropertyBoolean*)udata; | |
| auto* iv = p->instanceValue(); | |
| lua_userdatadirectfield_setboolean( | |
| result, | |
| iv ? (iv->as<ViewModelInstanceBoolean>()->propertyValue() ? 1 : 0) : 0); | |
| } | |
| static void property_list_direct_length(void* udata, void* result) | |
| { | |
| auto* p = (ScriptedPropertyList*)udata; | |
| auto* iv = p->instanceValue(); | |
| lua_userdatadirectfield_setnumber( | |
| result, | |
| iv ? (double)iv->as<ViewModelInstanceList>()->listItems().size() : 0); | |
| } | |
| static int property_list_index(lua_State* L) | |
| { | |
| int atom; | |
| const char* key = lua_tostringatom(L, 2, &atom); | |
| auto propertyList = lua_torive<ScriptedPropertyList>(L, 1); | |
| // if it's not an atom it should be an index into the array | |
| if (!key) | |
| { | |
| int index = luaL_checkinteger(L, 2); | |
| return propertyList->pushValue(index - 1); | |
| } | |
| switch (atom) | |
| { | |
| case (int)LuaAtoms::length: | |
| assert(propertyList->state() == L); | |
| return propertyList->pushLength(); | |
| default: | |
| return 0; | |
| } | |
| } | |
| static int enum_value_length(lua_State* L) | |
| { | |
| auto enumValues = lua_torive<ScriptedEnumValues>(L, 1); | |
| assert(enumValues->state() == L); | |
| return enumValues->pushLength(); | |
| } | |
| static int enum_value_index(lua_State* L) | |
| { | |
| int atom; | |
| const char* key = lua_tostringatom(L, 2, &atom); | |
| auto enumValues = lua_torive<ScriptedEnumValues>(L, 1); | |
| // if it's not an atom it should be an index into the array | |
| if (!key) | |
| { | |
| int index = luaL_checkinteger(L, 2); | |
| return enumValues->pushValue(index - 1); | |
| } | |
| return 0; | |
| } | |
| static int property_number_index(lua_State* L) | |
| { | |
| int atom; | |
| const char* key = lua_tostringatom(L, 2, &atom); | |
| if (!key) | |
| { | |
| luaL_typeerrorL(L, 2, lua_typename(L, LUA_TSTRING)); | |
| return 0; | |
| } | |
| auto propertyNumber = lua_torive<ScriptedPropertyNumber>(L, 1); | |
| switch (atom) | |
| { | |
| case (int)LuaAtoms::value: | |
| assert(propertyNumber->state() == L); | |
| return propertyNumber->pushValue(); | |
| default: | |
| return 0; | |
| } | |
| } | |
| static int property_color_index(lua_State* L) | |
| { | |
| int atom; | |
| const char* key = lua_tostringatom(L, 2, &atom); | |
| if (!key) | |
| { | |
| luaL_typeerrorL(L, 2, lua_typename(L, LUA_TSTRING)); | |
| return 0; | |
| } | |
| auto propertyColor = lua_torive<ScriptedPropertyColor>(L, 1); | |
| switch (atom) | |
| { | |
| case (int)LuaAtoms::value: | |
| assert(propertyColor->state() == L); | |
| return propertyColor->pushValue(); | |
| default: | |
| return 0; | |
| } | |
| } | |
| static int property_number_newindex(lua_State* L) | |
| { | |
| int atom; | |
| const char* key = lua_tostringatom(L, 2, &atom); | |
| if (!key) | |
| { | |
| luaL_typeerrorL(L, 2, lua_typename(L, LUA_TSTRING)); | |
| return 0; | |
| } | |
| auto propertyNumber = lua_torive<ScriptedPropertyNumber>(L, 1); | |
| switch (atom) | |
| { | |
| case (int)LuaAtoms::value: | |
| propertyNumber->setValue(float(luaL_checknumber(L, 3))); | |
| default: | |
| return 0; | |
| } | |
| return 0; | |
| } | |
| static int property_color_newindex(lua_State* L) | |
| { | |
| int atom; | |
| const char* key = lua_tostringatom(L, 2, &atom); | |
| if (!key) | |
| { | |
| luaL_typeerrorL(L, 2, lua_typename(L, LUA_TSTRING)); | |
| return 0; | |
| } | |
| auto propertyColor = lua_torive<ScriptedPropertyColor>(L, 1); | |
| switch (atom) | |
| { | |
| case (int)LuaAtoms::value: | |
| propertyColor->setValue(luaL_checkunsigned(L, 3)); | |
| default: | |
| return 0; | |
| } | |
| return 0; | |
| } | |
| static int property_string_index(lua_State* L) | |
| { | |
| int atom; | |
| const char* key = lua_tostringatom(L, 2, &atom); | |
| if (!key) | |
| { | |
| luaL_typeerrorL(L, 2, lua_typename(L, LUA_TSTRING)); | |
| return 0; | |
| } | |
| auto propertyString = lua_torive<ScriptedPropertyString>(L, 1); | |
| switch (atom) | |
| { | |
| case (int)LuaAtoms::value: | |
| assert(propertyString->state() == L); | |
| return propertyString->pushValue(); | |
| default: | |
| return 0; | |
| } | |
| } | |
| static int property_string_newindex(lua_State* L) | |
| { | |
| int atom; | |
| const char* key = lua_tostringatom(L, 2, &atom); | |
| if (!key) | |
| { | |
| luaL_typeerrorL(L, 2, lua_typename(L, LUA_TSTRING)); | |
| return 0; | |
| } | |
| auto propertyString = lua_torive<ScriptedPropertyString>(L, 1); | |
| switch (atom) | |
| { | |
| case (int)LuaAtoms::value: | |
| propertyString->setValue(luaL_checkstring(L, 3)); | |
| default: | |
| return 0; | |
| } | |
| return 0; | |
| } | |
| static int property_boolean_index(lua_State* L) | |
| { | |
| int atom; | |
| const char* key = lua_tostringatom(L, 2, &atom); | |
| if (!key) | |
| { | |
| luaL_typeerrorL(L, 2, lua_typename(L, LUA_TSTRING)); | |
| return 0; | |
| } | |
| auto propertyBoolean = lua_torive<ScriptedPropertyBoolean>(L, 1); | |
| switch (atom) | |
| { | |
| case (int)LuaAtoms::value: | |
| assert(propertyBoolean->state() == L); | |
| return propertyBoolean->pushValue(); | |
| default: | |
| return 0; | |
| } | |
| } | |
| static int property_boolean_newindex(lua_State* L) | |
| { | |
| int atom; | |
| const char* key = lua_tostringatom(L, 2, &atom); | |
| if (!key) | |
| { | |
| luaL_typeerrorL(L, 2, lua_typename(L, LUA_TSTRING)); | |
| return 0; | |
| } | |
| auto propertyBoolean = lua_torive<ScriptedPropertyBoolean>(L, 1); | |
| switch (atom) | |
| { | |
| case (int)LuaAtoms::value: | |
| propertyBoolean->setValue(luaL_checkboolean(L, 3)); | |
| default: | |
| return 0; | |
| } | |
| return 0; | |
| } | |
| static int property_enum_index(lua_State* L) | |
| { | |
| int atom; | |
| const char* key = lua_tostringatom(L, 2, &atom); | |
| if (!key) | |
| { | |
| luaL_typeerrorL(L, 2, lua_typename(L, LUA_TSTRING)); | |
| return 0; | |
| } | |
| auto propertyEnum = lua_torive<ScriptedPropertyEnum>(L, 1); | |
| switch (atom) | |
| { | |
| case (int)LuaAtoms::value: | |
| assert(propertyEnum->state() == L); | |
| return propertyEnum->pushValue(); | |
| default: | |
| return 0; | |
| } | |
| } | |
| static int property_enum_newindex(lua_State* L) | |
| { | |
| int atom; | |
| const char* key = lua_tostringatom(L, 2, &atom); | |
| if (!key) | |
| { | |
| luaL_typeerrorL(L, 2, lua_typename(L, LUA_TSTRING)); | |
| return 0; | |
| } | |
| auto propertyEnum = lua_torive<ScriptedPropertyEnum>(L, 1); | |
| switch (atom) | |
| { | |
| case (int)LuaAtoms::value: | |
| { | |
| propertyEnum->setValue(luaL_checkstring(L, 3)); | |
| } | |
| default: | |
| return 0; | |
| } | |
| return 0; | |
| } | |
| static int property_image_index(lua_State* L) | |
| { | |
| int atom; | |
| const char* key = lua_tostringatom(L, 2, &atom); | |
| if (!key) | |
| { | |
| luaL_typeerrorL(L, 2, lua_typename(L, LUA_TSTRING)); | |
| return 0; | |
| } | |
| auto propertyImage = lua_torive<ScriptedPropertyImage>(L, 1); | |
| switch (atom) | |
| { | |
| case (int)LuaAtoms::value: | |
| assert(propertyImage->state() == L); | |
| return propertyImage->pushValue(); | |
| default: | |
| return 0; | |
| } | |
| } | |
| static int property_image_newindex(lua_State* L) | |
| { | |
| int atom; | |
| const char* key = lua_tostringatom(L, 2, &atom); | |
| if (!key) | |
| { | |
| luaL_typeerrorL(L, 2, lua_typename(L, LUA_TSTRING)); | |
| return 0; | |
| } | |
| auto propertyImage = lua_torive<ScriptedPropertyImage>(L, 1); | |
| switch (atom) | |
| { | |
| case (int)LuaAtoms::value: | |
| { | |
| auto image = lua_torive<ScriptedImage>(L, 3); | |
| propertyImage->setValue(image); | |
| break; | |
| } | |
| default: | |
| return 0; | |
| } | |
| return 0; | |
| } | |
| static int property_font_index(lua_State* L) | |
| { | |
| int atom; | |
| const char* key = lua_tostringatom(L, 2, &atom); | |
| if (!key) | |
| { | |
| luaL_typeerrorL(L, 2, lua_typename(L, LUA_TSTRING)); | |
| return 0; | |
| } | |
| auto propertyFont = lua_torive<ScriptedPropertyFont>(L, 1); | |
| switch (atom) | |
| { | |
| case (int)LuaAtoms::value: | |
| assert(propertyFont->state() == L); | |
| return propertyFont->pushValue(); | |
| default: | |
| return 0; | |
| } | |
| } | |
| static int property_font_newindex(lua_State* L) | |
| { | |
| int atom; | |
| const char* key = lua_tostringatom(L, 2, &atom); | |
| if (!key) | |
| { | |
| luaL_typeerrorL(L, 2, lua_typename(L, LUA_TSTRING)); | |
| return 0; | |
| } | |
| auto propertyFont = lua_torive<ScriptedPropertyFont>(L, 1); | |
| switch (atom) | |
| { | |
| case (int)LuaAtoms::value: | |
| { | |
| auto font = lua_torive<ScriptedFont>(L, 3); | |
| propertyFont->setValue(font); | |
| break; | |
| } | |
| default: | |
| return 0; | |
| } | |
| return 0; | |
| } | |
| static int property_blob_index(lua_State* L) | |
| { | |
| int atom; | |
| const char* key = lua_tostringatom(L, 2, &atom); | |
| if (!key) | |
| { | |
| luaL_typeerrorL(L, 2, lua_typename(L, LUA_TSTRING)); | |
| return 0; | |
| } | |
| auto propertyBlob = lua_torive<ScriptedPropertyBlob>(L, 1); | |
| switch (atom) | |
| { | |
| case (int)LuaAtoms::value: | |
| assert(propertyBlob->state() == L); | |
| return propertyBlob->pushValue(); | |
| default: | |
| return 0; | |
| } | |
| } | |
| static int property_blob_newindex(lua_State* L) | |
| { | |
| int atom; | |
| const char* key = lua_tostringatom(L, 2, &atom); | |
| if (!key) | |
| { | |
| luaL_typeerrorL(L, 2, lua_typename(L, LUA_TSTRING)); | |
| return 0; | |
| } | |
| auto propertyBlob = lua_torive<ScriptedPropertyBlob>(L, 1); | |
| switch (atom) | |
| { | |
| case (int)LuaAtoms::value: | |
| { | |
| if (lua_isnil(L, 3)) | |
| { | |
| propertyBlob->setValue(nullptr); | |
| break; | |
| } | |
| // Accept raw bytes (a Luau buffer or a string) and build a blob, or | |
| // an existing Blob userdata. | |
| if (lua_isbuffer(L, 3)) | |
| { | |
| size_t len = 0; | |
| void* data = lua_tobuffer(L, 3, &len); | |
| auto blobAsset = make_rcp<BlobAsset>(); | |
| SimpleArray<uint8_t> bytes((const uint8_t*)data, len); | |
| blobAsset->decode(bytes, nullptr); | |
| propertyBlob->setValue(blobAsset.get()); | |
| break; | |
| } | |
| if (lua_type(L, 3) == LUA_TSTRING) | |
| { | |
| size_t len = 0; | |
| const char* data = lua_tolstring(L, 3, &len); | |
| auto blobAsset = make_rcp<BlobAsset>(); | |
| SimpleArray<uint8_t> bytes((const uint8_t*)data, len); | |
| blobAsset->decode(bytes, nullptr); | |
| propertyBlob->setValue(blobAsset.get()); | |
| break; | |
| } | |
| auto blob = lua_torive<ScriptedBlob>(L, 3); | |
| propertyBlob->setValue(blob->asset ? blob->asset->as<BlobAsset>() | |
| : nullptr); | |
| break; | |
| } | |
| default: | |
| return 0; | |
| } | |
| return 0; | |
| } | |
| static int vm_eq(lua_State* L) | |
| { | |
| auto lhs = lua_torive<ScriptedViewModel>(L, 1); | |
| auto rhs = lua_torive<ScriptedViewModel>(L, 2); | |
| lua_pushboolean(L, lhs->viewModelInstance() == rhs->viewModelInstance()); | |
| return 1; | |
| } | |
| int luaopen_rive_properties(lua_State* L) | |
| { | |
| { | |
| lua_register_rive<ScriptedViewModel>(L); | |
| lua_pushcfunction(L, vm_index, nullptr); | |
| lua_setfield(L, -2, "__index"); | |
| lua_pushcfunction(L, vm_namecall, nullptr); | |
| lua_setfield(L, -2, "__namecall"); | |
| lua_pushcfunction(L, vm_eq, nullptr); | |
| lua_setfield(L, -2, "__eq"); | |
| lua_setreadonly(L, -1, true); | |
| lua_pop(L, 1); // pop the metatable | |
| } | |
| { | |
| lua_register_rive<ScriptedPropertyViewModel>(L); | |
| lua_pushcfunction(L, property_vm_index, nullptr); | |
| lua_setfield(L, -2, "__index"); | |
| lua_pushcfunction(L, property_vm_newindex, nullptr); | |
| lua_setfield(L, -2, "__newindex"); | |
| lua_pushcfunction(L, property_vm_namecall, nullptr); | |
| lua_setfield(L, -2, "__namecall"); | |
| lua_setreadonly(L, -1, true); | |
| lua_pop(L, 1); // pop the metatable | |
| } | |
| { | |
| lua_register_rive<ScriptedPropertyNumber>(L); | |
| lua_pushcfunction(L, property_number_index, nullptr); | |
| lua_setfield(L, -2, "__index"); | |
| lua_pushcfunction(L, property_number_newindex, nullptr); | |
| lua_setfield(L, -2, "__newindex"); | |
| lua_pushcfunction(L, property_namecall, nullptr); | |
| lua_setfield(L, -2, "__namecall"); | |
| lua_setreadonly(L, -1, true); | |
| lua_pop(L, 1); // pop the metatable | |
| lua_registeruserdatadirectfieldget(L, | |
| ScriptedPropertyNumber::luaTag, | |
| "value", | |
| property_number_direct_value); | |
| } | |
| { | |
| lua_register_rive<ScriptedPropertyColor>(L); | |
| lua_pushcfunction(L, property_color_index, nullptr); | |
| lua_setfield(L, -2, "__index"); | |
| lua_pushcfunction(L, property_color_newindex, nullptr); | |
| lua_setfield(L, -2, "__newindex"); | |
| lua_pushcfunction(L, property_namecall, nullptr); | |
| lua_setfield(L, -2, "__namecall"); | |
| lua_setreadonly(L, -1, true); | |
| lua_pop(L, 1); // pop the metatable | |
| lua_registeruserdatadirectfieldget(L, | |
| ScriptedPropertyColor::luaTag, | |
| "value", | |
| property_color_direct_value); | |
| } | |
| { | |
| lua_register_rive<ScriptedPropertyString>(L); | |
| lua_pushcfunction(L, property_string_index, nullptr); | |
| lua_setfield(L, -2, "__index"); | |
| lua_pushcfunction(L, property_string_newindex, nullptr); | |
| lua_setfield(L, -2, "__newindex"); | |
| lua_pushcfunction(L, property_namecall, nullptr); | |
| lua_setfield(L, -2, "__namecall"); | |
| lua_setreadonly(L, -1, true); | |
| lua_pop(L, 1); // pop the metatable | |
| } | |
| { | |
| lua_register_rive<ScriptedPropertyBoolean>(L); | |
| lua_pushcfunction(L, property_boolean_index, nullptr); | |
| lua_setfield(L, -2, "__index"); | |
| lua_pushcfunction(L, property_boolean_newindex, nullptr); | |
| lua_setfield(L, -2, "__newindex"); | |
| lua_pushcfunction(L, property_namecall, nullptr); | |
| lua_setfield(L, -2, "__namecall"); | |
| lua_setreadonly(L, -1, true); | |
| lua_pop(L, 1); // pop the metatable | |
| lua_registeruserdatadirectfieldget(L, | |
| ScriptedPropertyBoolean::luaTag, | |
| "value", | |
| property_boolean_direct_value); | |
| } | |
| { | |
| lua_register_rive<ScriptedPropertyEnum>(L); | |
| lua_pushcfunction(L, property_enum_index, nullptr); | |
| lua_setfield(L, -2, "__index"); | |
| lua_pushcfunction(L, property_enum_newindex, nullptr); | |
| lua_setfield(L, -2, "__newindex"); | |
| lua_pushcfunction(L, property_namecall, nullptr); | |
| lua_setfield(L, -2, "__namecall"); | |
| lua_setreadonly(L, -1, true); | |
| lua_pop(L, 1); // pop the metatable | |
| } | |
| { | |
| lua_register_rive<ScriptedPropertyTrigger>(L); | |
| lua_pushcfunction(L, property_namecall, nullptr); | |
| lua_setfield(L, -2, "__namecall"); | |
| lua_setreadonly(L, -1, true); | |
| lua_pop(L, 1); // pop the metatable | |
| } | |
| { | |
| lua_register_rive<ScriptedPropertyList>(L); | |
| lua_pushcfunction(L, property_namecall, nullptr); | |
| lua_setfield(L, -2, "__namecall"); | |
| lua_pushcfunction(L, property_list_index, nullptr); | |
| lua_setfield(L, -2, "__index"); | |
| lua_setreadonly(L, -1, true); | |
| lua_pop(L, 1); // pop the metatable | |
| lua_registeruserdatadirectfieldget(L, | |
| ScriptedPropertyList::luaTag, | |
| "length", | |
| property_list_direct_length); | |
| } | |
| { | |
| lua_register_rive<ScriptedPropertyImage>(L); | |
| lua_pushcfunction(L, property_namecall, nullptr); | |
| lua_setfield(L, -2, "__namecall"); | |
| lua_pushcfunction(L, property_image_index, nullptr); | |
| lua_setfield(L, -2, "__index"); | |
| lua_pushcfunction(L, property_image_newindex, nullptr); | |
| lua_setfield(L, -2, "__newindex"); | |
| lua_setreadonly(L, -1, true); | |
| lua_pop(L, 1); // pop the metatable | |
| } | |
| { | |
| // No metatable, but the rcp<Font> member needs its destructor run | |
| // when the userdata is collected. | |
| lua_register_rive<ScriptedFont>(L); | |
| } | |
| { | |
| lua_register_rive<ScriptedPropertyFont>(L); | |
| lua_pushcfunction(L, property_namecall, nullptr); | |
| lua_setfield(L, -2, "__namecall"); | |
| lua_pushcfunction(L, property_font_index, nullptr); | |
| lua_setfield(L, -2, "__index"); | |
| lua_pushcfunction(L, property_font_newindex, nullptr); | |
| lua_setfield(L, -2, "__newindex"); | |
| lua_setreadonly(L, -1, true); | |
| lua_pop(L, 1); // pop the metatable | |
| } | |
| { | |
| lua_register_rive<ScriptedPropertyBlob>(L); | |
| lua_pushcfunction(L, property_namecall, nullptr); | |
| lua_setfield(L, -2, "__namecall"); | |
| lua_pushcfunction(L, property_blob_index, nullptr); | |
| lua_setfield(L, -2, "__index"); | |
| lua_pushcfunction(L, property_blob_newindex, nullptr); | |
| lua_setfield(L, -2, "__newindex"); | |
| lua_setreadonly(L, -1, true); | |
| lua_pop(L, 1); // pop the metatable | |
| } | |
| { | |
| lua_register_rive<ScriptedEnumValues>(L); | |
| lua_pushcfunction(L, enum_value_index, nullptr); | |
| lua_setfield(L, -2, "__index"); | |
| lua_pushcfunction(L, enum_value_length, nullptr); | |
| lua_setfield(L, -2, "__len"); | |
| lua_setreadonly(L, -1, true); | |
| lua_pop(L, 1); // pop the metatable | |
| } | |
| return 0; | |
| } | |
| #endif |