blob: 9a6d61aa91e7ed7babc5e9c8df89490f73033557 [file]
#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_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,
rcp<ViewModel> viewModel,
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:
lua_newrive<ScriptedPropertyViewModel>(
L,
L,
viewModel,
ref_rcp(propValue->as<ViewModelInstanceViewModel>()));
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;
}
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::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;
}
#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;
}
clearListeners();
}
void ScriptedProperty::valueChanged()
{
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();
parentViewModelInstance->replaceViewModelByProperty(
instanceValue,
rcp<ViewModelInstance>(viewModelInstance));
// Invalidate cached Lua-side value so the next push reflects the new
// instance.
clearRef();
}
}
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)
{}
ScriptedViewModel::~ScriptedViewModel()
{
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;
}
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);
return 1;
}
// 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 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, m_viewModel, 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);
lua_newrive<ScriptedViewModel>(
m_state,
m_state,
m_viewModel,
m_instanceValue->as<ViewModelInstanceViewModel>()
->referenceViewModelInstance());
}
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::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;
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_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);
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 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
}
{
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