feat: Scripted Enum (#10829) 650a980d41

Adds support for ScriptedEnums (ViewModel enums). This PR does not include enums as Inputs, that will follow subsequently

Co-authored-by: Philip Chung <philterdesign@gmail.com>
This commit is contained in:
philter
2025-10-18 20:22:26 +00:00
parent 21b397d17a
commit f47345a807
5 changed files with 198 additions and 5 deletions

View File

@@ -1 +1 @@
396c65832ecf6122fd9a0976726d6e40bb0b5be1
650a980d413d906648698116815311e26ae3199d

View File

@@ -10,6 +10,7 @@
#include "rive/shapes/paint/image_sampler.hpp"
#include "rive/viewmodel/viewmodel_instance_boolean.hpp"
#include "rive/viewmodel/viewmodel_instance_color.hpp"
#include "rive/viewmodel/viewmodel_instance_enum.hpp"
#include "rive/viewmodel/viewmodel_instance_value.hpp"
#include "rive/viewmodel/viewmodel_instance_viewmodel.hpp"
#include "rive/viewmodel/viewmodel_instance_number.hpp"
@@ -510,13 +511,22 @@ public:
void setValue(bool value);
};
class ScriptedPropertyEnum : public ScriptedProperty
{
public:
ScriptedPropertyEnum(lua_State* L, rcp<ViewModelInstanceEnum> value);
static constexpr uint8_t luaTag = LUA_T_COUNT + 19;
static constexpr const char* luaName = "Property<enum>";
static constexpr bool hasMetatable = true;
int pushValue();
void setValue(const std::string& value);
};
// Make
// ScriptedPropertyViewModel
// - Nullable ViewModelInstanceValue (ViewModelInstanceViewModel)
// - Requires ViewModel to know which properties to expect
// ScriptedPropertyEnum
// - Nullable ViewModelInstanceValue (ViewModelInstanceEnum)
// - Requires DataEnum for expected types
// ScriptedPropertyArtboard
// - Nullable ViewModelInstanceValue (ViewModelInstanceArtboard)

View File

@@ -2,6 +2,7 @@
#include "rive/lua/rive_lua_libs.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"
@@ -53,6 +54,12 @@ static void pushViewModelInstanceValue(lua_State* L,
L,
ref_rcp(propValue->as<ViewModelInstanceBoolean>()));
break;
case ViewModelInstanceEnumBase::typeKey:
lua_newrive<ScriptedPropertyEnum>(
L,
L,
ref_rcp(propValue->as<ViewModelInstanceEnum>()));
break;
default:
lua_pushnil(L);
break;
@@ -279,6 +286,11 @@ int ScriptedViewModel::pushValue(const char* name, int coreType)
m_state,
nullptr);
break;
case ViewModelPropertyEnumBase::typeKey:
lua_newrive<ScriptedPropertyEnum>(m_state,
m_state,
nullptr);
break;
}
}
}
@@ -472,6 +484,9 @@ static int property_namecall(lua_State* L)
case ScriptedPropertyBoolean::luaTag:
name = ScriptedPropertyBoolean::luaName;
break;
case ScriptedPropertyEnum::luaTag:
name = ScriptedPropertyEnum::luaName;
break;
default:
luaL_typeerror(L, 1, name);
break;
@@ -612,6 +627,40 @@ int ScriptedPropertyBoolean::pushValue()
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))
@@ -903,6 +952,51 @@ static int property_boolean_newindex(lua_State* L)
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;
}
int luaopen_rive_properties(lua_State* L)
{
{
@@ -995,6 +1089,22 @@ int luaopen_rive_properties(lua_State* L)
lua_pop(L, 1); // pop the metatable
}
{
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);
@@ -1018,6 +1128,6 @@ int luaopen_rive_properties(lua_State* L)
lua_pop(L, 1); // pop the metatable
}
return 8;
return 9;
}
#endif

Binary file not shown.

View File

@@ -478,4 +478,77 @@ end
CHECK(vm.console[1] == ("bool changed to true"));
CHECK(vm.console[2] == ("bool is true"));
CHECK(vm.console[3] == ("bool changed to false"));
}
TEST_CASE("scripted enum can be passed to luau", "[scripting_properties]")
{
auto file = ReadRiveFile("assets/scripted_enum.riv");
auto artboard = file->artboard("EnumArtboard")->instance();
REQUIRE(artboard != nullptr);
auto viewModelInstance =
file->createDefaultViewModelInstance(artboard.get());
REQUIRE(viewModelInstance != nullptr);
artboard->bindViewModelInstance(viewModelInstance);
artboard->advance(0.0f);
CHECK(viewModelInstance->viewModel()->name() == "EnumVM");
auto property = viewModelInstance->propertyValue("EnumProp");
REQUIRE(property != nullptr);
REQUIRE(property->is<rive::ViewModelInstanceEnum>());
ScriptingTest vm(
R"TEST_SRC(
function init(vm)
print(`enum init to {vm.EnumProp.value}`)
vm.EnumProp:addListener(vm.EnumProp, enumChanged)
end
function setValue(vm, value:string)
vm.EnumProp.value = value
print(`enum is {vm.EnumProp.value}`)
end
function enumChanged(e)
print(`enum changed to {e.value}`)
end
)TEST_SRC");
auto L = vm.state();
lua_newrive<ScriptedViewModel>(L,
L,
ref_rcp(viewModelInstance->viewModel()),
viewModelInstance);
lua_getglobal(L, "init");
lua_pushvalue(L, -2);
CHECK(lua_pcall(L, 1, 0, 0) == LUA_OK);
// Change the enum value via script multiple times
lua_getglobal(L, "setValue");
lua_pushvalue(L, -2); // vm
lua_pushstring(L, "blue");
CHECK(lua_pcall(L, 2, 0, 0) == LUA_OK);
lua_getglobal(L, "setValue");
lua_pushvalue(L, -2); // vm
lua_pushstring(L, "orange");
CHECK(lua_pcall(L, 2, 0, 0) == LUA_OK);
lua_getglobal(L, "setValue");
lua_pushvalue(L, -2); // vm
lua_pushstring(L, "red");
CHECK(lua_pcall(L, 2, 0, 0) == LUA_OK);
CHECK(vm.console.size() == 7);
CHECK(vm.console[0] == std::string("enum init to ") + std::string("white"));
CHECK(vm.console[1] ==
std::string("enum changed to ") + std::string("blue"));
CHECK(vm.console[2] == std::string("enum is ") + std::string("blue"));
CHECK(vm.console[3] ==
std::string("enum changed to ") + std::string("orange"));
CHECK(vm.console[4] == std::string("enum is ") + std::string("orange"));
CHECK(vm.console[5] ==
std::string("enum changed to ") + std::string("red"));
CHECK(vm.console[6] == std::string("enum is ") + std::string("red"));
}