| #ifdef WITH_RIVE_SCRIPTING | |
| #include "rive/lua/rive_lua_libs.hpp" | |
| #include "rive/file.hpp" | |
| #include "rive/artboard.hpp" | |
| #include "rive/animation/listener_invocation.hpp" | |
| #include "rive/animation/state_machine_instance.hpp" | |
| #include "rive/input/focus_manager.hpp" | |
| #include "rive/viewmodel/viewmodel_property_number.hpp" | |
| #include "rive/viewmodel/viewmodel_property_trigger.hpp" | |
| #include "rive/node.hpp" | |
| #include "rive/bones/root_bone.hpp" | |
| #include "rive/constraints/constraint.hpp" | |
| #include "rive/math/transform_components.hpp" | |
| #include "rive/shapes/path.hpp" | |
| #include <math.h> | |
| #include <stdio.h> | |
| using namespace rive; | |
| ScriptReffedArtboard::ScriptReffedArtboard( | |
| File* file, | |
| std::unique_ptr<ArtboardInstance>&& artboardInstance, | |
| rcp<ViewModelInstance> viewModelInstance, | |
| rcp<DataContext> parentDataContext, | |
| ScriptingContext* scriptingContext | |
| #ifdef WITH_RIVE_TOOLS | |
| , | |
| rcp<File> filePin | |
| #endif | |
| ) : | |
| m_file(file), | |
| #ifdef WITH_RIVE_TOOLS | |
| m_filePin(std::move(filePin)), | |
| #endif | |
| m_artboard(std::move(artboardInstance)), | |
| m_stateMachine(m_artboard->defaultStateMachine()), | |
| m_scriptingContext(scriptingContext) | |
| { | |
| // A scripted artboard is a root: nothing hosts it in another artboard's | |
| // focus tree, so it owns its FocusManager and builds its own focus tree. | |
| m_artboard->buildFocusTree(m_artboard->ensureFocusManager(), nullptr); | |
| if (viewModelInstance) | |
| { | |
| m_viewModelInstance = viewModelInstance; | |
| } | |
| else | |
| { | |
| m_viewModelInstance = m_file->createViewModelInstance(m_artboard.get()); | |
| } | |
| if (m_stateMachine && m_viewModelInstance) | |
| { | |
| if (parentDataContext) | |
| { | |
| auto dataContext = make_rcp<DataContext>(m_viewModelInstance); | |
| dataContext->parent(parentDataContext); | |
| m_stateMachine->bindDataContext(dataContext); | |
| } | |
| else | |
| { | |
| m_stateMachine->bindViewModelInstance(m_viewModelInstance); | |
| } | |
| } | |
| // Keep the bound instance tracked for end-of-frame advance for as long as | |
| // this artboard uses it — even if the script drops its ScriptedViewModel | |
| // wrapper. Covers both the passed-in and the auto-created instance. | |
| if (m_scriptingContext != nullptr) | |
| { | |
| m_scriptingContext->trackViewModelInstance(m_viewModelInstance); | |
| } | |
| } | |
| ScriptReffedArtboard::~ScriptReffedArtboard() | |
| { | |
| if (m_scriptingContext != nullptr) | |
| { | |
| m_scriptingContext->untrackViewModelInstance(m_viewModelInstance.get()); | |
| } | |
| // Make sure state machine is deleted before artboard since | |
| // StateMachineInstance destructor accesses the artboard. | |
| m_stateMachine = nullptr; | |
| // Make sure artboard is deleted before file. | |
| m_artboard = nullptr; | |
| m_file = nullptr; | |
| } | |
| rive::File* ScriptReffedArtboard::file() { return m_file; } | |
| Artboard* ScriptReffedArtboard::artboard() { return m_artboard.get(); } | |
| StateMachineInstance* ScriptReffedArtboard::stateMachine() | |
| { | |
| return m_stateMachine.get(); | |
| } | |
| static int artboard_draw(lua_State* L) | |
| { | |
| auto scriptedArtboard = lua_torive<ScriptedArtboard>(L, 1); | |
| auto scriptedRenderer = lua_torive<ScriptedRenderer>(L, 2); | |
| auto renderer = scriptedRenderer->validate(L); | |
| scriptedArtboard->artboard()->drawInternal(renderer); | |
| return 0; | |
| } | |
| bool ScriptedArtboard::advance(float seconds) | |
| { | |
| auto machine = stateMachine(); | |
| if (machine) | |
| { | |
| // A scripted artboard's view models are advanced/reset by the host | |
| // frame, not by this script-driven advance, so skip the VM consume. | |
| return machine->advanceAndApply(seconds, false); | |
| } | |
| else | |
| { | |
| return artboard()->advance(seconds); | |
| } | |
| } | |
| static int apply_pointer_event(lua_State* L, int atom) | |
| { | |
| auto scriptedArtboard = lua_torive<ScriptedArtboard>(L, 1); | |
| auto pointerEvent = lua_torive<ScriptedPointerEvent>(L, 2); | |
| auto result = 0; | |
| if (scriptedArtboard->stateMachine()) | |
| { | |
| switch (atom) | |
| { | |
| case (int)LuaAtoms::pointerDown: | |
| result = (int)scriptedArtboard->stateMachine()->pointerDown( | |
| pointerEvent->m_position, | |
| pointerEvent->m_id); | |
| break; | |
| case (int)LuaAtoms::pointerMove: | |
| result = (int)scriptedArtboard->stateMachine()->pointerMove( | |
| pointerEvent->m_position, | |
| 0, | |
| pointerEvent->m_id); | |
| break; | |
| case (int)LuaAtoms::pointerUp: | |
| result = (int)scriptedArtboard->stateMachine()->pointerUp( | |
| pointerEvent->m_position, | |
| pointerEvent->m_id); | |
| break; | |
| case (int)LuaAtoms::pointerExit: | |
| result = (int)scriptedArtboard->stateMachine()->pointerExit( | |
| pointerEvent->m_position, | |
| pointerEvent->m_id); | |
| break; | |
| } | |
| } | |
| lua_pushinteger(L, result); | |
| return 1; | |
| } | |
| static int apply_gamepad_event(lua_State* L, int atom) | |
| { | |
| auto scriptedArtboard = lua_torive<ScriptedArtboard>(L, 1); | |
| auto result = 0; | |
| auto stateMachine = scriptedArtboard->stateMachine(); | |
| if (stateMachine) | |
| { | |
| auto dispatch = [&](const ListenerInvocation& invocation) { | |
| ScriptedDrawable* dispatched = nullptr; | |
| if (auto* fm = stateMachine->focusManager()) | |
| { | |
| (void)fm->gamepadDispatch(invocation, &dispatched); | |
| } | |
| result = (int)stateMachine->broadcastGamepadToScriptedDrawables( | |
| invocation, | |
| dispatched); | |
| }; | |
| switch (atom) | |
| { | |
| case (int)LuaAtoms::gamepadConnected: | |
| { | |
| auto* connected = lua_torive<ScriptedGamepadConnected>(L, 2); | |
| dispatch(ListenerInvocation::gamepadConnected( | |
| connected->m_snapshot)); | |
| break; | |
| } | |
| case (int)LuaAtoms::gamepadEvent: | |
| { | |
| auto* event = lua_torive<ScriptedGamepadEvent>(L, 2); | |
| dispatch(ListenerInvocation::gamepadEvent(event->m_data)); | |
| break; | |
| } | |
| case (int)LuaAtoms::gamepadDisconnected: | |
| { | |
| auto* disconnected = | |
| lua_torive<ScriptedGamepadDisconnected>(L, 2); | |
| dispatch(ListenerInvocation::gamepadDisconnected( | |
| disconnected->m_deviceId)); | |
| break; | |
| } | |
| } | |
| } | |
| lua_pushinteger(L, result); | |
| return 1; | |
| } | |
| static int artboard_advance(lua_State* L) | |
| { | |
| auto scriptedArtboard = lua_torive<ScriptedArtboard>(L, 1); | |
| auto seconds = float(luaL_checknumber(L, 2)); | |
| bool advanced = scriptedArtboard->advance(seconds); | |
| lua_pushboolean(L, advanced ? 1 : 0); | |
| return 1; | |
| } | |
| static int artboard_namecall(lua_State* L) | |
| { | |
| int atom; | |
| const char* str = lua_namecallatom(L, &atom); | |
| if (str != nullptr) | |
| { | |
| switch (atom) | |
| { | |
| case (int)LuaAtoms::draw: | |
| return artboard_draw(L); | |
| case (int)LuaAtoms::advance: | |
| return artboard_advance(L); | |
| case (int)LuaAtoms::instance: | |
| { | |
| int nargs = lua_gettop(L); | |
| auto scriptedArtboard = lua_torive<ScriptedArtboard>(L, 1); | |
| if (nargs == 2) | |
| { | |
| auto scriptedViewModel = | |
| lua_torive<ScriptedViewModel>(L, 2); | |
| return scriptedArtboard->instance( | |
| L, | |
| scriptedViewModel->viewModelInstance()); | |
| } | |
| return scriptedArtboard->instance(L, nullptr); | |
| } | |
| case (int)LuaAtoms::animation: | |
| { | |
| auto scriptedArtboard = lua_torive<ScriptedArtboard>(L, 1); | |
| const char* animationName = luaL_checkstring(L, 2); | |
| return scriptedArtboard->animation(L, animationName); | |
| } | |
| case (int)LuaAtoms::addToPath: | |
| { | |
| int nargs = lua_gettop(L); | |
| auto scriptedArtboard = lua_torive<ScriptedArtboard>(L, 1); | |
| auto scriptedPath = lua_torive<ScriptedPath>(L, 2); | |
| Mat2D* transform = nullptr; | |
| if (nargs == 3) | |
| { | |
| auto matrix = lua_torive<ScriptedMat2D>(L, 3); | |
| transform = &matrix->value; | |
| } | |
| scriptedArtboard->scriptReffedArtboard() | |
| ->artboard() | |
| ->addToRawPath(scriptedPath->rawPath, transform); | |
| scriptedPath->markDirty(); | |
| return 0; | |
| } | |
| case (int)LuaAtoms::bounds: | |
| { | |
| auto scriptedArtboard = lua_torive<ScriptedArtboard>(L, 1); | |
| const auto& bounds = scriptedArtboard->artboard()->bounds(); | |
| lua_pushvec2d(L, bounds.min()); | |
| lua_pushvec2d(L, bounds.max()); | |
| return 2; | |
| } | |
| case (int)LuaAtoms::node: | |
| { | |
| auto scriptedArtboard = lua_torive<ScriptedArtboard>(L, 1); | |
| const char* name = luaL_checkstring(L, 2); | |
| auto component = | |
| scriptedArtboard->artboard()->find<TransformComponent>( | |
| name); | |
| if (component == nullptr) | |
| { | |
| lua_pushnil(L); | |
| return 1; | |
| } | |
| lua_newrive<ScriptedNode>( | |
| L, | |
| scriptedArtboard->scriptReffedArtboard(), | |
| component); | |
| return 1; | |
| } | |
| case (int)LuaAtoms::pointerDown: | |
| case (int)LuaAtoms::pointerUp: | |
| case (int)LuaAtoms::pointerMove: | |
| case (int)LuaAtoms::pointerExit: | |
| { | |
| return apply_pointer_event(L, atom); | |
| } | |
| case (int)LuaAtoms::gamepadEvent: | |
| case (int)LuaAtoms::gamepadConnected: | |
| case (int)LuaAtoms::gamepadDisconnected: | |
| { | |
| return apply_gamepad_event(L, atom); | |
| } | |
| } | |
| } | |
| luaL_error(L, | |
| "%s is not a valid method of %s", | |
| str, | |
| ScriptedArtboard::luaName); | |
| return 0; | |
| } | |
| int ScriptedArtboard::pushData(lua_State* L) | |
| { | |
| if (m_dataRef != 0) | |
| { | |
| lua_rawgeti(L, LUA_REGISTRYINDEX, m_dataRef); | |
| return 1; | |
| } | |
| auto vm = viewModelInstance(); | |
| if (!vm) | |
| { | |
| lua_pushnil(L); | |
| } | |
| else | |
| { | |
| lua_newrive<ScriptedViewModel>(L, L, ref_rcp(vm->viewModel()), vm); | |
| } | |
| m_dataRef = lua_ref(L, -1); | |
| return 1; | |
| } | |
| int ScriptedArtboard::instance(lua_State* L, | |
| rcp<ViewModelInstance> viewModelInstance) | |
| { | |
| auto artboardInstance = artboard()->instance(); | |
| artboardInstance->frameOrigin(false); | |
| // Clones share the source's dependency on a cross-lifetime host file. | |
| lua_newrive<ScriptedArtboard>(L, | |
| L, | |
| m_scriptReffedArtboard->file(), | |
| std::move(artboardInstance), | |
| viewModelInstance, | |
| m_dataContext | |
| #ifdef WITH_RIVE_TOOLS | |
| , | |
| m_scriptReffedArtboard->filePin() | |
| #endif | |
| ); | |
| return 1; | |
| } | |
| int ScriptedArtboard::animation(lua_State* L, const char* animationName) | |
| { | |
| if (artboard()->isInstance()) | |
| { | |
| auto animation = static_cast<ArtboardInstance*>(artboard()) | |
| ->animationNamed(animationName); | |
| if (animation) | |
| { | |
| lua_newrive<ScriptedAnimation>(L, L, std::move(animation)); | |
| return 1; | |
| } | |
| } | |
| return 0; | |
| } | |
| ScriptedNode::ScriptedNode(rcp<ScriptReffedArtboard> artboard, | |
| TransformComponent* component) : | |
| m_artboard(artboard), m_component(component) | |
| {} | |
| const ShapePaint* ScriptedNode::shapePaint() | |
| { | |
| if (m_shapePaint) | |
| { | |
| return m_shapePaint; | |
| } | |
| if (m_component != nullptr && m_component->is<ShapePaint>()) | |
| { | |
| return m_component->as<ShapePaint>(); | |
| } | |
| return nullptr; | |
| } | |
| // Direct field getters bypass the __index Lua frame. They are dispatched by | |
| // LOP_GETTABLEKS via global_State::udatadirectfields[tag]. Only fields that | |
| // read a primitive without allocation or side effects belong here; the | |
| // __index slow path still handles every other key. | |
| static void artboard_direct_width(void* udata, void* result) | |
| { | |
| lua_userdatadirectfield_setnumber( | |
| result, | |
| ((ScriptedArtboard*)udata)->artboard()->width()); | |
| } | |
| static void artboard_direct_height(void* udata, void* result) | |
| { | |
| lua_userdatadirectfield_setnumber( | |
| result, | |
| ((ScriptedArtboard*)udata)->artboard()->height()); | |
| } | |
| static void artboard_direct_frameOrigin(void* udata, void* result) | |
| { | |
| lua_userdatadirectfield_setboolean( | |
| result, | |
| ((ScriptedArtboard*)udata)->artboard()->frameOrigin() ? 1 : 0); | |
| } | |
| static int artboard_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 scriptedArtboard = lua_torive<ScriptedArtboard>(L, 1); | |
| switch (atom) | |
| { | |
| case (int)LuaAtoms::frameOrigin: | |
| lua_pushboolean(L, | |
| scriptedArtboard->artboard()->frameOrigin() ? 1 | |
| : 0); | |
| return 1; | |
| case (int)LuaAtoms::width: | |
| lua_pushnumber(L, scriptedArtboard->artboard()->width()); | |
| return 1; | |
| case (int)LuaAtoms::height: | |
| lua_pushnumber(L, scriptedArtboard->artboard()->height()); | |
| return 1; | |
| case (int)LuaAtoms::data: | |
| return scriptedArtboard->pushData(L); | |
| default: | |
| break; | |
| } | |
| luaL_error(L, | |
| "'%s' is not a valid index of %s", | |
| key, | |
| ScriptedArtboard::luaName); | |
| return 0; | |
| } | |
| static int artboard_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 scriptedArtboard = lua_torive<ScriptedArtboard>(L, 1); | |
| switch (atom) | |
| { | |
| case (int)LuaAtoms::frameOrigin: | |
| scriptedArtboard->artboard()->frameOrigin(luaL_checkboolean(L, 3) != | |
| 0); | |
| return 0; | |
| case (int)LuaAtoms::width: | |
| scriptedArtboard->artboard()->width(luaL_checknumber(L, 3)); | |
| return 0; | |
| case (int)LuaAtoms::height: | |
| scriptedArtboard->artboard()->height(luaL_checknumber(L, 3)); | |
| return 0; | |
| default: | |
| break; | |
| } | |
| luaL_error(L, | |
| "'%s' is not a valid index of %s", | |
| key, | |
| ScriptedArtboard::luaName); | |
| return 0; | |
| } | |
| ScriptedArtboard::ScriptedArtboard( | |
| lua_State* L, | |
| File* file, | |
| std::unique_ptr<ArtboardInstance>&& artboardInstance, | |
| rcp<ViewModelInstance> viewModelInstance, | |
| rcp<DataContext> dataContext | |
| #ifdef WITH_RIVE_TOOLS | |
| , | |
| rcp<File> filePin | |
| #endif | |
| ) : | |
| m_state(L), | |
| m_scriptReffedArtboard(make_rcp<ScriptReffedArtboard>( | |
| file, | |
| std::move(artboardInstance), | |
| viewModelInstance, | |
| dataContext, | |
| static_cast<ScriptingContext*>(lua_getthreaddata(L)) | |
| #ifdef WITH_RIVE_TOOLS | |
| , | |
| std::move(filePin) | |
| #endif | |
| )), | |
| m_dataContext(dataContext) | |
| {} | |
| ScriptedArtboard::~ScriptedArtboard() | |
| { | |
| lua_unref(m_state, m_dataRef); | |
| m_scriptReffedArtboard = nullptr; | |
| } | |
| ScriptedAnimation::ScriptedAnimation( | |
| lua_State* L, | |
| std::unique_ptr<LinearAnimationInstance> animation) : | |
| m_state(L), m_animation(std::move(animation)) | |
| {} | |
| float ScriptedAnimation::duration() | |
| { | |
| auto durationFrames = (float)m_animation->duration(); | |
| auto fps = (float)m_animation->fps(); | |
| return durationFrames / fps; | |
| } | |
| int ScriptedAnimation::advance() | |
| { | |
| auto seconds = float(luaL_checknumber(m_state, 2)); | |
| bool advanced = m_animation->advance(seconds); | |
| m_animation->apply(); | |
| lua_pushboolean(m_state, advanced ? 1 : 0); | |
| return 1; | |
| } | |
| int ScriptedAnimation::setTime(std::string mode) | |
| { | |
| float seconds = 0; | |
| if (mode == "seconds") | |
| { | |
| seconds = float(luaL_checknumber(m_state, 2)); | |
| } | |
| else if (mode == "frames") | |
| { | |
| auto frames = float(luaL_checknumber(m_state, 2)); | |
| seconds = frames / m_animation->fps(); | |
| } | |
| else if (mode == "percentage") | |
| { | |
| auto percentage = float(luaL_checknumber(m_state, 2)); | |
| seconds = percentage * duration(); | |
| } | |
| m_animation->time(m_animation->animation()->globalToLocalSeconds(seconds)); | |
| m_animation->apply(); | |
| return 0; | |
| } | |
| static void node_direct_x(void* udata, void* result) | |
| { | |
| lua_userdatadirectfield_setnumber(result, | |
| ((ScriptedNode*)udata)->component()->x()); | |
| } | |
| static void node_direct_y(void* udata, void* result) | |
| { | |
| lua_userdatadirectfield_setnumber(result, | |
| ((ScriptedNode*)udata)->component()->y()); | |
| } | |
| static void node_direct_rotation(void* udata, void* result) | |
| { | |
| lua_userdatadirectfield_setnumber( | |
| result, | |
| ((ScriptedNode*)udata)->component()->rotation()); | |
| } | |
| static void node_direct_scaleX(void* udata, void* result) | |
| { | |
| lua_userdatadirectfield_setnumber( | |
| result, | |
| ((ScriptedNode*)udata)->component()->scaleX()); | |
| } | |
| static void node_direct_scaleY(void* udata, void* result) | |
| { | |
| lua_userdatadirectfield_setnumber( | |
| result, | |
| ((ScriptedNode*)udata)->component()->scaleY()); | |
| } | |
| static void node_direct_position(void* udata, void* result) | |
| { | |
| auto component = ((ScriptedNode*)udata)->component(); | |
| lua_userdatadirectfield_setvector(result, | |
| component->x(), | |
| component->y(), | |
| 0.0f | |
| #if LUA_VECTOR_SIZE == 4 | |
| , | |
| 0.0f | |
| #endif | |
| ); | |
| } | |
| static void node_direct_scale(void* udata, void* result) | |
| { | |
| auto component = ((ScriptedNode*)udata)->component(); | |
| lua_userdatadirectfield_setvector(result, | |
| component->scaleX(), | |
| component->scaleY(), | |
| 0.0f | |
| #if LUA_VECTOR_SIZE == 4 | |
| , | |
| 0.0f | |
| #endif | |
| ); | |
| } | |
| static int node_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 scriptedNode = lua_torive<ScriptedNode>(L, 1); | |
| switch (atom) | |
| { | |
| case (int)LuaAtoms::position: | |
| lua_pushvector2(L, | |
| scriptedNode->component()->x(), | |
| scriptedNode->component()->y()); | |
| return 1; | |
| case (int)LuaAtoms::rotation: | |
| lua_pushnumber(L, scriptedNode->component()->rotation()); | |
| return 1; | |
| case (int)LuaAtoms::scale: | |
| lua_pushvector2(L, | |
| scriptedNode->component()->scaleX(), | |
| scriptedNode->component()->scaleY()); | |
| return 1; | |
| case (int)LuaAtoms::scaleX: | |
| lua_pushnumber(L, scriptedNode->component()->scaleX()); | |
| return 1; | |
| case (int)LuaAtoms::scaleY: | |
| lua_pushnumber(L, scriptedNode->component()->scaleY()); | |
| return 1; | |
| case (int)LuaAtoms::worldTransform: | |
| lua_newrive<ScriptedMat2D>( | |
| L, | |
| scriptedNode->component()->worldTransform()); | |
| return 1; | |
| case (int)LuaAtoms::children: | |
| { | |
| auto component = scriptedNode->component(); | |
| if (component->is<ContainerComponent>()) | |
| { | |
| auto container = component->as<ContainerComponent>(); | |
| // speculative pre-alloc the table | |
| auto& children = container->children(); | |
| lua_createtable(L, (int)children.size(), 0); | |
| int index = 1; | |
| for (auto child : children) | |
| { | |
| if (child->is<TransformComponent>()) | |
| { | |
| lua_newrive<ScriptedNode>( | |
| L, | |
| scriptedNode->artboard(), | |
| child->as<TransformComponent>()); | |
| // Set table[i] = value, pops the value | |
| lua_rawseti(L, -2, index++); | |
| } | |
| } | |
| } | |
| return 1; | |
| } | |
| case (int)LuaAtoms::parent: | |
| { | |
| auto parent = scriptedNode->component()->parent(); | |
| if (parent != nullptr && parent->is<TransformComponent>()) | |
| { | |
| lua_newrive<ScriptedNode>(L, | |
| scriptedNode->artboard(), | |
| parent->as<TransformComponent>()); | |
| } | |
| else | |
| { | |
| lua_pushnil(L); | |
| } | |
| return 1; | |
| } | |
| case (int)LuaAtoms::paint: | |
| { | |
| auto shapePaint = scriptedNode->shapePaint(); | |
| if (shapePaint) | |
| { | |
| lua_newrive<ScriptedPaintData>(L, shapePaint); | |
| } | |
| else | |
| { | |
| lua_pushnil(L); | |
| } | |
| return 1; | |
| } | |
| default: | |
| switch (key[0]) | |
| { | |
| case 'x': | |
| lua_pushnumber(L, scriptedNode->component()->x()); | |
| return 1; | |
| case 'y': | |
| lua_pushnumber(L, scriptedNode->component()->y()); | |
| return 1; | |
| } | |
| } | |
| luaL_error(L, | |
| "'%s' is not a valid index of %s", | |
| key, | |
| ScriptedNode::luaName); | |
| return 0; | |
| } | |
| static int node_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 scriptedNode = lua_torive<ScriptedNode>(L, 1); | |
| auto component = scriptedNode->component(); | |
| switch (atom) | |
| { | |
| case (int)LuaAtoms::position: | |
| { | |
| const float* vec = luaL_checkvector(L, 3); | |
| if (component->is<Node>()) | |
| { | |
| Node* node = component->as<Node>(); | |
| node->x(vec[0]); | |
| node->y(vec[1]); | |
| } | |
| else if (component->is<RootBone>()) | |
| { | |
| RootBone* bone = component->as<RootBone>(); | |
| bone->x(vec[0]); | |
| bone->y(vec[1]); | |
| } | |
| return 0; | |
| } | |
| case (int)LuaAtoms::rotation: | |
| component->rotation(float(luaL_checknumber(L, 3))); | |
| return 0; | |
| case (int)LuaAtoms::scale: | |
| { | |
| const float* vec = luaL_checkvector(L, 3); | |
| component->scaleX(vec[0]); | |
| component->scaleY(vec[1]); | |
| return 0; | |
| } | |
| case (int)LuaAtoms::scaleX: | |
| component->scaleX(float(luaL_checknumber(L, 3))); | |
| return 0; | |
| case (int)LuaAtoms::scaleY: | |
| component->scaleY(float(luaL_checknumber(L, 3))); | |
| return 0; | |
| case (int)LuaAtoms::worldTransform: | |
| { | |
| Mat2D& transform = component->mutableWorldTransform(); | |
| transform = *(Mat2D*)lua_torive<ScriptedMat2D>(L, 3); | |
| return 0; | |
| } | |
| default: | |
| switch (key[0]) | |
| { | |
| case 'x': | |
| if (component->is<Node>()) | |
| { | |
| Node* node = component->as<Node>(); | |
| node->x(float(luaL_checknumber(L, 3))); | |
| } | |
| else if (component->is<RootBone>()) | |
| { | |
| RootBone* bone = component->as<RootBone>(); | |
| bone->x(float(luaL_checknumber(L, 3))); | |
| } | |
| return 0; | |
| case 'y': | |
| if (component->is<Node>()) | |
| { | |
| Node* node = component->as<Node>(); | |
| node->y(float(luaL_checknumber(L, 3))); | |
| } | |
| else if (component->is<RootBone>()) | |
| { | |
| RootBone* bone = component->as<RootBone>(); | |
| bone->y(float(luaL_checknumber(L, 3))); | |
| } | |
| return 0; | |
| } | |
| } | |
| luaL_error(L, | |
| "'%s' is not a valid index of %s", | |
| key, | |
| ScriptedNode::luaName); | |
| return 0; | |
| } | |
| static int node_namecall(lua_State* L) | |
| { | |
| int atom; | |
| const char* str = lua_namecallatom(L, &atom); | |
| if (str != nullptr) | |
| { | |
| switch (atom) | |
| { | |
| case (int)LuaAtoms::decompose: | |
| { | |
| auto scriptedNode = lua_torive<ScriptedNode>(L, 1); | |
| auto component = scriptedNode->component(); | |
| Mat2D world = getParentWorld(*component).invertOrIdentity() * | |
| (*(Mat2D*)lua_torive<ScriptedMat2D>(L, 2)); | |
| TransformComponents components = world.decompose(); | |
| if (component->is<Node>()) | |
| { | |
| Node* node = component->as<Node>(); | |
| node->x(components.x()); | |
| node->y(components.y()); | |
| } | |
| else if (component->is<RootBone>()) | |
| { | |
| RootBone* bone = component->as<RootBone>(); | |
| bone->x(components.x()); | |
| bone->y(components.y()); | |
| } | |
| component->scaleX(components.scaleX()); | |
| component->scaleY(components.scaleY()); | |
| component->rotation(components.rotation()); | |
| return 0; | |
| } | |
| case (int)LuaAtoms::asPath: | |
| { | |
| auto scriptedNode = lua_torive<ScriptedNode>(L, 1); | |
| auto component = scriptedNode->component(); | |
| if (component->is<Path>()) | |
| { | |
| lua_newrive<ScriptedPathData>( | |
| L, | |
| &component->as<Path>()->rawPath()); | |
| } | |
| else | |
| { | |
| lua_pushnil(L); | |
| } | |
| return 1; | |
| } | |
| case (int)LuaAtoms::asPaint: | |
| { | |
| auto scriptedNode = lua_torive<ScriptedNode>(L, 1); | |
| auto shapePaint = scriptedNode->shapePaint(); | |
| if (shapePaint) | |
| { | |
| lua_newrive<ScriptedPaintData>(L, shapePaint); | |
| } | |
| else | |
| { | |
| lua_pushnil(L); | |
| } | |
| return 1; | |
| } | |
| } | |
| } | |
| luaL_error(L, "%s is not a valid method of %s", str, ScriptedNode::luaName); | |
| return 0; | |
| } | |
| static void animation_direct_duration(void* udata, void* result) | |
| { | |
| lua_userdatadirectfield_setnumber(result, | |
| ((ScriptedAnimation*)udata)->duration()); | |
| } | |
| static int animation_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 scriptedAnimation = lua_torive<ScriptedAnimation>(L, 1); | |
| switch (atom) | |
| { | |
| case (int)LuaAtoms::duration: | |
| lua_pushnumber(L, scriptedAnimation->duration()); | |
| return 1; | |
| } | |
| luaL_error(L, | |
| "'%s' is not a valid index of %s", | |
| key, | |
| ScriptedNode::luaName); | |
| return 0; | |
| } | |
| static int animation_namecall(lua_State* L) | |
| { | |
| int atom; | |
| const char* str = lua_namecallatom(L, &atom); | |
| if (str != nullptr) | |
| { | |
| auto scriptedAnimation = lua_torive<ScriptedAnimation>(L, 1); | |
| switch (atom) | |
| { | |
| case (int)LuaAtoms::advance: | |
| { | |
| return scriptedAnimation->advance(); | |
| } | |
| case (int)LuaAtoms::setTime: | |
| { | |
| return scriptedAnimation->setTime("seconds"); | |
| } | |
| case (int)LuaAtoms::setTimeFrames: | |
| { | |
| return scriptedAnimation->setTime("frames"); | |
| } | |
| case (int)LuaAtoms::setTimePercentage: | |
| { | |
| return scriptedAnimation->setTime("percentage"); | |
| } | |
| } | |
| } | |
| luaL_error(L, "%s is not a valid method of %s", str, ScriptedNode::luaName); | |
| return 0; | |
| } | |
| int luaopen_rive_artboards(lua_State* L) | |
| { | |
| lua_register_rive<ScriptedArtboard>(L); | |
| lua_pushcfunction(L, artboard_index, nullptr); | |
| lua_setfield(L, -2, "__index"); | |
| lua_pushcfunction(L, artboard_newindex, nullptr); | |
| lua_setfield(L, -2, "__newindex"); | |
| lua_pushcfunction(L, artboard_namecall, nullptr); | |
| lua_setfield(L, -2, "__namecall"); | |
| lua_setreadonly(L, -1, true); | |
| lua_pop(L, 1); // pop the metatable | |
| lua_registeruserdatadirectfieldget(L, | |
| ScriptedArtboard::luaTag, | |
| "width", | |
| artboard_direct_width); | |
| lua_registeruserdatadirectfieldget(L, | |
| ScriptedArtboard::luaTag, | |
| "height", | |
| artboard_direct_height); | |
| lua_registeruserdatadirectfieldget(L, | |
| ScriptedArtboard::luaTag, | |
| "frameOrigin", | |
| artboard_direct_frameOrigin); | |
| lua_register_rive<ScriptedNode>(L); | |
| lua_pushcfunction(L, node_index, nullptr); | |
| lua_setfield(L, -2, "__index"); | |
| lua_pushcfunction(L, node_newindex, nullptr); | |
| lua_setfield(L, -2, "__newindex"); | |
| lua_pushcfunction(L, node_namecall, nullptr); | |
| lua_setfield(L, -2, "__namecall"); | |
| lua_setreadonly(L, -1, true); | |
| lua_pop(L, 1); // pop the metatable | |
| lua_registeruserdatadirectfieldget(L, | |
| ScriptedNode::luaTag, | |
| "x", | |
| node_direct_x); | |
| lua_registeruserdatadirectfieldget(L, | |
| ScriptedNode::luaTag, | |
| "y", | |
| node_direct_y); | |
| lua_registeruserdatadirectfieldget(L, | |
| ScriptedNode::luaTag, | |
| "rotation", | |
| node_direct_rotation); | |
| lua_registeruserdatadirectfieldget(L, | |
| ScriptedNode::luaTag, | |
| "scaleX", | |
| node_direct_scaleX); | |
| lua_registeruserdatadirectfieldget(L, | |
| ScriptedNode::luaTag, | |
| "scaleY", | |
| node_direct_scaleY); | |
| lua_registeruserdatadirectfieldget(L, | |
| ScriptedNode::luaTag, | |
| "position", | |
| node_direct_position); | |
| lua_registeruserdatadirectfieldget(L, | |
| ScriptedNode::luaTag, | |
| "scale", | |
| node_direct_scale); | |
| lua_register_rive<ScriptedAnimation>(L); | |
| lua_pushcfunction(L, animation_index, nullptr); | |
| lua_setfield(L, -2, "__index"); | |
| lua_pushcfunction(L, animation_namecall, nullptr); | |
| lua_setfield(L, -2, "__namecall"); | |
| lua_pop(L, 1); // pop the metatable | |
| lua_registeruserdatadirectfieldget(L, | |
| ScriptedAnimation::luaTag, | |
| "duration", | |
| animation_direct_duration); | |
| return 0; | |
| } | |
| #endif |