mirror of
https://github.com/rive-app/rive-cpp.git
synced 2026-01-18 21:21:17 +01:00
Nnnnn add data metatable and viewmodels (#11269) f23eb70aec
* feature(scripting): add support for accessing view models from context and creating view model from global data Co-authored-by: hernan <hernan@rive.app>
This commit is contained in:
@@ -1 +1 @@
|
||||
332a226197975255adecf4f80f2aca99c39fbdd5
|
||||
f23eb70aecf373d9413b14074f00c9607259093e
|
||||
|
||||
@@ -4,9 +4,11 @@
|
||||
#ifdef WITH_RIVE_SCRIPTING
|
||||
#include "lua.h"
|
||||
#endif
|
||||
#include <vector>
|
||||
|
||||
namespace rive
|
||||
{
|
||||
class ViewModel;
|
||||
class LuaState
|
||||
{
|
||||
public:
|
||||
@@ -14,6 +16,7 @@ public:
|
||||
|
||||
LuaState(lua_State* state) : state(state) {}
|
||||
~LuaState() { state = nullptr; }
|
||||
void initializeData(std::vector<ViewModel*>&);
|
||||
|
||||
lua_State* state;
|
||||
#endif
|
||||
|
||||
@@ -162,6 +162,7 @@ enum class LuaAtoms : int16_t
|
||||
frameOrigin,
|
||||
data,
|
||||
instance,
|
||||
newAtom,
|
||||
bounds,
|
||||
pointerDown,
|
||||
pointerMove,
|
||||
@@ -201,6 +202,7 @@ enum class LuaAtoms : int16_t
|
||||
|
||||
// Scripted Context
|
||||
markNeedsUpdate,
|
||||
viewModel,
|
||||
};
|
||||
|
||||
struct ScriptedMat2D
|
||||
|
||||
@@ -620,6 +620,7 @@ void File::makeScriptingVM()
|
||||
rivestd::make_unique<CPPRuntimeScriptingContext>(m_factory);
|
||||
m_scriptingVM = rivestd::make_unique<ScriptingVM>(m_scriptingContext.get());
|
||||
m_luaState = new LuaState(m_scriptingVM->state());
|
||||
m_luaState->initializeData(m_ViewModels);
|
||||
}
|
||||
|
||||
void File::cleanupScriptingVM()
|
||||
|
||||
@@ -537,6 +537,7 @@ static int vm_namecall(lua_State* L)
|
||||
return vm->pushValue(name, ViewModelInstanceListBase::typeKey);
|
||||
}
|
||||
case (int)LuaAtoms::instance:
|
||||
case (int)LuaAtoms::newAtom:
|
||||
{
|
||||
assert(vm->state() == L);
|
||||
return vm->instance(L);
|
||||
|
||||
@@ -26,6 +26,22 @@ static int context_namecall(lua_State* L)
|
||||
scriptedObject->markNeedsUpdate();
|
||||
return 0;
|
||||
}
|
||||
case (int)LuaAtoms::viewModel:
|
||||
{
|
||||
auto scriptedObject = scriptedContext->scriptedObject();
|
||||
auto dataContext = scriptedObject->dataContext();
|
||||
if (dataContext && dataContext->viewModelInstance())
|
||||
{
|
||||
auto viewModelInstance = dataContext->viewModelInstance();
|
||||
lua_newrive<ScriptedViewModel>(
|
||||
L,
|
||||
L,
|
||||
ref_rcp(viewModelInstance->viewModel()),
|
||||
viewModelInstance);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
66
src/lua/lua_state.cpp
Normal file
66
src/lua/lua_state.cpp
Normal file
@@ -0,0 +1,66 @@
|
||||
|
||||
#include "rive/lua/lua_state.hpp"
|
||||
#ifdef WITH_RIVE_SCRIPTING
|
||||
#include "rive/lua/rive_lua_libs.hpp"
|
||||
#endif
|
||||
#include "rive/viewmodel/viewmodel.hpp"
|
||||
#include <stdio.h>
|
||||
using namespace rive;
|
||||
|
||||
#ifdef WITH_RIVE_SCRIPTING
|
||||
static int viewmodel_new(lua_State* L)
|
||||
{
|
||||
// Get the viewModel pointer from the upvalue
|
||||
ViewModel* viewModel = (ViewModel*)lua_touserdata(L, lua_upvalueindex(1));
|
||||
if (viewModel)
|
||||
{
|
||||
auto instance = viewModel->createInstance();
|
||||
lua_newrive<ScriptedViewModel>(L, L, ref_rcp(viewModel), instance);
|
||||
return 1;
|
||||
}
|
||||
lua_pushnil(L);
|
||||
return 1;
|
||||
}
|
||||
|
||||
void LuaState::initializeData(std::vector<ViewModel*>& viewModels)
|
||||
{
|
||||
if (state)
|
||||
{
|
||||
// create metatable for global Data
|
||||
luaL_newmetatable(state, "Data");
|
||||
|
||||
// push it again as lua_setuserdatametatable pops it
|
||||
lua_pushvalue(state, -1);
|
||||
lua_setuserdatametatable(state, LUA_T_COUNT + 31);
|
||||
lua_pop(state, 1);
|
||||
lua_getfield(state, luaRegistryIndex, "Data");
|
||||
lua_setglobal(state, "Data");
|
||||
|
||||
// Get the Data metatable back on the stack
|
||||
lua_getfield(state, luaRegistryIndex, "Data");
|
||||
|
||||
for (auto& viewModel : viewModels)
|
||||
{
|
||||
// Create a new table for this viewModel
|
||||
lua_createtable(state, 0, 1);
|
||||
|
||||
// Push the viewModel pointer as a light userdata (upvalue)
|
||||
lua_pushlightuserdata(state, viewModel);
|
||||
|
||||
// Create a closure with the viewModel as an upvalue
|
||||
lua_pushcclosurek(state, viewmodel_new, "new", 1, nullptr);
|
||||
|
||||
// Set the function as "new" in the table
|
||||
lua_setfield(state, -2, "new");
|
||||
|
||||
// Set the table as a field in Data metatable using the viewModel
|
||||
// name
|
||||
lua_setfield(state, -2, viewModel->name().c_str());
|
||||
}
|
||||
|
||||
// Pop the Data metatable from the stack
|
||||
lua_pop(state, 1);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -106,6 +106,7 @@ std::unordered_map<std::string, int16_t> atoms = {
|
||||
{"frameOrigin", (int16_t)LuaAtoms::frameOrigin},
|
||||
{"data", (int16_t)LuaAtoms::data},
|
||||
{"instance", (int16_t)LuaAtoms::instance},
|
||||
{"new", (int16_t)LuaAtoms::newAtom},
|
||||
{"bounds", (int16_t)LuaAtoms::bounds},
|
||||
{"pointerDown", (int16_t)LuaAtoms::pointerDown},
|
||||
{"pointerUp", (int16_t)LuaAtoms::pointerUp},
|
||||
@@ -134,6 +135,7 @@ std::unordered_map<std::string, int16_t> atoms = {
|
||||
{"next", (int16_t)LuaAtoms::next},
|
||||
{"isClosed", (int16_t)LuaAtoms::isClosed},
|
||||
{"markNeedsUpdate", (int16_t)LuaAtoms::markNeedsUpdate},
|
||||
{"viewModel", (int16_t)LuaAtoms::viewModel},
|
||||
};
|
||||
|
||||
static const luaL_Reg lualibs[] = {
|
||||
|
||||
BIN
tests/unit_tests/assets/script_create_viewmodel_instance.riv
Normal file
BIN
tests/unit_tests/assets/script_create_viewmodel_instance.riv
Normal file
Binary file not shown.
BIN
tests/unit_tests/assets/viewmodel_from_context.riv
Normal file
BIN
tests/unit_tests/assets/viewmodel_from_context.riv
Normal file
Binary file not shown.
@@ -2,6 +2,8 @@
|
||||
#include "catch.hpp"
|
||||
#include "scripting_test_utilities.hpp"
|
||||
#include "rive/lua/rive_lua_libs.hpp"
|
||||
#include "rive/animation/state_machine_instance.hpp"
|
||||
#include "rive_file_reader.hpp"
|
||||
|
||||
using namespace rive;
|
||||
|
||||
@@ -30,3 +32,176 @@ end
|
||||
CHECK(scriptedObjectTest.needsUpdate());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("script has access to user created view models via Data", "[silver]")
|
||||
{
|
||||
rive::SerializingFactory silver;
|
||||
auto file =
|
||||
ReadRiveFile("assets/script_create_viewmodel_instance.riv", &silver);
|
||||
auto artboard = file->artboardNamed("main");
|
||||
|
||||
silver.frameSize(artboard->width(), artboard->height());
|
||||
REQUIRE(artboard != nullptr);
|
||||
auto stateMachine = artboard->stateMachineAt(0);
|
||||
int viewModelId = artboard.get()->viewModelId();
|
||||
|
||||
auto vmi = viewModelId == -1
|
||||
? file->createViewModelInstance(artboard.get())
|
||||
: file->createViewModelInstance(viewModelId, 0);
|
||||
stateMachine->bindViewModelInstance(vmi);
|
||||
stateMachine->advanceAndApply(0.1f);
|
||||
|
||||
auto renderer = silver.makeRenderer();
|
||||
artboard->draw(renderer.get());
|
||||
|
||||
// Push element
|
||||
{
|
||||
silver.addFrame();
|
||||
rive::ViewModelInstanceViewModel* button =
|
||||
vmi->propertyValue("newButton")
|
||||
->as<rive::ViewModelInstanceViewModel>();
|
||||
REQUIRE(button != nullptr);
|
||||
auto instance = button->referenceViewModelInstance();
|
||||
rive::ViewModelInstanceTrigger* trigger =
|
||||
instance->propertyValue("onClick")
|
||||
->as<rive::ViewModelInstanceTrigger>();
|
||||
REQUIRE(trigger != nullptr);
|
||||
trigger->trigger();
|
||||
stateMachine->advanceAndApply(0.1f);
|
||||
artboard->draw(renderer.get());
|
||||
}
|
||||
|
||||
// Push element at specific index
|
||||
{
|
||||
silver.addFrame();
|
||||
rive::ViewModelInstanceViewModel* button =
|
||||
vmi->propertyValue("newAtButton")
|
||||
->as<rive::ViewModelInstanceViewModel>();
|
||||
REQUIRE(button != nullptr);
|
||||
auto instance = button->referenceViewModelInstance();
|
||||
rive::ViewModelInstanceTrigger* trigger =
|
||||
instance->propertyValue("onClick")
|
||||
->as<rive::ViewModelInstanceTrigger>();
|
||||
REQUIRE(trigger != nullptr);
|
||||
trigger->trigger();
|
||||
stateMachine->advanceAndApply(0.1f);
|
||||
artboard->draw(renderer.get());
|
||||
}
|
||||
|
||||
// Swap elements from indexes
|
||||
{
|
||||
silver.addFrame();
|
||||
rive::ViewModelInstanceViewModel* button =
|
||||
vmi->propertyValue("swapButton")
|
||||
->as<rive::ViewModelInstanceViewModel>();
|
||||
REQUIRE(button != nullptr);
|
||||
auto instance = button->referenceViewModelInstance();
|
||||
rive::ViewModelInstanceTrigger* trigger =
|
||||
instance->propertyValue("onClick")
|
||||
->as<rive::ViewModelInstanceTrigger>();
|
||||
REQUIRE(trigger != nullptr);
|
||||
trigger->trigger();
|
||||
stateMachine->advanceAndApply(0.1f);
|
||||
artboard->draw(renderer.get());
|
||||
}
|
||||
|
||||
// Shift first element
|
||||
{
|
||||
silver.addFrame();
|
||||
rive::ViewModelInstanceViewModel* button =
|
||||
vmi->propertyValue("shiftButton")
|
||||
->as<rive::ViewModelInstanceViewModel>();
|
||||
REQUIRE(button != nullptr);
|
||||
auto instance = button->referenceViewModelInstance();
|
||||
rive::ViewModelInstanceTrigger* trigger =
|
||||
instance->propertyValue("onClick")
|
||||
->as<rive::ViewModelInstanceTrigger>();
|
||||
REQUIRE(trigger != nullptr);
|
||||
trigger->trigger();
|
||||
stateMachine->advanceAndApply(0.1f);
|
||||
artboard->draw(renderer.get());
|
||||
}
|
||||
|
||||
// Pop last element
|
||||
{
|
||||
silver.addFrame();
|
||||
rive::ViewModelInstanceViewModel* button =
|
||||
vmi->propertyValue("popButton")
|
||||
->as<rive::ViewModelInstanceViewModel>();
|
||||
REQUIRE(button != nullptr);
|
||||
auto instance = button->referenceViewModelInstance();
|
||||
rive::ViewModelInstanceTrigger* trigger =
|
||||
instance->propertyValue("onClick")
|
||||
->as<rive::ViewModelInstanceTrigger>();
|
||||
REQUIRE(trigger != nullptr);
|
||||
trigger->trigger();
|
||||
stateMachine->advanceAndApply(0.1f);
|
||||
artboard->draw(renderer.get());
|
||||
}
|
||||
|
||||
// Pop all elements and pop beyond empty list
|
||||
{
|
||||
silver.addFrame();
|
||||
rive::ViewModelInstanceViewModel* button =
|
||||
vmi->propertyValue("popButton")
|
||||
->as<rive::ViewModelInstanceViewModel>();
|
||||
REQUIRE(button != nullptr);
|
||||
auto instance = button->referenceViewModelInstance();
|
||||
rive::ViewModelInstanceTrigger* trigger =
|
||||
instance->propertyValue("onClick")
|
||||
->as<rive::ViewModelInstanceTrigger>();
|
||||
REQUIRE(trigger != nullptr);
|
||||
trigger->trigger();
|
||||
trigger->trigger();
|
||||
trigger->trigger();
|
||||
trigger->trigger();
|
||||
stateMachine->advanceAndApply(0.1f);
|
||||
artboard->draw(renderer.get());
|
||||
}
|
||||
|
||||
// Push 2 elements
|
||||
{
|
||||
silver.addFrame();
|
||||
rive::ViewModelInstanceViewModel* button =
|
||||
vmi->propertyValue("newButton")
|
||||
->as<rive::ViewModelInstanceViewModel>();
|
||||
REQUIRE(button != nullptr);
|
||||
auto instance = button->referenceViewModelInstance();
|
||||
rive::ViewModelInstanceTrigger* trigger =
|
||||
instance->propertyValue("onClick")
|
||||
->as<rive::ViewModelInstanceTrigger>();
|
||||
REQUIRE(trigger != nullptr);
|
||||
trigger->trigger();
|
||||
trigger->trigger();
|
||||
stateMachine->advanceAndApply(0.1f);
|
||||
artboard->draw(renderer.get());
|
||||
}
|
||||
|
||||
CHECK(silver.matches("script_create_viewmodel_instance"));
|
||||
}
|
||||
|
||||
TEST_CASE("script has access to the data bound view model", "[silver]")
|
||||
{
|
||||
rive::SerializingFactory silver;
|
||||
auto file = ReadRiveFile("assets/viewmodel_from_context.riv", &silver);
|
||||
auto artboard = file->artboardNamed("main");
|
||||
|
||||
silver.frameSize(artboard->width(), artboard->height());
|
||||
REQUIRE(artboard != nullptr);
|
||||
auto stateMachine = artboard->stateMachineAt(0);
|
||||
int viewModelId = artboard.get()->viewModelId();
|
||||
|
||||
auto vmi = viewModelId == -1
|
||||
? file->createViewModelInstance(artboard.get())
|
||||
: file->createViewModelInstance(viewModelId, 0);
|
||||
stateMachine->bindViewModelInstance(vmi);
|
||||
stateMachine->advanceAndApply(0.1f);
|
||||
|
||||
auto renderer = silver.makeRenderer();
|
||||
artboard->draw(renderer.get());
|
||||
silver.addFrame();
|
||||
stateMachine->advanceAndApply(0.1f);
|
||||
artboard->draw(renderer.get());
|
||||
|
||||
CHECK(silver.matches("viewmodel_from_context"));
|
||||
}
|
||||
|
||||
BIN
tests/unit_tests/silvers/script_create_viewmodel_instance.sriv
Normal file
BIN
tests/unit_tests/silvers/script_create_viewmodel_instance.sriv
Normal file
Binary file not shown.
BIN
tests/unit_tests/silvers/viewmodel_from_context.sriv
Normal file
BIN
tests/unit_tests/silvers/viewmodel_from_context.sriv
Normal file
Binary file not shown.
Reference in New Issue
Block a user