diff --git a/.gitignore b/.gitignore index 0bef3551..5abaaacc 100644 --- a/.gitignore +++ b/.gitignore @@ -8,8 +8,7 @@ Thumbs.db .project .cproject builtins -/utils/cmake -/utils/build +/utils/**/build /bundle* bob.jar /defold-rive/lib/**/ext.settings diff --git a/README.md b/README.md index 58dbfc20..e2225482 100644 --- a/README.md +++ b/README.md @@ -8,4 +8,11 @@ Defold [native extension](https://www.defold.com/manuals/extensions/) for intera # Platform support -All platforms are supported except WebGL1, Windows x86 and consoles. +All platforms are supported except WebGL1, Android with armv7 and consoles. WebGL2 and arm64 for Android are supported however. + + +# Credits + +* https://rive.app/marketplace/14447-40690-game-achievement-badge-data-binding/ (CC-BY 4.0) + + * By https://rive.app/@setyosn/ diff --git a/assets/rive/404.riv b/assets/rive/404.riv deleted file mode 100644 index c7a33a13..00000000 Binary files a/assets/rive/404.riv and /dev/null differ diff --git a/assets/rive/clipped_circle_star_2.riv b/assets/rive/clipped_circle_star_2.riv deleted file mode 100644 index 41726400..00000000 Binary files a/assets/rive/clipped_circle_star_2.riv and /dev/null differ diff --git a/assets/rive/control.riv b/assets/rive/control.riv deleted file mode 100644 index 3f7b0443..00000000 Binary files a/assets/rive/control.riv and /dev/null differ diff --git a/assets/rive/juice.riv b/assets/rive/juice.riv deleted file mode 100644 index 5df55e4e..00000000 Binary files a/assets/rive/juice.riv and /dev/null differ diff --git a/assets/rive/marty.riv b/assets/rive/marty.riv deleted file mode 100644 index abc309f3..00000000 Binary files a/assets/rive/marty.riv and /dev/null differ diff --git a/assets/rive/marty_v2.riv b/assets/rive/marty_v2.riv deleted file mode 100644 index 774fee69..00000000 Binary files a/assets/rive/marty_v2.riv and /dev/null differ diff --git a/assets/rive/off_road_car.riv b/assets/rive/off_road_car.riv deleted file mode 100644 index 81202cb8..00000000 Binary files a/assets/rive/off_road_car.riv and /dev/null differ diff --git a/assets/rive/polygon_party.riv b/assets/rive/polygon_party.riv deleted file mode 100644 index 04c51487..00000000 Binary files a/assets/rive/polygon_party.riv and /dev/null differ diff --git a/assets/rive/rotate_square.riv b/assets/rive/rotate_square.riv deleted file mode 100644 index eba670a2..00000000 Binary files a/assets/rive/rotate_square.riv and /dev/null differ diff --git a/assets/rive/runner.riv b/assets/rive/runner.riv deleted file mode 100644 index b6a0febd..00000000 Binary files a/assets/rive/runner.riv and /dev/null differ diff --git a/assets/rive/runner_boy.riv b/assets/rive/runner_boy.riv deleted file mode 100644 index f4b37806..00000000 Binary files a/assets/rive/runner_boy.riv and /dev/null differ diff --git a/assets/rive/simple_stroke_only.riv b/assets/rive/simple_stroke_only.riv deleted file mode 100644 index 8fc84f14..00000000 Binary files a/assets/rive/simple_stroke_only.riv and /dev/null differ diff --git a/assets/rive/zombie_leg.riv b/assets/rive/zombie_leg.riv deleted file mode 100644 index 5df4bdfd..00000000 Binary files a/assets/rive/zombie_leg.riv and /dev/null differ diff --git a/defold-rive/api/rive.lua b/defold-rive/api/rive.lua new file mode 100644 index 00000000..0336029f --- /dev/null +++ b/defold-rive/api/rive.lua @@ -0,0 +1,464 @@ +-- Auto generated from utils/update_script_api.py +-- WARNING: Do not edit manually. + +--[[ +Rive API documentation +Functions and constants for interacting with Rive models +--]] + +---@meta +---@diagnostic disable: lowercase-global +---@diagnostic disable: missing-return +---@diagnostic disable: duplicate-doc-param +---@diagnostic disable: duplicate-set-field +---@diagnostic disable: args-after-dots + +---@alias ArtboardHandle integer +---@alias AudioSourceHandle integer +---@alias FileHandle integer +---@alias FontHandle integer +---@alias RenderImageHandle integer +---@alias StateMachineHandle integer +---@alias ViewModelInstanceHandle integer + +--- @class rive +rive = {} + +--- Lua wrapper for pointer movement. +---@param url url Component receiving the pointer move. +---@param x number Pointer x coordinate in component space. +---@param y number Pointer y coordinate in component space. +function rive.pointer_move(url, x, y) end + +--- Lua wrapper for pointer up events. +---@param url url Component receiving the pointer release. +---@param x number Pointer x coordinate. +---@param y number Pointer y coordinate. +function rive.pointer_up(url, x, y) end + +--- Lua wrapper for pointer down events. +---@param url url Component receiving the pointer press. +---@param x number Pointer x coordinate. +---@param y number Pointer y coordinate. +function rive.pointer_down(url, x, y) end + +--- Lua wrapper for pointer exit events. +---@param url url Component receiving the pointer leave. +---@param x number Pointer x coordinate. +---@param y number Pointer y coordinate. +function rive.pointer_exit(url, x, y) end + +--- Returns the projection matrix in render coordinates. +---@return vmath.matrix4 matrix Current projection matrix for the window. +function rive.get_projection_matrix() end + +--- Sets or clears the global file listener callback. +---@param callback? fun(self, event, data) Callback invoked for file system events; pass nil to disable. +---@param callback_self object The calling script instance. +---@param callback_event string One of: onFileLoaded, onFileDeleted, onFileError, onArtboardsListed, onViewModelsListed, onViewModelInstanceNamesListed, onViewModelPropertiesListed, onViewModelEnumsListed +---@param callback_data table Additional fields vary by event. Common keys include: +---@param callback_data_file FileHandle File handle involved in the event. +---@param callback_data_viewModelName string View model name for the request, when applicable. +---@param callback_data_instanceNames table Array of instance name strings. +---@param callback_data_artboardNames table Array of artboard name strings. +---@param callback_data_properties table Array of property metadata tables. +---@param callback_data_enums table Array of enum definitions. +---@param callback_data_error string Error message when a failure occurs. +function rive.set_file_listener(callback) end + +--- Sets or clears the artboard listener callback. +---@param callback? fun(self, event, data) Callback invoked for artboard-related events; pass nil to disable. +---@param callback_self object The calling script instance. +---@param callback_event string One of: onArtboardError, onDefaultViewModelInfoReceived, onArtboardDeleted, onStateMachinesListed +---@param callback_data table Additional data per event, typically: +---@param callback_data_artboard ArtboardHandle Artboard handle involved. +---@param callback_data_viewModelName string View model name for defaults (received event). +---@param callback_data_instanceName string Instance name for defaults. +---@param callback_data_stateMachineNames table Array of state machine name strings. +---@param callback_data_error string Error message when an error event fires. +function rive.set_artboard_listener(callback) end + +--- Sets or clears the state machine listener callback. +---@param callback? fun(self, event, data) Callback invoked for state machine events; pass nil to disable. +---@param callback_self object The calling script instance. +---@param callback_event string One of: onStateMachineError, onStateMachineDeleted, onStateMachineSettled +---@param callback_data table Event-specific details: +---@param callback_data_stateMachine StateMachineHandle Active state machine handle. +---@param callback_data_error string Error message when an error occurs. +function rive.set_state_machine_listener(callback) end + +--- Sets or clears the view model instance listener callback. +---@param callback? fun(self, event, data) Callback invoked for view model instance events; pass nil to disable. +---@param callback_self object The calling script instance. +---@param callback_event string One of: onViewModelInstanceError, onViewModelDeleted, onViewModelDataReceived, onViewModelListSizeReceived +---@param callback_data table Additional payload per event: +---@param callback_data_viewModel ViewModelInstanceHandle Handle of the affected view model instance. +---@param callback_data_error string Error description when an error fires. +---@param callback_data_path string Path being inspected when list size arrives. +---@param callback_data_size number List size value for list-size events. +function rive.set_view_model_instance_listener(callback) end + +--- Sets or clears the render image listener callback. +---@param callback? fun(self, event, data) Callback invoked for render image events; pass nil to disable. +---@param callback_self object The calling script instance. +---@param callback_event string One of: onRenderImageDecoded, onRenderImageError, onRenderImageDeleted +---@param callback_data table Additional fields: +---@param callback_data_renderImage RenderImageHandle Handle of the render image. +---@param callback_data_error string Error message for the failure event. +function rive.set_render_image_listener(callback) end + +--- Sets or clears the audio source listener callback. +---@param callback? fun(self, event, data) Callback invoked for audio source events; pass nil to disable. +---@param callback_self object The calling script instance. +---@param callback_event string One of: onAudioSourceDecoded, onAudioSourceError, onAudioSourceDeleted +---@param callback_data table Additional fields: +---@param callback_data_audioSource AudioSourceHandle Audio source handle for the event. +---@param callback_data_error string Error message when provided. +function rive.set_audio_source_listener(callback) end + +--- Sets or clears the font listener callback. +---@param callback? fun(self, event, data) Callback invoked for font events; pass nil to disable. +---@param callback_self object The calling script instance. +---@param callback_event string One of: onFontDecoded, onFontError, onFontDeleted +---@param callback_data table Additional fields: +---@param callback_data_font FontHandle Font handle for the associated event. +---@param callback_data_error string Error message for failure events. +function rive.set_font_listener(callback) end + +--- Returns the Rive file handle tied to the component. +---@param url url Component whose file handle to query. +---@return FileHandle file_handle Handle identifying the loaded Rive file. +function rive.get_file(url) end + +--- Switches the active artboard for the component. +---@param url url Component using the artboard. +---@param name? string Name of the artboard to create and set. Pass nil to create a default artboard. +---@return ArtboardHandle artboard Old artboard handle +function rive.set_artboard(url, name) end + +--- Queries the current artboard handle for the component. +---@param url url Component whose artboard handle to return. +---@return ArtboardHandle artboard Active artboard handle. +function rive.get_artboard(url) end + +--- Selects a state machine by name on the component. +---@param url url Component owning the state machine. +---@param name? string Name of the state machine to create and set. Pass nil to create a default state machine. +---@return StateMachineHandle state_machine Old state machine handle +function rive.set_state_machine(url, name) end + +--- Returns the active state machine handle for the component. +---@param url url Component whose active state machine to query. +---@return StateMachineHandle state_machine Current state machine handle. +function rive.get_state_machine(url) end + +--- Selects a view model instance by name. +---@param url url Component owning the view model instance. +---@param name string View model instance name to activate. +---@return boolean success True if the view model instance was activated. +function rive.set_view_model_instance(url, name) end + +--- Returns the handle of the currently bound view model instance. +---@param url url Component whose view model instance handle to query. +---@return ViewModelInstanceHandle view_model_instance_handle Handle for the active view model instance. +function rive.get_view_model_instance(url) end + +--- @class rive.cmd +rive.cmd = {} + +--- Returns the artboard handle created for the named view model. +---@param file_handle FileHandle Handle to a previously loaded Rive file. +---@param viewmodel_name string Name of the view model to instantiate. +---@return ArtboardHandle artboard_handle Artboard handle created for the named view model. +function rive.cmd.instantiateArtboardNamed(file_handle, viewmodel_name) end + +--- Returns the default artboard handle for the file. +---@param file_handle FileHandle Handle to a previously loaded Rive file. +---@return ArtboardHandle artboard_handle Default artboard handle for the file. +function rive.cmd.instantiateDefaultArtboard(file_handle) end + +--- Returns a blank view model instance handle for the given artboard or view model. +---@param file_handle FileHandle Handle to a previously loaded Rive file. +---@param artboard_or_viewmodel ArtboardHandle|string Artboard handle or view model name that identifies where to instantiate. +---@return ViewModelInstanceHandle view_model_instance_handle Blank view model instance handle. +function rive.cmd.instantiateBlankViewModelInstance(file_handle, artboard_or_viewmodel) end + +--- Returns a default-populated view model instance handle for the given artboard or view model. +---@param file_handle FileHandle Handle to a previously loaded Rive file. +---@param artboard_or_viewmodel ArtboardHandle|string Artboard handle or view model name the instance should derive from. +---@return ViewModelInstanceHandle view_model_instance_handle Default view model instance handle. +function rive.cmd.instantiateDefaultViewModelInstance(file_handle, artboard_or_viewmodel) end + +--- Creates a named view model instance and returns its handle. +---@param file_handle FileHandle Handle to a previously loaded Rive file. +---@param artboard_or_viewmodel ArtboardHandle|string Artboard handle or view model name that will host the instance. +---@param instance_name string Name to assign to the new view model instance. +---@return ViewModelInstanceHandle view_model_instance_handle Named view model instance handle. +function rive.cmd.instantiateViewModelInstanceNamed(file_handle, artboard_or_viewmodel, instance_name) end + +--- Returns a handle to the nested view model at the given path. +---@param view_model_handle ViewModelInstanceHandle Parent view model instance handle. +---@param path string Dot-delimited path to the nested view model. +---@return ViewModelInstanceHandle view_model_handle Handle to the nested view model. +function rive.cmd.referenceNestedViewModelInstance(view_model_handle, path) end + +--- Returns the handle for the list entry at the specified path and index. +---@param view_model_handle ViewModelInstanceHandle Parent view model instance handle. +---@param path string Dot-delimited path to the list view model. +---@param index number Index within the list entry to reference. +---@return ViewModelInstanceHandle view_model_handle Handle to the referenced list entry. +function rive.cmd.referenceListViewModelInstance(view_model_handle, path, index) end + +--- Deletes an instantiated artboard. +---@param artboard_handle ArtboardHandle Handle to the artboard that should be removed. +function rive.cmd.deleteArtboard(artboard_handle) end + +--- Creates a named state machine for the provided artboard. +---@param artboard_handle ArtboardHandle Artboard where the state machine resides. +---@param name string Name to assign to the new state machine. +---@return StateMachineHandle state_machine_handle Handle referencing the created state machine. +function rive.cmd.instantiateStateMachineNamed(artboard_handle, name) end + +--- Creates the default state machine for an artboard. +---@param artboard_handle ArtboardHandle Artboard used as the source for the state machine. +---@return StateMachineHandle state_machine_handle Handle referencing the created state machine. +function rive.cmd.instantiateDefaultStateMachine(artboard_handle) end + +--- Advances the state machine by the requested delta time. +---@param state_machine_handle StateMachineHandle State machine to advance. +---@param delta number Time in seconds to advance the state machine. +function rive.cmd.advanceStateMachine(state_machine_handle, delta) end + +--- Deletes a created state machine. +---@param state_machine_handle StateMachineHandle Handle to the state machine to delete. +function rive.cmd.deleteStateMachine(state_machine_handle) end + +--- Replaces the nested view model at the given path with the supplied handle. +---@param view_model_handle ViewModelInstanceHandle View model instance whose nested child is updated. +---@param path string Path to the nested view model. +---@param nested_handle ViewModelInstanceHandle Handle of the nested view model to attach. +function rive.cmd.setViewModelInstanceNestedViewModel(view_model_handle, path, nested_handle) end + +--- Inserts a nested view model into the list at the given index. +---@param view_model_handle ViewModelInstanceHandle View model instance owning the list. +---@param path string Path to the target list. +---@param nested_handle ViewModelInstanceHandle Handle of the view model to insert. +---@param index number Destination index for the insertion. +function rive.cmd.insertViewModelInstanceListViewModel(view_model_handle, path, nested_handle, index) end + +--- Appends a nested view model to the list at the specified path. +---@param view_model_handle ViewModelInstanceHandle View model instance owning the list. +---@param path string Path to the target list. +---@param nested_handle ViewModelInstanceHandle Handle of the view model to append. +function rive.cmd.appendViewModelInstanceListViewModel(view_model_handle, path, nested_handle) end + +--- Swaps two entries in the nested list. +---@param view_model_handle ViewModelInstanceHandle View model instance that owns the list. +---@param path string Path to the nested list. +---@param indexa number First index to swap. +---@param indexb number Second index to swap. +function rive.cmd.swapViewModelInstanceListValues(view_model_handle, path, indexa, indexb) end + +--- Removes the entry at the supplied index from the nested list. +---@param view_model_handle ViewModelInstanceHandle View model instance owning the list. +---@param path string Path to the target list. +---@param index number Index of the entry to remove. +function rive.cmd.removeViewModelInstanceListViewModelIndex(view_model_handle, path, index) end + +--- Removes the specified nested view model from the list at the path. +---@param view_model_handle ViewModelInstanceHandle View model instance owning the list. +---@param path string Path to the target list. +---@param nested_handle ViewModelInstanceHandle Handle of the view model to remove. +function rive.cmd.removeViewModelInstanceListViewModel(view_model_handle, path, nested_handle) end + +--- Binds the state machine to the provided view model instance. +---@param state_machine_handle StateMachineHandle State machine handle. +---@param view_model_handle ViewModelInstanceHandle View model instance handle. +function rive.cmd.bindViewModelInstance(state_machine_handle, view_model_handle) end + +--- Deletes the view model instance handle. +---@param view_model_handle ViewModelInstanceHandle View model instance to delete. +function rive.cmd.deleteViewModelInstance(view_model_handle) end + +--- Fires a trigger on the view model instance. +---@param view_model_handle ViewModelInstanceHandle View model instance handle. +---@param trigger_path string Trigger path to activate. +function rive.cmd.fireViewModelTrigger(view_model_handle, trigger_path) end + +--- Updates the boolean property at the path. +---@param view_model_handle ViewModelInstanceHandle View model instance handle. +---@param path string Path to the boolean property. +---@param value boolean New boolean value. +function rive.cmd.setViewModelInstanceBool(view_model_handle, path, value) end + +--- Updates the numeric property at the path. +---@param view_model_handle ViewModelInstanceHandle View model instance handle. +---@param path string Path to the numeric property. +---@param value number New numeric value. +function rive.cmd.setViewModelInstanceNumber(view_model_handle, path, value) end + +--- Updates the color property using the supplied vector. +---@param view_model_handle ViewModelInstanceHandle View model instance handle. +---@param path string Path to the color property. +---@param vector4_color vector4 Color encoded as a Defold Vector4 (WXYZ). +function rive.cmd.setViewModelInstanceColor(view_model_handle, path, vector4_color) end + +--- Updates the enum property at the path. +---@param view_model_handle ViewModelInstanceHandle View model instance handle. +---@param path string Path to the enum property. +---@param enum_string string Enum name to select. +function rive.cmd.setViewModelInstanceEnum(view_model_handle, path, enum_string) end + +--- Updates the string property at the path. +---@param view_model_handle ViewModelInstanceHandle View model instance handle. +---@param path string Path to the string property. +---@param string_value string New string value. +function rive.cmd.setViewModelInstanceString(view_model_handle, path, string_value) end + +--- Updates the image property at the path. +---@param view_model_handle ViewModelInstanceHandle View model instance handle. +---@param path string Path to the image property. +---@param render_image_handle RenderImageHandle Render image handle to assign. +function rive.cmd.setViewModelInstanceImage(view_model_handle, path, render_image_handle) end + +--- Updates the artboard reference at the path. +---@param view_model_handle ViewModelInstanceHandle View model instance handle. +---@param path string Path to the artboard reference. +---@param artboard_handle ArtboardHandle Artboard handle to assign. +function rive.cmd.setViewModelInstanceArtboard(view_model_handle, path, artboard_handle) end + +--- Subscribes for updates to the named property. +---@param view_model_handle ViewModelInstanceHandle View model instance handle. +---@param path string Path to the property to subscribe. +---@param data_type enum Data type identifier from rive::DataType. +function rive.cmd.subscribeToViewModelProperty(view_model_handle, path, data_type) end + +--- Cancels a previous subscription. +---@param view_model_handle ViewModelInstanceHandle View model instance handle. +---@param path string Path to the property. +---@param data_type enum Data type identifier previously subscribed. +function rive.cmd.unsubscribeToViewModelProperty(view_model_handle, path, data_type) end + +--- Loads Rive bytes and returns a file handle. +---@param riv_bytes string Raw Rive file data. +---@return FileHandle file_handle Loaded Rive file handle. +function rive.cmd.loadFile(riv_bytes) end + +--- Deletes the runtime file handle. +---@param file_handle FileHandle File handle to delete. +function rive.cmd.deleteFile(file_handle) end + +--- Decodes image bytes and returns a render image handle. +---@param image_bytes string Encoded image data. +---@return RenderImageHandle render_image_handle Decoded render image handle. +function rive.cmd.decodeImage(image_bytes) end + +--- Deletes the render image handle. +---@param image_handle RenderImageHandle Render image handle to delete. +function rive.cmd.deleteImage(image_handle) end + +--- Decodes audio bytes and returns an audio source handle. +---@param audio_bytes string Encoded audio data. +---@return AudioSourceHandle audio_handle Decoded audio source handle. +function rive.cmd.decodeAudio(audio_bytes) end + +--- Deletes the audio source handle. +---@param audio_handle AudioSourceHandle Audio source handle to delete. +function rive.cmd.deleteAudio(audio_handle) end + +--- Decodes font bytes and returns a font handle. +---@param font_bytes string Encoded font data. +---@return FontHandle font_handle Decoded font handle. +function rive.cmd.decodeFont(font_bytes) end + +--- Deletes the font handle. +---@param font_handle FontHandle Font handle to delete. +function rive.cmd.deleteFont(font_handle) end + +--- Registers a global image asset. +---@param asset_name string Name used to reference the global asset. +---@param render_image_handle RenderImageHandle Render image handle to register. +function rive.cmd.addGlobalImageAsset(asset_name, render_image_handle) end + +--- Unregisters the named global image asset. +---@param asset_name string Name of the asset to remove. +function rive.cmd.removeGlobalImageAsset(asset_name) end + +--- Registers a global audio asset. +---@param asset_name string Name used to reference the global audio. +---@param audio_handle AudioSourceHandle Audio source handle to register. +function rive.cmd.addGlobalAudioAsset(asset_name, audio_handle) end + +--- Unregisters the named global audio asset. +---@param asset_name string Name of the audio asset to remove. +function rive.cmd.removeGlobalAudioAsset(asset_name) end + +--- Registers a global font asset. +---@param asset_name string Name used to reference the global font. +---@param font_handle FontHandle Font handle to register. +function rive.cmd.addGlobalFontAsset(asset_name, font_handle) end + +--- Unregisters the named global font asset. +---@param asset_name string Name of the font asset to remove. +function rive.cmd.removeGlobalFontAsset(asset_name) end + +--- Requests view model names for the file. +---@param file_handle FileHandle File handle whose view models to query. +function rive.cmd.requestViewModelNames(file_handle) end + +--- Requests artboard names for the file. +---@param file_handle FileHandle File handle whose artboards to query. +function rive.cmd.requestArtboardNames(file_handle) end + +--- Requests enum definitions for the file. +---@param file_handle FileHandle File handle whose enums to query. +function rive.cmd.requestViewModelEnums(file_handle) end + +--- Requests property definitions for the view model. +---@param file_handle FileHandle File handle whose view models to query. +---@param viewmodel_name string View model name whose property metadata to request. +function rive.cmd.requestViewModelPropertyDefinitions(file_handle, viewmodel_name) end + +--- Requests instance names for the view model. +---@param file_handle FileHandle File handle whose view models to query. +---@param viewmodel_name string View model name to inspect. +function rive.cmd.requestViewModelInstanceNames(file_handle, viewmodel_name) end + +--- Requests the boolean value for the property. +---@param instance_handle ViewModelInstanceHandle View model instance handle. +---@param property_name string Name of the bool property. +function rive.cmd.requestViewModelInstanceBool(instance_handle, property_name) end + +--- Requests the numeric value for the property. +---@param instance_handle ViewModelInstanceHandle View model instance handle. +---@param property_name string Name of the numeric property. +function rive.cmd.requestViewModelInstanceNumber(instance_handle, property_name) end + +--- Requests the color value for the property. +---@param instance_handle ViewModelInstanceHandle View model instance handle. +---@param property_name string Name of the color property. +function rive.cmd.requestViewModelInstanceColor(instance_handle, property_name) end + +--- Requests the enum value for the property. +---@param instance_handle ViewModelInstanceHandle View model instance handle. +---@param property_name string Name of the enum property. +function rive.cmd.requestViewModelInstanceEnum(instance_handle, property_name) end + +--- Requests the string value for the property. +---@param instance_handle ViewModelInstanceHandle View model instance handle. +---@param property_name string Name of the string property. +function rive.cmd.requestViewModelInstanceString(instance_handle, property_name) end + +--- Requests the list size for the property. +---@param instance_handle ViewModelInstanceHandle View model instance handle. +---@param property_name string Name of the list property. +function rive.cmd.requestViewModelInstanceListSize(instance_handle, property_name) end + +--- Requests state machine names for the artboard. +---@param artboard_handle ArtboardHandle Artboard handle to query. +function rive.cmd.requestStateMachineNames(artboard_handle) end + +--- Requests metadata about the default view model or artboard. +---@param artboard_handle ArtboardHandle Artboard handle to query. +---@param file_handle FileHandle File handle providing metadata. +function rive.cmd.requestDefaultViewModelInfo(artboard_handle, file_handle) end diff --git a/defold-rive/api/rive.script_api b/defold-rive/api/rive.script_api index a85f0e7c..89881b0d 100644 --- a/defold-rive/api/rive.script_api +++ b/defold-rive/api/rive.script_api @@ -1,504 +1,1199 @@ - name: rive type: table - desc: Functions and constants for interacting with Rive models + desc: Rive animation helpers exposed to Lua scripts members: - - name: play_anim + - name: pointer_move type: function - desc: Plays the specified animation on a Rive model + desc: Lua wrapper for pointer movement. + parameters: + - name: url + type: url + desc: Component receiving the pointer move. + - name: x + type: number + desc: Pointer x coordinate in component space. + - name: y + type: number + desc: Pointer y coordinate in component space. + +#***************************************************************************************************** + - name: pointer_up + type: function + desc: Lua wrapper for pointer up events. parameters: - name: url type: url - desc: The Rive model component for which to play an animation + desc: Component receiving the pointer release. + - name: x + type: number + desc: Pointer x coordinate. + - name: y + type: number + desc: Pointer y coordinate. - - name: anim_id - type: hash - desc: Id of the animation to play +#***************************************************************************************************** - - name: playback + - name: pointer_down + type: function + desc: Lua wrapper for pointer down events. + parameters: + - name: url + type: url + desc: Component receiving the pointer press. + - name: x + type: number + desc: Pointer x coordinate. + - name: y type: number - desc: Playback mode of the animation (from go.PLAYBACK_*) + desc: Pointer y coordinate. - - name: options - type: table - desc: Playback options - parameters: - - name: offset - type: number - desc: The normalized initial value of the animation cursor when the animation starts playing +#***************************************************************************************************** - - name: playback_rate - type: constant - desc: The rate with which the animation will be played. Must be positive. + - name: pointer_exit + type: function + desc: Lua wrapper for pointer exit events. + parameters: + - name: url + type: url + desc: Component receiving the pointer leave. + - name: x + type: number + desc: Pointer x coordinate. + - name: y + type: number + desc: Pointer y coordinate. + +#***************************************************************************************************** + + - name: get_projection_matrix + type: function + desc: Returns the projection matrix in render coordinates. + return: + - name: matrix + type: vmath.matrix4 + desc: Current projection matrix for the window. + +#***************************************************************************************************** - - name: complete_function - type: function - desc: function to call when the animation has completed + - name: set_file_listener + type: function + desc: Sets or clears the global file listener callback. + parameters: + - name: callback + type: function(self, event, data)|nil + desc: Callback invoked for file system events; pass nil to disable. parameters: - name: self type: object - desc: The context of the calling script + desc: The calling script instance. + - name: event + type: string + desc: "One of: onFileLoaded, onFileDeleted, onFileError, onArtboardsListed, onViewModelsListed, onViewModelInstanceNamesListed, onViewModelPropertiesListed, onViewModelEnumsListed" + - name: data + type: table + desc: Additional fields vary by event. Common keys include + parameters: + - name: file + type: FileHandle + desc: File handle involved in the event. + - name: viewModelName + type: string + desc: View model name for the request, when applicable. + - name: instanceNames + type: table + desc: Array of instance name strings. + - name: artboardNames + type: table + desc: Array of artboard name strings. + - name: properties + type: table + desc: Array of property metadata tables. + - name: enums + type: table + desc: Array of enum definitions. + - name: error + type: string + desc: Error message when a failure occurs. - - name: message_id - type: hash - desc: The name of the completion message ("rive_animation_done") +#***************************************************************************************************** - - name: message + - name: set_artboard_listener + type: function + desc: Sets or clears the artboard listener callback. + parameters: + - name: callback + type: function(self, event, data)|nil + desc: Callback invoked for artboard-related events; pass nil to disable. + parameters: + - name: self + type: object + desc: The calling script instance. + - name: event + type: string + desc: "One of: onArtboardError, onDefaultViewModelInfoReceived, onArtboardDeleted, onStateMachinesListed" + - name: data type: table - desc: A table that contains the response + desc: Additional data per event, typically parameters: - - name: animation_id - type: hash - desc: the animation that was completed + - name: artboard + type: ArtboardHandle + desc: Artboard handle involved. + - name: viewModelName + type: string + desc: View model name for defaults (received event). + - name: instanceName + type: string + desc: Instance name for defaults. + - name: stateMachineNames + type: table + desc: Array of state machine name strings. + - name: error + type: string + desc: Error message when an error event fires. - - name: playback - type: constant - desc: the playback mode for the animation +#***************************************************************************************************** - - name: sender - type: url - desc: The invoker of the callback - the Rive model component + - name: set_state_machine_listener + type: function + desc: Sets or clears the state machine listener callback. + parameters: + - name: callback + type: function(self, event, data)|nil + desc: Callback invoked for state machine events; pass nil to disable. + parameters: + - name: self + type: object + desc: The calling script instance. + - name: event + type: string + desc: "One of: onStateMachineError, onStateMachineDeleted, onStateMachineSettled" + - name: data + type: table + desc: Event-specific details + parameters: + - name: stateMachine + type: StateMachineHandle + desc: Active state machine handle. + - name: error + type: string + desc: Error message when an error occurs. #***************************************************************************************************** - - name: play_state_machine + - name: set_view_model_instance_listener type: function - desc: Plays the specified animation on a Rive model - + desc: Sets or clears the view model instance listener callback. parameters: - - name: url - type: url - desc: The Rive model component for which to play an animation + - name: callback + type: function(self, event, data)|nil + desc: Callback invoked for view model instance events; pass nil to disable. + parameters: + - name: self + type: object + desc: The calling script instance. + - name: event + type: string + desc: "One of: onViewModelInstanceError, onViewModelDeleted, onViewModelDataReceived, onViewModelListSizeReceived" + - name: data + type: table + desc: Additional payload per event + parameters: + - name: viewModel + type: ViewModelInstanceHandle + desc: Handle of the affected view model instance. + - name: error + type: string + desc: Error description when an error fires. + - name: path + type: string + desc: Path being inspected when list size arrives. + - name: size + type: number + desc: List size value for list-size events. - - name: state_machine_id - type: hash - desc: Id of the state machine to play +#***************************************************************************************************** - - name: options - type: table - desc: Playback options + - name: set_render_image_listener + type: function + desc: Sets or clears the render image listener callback. + parameters: + - name: callback + type: function(self, event, data)|nil + desc: Callback invoked for render image events; pass nil to disable. parameters: - - name: playback_rate - type: constant - desc: The rate with which the animation will be played. Must be positive. + - name: self + type: object + desc: The calling script instance. + - name: event + type: string + desc: "One of: onRenderImageDecoded, onRenderImageError, onRenderImageDeleted" + - name: data + type: table + desc: Additional fields + parameters: + - name: renderImage + type: RenderImageHandle + desc: Handle of the render image. + - name: error + type: string + desc: Error message for the failure event. + +#***************************************************************************************************** - - name: callback_function - type: function - desc: function to call when a playback event occurs + - name: set_audio_source_listener + type: function + desc: Sets or clears the audio source listener callback. + parameters: + - name: callback + type: function(self, event, data)|nil + desc: Callback invoked for audio source events; pass nil to disable. parameters: - name: self type: object - desc: The context of the calling script + desc: The calling script instance. + - name: event + type: string + desc: "One of: onAudioSourceDecoded, onAudioSourceError, onAudioSourceDeleted" + - name: data + type: table + desc: Additional fields + parameters: + - name: audioSource + type: AudioSourceHandle + desc: Audio source handle for the event. + - name: error + type: string + desc: Error message when provided. - - name: message_id - type: hash - desc: The name of the event +#***************************************************************************************************** - - name: message + - name: set_font_listener + type: function + desc: Sets or clears the font listener callback. + parameters: + - name: callback + type: function(self, event, data)|nil + desc: Callback invoked for font events; pass nil to disable. + parameters: + - name: self + type: object + desc: The calling script instance. + - name: event + type: string + desc: "One of: onFontDecoded, onFontError, onFontDeleted" + - name: data type: table - desc: A table that contains the event properties + desc: Additional fields + parameters: + - name: font + type: FontHandle + desc: Font handle for the associated event. + - name: error + type: string + desc: Error message for failure events. #***************************************************************************************************** - - name: cancel + - name: get_file type: function - desc: Cancels all running animations on a specified spine model component + desc: Returns the Rive file handle tied to the component. + parameters: + - name: url + type: url + desc: Component whose file handle to query. + return: + - name: file_handle + type: FileHandle + desc: Handle identifying the loaded Rive file. +#***************************************************************************************************** + + - name: set_artboard + type: function + desc: Switches the active artboard for the component. parameters: - name: url type: url - desc: The Rive model component for which to cancel the animation + desc: Component using the artboard. + - name: name + type: string|nil + desc: Name of the artboard to create and set. Pass nil to create a default artboard. + return: + - name: artboard + type: ArtboardHandle + desc: Old artboard handle #***************************************************************************************************** - - name: get_go + - name: get_artboard type: function - desc: Returns the id of the game object that corresponds to a specified skeleton bone. + desc: Queries the current artboard handle for the component. + parameters: + - name: url + type: url + desc: Component whose artboard handle to return. + return: + - name: artboard + type: ArtboardHandle + desc: Active artboard handle. +#***************************************************************************************************** + + - name: set_state_machine + type: function + desc: Selects a state machine by name on the component. parameters: - name: url type: url - desc: The Rive model component to query + desc: Component owning the state machine. + - name: name + type: string|nil + desc: Name of the state machine to create and set. Pass nil to create a default state machine. + return: + - name: state_machine + type: StateMachineHandle + desc: Old state machine handle - - name: bone_id - type: hash - desc: Id of the corresponding bone +#***************************************************************************************************** + - name: get_state_machine + type: function + desc: Returns the active state machine handle for the component. + parameters: + - name: url + type: url + desc: Component whose active state machine to query. return: - - name: id - type: hash - desc: Id of the game object + - name: state_machine + type: StateMachineHandle + desc: Current state machine handle. #***************************************************************************************************** - - name: pointer_move + - name: set_view_model_instance type: function - desc: Forward mouse/touch movement to a component + desc: Selects a view model instance by name. + parameters: + - name: url + type: url + desc: Component owning the view model instance. + - name: name + type: string + desc: View model instance name to activate. + return: + - name: success + type: boolean + desc: True if the view model instance was activated. +#***************************************************************************************************** + + - name: get_view_model_instance + type: function + desc: Returns the handle of the currently bound view model instance. parameters: - name: url type: url - desc: The Rive model component + desc: Component whose view model instance handle to query. + return: + - name: view_model_instance_handle + type: ViewModelInstanceHandle + desc: Handle for the active view model instance. - - name: x - type: number - desc: Horizontal position +#***************************************************************************************************** - - name: y - type: number - desc: Vertical position +- name: rive.cmd + type: table + desc: Command queue helpers for interacting with the Rive runtime + + members: + + - name: instantiateArtboardNamed + type: function + desc: Returns the artboard handle created for the named view model. + parameters: + - name: file_handle + type: FileHandle + desc: Handle to a previously loaded Rive file. + - name: viewmodel_name + type: string + desc: Name of the view model to instantiate. + return: + - name: artboard_handle + type: ArtboardHandle + desc: Artboard handle created for the named view model. #***************************************************************************************************** - - name: pointer_up + - name: instantiateDefaultArtboard type: function - desc: Forward mouse/touch release event to a component + desc: Returns the default artboard handle for the file. + parameters: + - name: file_handle + type: FileHandle + desc: Handle to a previously loaded Rive file. + return: + - name: artboard_handle + type: ArtboardHandle + desc: Default artboard handle for the file. +#***************************************************************************************************** + + - name: instantiateBlankViewModelInstance + type: function + desc: Returns a blank view model instance handle for the given artboard or view model. parameters: - - name: url - type: url - desc: The Rive model component + - name: file_handle + type: FileHandle + desc: Handle to a previously loaded Rive file. + - name: artboard_or_viewmodel + type: ArtboardHandle|string + desc: Artboard handle or view model name that identifies where to instantiate. + return: + - name: view_model_instance_handle + type: ViewModelInstanceHandle + desc: Blank view model instance handle. - - name: x - type: number - desc: Horizontal position +#***************************************************************************************************** - - name: y - type: number - desc: Vertical position + - name: instantiateDefaultViewModelInstance + type: function + desc: Returns a default-populated view model instance handle for the given artboard or view model. + parameters: + - name: file_handle + type: FileHandle + desc: Handle to a previously loaded Rive file. + - name: artboard_or_viewmodel + type: ArtboardHandle|string + desc: Artboard handle or view model name the instance should derive from. + return: + - name: view_model_instance_handle + type: ViewModelInstanceHandle + desc: Default view model instance handle. #***************************************************************************************************** - - name: pointer_down + - name: instantiateViewModelInstanceNamed type: function - desc: Forward mouse/touch press event to a component + desc: Creates a named view model instance and returns its handle. + parameters: + - name: file_handle + type: FileHandle + desc: Handle to a previously loaded Rive file. + - name: artboard_or_viewmodel + type: ArtboardHandle|string + desc: Artboard handle or view model name that will host the instance. + - name: instance_name + type: string + desc: Name to assign to the new view model instance. + return: + - name: view_model_instance_handle + type: ViewModelInstanceHandle + desc: Named view model instance handle. + +#***************************************************************************************************** + - name: referenceNestedViewModelInstance + type: function + desc: Returns a handle to the nested view model at the given path. parameters: - - name: url - type: url - desc: The Rive model component + - name: view_model_handle + type: ViewModelInstanceHandle + desc: Parent view model instance handle. + - name: path + type: string + desc: Dot-delimited path to the nested view model. + return: + - name: view_model_handle + type: ViewModelInstanceHandle + desc: Handle to the nested view model. - - name: x - type: number - desc: Horizontal position +#***************************************************************************************************** - - name: y + - name: referenceListViewModelInstance + type: function + desc: Returns the handle for the list entry at the specified path and index. + parameters: + - name: view_model_handle + type: ViewModelInstanceHandle + desc: Parent view model instance handle. + - name: path + type: string + desc: Dot-delimited path to the list view model. + - name: index type: number - desc: Vertical position + desc: Index within the list entry to reference. + return: + - name: view_model_handle + type: ViewModelInstanceHandle + desc: Handle to the referenced list entry. #***************************************************************************************************** - - name: get_text_run + - name: deleteArtboard type: function - desc: Gets the text run of a specified text component from within the Rive artboard assigned to the component. - + desc: Deletes an instantiated artboard. parameters: - - name: url - type: url - desc: The Rive model component for which to get the text run from + - name: artboard_handle + type: ArtboardHandle + desc: Handle to the artboard that should be removed. + +#***************************************************************************************************** + - name: instantiateStateMachineNamed + type: function + desc: Creates a named state machine for the provided artboard. + parameters: + - name: artboard_handle + type: ArtboardHandle + desc: Artboard where the state machine resides. - name: name type: string - desc: The name of the text run from the Rive artboard. + desc: Name to assign to the new state machine. + return: + - name: state_machine_handle + type: StateMachineHandle + desc: Handle referencing the created state machine. - - name: nested_artboard - type: string - desc: (OPTIONAL) If specified, the text run will be retrieved from the specified nested artboard +#***************************************************************************************************** + - name: instantiateDefaultStateMachine + type: function + desc: Creates the default state machine for an artboard. + parameters: + - name: artboard_handle + type: ArtboardHandle + desc: Artboard used as the source for the state machine. return: - - name: text_run - type: string - desc: The text run text + - name: state_machine_handle + type: StateMachineHandle + desc: Handle referencing the created state machine. #***************************************************************************************************** - - name: set_text_run + - name: advanceStateMachine type: function - desc: Set the text run of a specified text component from within the Rive artboard assigned to the component. + desc: Advances the state machine by the requested delta time. + parameters: + - name: state_machine_handle + type: StateMachineHandle + desc: State machine to advance. + - name: delta + type: number + desc: Time in seconds to advance the state machine. + +#***************************************************************************************************** + - name: deleteStateMachine + type: function + desc: Deletes a created state machine. parameters: - - name: url - type: url - desc: The Rive model component for which to set the text run for + - name: state_machine_handle + type: StateMachineHandle + desc: Handle to the state machine to delete. - - name: name +#***************************************************************************************************** + + - name: setViewModelInstanceNestedViewModel + type: function + desc: Replaces the nested view model at the given path with the supplied handle. + parameters: + - name: view_model_handle + type: ViewModelInstanceHandle + desc: View model instance whose nested child is updated. + - name: path type: string - desc: The name of the text run from the Rive artboard. + desc: Path to the nested view model. + - name: nested_handle + type: ViewModelInstanceHandle + desc: Handle of the nested view model to attach. + +#***************************************************************************************************** - - name: text_run + - name: insertViewModelInstanceListViewModel + type: function + desc: Inserts a nested view model into the list at the given index. + parameters: + - name: view_model_handle + type: ViewModelInstanceHandle + desc: View model instance owning the list. + - name: path type: string - desc: The text run contents to update with. + desc: Path to the target list. + - name: nested_handle + type: ViewModelInstanceHandle + desc: Handle of the view model to insert. + - name: index + type: number + desc: Destination index for the insertion. + +#***************************************************************************************************** - - name: nested_artboard + - name: appendViewModelInstanceListViewModel + type: function + desc: Appends a nested view model to the list at the specified path. + parameters: + - name: view_model_handle + type: ViewModelInstanceHandle + desc: View model instance owning the list. + - name: path type: string - desc: (OPTIONAL) If specified, the text run will be set in the specified nested artboard + desc: Path to the target list. + - name: nested_handle + type: ViewModelInstanceHandle + desc: Handle of the view model to append. #***************************************************************************************************** - - name: get_projection_matrix + - name: swapViewModelInstanceListValues type: function - desc: Get an orthographic projection matrix that can be used to project regular Defold components into the same coordinate space as the rive model when using the 'fullscreen' coordinate space. + desc: Swaps two entries in the nested list. + parameters: + - name: view_model_handle + type: ViewModelInstanceHandle + desc: View model instance that owns the list. + - name: path + type: string + desc: Path to the nested list. + - name: indexa + type: number + desc: First index to swap. + - name: indexb + type: number + desc: Second index to swap. - return: - - name: matrix - type: vmath.matrix4 - desc: The projection matrix +#***************************************************************************************************** + + - name: removeViewModelInstanceListViewModelIndex + type: function + desc: Removes the entry at the supplied index from the nested list. + parameters: + - name: view_model_handle + type: ViewModelInstanceHandle + desc: View model instance owning the list. + - name: path + type: string + desc: Path to the target list. + - name: index + type: number + desc: Index of the entry to remove. #***************************************************************************************************** - - name: get_state_machine_input + - name: removeViewModelInstanceListViewModel type: function - desc: Get the input values from a state machine input, either from the current top-level artboard, or from a nested artboard inside the Rive model artboard. Note that trigger inputs will not generate a value! + desc: Removes the specified nested view model from the list at the path. + parameters: + - name: view_model_handle + type: ViewModelInstanceHandle + desc: View model instance owning the list. + - name: path + type: string + desc: Path to the target list. + - name: nested_handle + type: ViewModelInstanceHandle + desc: Handle of the view model to remove. +#***************************************************************************************************** + + - name: bindViewModelInstance + type: function + desc: Binds the state machine to the provided view model instance. parameters: - - name: url - type: url - desc: The Rive model component + - name: state_machine_handle + type: StateMachineHandle + desc: State machine handle. + - name: view_model_handle + type: ViewModelInstanceHandle + desc: View model instance handle. - - name: name +#***************************************************************************************************** + + - name: deleteViewModelInstance + type: function + desc: Deletes the view model instance handle. + parameters: + - name: view_model_handle + type: ViewModelInstanceHandle + desc: View model instance to delete. + +#***************************************************************************************************** + + - name: fireViewModelTrigger + type: function + desc: Fires a trigger on the view model instance. + parameters: + - name: view_model_handle + type: ViewModelInstanceHandle + desc: View model instance handle. + - name: trigger_path type: string - desc: The name of the input + desc: Trigger path to activate. + +#***************************************************************************************************** - - name: nested_artboard + - name: setViewModelInstanceBool + type: function + desc: Updates the boolean property at the path. + parameters: + - name: view_model_handle + type: ViewModelInstanceHandle + desc: View model instance handle. + - name: path type: string - desc: (OPTIONAL) If specified, the input will be queried for the specified nested artboard + desc: Path to the boolean property. + - name: value + type: boolean + desc: New boolean value. - return: - - name: value - type: number|bool - desc: The value of the input +#***************************************************************************************************** + + - name: setViewModelInstanceNumber + type: function + desc: Updates the numeric property at the path. + parameters: + - name: view_model_handle + type: ViewModelInstanceHandle + desc: View model instance handle. + - name: path + type: string + desc: Path to the numeric property. + - name: value + type: number + desc: New numeric value. #***************************************************************************************************** - - name: set_state_machine_input + - name: setViewModelInstanceColor type: function - desc: Set the input values from a state machine input, either from the current top-level artboard, or from a nested artboard inside the Rive model artboard. Note - To set input for a trigger, use a bool value. + desc: Updates the color property using the supplied vector. + parameters: + - name: view_model_handle + type: ViewModelInstanceHandle + desc: View model instance handle. + - name: path + type: string + desc: Path to the color property. + - name: vector4_color + type: vector4 + desc: Color encoded as a Defold Vector4 (WXYZ). +#***************************************************************************************************** + + - name: setViewModelInstanceEnum + type: function + desc: Updates the enum property at the path. parameters: - - name: url - type: url - desc: The Rive model component + - name: view_model_handle + type: ViewModelInstanceHandle + desc: View model instance handle. + - name: path + type: string + desc: Path to the enum property. + - name: enum_string + type: string + desc: Enum name to select. - - name: name +#***************************************************************************************************** + + - name: setViewModelInstanceString + type: function + desc: Updates the string property at the path. + parameters: + - name: view_model_handle + type: ViewModelInstanceHandle + desc: View model instance handle. + - name: path + type: string + desc: Path to the string property. + - name: string_value type: string - desc: The name of the input + desc: New string value. - - name: value - type: number|bool - desc: The value of the input to set +#***************************************************************************************************** + + - name: setViewModelInstanceImage + type: function + desc: Updates the image property at the path. + parameters: + - name: view_model_handle + type: ViewModelInstanceHandle + desc: View model instance handle. + - name: path + type: string + desc: Path to the image property. + - name: render_image_handle + type: RenderImageHandle + desc: Render image handle to assign. - - name: nested_artboard +#***************************************************************************************************** + + - name: setViewModelInstanceArtboard + type: function + desc: Updates the artboard reference at the path. + parameters: + - name: view_model_handle + type: ViewModelInstanceHandle + desc: View model instance handle. + - name: path type: string - desc: (OPTIONAL) If specified, the input will be queried for the specified nested artboard + desc: Path to the artboard reference. + - name: artboard_handle + type: ArtboardHandle + desc: Artboard handle to assign. #***************************************************************************************************** - - name: riv_swap_asset + - name: subscribeToViewModelProperty type: function - desc: Replace an asset in runtime. - Rive doc: https://rive.app/docs/game-runtimes/unreal/runtime-asset-swapping + desc: Subscribes for updates to the named property. + parameters: + - name: view_model_handle + type: ViewModelInstanceHandle + desc: View model instance handle. + - name: path + type: string + desc: Path to the property to subscribe. + - name: data_type + type: enum + desc: Data type identifier from rive::DataType. + +#***************************************************************************************************** + - name: unsubscribeToViewModelProperty + type: function + desc: Cancels a previous subscription. parameters: - - name: riv_path - type: string,hash - desc: The Rive (.rivc) path. E.g. "/path/to/file.rivc" + - name: view_model_handle + type: ViewModelInstanceHandle + desc: View model instance handle. + - name: path + type: string + desc: Path to the property. + - name: data_type + type: enum + desc: Data type identifier previously subscribed. - - name: asset_name +#***************************************************************************************************** + + - name: loadFile + type: function + desc: Loads Rive bytes and returns a file handle. + parameters: + - name: riv_bytes type: string - desc: The name of the FileAsset inside the .riv file + desc: Raw Rive file data. + return: + - name: file_handle + type: FileHandle + desc: Loaded Rive file handle. - - name: options - type: table - desc: A table of options containing +#***************************************************************************************************** - members: + - name: deleteFile + type: function + desc: Deletes the runtime file handle. + parameters: + - name: file_handle + type: FileHandle + desc: File handle to delete. - - name: path - type: string - desc: The path of the asset file to replace with. E.g. "/path/to/file.png" +#***************************************************************************************************** - - name: payload - type: string - desc: The payload of the asset file to replace with. E.g. a .png binary file. - Takes precedence over the `path` option. + - name: decodeImage + type: function + desc: Decodes image bytes and returns a render image handle. + parameters: + - name: image_bytes + type: string + desc: Encoded image data. + return: + - name: render_image_handle + type: RenderImageHandle + desc: Decoded render image handle. #***************************************************************************************************** - - name: set_font_fallback_path + - name: deleteImage type: function - desc: Register a fallback font from a file path. This font will be used if glyphs are missing in the current font. Note that only one font fallback can be active at any time. + desc: Deletes the render image handle. + parameters: + - name: image_handle + type: RenderImageHandle + desc: Render image handle to delete. +#***************************************************************************************************** + + - name: decodeAudio + type: function + desc: Decodes audio bytes and returns an audio source handle. parameters: - - name: path + - name: audio_bytes type: string - desc: The resource path to the font file. + desc: Encoded audio data. + return: + - name: audio_handle + type: AudioSourceHandle + desc: Decoded audio source handle. #***************************************************************************************************** - - name: set_font_fallback_memory + - name: deleteAudio type: function - desc: Register a fallback font from a memory payload. This font will be used if glyphs are missing in the current font. Note that only one font fallback can be active at any time. + desc: Deletes the audio source handle. + parameters: + - name: audio_handle + type: AudioSourceHandle + desc: Audio source handle to delete. +#***************************************************************************************************** + + - name: decodeFont + type: function + desc: Decodes font bytes and returns a font handle. parameters: - - name: payload + - name: font_bytes type: string - desc: The font file contents. + desc: Encoded font data. + return: + - name: font_handle + type: FontHandle + desc: Decoded font handle. #***************************************************************************************************** - - name: clear_font_fallback + - name: deleteFont type: function - desc: Clear any registered fallback font. + desc: Deletes the font handle. + parameters: + - name: font_handle + type: FontHandle + desc: Font handle to delete. #***************************************************************************************************** - - name: databind - type: table - desc: Functions and constants for interacting with Rive data bindings + - name: addGlobalImageAsset + type: function + desc: Registers a global image asset. + parameters: + - name: asset_name + type: string + desc: Name used to reference the global asset. + - name: render_image_handle + type: RenderImageHandle + desc: Render image handle to register. - members: - - name: create_view_model_instance_runtime - type: function - desc: Creates a ViewModelInstanceRuntime +#***************************************************************************************************** - parameters: - - name: url - type: url - desc: The Rive model component + - name: removeGlobalImageAsset + type: function + desc: Unregisters the named global image asset. + parameters: + - name: asset_name + type: string + desc: Name of the asset to remove. - - name: name - type: string, hash - desc: The name of the view model to instantiate +#***************************************************************************************************** - return: - - name: handle - type: integer - desc: The handle to the ViewModelInstanceRuntime instance + - name: addGlobalAudioAsset + type: function + desc: Registers a global audio asset. + parameters: + - name: asset_name + type: string + desc: Name used to reference the global audio. + - name: audio_handle + type: AudioSourceHandle + desc: Audio source handle to register. - - name: error - type: string|nil - desc: The error message if something went wrong +#***************************************************************************************************** - - name: destroy_view_model_instance_runtime - type: function - desc: Releases the previously created ViewModelInstanceRuntime + - name: removeGlobalAudioAsset + type: function + desc: Unregisters the named global audio asset. + parameters: + - name: asset_name + type: string + desc: Name of the audio asset to remove. - parameters: - - name: url - type: url - desc: The Rive model component +#***************************************************************************************************** - - name: handle - type: integer - desc: The handle to the ViewModelInstanceRuntime instance + - name: addGlobalFontAsset + type: function + desc: Registers a global font asset. + parameters: + - name: asset_name + type: string + desc: Name used to reference the global font. + - name: font_handle + type: FontHandle + desc: Font handle to register. - - name: set_view_model_instance_runtime - type: function - desc: Sets the current ViewModelInstanceRuntime +#***************************************************************************************************** - parameters: - - name: url - type: url - desc: The Rive model component + - name: removeGlobalFontAsset + type: function + desc: Unregisters the named global font asset. + parameters: + - name: asset_name + type: string + desc: Name of the font asset to remove. - - name: handle - type: integer - desc: The handle to the ViewModelInstanceRuntime instance +#***************************************************************************************************** - - name: get_view_model_instance_runtime - type: function - desc: Gets the current ViewModelInstanceRuntime + - name: requestViewModelNames + type: function + desc: Requests view model names for the file. + parameters: + - name: file_handle + type: FileHandle + desc: File handle whose view models to query. - parameters: - - name: url - type: url - desc: The Rive model component +#***************************************************************************************************** - return: - - name: handle - type: integer - desc: The handle to the currently set ViewModelInstanceRuntime instance + - name: requestArtboardNames + type: function + desc: Requests artboard names for the file. + parameters: + - name: file_handle + type: FileHandle + desc: File handle whose artboards to query. +#***************************************************************************************************** - - name: set_properties - type: function - desc: Sets properties to the ViewModelInstanceRuntime instance + - name: requestViewModelEnums + type: function + desc: Requests enum definitions for the file. + parameters: + - name: file_handle + type: FileHandle + desc: File handle whose enums to query. - parameters: - - name: url - type: url - desc: The Rive model component +#***************************************************************************************************** - - name: handle - type: integer - desc: The handle to the ViewModelInstanceRuntime instance + - name: requestViewModelPropertyDefinitions + type: function + desc: Requests property definitions for the view model. + parameters: + - name: file_handle + type: FileHandle + desc: File handle whose view models to query. + - name: viewmodel_name + type: string + desc: View model name whose property metadata to request. - - name: properties - type: table - desc: A table of properties, where each key is a Rive "path", and the values are mapped to the corresponding property value type. +#***************************************************************************************************** - - name: get_property - type: function - desc: Gets a property from the ViewModelInstanceRuntime instance + - name: requestViewModelInstanceNames + type: function + desc: Requests instance names for the view model. + parameters: + - name: file_handle + type: FileHandle + desc: File handle whose view models to query. + - name: viewmodel_name + type: string + desc: View model name to inspect. - parameters: - - name: url - type: url - desc: The Rive model component +#***************************************************************************************************** - - name: handle - type: integer - desc: The handle to the ViewModelInstanceRuntime instance + - name: requestViewModelInstanceBool + type: function + desc: Requests the boolean value for the property. + parameters: + - name: instance_handle + type: ViewModelInstanceHandle + desc: View model instance handle. + - name: property_name + type: string + desc: Name of the bool property. - - name: path - type: string - desc: The path to the property +#***************************************************************************************************** - return: - - name: result - type: string,number,vmath.vector4 - desc: The value of the selected property. Raises error if property doesn't exist. + - name: requestViewModelInstanceNumber + type: function + desc: Requests the numeric value for the property. + parameters: + - name: instance_handle + type: ViewModelInstanceHandle + desc: View model instance handle. + - name: property_name + type: string + desc: Name of the numeric property. - - name: list_add_instance - type: function - desc: Add a ViewModelInstanceRuntime instance to a list property +#***************************************************************************************************** - parameters: - - name: url - type: url - desc: The Rive model component + - name: requestViewModelInstanceColor + type: function + desc: Requests the color value for the property. + parameters: + - name: instance_handle + type: ViewModelInstanceHandle + desc: View model instance handle. + - name: property_name + type: string + desc: Name of the color property. - - name: handle - type: integer - desc: The handle to the ViewModelInstanceRuntime instance +#***************************************************************************************************** - - name: path - type: string - desc: The path to the list property + - name: requestViewModelInstanceEnum + type: function + desc: Requests the enum value for the property. + parameters: + - name: instance_handle + type: ViewModelInstanceHandle + desc: View model instance handle. + - name: property_name + type: string + desc: Name of the enum property. - - name: instance_handle - type: integer - desc: The handle to the ViewModelInstanceRuntime instance to add to the list +#***************************************************************************************************** - - name: list_remove_instance - type: function - desc: Remove a ViewModelInstanceRuntime instance from a list property + - name: requestViewModelInstanceString + type: function + desc: Requests the string value for the property. + parameters: + - name: instance_handle + type: ViewModelInstanceHandle + desc: View model instance handle. + - name: property_name + type: string + desc: Name of the string property. - parameters: - - name: url - type: url - desc: The Rive model component +#***************************************************************************************************** - - name: handle - type: integer - desc: The handle to the ViewModelInstanceRuntime instance + - name: requestViewModelInstanceListSize + type: function + desc: Requests the list size for the property. + parameters: + - name: instance_handle + type: ViewModelInstanceHandle + desc: View model instance handle. + - name: property_name + type: string + desc: Name of the list property. - - name: path - type: string - desc: The path to the list property +#***************************************************************************************************** - - name: instance_handle - type: integer - desc: The handle to the ViewModelInstanceRuntime instance to add to the list + - name: requestStateMachineNames + type: function + desc: Requests state machine names for the artboard. + parameters: + - name: artboard_handle + type: ArtboardHandle + desc: Artboard handle to query. + +#***************************************************************************************************** + + - name: requestDefaultViewModelInfo + type: function + desc: Requests metadata about the default view model or artboard. + parameters: + - name: artboard_handle + type: ArtboardHandle + desc: Artboard handle to query. + - name: file_handle + type: FileHandle + desc: File handle providing metadata. diff --git a/defold-rive/commonsrc/atlas.cpp b/defold-rive/commonsrc/atlas.cpp index 7f44cd79..aab1d7d5 100644 --- a/defold-rive/commonsrc/atlas.cpp +++ b/defold-rive/commonsrc/atlas.cpp @@ -13,7 +13,6 @@ #include #include #include -#include #include #include @@ -208,16 +207,10 @@ namespace dmRive { rive::rcp LoadImageFromMemory(HRenderContext context, void* resource, uint32_t resource_size) { - // Auto-detect ASTC format - if (IsASTCData((const uint8_t*)resource, resource_size)) - { - DEBUGLOG("Loading ASTC texture (%u bytes)", resource_size); - return CreateRiveRenderImageASTC(context, resource, resource_size); - } return CreateRiveRenderImage(context, resource, resource_size); } - rive::rcp LoadImageFromFactory(dmResource::HFactory factory, HRenderContext context, const char* path, bool out_of_band) + rive::rcp LoadImageFromFactory(dmResource::HFactory factory, HRenderContext context, const char* path) { if (!factory) { @@ -236,11 +229,7 @@ namespace dmRive { dmResource::Result r = dmResource::GetRaw(factory, path_buffer, &resource, &resource_size); if (dmResource::RESULT_OK != r) { - if (!out_of_band) - { - // If it's not during loading, we want to know the error - dmLogError("Error getting file '%s': %d", path_buffer, r); - } + dmLogError("Error getting file '%s': %d", path_buffer, r); return rive::rcp(); } @@ -257,40 +246,36 @@ namespace dmRive { { DEBUGLOG("loadContents"); - if (m_Assets.Full()) - { - m_Assets.OffsetCapacity(8); - } - m_Assets.Push(&_asset); + dmLogError("MAWE TODO UNIMPLEMENTED: %s", __FUNCTION__); - bool out_of_band = inBandBytes.size() == 0; - if (_asset.is()) - { - rive::ImageAsset* asset = _asset.as(); - const std::string& name = asset->name(); - - rive::rcp image; - - if (out_of_band) - { - image = LoadImageFromFactory(m_Factory, m_RiveRenderContext, name.c_str(), true); - - if (!image) - { - // Missing references is ok, as they may be added later - return false; - } - } - - if (!image) - { - image = LoadImageFromMemory(m_RiveRenderContext, (void*) inBandBytes.data(), inBandBytes.size()); - DEBUGLOG(" In band asset: file: '%s' data: %u bytes", name.c_str(), (uint32_t)inBandBytes.size()); - } - - asset->renderImage(image); - return (bool)image; - } + // if (m_Assets.Full()) + // { + // m_Assets.OffsetCapacity(8); + // } + // m_Assets.Push(&_asset); + + // bool out_of_band = inBandBytes.size() == 0; + // if (_asset.is()) + // { + // rive::ImageAsset* asset = _asset.as(); + // const std::string& name = asset->name(); + + // rive::rcp image; + + // if (out_of_band) + // { + // image = LoadImageFromFactory(m_Factory, m_RiveRenderContext, name.c_str()); + // } + + // if (!image) + // { + // image = CreateRiveRenderImage(m_RiveRenderContext, (void*) inBandBytes.data(), inBandBytes.size()); + // DEBUGLOG(" In band asset: file: '%s' data: %u bytes", name.c_str(), (uint32_t)inBandBytes.size()); + // } + + // asset->renderImage(image); + // return (bool)image; + // } return false; } diff --git a/defold-rive/commonsrc/commands.cpp b/defold-rive/commonsrc/commands.cpp new file mode 100644 index 00000000..ed882a76 --- /dev/null +++ b/defold-rive/commonsrc/commands.cpp @@ -0,0 +1,123 @@ +// Copyright 2020-2025 The Defold Foundation +// Licensed under the Defold License version 1.0 (the "License"); you may not use +// this file except in compliance with the License. +// +// You may obtain a copy of the License, together with FAQs at +// https://www.defold.com/license +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#include + +#include +#include +#include + +#include +#include +#include + +#include +#include + + +namespace dmRiveCommands { + +struct Context +{ + dmThread::Thread m_Thread; + int32_atomic_t m_Run; + + dmRive::HRenderContext m_RenderContext; + rive::Factory* m_Factory; + rive::CommandServer* m_CommandServer; + rive::rcp m_CommandQueue; +}; + +Context* g_Context = 0; + +// static void RiveCommandThread(void* _ctx) +// { +// Context* ctx = (Context*)_ctx; + +// while (dmAtomicGet32(&g_Context->m_Run)) +// { + +// } +// } + +Result Initialize(InitParams* params) +{ + assert(g_Context == 0); + + g_Context = new Context; + memset(g_Context, 0, sizeof(*g_Context)); + + if (params->m_UseThreads) + { + // dmAtomicAdd32(&g_Context->m_Run, 1); + // g_Context->m_Thread = dmThread::New(RiveCommandThread, 1 * 1024*1024, g_Context, "RiveCommandThread"); + // if (!g_Context->m_Thread) + // { + // return RESULT_FAILED_CREATE_THREAD; + // } + } + + g_Context->m_RenderContext = params->m_RenderContext; + g_Context->m_Factory = params->m_Factory; + + g_Context->m_CommandQueue = rive::make_rcp(); + g_Context->m_CommandServer = new rive::CommandServer(g_Context->m_CommandQueue, g_Context->m_Factory); + + return RESULT_OK; +} + +Result Finalize() +{ + assert(g_Context != 0); + + if (dmAtomicGet32(&g_Context->m_Run)) + { + dmAtomicSub32(&g_Context->m_Run, 1); + dmThread::Join(g_Context->m_Thread); + g_Context->m_Thread = 0; + } + + g_Context->m_CommandQueue.reset(); + delete g_Context->m_CommandServer; + + delete g_Context; + g_Context = 0; + return RESULT_OK; +} + +rive::rcp GetCommandQueue() +{ + return g_Context->m_CommandQueue; +} + +rive::Factory* GetFactory() +{ + assert(g_Context != 0); + return g_Context->m_Factory; +} + +dmRive::HRenderContext GetDefoldRenderContext() +{ + assert(g_Context != 0); + return g_Context->m_RenderContext; +} + +Result ProcessMessages() +{ + assert(g_Context != 0); + g_Context->m_CommandServer->processCommands(); + g_Context->m_CommandQueue->processMessages(); + return RESULT_OK; +} + + +} // namespace diff --git a/defold-rive/src/private/renderer_context.h b/defold-rive/commonsrc/renderer/renderer_context.h similarity index 100% rename from defold-rive/src/private/renderer_context.h rename to defold-rive/commonsrc/renderer/renderer_context.h diff --git a/defold-rive/src/private/renderer_context_metal.mm b/defold-rive/commonsrc/renderer/renderer_context_metal.mm similarity index 100% rename from defold-rive/src/private/renderer_context_metal.mm rename to defold-rive/commonsrc/renderer/renderer_context_metal.mm diff --git a/defold-rive/src/private/renderer_context_opengl.cpp b/defold-rive/commonsrc/renderer/renderer_context_opengl.cpp similarity index 99% rename from defold-rive/src/private/renderer_context_opengl.cpp rename to defold-rive/commonsrc/renderer/renderer_context_opengl.cpp index 7326a36f..2bea987c 100644 --- a/defold-rive/src/private/renderer_context_opengl.cpp +++ b/defold-rive/commonsrc/renderer/renderer_context_opengl.cpp @@ -1,5 +1,5 @@ -#ifdef DM_RIVE_USE_OPENGL +#if defined(DM_RIVE_USE_OPENGL) && !defined(DM_HEADLESS) #if defined(RIVE_ANDROID) || defined(RIVE_WEBGL) diff --git a/defold-rive/src/private/renderer_context_webgpu.cpp b/defold-rive/commonsrc/renderer/renderer_context_webgpu.cpp similarity index 100% rename from defold-rive/src/private/renderer_context_webgpu.cpp rename to defold-rive/commonsrc/renderer/renderer_context_webgpu.cpp diff --git a/defold-rive/src/private/renderer_private.cpp b/defold-rive/commonsrc/renderer/renderer_private.cpp similarity index 97% rename from defold-rive/src/private/renderer_private.cpp rename to defold-rive/commonsrc/renderer/renderer_private.cpp index 1c5bf435..d53a5e9b 100644 --- a/defold-rive/src/private/renderer_private.cpp +++ b/defold-rive/commonsrc/renderer/renderer_private.cpp @@ -18,6 +18,17 @@ #include + +#if defined(DM_HEADLESS) +namespace dmRive +{ + rive::rcp CreateRiveRenderImage(HRenderContext context, void* bytes, uint32_t byte_count) + { + return rive::rcp(); + } +} +#else + namespace dmResource { void IncRef(HFactory factory, void* resource); @@ -138,6 +149,7 @@ namespace dmRive } #endif + renderer->m_RenderContext->BeginFrame({ .renderTargetWidth = width, .renderTargetHeight = height, @@ -296,3 +308,5 @@ namespace dmRive return viewTransform; } } + +#endif // DM_HEADLESS diff --git a/defold-rive/commonsrc/rive_ddf.proto b/defold-rive/commonsrc/rive_ddf.proto index 4908ba1e..50235255 100644 --- a/defold-rive/commonsrc/rive_ddf.proto +++ b/defold-rive/commonsrc/rive_ddf.proto @@ -57,7 +57,7 @@ message RiveModelDesc } required string scene = 1 [(resource)=true]; - required string default_animation = 2; + optional string default_animation = 2; // Deprecated optional string material = 3 [(resource)=true, default="/defold-rive/assets/rivemodel.material"]; optional BlendMode blend_mode = 4 [default = BLEND_MODE_ALPHA]; optional string default_state_machine = 5; @@ -73,42 +73,3 @@ message RiveModelDesc optional string blit_material = 13 [(resource)=true, default="/defold-rive/assets/shader-library/rivemodel_blit.material"]; } -message RivePlayAnimation -{ - required uint64 animation_id = 1; - required uint32 playback = 2; // matches dmGameObject::Playback in gameobject.h - optional float offset = 3 [default = 0.0]; - optional float playback_rate = 4 [default = 1.0]; - optional bool is_state_machine = 5 [default = false]; -} - -message RiveCancelAnimation -{ -} - -message RiveAnimationDone -{ - required uint64 animation_id = 1; - required uint32 playback = 2; // matches dmGameObject::Playback in gameobject.h -} - -message RiveEventTrigger -{ - required string name = 1; - optional float number = 2; - optional bool trigger = 3; - optional string text = 4; -} - -// Function wrapper documented in gamesys_script.cpp -message SetConstantRiveModel -{ - required uint64 name_hash = 1; - required dmMath.Vector4 value = 2; -} - -// Function wrapper documented in gamesys_script.cpp -message ResetConstantRiveModel -{ - required uint64 name_hash = 1; -} diff --git a/defold-rive/include/common/commands.h b/defold-rive/include/common/commands.h new file mode 100644 index 00000000..ef9429a5 --- /dev/null +++ b/defold-rive/include/common/commands.h @@ -0,0 +1,53 @@ +// Copyright 2020-2025 The Defold Foundation +// Licensed under the Defold License version 1.0 (the "License"); you may not use +// this file except in compliance with the License. +// +// You may obtain a copy of the License, together with FAQs at +// https://www.defold.com/license +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#include + +#include +#include + +namespace rive +{ + class Factory; +} + +namespace dmRiveCommands +{ + enum Result + { + RESULT_OK = 0, + RESULT_FAILED_CREATE_THREAD = -1, + }; + + struct InitParams + { + dmRive::HRenderContext m_RenderContext; + rive::Factory* m_Factory; + bool m_UseThreads; + + InitParams() + : m_RenderContext(0) + , m_Factory(0) + , m_UseThreads(false) + {} + }; + + Result Initialize(InitParams* params); // Once per session + Result Finalize(); // Once per session + + Result ProcessMessages(); + + // Getters + rive::Factory* GetFactory(); + dmRive::HRenderContext GetDefoldRenderContext(); + rive::rcp GetCommandQueue(); +} diff --git a/defold-rive/include/defold/rive.h b/defold-rive/include/defold/rive.h new file mode 100644 index 00000000..4137975f --- /dev/null +++ b/defold-rive/include/defold/rive.h @@ -0,0 +1,27 @@ +// Copyright 2020-2025 The Defold Foundation +// Licensed under the Defold License version 1.0 (the "License"); you may not use +// this file except in compliance with the License. +// +// You may obtain a copy of the License, together with FAQs at +// https://www.defold.com/license +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef DM_RIVE_H +#define DM_RIVE_H + +#if defined(__linux__) && defined(None) + #undef None +#endif + +#include +#include + +#include +#include + + +#endif // DM_RIVE_H diff --git a/defold-rive/plugins/lib/arm64-osx/libRiveExt.dylib b/defold-rive/plugins/lib/arm64-osx/libRiveExt.dylib index d14248ab..60c1a77d 100644 Binary files a/defold-rive/plugins/lib/arm64-osx/libRiveExt.dylib and b/defold-rive/plugins/lib/arm64-osx/libRiveExt.dylib differ diff --git a/defold-rive/plugins/lib/x86_64-osx/libRiveExt.dylib b/defold-rive/plugins/lib/x86_64-osx/libRiveExt.dylib index 98c5f52e..c1c120bd 100644 Binary files a/defold-rive/plugins/lib/x86_64-osx/libRiveExt.dylib and b/defold-rive/plugins/lib/x86_64-osx/libRiveExt.dylib differ diff --git a/defold-rive/plugins/lib/x86_64-win32/libRiveExt.dll b/defold-rive/plugins/lib/x86_64-win32/libRiveExt.dll index bdb6f85e..bb494597 100644 Binary files a/defold-rive/plugins/lib/x86_64-win32/libRiveExt.dll and b/defold-rive/plugins/lib/x86_64-win32/libRiveExt.dll differ diff --git a/defold-rive/plugins/share/pluginRiveExt.jar b/defold-rive/plugins/share/pluginRiveExt.jar index d35a5c81..1a0e8da6 100644 Binary files a/defold-rive/plugins/share/pluginRiveExt.jar and b/defold-rive/plugins/share/pluginRiveExt.jar differ diff --git a/defold-rive/pluginsrc/rive_renderer_null.cpp b/defold-rive/pluginsrc/rive_renderer_null.cpp deleted file mode 100644 index 35009445..00000000 --- a/defold-rive/pluginsrc/rive_renderer_null.cpp +++ /dev/null @@ -1,13 +0,0 @@ - -#include -#include - -#include - -namespace dmRive -{ - rive::rcp CreateRiveRenderImage(HRenderContext context, void* bytes, uint32_t byte_count) - { - return nullptr; - } -} diff --git a/defold-rive/src/comp_rive.cpp b/defold-rive/src/comp_rive.cpp index 774e3246..65193411 100644 --- a/defold-rive/src/comp_rive.cpp +++ b/defold-rive/src/comp_rive.cpp @@ -1,4 +1,4 @@ -// Copyright 2020 The Defold Foundation +// Copyright 2020-2025 The Defold Foundation // Licensed under the Defold License version 1.0 (the "License"); you may not use // this file except in compliance with the License. // @@ -14,33 +14,22 @@ #include // memset -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include // Artboard -#include -#include - // Rive extension #include "comp_rive.h" #include "comp_rive_private.h" #include "res_rive_data.h" #include "res_rive_scene.h" #include "res_rive_model.h" +#include "script_rive_listeners.h" +#include "viewmodel_instance_registry.h" #include +#include #include #include // Defold Rive Renderer +#include #include // DMSDK @@ -79,12 +68,12 @@ namespace dmRive { using namespace dmVMath; - static const dmhash_t PROP_ANIMATION = dmHashString64("animation"); - static const dmhash_t PROP_CURSOR = dmHashString64("cursor"); - static const dmhash_t PROP_PLAYBACK_RATE = dmHashString64("playback_rate"); - static const dmhash_t PROP_MATERIAL = dmHashString64("material"); - static const dmhash_t MATERIAL_EXT_HASH = dmHashString64("materialc"); - static const dmhash_t PROP_RIVE_FILE = dmHashString64("rive_file"); + static const dmhash_t PROP_ARTBOARD = dmHashString64("artboard"); + static const dmhash_t PROP_STATE_MACHINE = dmHashString64("state_machine"); + static const dmhash_t PROP_PLAYBACK_RATE = dmHashString64("playback_rate"); + static const dmhash_t PROP_MATERIAL = dmHashString64("material"); + static const dmhash_t MATERIAL_EXT_HASH = dmHashString64("materialc"); + static const dmhash_t PROP_RIVE_FILE = dmHashString64("rive_file"); static float g_DisplayFactor = 0.0f; static float g_OriginalWindowWidth = 0.0f; @@ -109,6 +98,7 @@ namespace dmRive dmRender::HRenderContext m_RenderContext; dmGraphics::HContext m_GraphicsContext; dmGraphics::HTexture m_NullTexture; + HRenderContext m_RiveRenderContext; uint32_t m_MaxInstanceCount; }; @@ -126,6 +116,55 @@ namespace dmRive bool m_DidWork; // did we get any batch workload ? }; + class SimpleArtboardListener : public rive::CommandQueue::ArtboardListener + { + public: + virtual void onStateMachinesListed(const rive::ArtboardHandle fileHandle, uint64_t requestId, + std::vector stateMachineNames) override + { + // we can guarantee that this is the only listener that will receive + // this callback, so it's fine to move the params + //m_stateMachineNames = std::move(stateMachineNames); + } + + RiveComponent* m_Component; + + //std::vector m_stateMachineNames; + }; + + static inline rive::Fit DDFToRiveFit(dmRiveDDF::RiveModelDesc::Fit fit) + { + switch(fit) + { + case dmRiveDDF::RiveModelDesc::FIT_FILL: return rive::Fit::fill; + case dmRiveDDF::RiveModelDesc::FIT_CONTAIN: return rive::Fit::contain; + case dmRiveDDF::RiveModelDesc::FIT_COVER: return rive::Fit::cover; + case dmRiveDDF::RiveModelDesc::FIT_FIT_WIDTH: return rive::Fit::fitWidth; + case dmRiveDDF::RiveModelDesc::FIT_FIT_HEIGHT: return rive::Fit::fitHeight; + case dmRiveDDF::RiveModelDesc::FIT_NONE: return rive::Fit::none; + case dmRiveDDF::RiveModelDesc::FIT_SCALE_DOWN: return rive::Fit::scaleDown; + case dmRiveDDF::RiveModelDesc::FIT_LAYOUT: return rive::Fit::layout; + } + return rive::Fit::none; + } + + static inline rive::Alignment DDFToRiveAlignment(dmRiveDDF::RiveModelDesc::Alignment alignment) + { + switch(alignment) + { + case dmRiveDDF::RiveModelDesc::ALIGNMENT_TOP_LEFT: return rive::Alignment::topLeft; + case dmRiveDDF::RiveModelDesc::ALIGNMENT_TOP_CENTER: return rive::Alignment::topCenter; + case dmRiveDDF::RiveModelDesc::ALIGNMENT_TOP_RIGHT: return rive::Alignment::topRight; + case dmRiveDDF::RiveModelDesc::ALIGNMENT_CENTER_LEFT: return rive::Alignment::centerLeft; + case dmRiveDDF::RiveModelDesc::ALIGNMENT_CENTER: return rive::Alignment::center; + case dmRiveDDF::RiveModelDesc::ALIGNMENT_CENTER_RIGHT: return rive::Alignment::centerRight; + case dmRiveDDF::RiveModelDesc::ALIGNMENT_BOTTOM_LEFT: return rive::Alignment::bottomLeft; + case dmRiveDDF::RiveModelDesc::ALIGNMENT_BOTTOM_CENTER: return rive::Alignment::bottomCenter; + case dmRiveDDF::RiveModelDesc::ALIGNMENT_BOTTOM_RIGHT: return rive::Alignment::bottomRight; + } + return rive::Alignment::center; + } + dmGameObject::CreateResult CompRiveNewWorld(const dmGameObject::ComponentNewWorldParams& params) { CompRiveContext* context = (CompRiveContext*)params.m_Context; @@ -138,6 +177,7 @@ namespace dmRive world->m_RenderConstants.SetSize(context->m_MaxInstanceCount); world->m_BlitMaterial = 0; world->m_DidWork = false; + world->m_RiveRenderContext = context->m_RiveRenderContext; float bottom = 0.0f; float top = 1.0f; @@ -187,20 +227,6 @@ namespace dmRive return dmGameObject::CREATE_RESULT_OK; } - RiveArtboardIdList* FindArtboardIdList(rive::Artboard* artboard, dmRive::RiveSceneData* data) - { - dmhash_t artboard_id = dmHashString64(artboard->name().c_str()); - - for (int i = 0; i < data->m_ArtboardIdLists.Size(); ++i) - { - if (data->m_ArtboardIdLists[i]->m_ArtboardNameHash == artboard_id) - { - return data->m_ArtboardIdLists[i]; - } - } - return 0x0; - } - static inline dmGameSystem::MaterialResource* GetMaterialResource(const RiveComponent* component, const RiveModelResource* resource) { return component->m_Material ? component->m_Material : resource->m_Material; @@ -224,12 +250,12 @@ namespace dmRive HashState32 state; bool reverse = false; RiveModelResource* resource = component->m_Resource; - dmRiveDDF::RiveModelDesc* ddf = resource->m_DDF; + //dmRiveDDF::RiveModelDesc* ddf = resource->m_DDF; dmRender::HMaterial material = GetMaterial(component, resource); dmGameSystem::TextureSetResource* texture_set = resource->m_Scene->m_TextureSet; dmHashInit32(&state, reverse); dmHashUpdateBuffer32(&state, &material, sizeof(material)); - dmHashUpdateBuffer32(&state, &ddf->m_BlendMode, sizeof(ddf->m_BlendMode)); + //dmHashUpdateBuffer32(&state, &ddf->m_BlendMode, sizeof(ddf->m_BlendMode)); // not currently used if (texture_set) dmHashUpdateBuffer32(&state, &texture_set, sizeof(texture_set)); component->m_MixedHash = dmHashFinal32(&state); @@ -248,24 +274,132 @@ namespace dmRive return GetComponentFromIndex(world, index); } - static void InstantiateArtboard(RiveComponent* component) + rive::FileHandle CompRiveGetFile(RiveComponent* component) + { + RiveSceneData* resource = GetRiveResource(component, component->m_Resource); + return resource->m_File; + } + + rive::ArtboardHandle CompRiveGetArtboard(RiveComponent* component) + { + return component->m_Artboard; + } + + rive::ArtboardHandle CompRiveSetArtboard(RiveComponent* component, const char* artboard_name) { dmRive::RiveSceneData* data = (dmRive::RiveSceneData*) component->m_Resource->m_Scene->m_Scene; + rive::FileHandle file = data->m_File; + rive::rcp queue = dmRiveCommands::GetCommandQueue(); + + rive::ArtboardHandle old_handle = component->m_Artboard; - if (component->m_Resource->m_DDF->m_Artboard && component->m_Resource->m_DDF->m_Artboard[0] != '\0') + if (artboard_name && artboard_name[0] != '\0') { - component->m_ArtboardInstance = data->m_File->artboardNamed(component->m_Resource->m_DDF->m_Artboard); + component->m_Artboard = queue->instantiateArtboardNamed(file, artboard_name); + if (!component->m_Artboard) + { + dmLogWarning("Could not find artboard with name '%s'", artboard_name); + } + } + + if (!component->m_Artboard) + { + component->m_Artboard = queue->instantiateDefaultArtboard(file); + } + + return old_handle; + } + + rive::StateMachineHandle CompRiveGetStateMachine(RiveComponent* component) + { + return component->m_StateMachine; + } + + rive::ViewModelInstanceHandle CompRiveGetViewModelInstance(RiveComponent* component) + { + return component->m_ViewModelInstance; + } - if (!component->m_ArtboardInstance) + static void BindViewModelInstance(RiveComponent* component) + { + rive::rcp queue = dmRiveCommands::GetCommandQueue(); + queue->bindViewModelInstance(component->m_StateMachine, component->m_ViewModelInstance); + } + + rive::StateMachineHandle CompRiveSetStateMachine(RiveComponent* component, const char* state_machine_name) + { + rive::ArtboardHandle artboard = component->m_Artboard; + rive::rcp queue = dmRiveCommands::GetCommandQueue(); + + rive::StateMachineHandle old_handle = component->m_StateMachine; + + if (state_machine_name && state_machine_name[0] != '\0') + { + component->m_StateMachine = queue->instantiateStateMachineNamed(artboard, state_machine_name); + if (!component->m_StateMachine) { - dmLogWarning("Could not find artboard with name '%s'", component->m_Resource->m_DDF->m_Artboard); + dmLogWarning("Could not find state_machine with name '%s'", state_machine_name); } } - if (!component->m_ArtboardInstance) + if (!component->m_StateMachine) { - component->m_ArtboardInstance = data->m_File->artboardDefault(); + component->m_StateMachine = queue->instantiateDefaultStateMachine(artboard); } + + component->m_Enabled = component->m_StateMachine != 0; + + if (component->m_StateMachine && component->m_Resource->m_DDF->m_AutoBind) + { + CompRiveSetViewModelInstance(component, 0); + BindViewModelInstance(component); + } + + return old_handle; + } + + rive::ViewModelInstanceHandle CompRiveSetViewModelInstance(RiveComponent* component, const char* viewmodel_name) + { + rive::FileHandle file = CompRiveGetFile(component); + rive::rcp queue = dmRiveCommands::GetCommandQueue(); + + rive::ViewModelInstanceHandle old_handle = component->m_ViewModelInstance; + + if (viewmodel_name && viewmodel_name[0] != '\0') + { + ViewModelInstanceListener* listener = new ViewModelInstanceListener(); + listener->SetAutoDeleteOnViewModelDeleted(true); + component->m_ViewModelInstance = queue->instantiateDefaultViewModelInstance(file, viewmodel_name, listener); + if (!component->m_ViewModelInstance) + { + dmLogWarning("Could not find view model instance with name '%s'", viewmodel_name); + delete listener; + } + else + { + RegisterViewModelInstanceListener(component->m_ViewModelInstance, listener); + RequestViewModelInstanceProperties(file, component->m_ViewModelInstance, viewmodel_name); + } + } + + if (!component->m_ViewModelInstance) + { + ViewModelInstanceListener* listener = new ViewModelInstanceListener(); + listener->SetAutoDeleteOnViewModelDeleted(true); + component->m_ViewModelInstance = queue->instantiateDefaultViewModelInstance(file, component->m_Artboard, listener); + if (!component->m_ViewModelInstance) + { + delete listener; + } + else + { + RegisterViewModelInstanceListener(component->m_ViewModelInstance, listener); + RequestDefaultViewModelInstanceProperties(file, component->m_Artboard, component->m_ViewModelInstance); + } + } + + component->m_Enabled = component->m_ViewModelInstance != 0; + return old_handle; } dmGameObject::CreateResult CompRiveCreate(const dmGameObject::ComponentCreateParams& params) @@ -290,116 +424,57 @@ namespace dmRive CompRiveAnimationReset(component); dmMessage::ResetURL(&component->m_Listener); + rive::rcp queue = dmRiveCommands::GetCommandQueue(); + component->m_ComponentIndex = params.m_ComponentIndex; component->m_Enabled = 1; component->m_World = Matrix4::identity(); component->m_DoRender = 0; component->m_RenderConstants = 0; component->m_CurrentViewModelInstanceRuntime = INVALID_HANDLE; - component->m_HandleCounter = 0; + component->m_DrawKey = queue->createDrawKey(); - InstantiateArtboard(component); + dmRiveDDF::RiveModelDesc* ddf = component->m_Resource->m_DDF; - component->m_ArtboardInstance->advance(0.0f); - - if (component->m_Resource->m_CreateGoBones) + component->m_Fullscreen = ddf->m_CoordinateSystem == dmRiveDDF::RiveModelDesc::COORDINATE_SYSTEM_FULLSCREEN; + component->m_Fit = rive::Fit::layout; + component->m_Alignment = rive::Alignment::center; + if (!component->m_Fullscreen) { - dmRive::GetAllBones(component->m_ArtboardInstance.get(), &component->m_Bones); - CreateBones(world, component); - UpdateBones(component); + component->m_Fit = DDFToRiveFit(ddf->m_ArtboardFit); + component->m_Alignment = DDFToRiveAlignment(ddf->m_ArtboardAlignment); } - dmhash_t empty_id = dmHashString64(""); - dmhash_t anim_id = dmHashString64(component->m_Resource->m_DDF->m_DefaultAnimation); - dmhash_t state_machine_id = dmHashString64(component->m_Resource->m_DDF->m_DefaultStateMachine); - - dmRiveDDF::RivePlayAnimation ddf; - ddf.m_Offset = 0.0; - ddf.m_PlaybackRate = 1.0; - - bool use_default_state_machine = component->m_ArtboardInstance->defaultStateMachineIndex() >= 0; - if (empty_id != state_machine_id || use_default_state_machine) - { - ddf.m_Playback = dmGameObject::PLAYBACK_NONE; - ddf.m_AnimationId = use_default_state_machine ? 0 : state_machine_id; - ddf.m_IsStateMachine = true; - if (!CompRivePlayStateMachine(component, &ddf, 0)) - { - dmLogError("Couldn't play state machine named '%s'", dmHashReverseSafe64(state_machine_id)); - } - } - else - { - ddf.m_AnimationId = anim_id; // May be 0 - ddf.m_Playback = dmGameObject::PLAYBACK_LOOP_FORWARD; - ddf.m_IsStateMachine = false; - if (!CompRivePlayAnimation(component, &ddf, 0)) - { - dmLogError("Couldn't play animation named '%s'", dmHashReverseSafe64(anim_id)); - } - } + CompRiveSetArtboard(component, ddf->m_Artboard); + CompRiveSetStateMachine(component, ddf->m_DefaultStateMachine); - component->m_CurrentViewModelInstanceRuntime = 0xFFFFFFFF; - if (component->m_Resource->m_DDF->m_AutoBind) - { - component->m_CurrentViewModelInstanceRuntime = CompRiveCreateViewModelInstanceRuntime(component, 0); - if (component->m_CurrentViewModelInstanceRuntime != dmRive::INVALID_HANDLE) - { - CompRiveSetViewModelInstanceRuntime(component, component->m_CurrentViewModelInstanceRuntime); - } - } component->m_ReHash = 1; *params.m_UserData = (uintptr_t)index; return dmGameObject::CREATE_RESULT_OK; } - static void CompRiveRunCallback(RiveComponent* component, const dmDDF::Descriptor* desc, const char* data, const dmMessage::URL* sender) - { - dmScript::LuaCallbackInfo* cbk = component->m_Callback; - if (!dmScript::IsCallbackValid(cbk)) - { - dmLogError("Rive callback is invalid."); - return; - } - - lua_State* L = dmScript::GetCallbackLuaContext(cbk); - DM_LUA_STACK_CHECK(L, 0); - - if (!dmScript::SetupCallback(cbk)) - { - dmLogError("Failed to setup Rive callback"); - return; - } - - dmScript::PushHash(L, desc->m_NameHash); - dmScript::PushDDF(L, desc, data, false); - dmScript::PushURL(L, *sender); - int ret = dmScript::PCall(L, 4, 0); - (void)ret; - dmScript::TeardownCallback(cbk); - } - - static void CompRiveClearCallback(RiveComponent* component) - { - if (component->m_Callback) - { - dmScript::DestroyCallback(component->m_Callback); - component->m_Callback = 0x0; - } - } - static void DestroyComponent(RiveWorld* world, uint32_t index) { + rive::rcp queue = dmRiveCommands::GetCommandQueue(); + RiveComponent* component = GetComponentFromIndex(world, index); - dmGameObject::DeleteBones(component->m_Instance); + //dmGameObject::DeleteBones(component->m_Instance); if (component->m_RenderConstants) dmGameSystem::DestroyRenderConstants(component->m_RenderConstants); - component->m_ArtboardInstance.reset(); - component->m_AnimationInstance.reset(); - component->m_StateMachineInstance.reset(); + if (component->m_ViewModelInstance) + queue->deleteViewModelInstance(component->m_ViewModelInstance); + component->m_ViewModelInstance = 0; + + if (component->m_StateMachine) + queue->deleteStateMachine(component->m_StateMachine); + component->m_StateMachine = 0; + + if (component->m_Artboard) + queue->deleteArtboard(component->m_Artboard); + component->m_Artboard = 0; delete component; world->m_Components.Free(index, true); @@ -414,48 +489,46 @@ namespace dmRive if (component->m_Material) { dmResource::Release(ctx->m_Factory, (void*)component->m_Material); } - if (component->m_Callback) { - CompRiveClearCallback(component); - } DestroyComponent(world, index); return dmGameObject::CREATE_RESULT_OK; } - // static void GetBlendFactorsFromBlendMode(dmRiveDDF::RiveModelDesc::BlendMode blend_mode, dmGraphics::BlendFactor* src, dmGraphics::BlendFactor* dst) - // { - // switch (blend_mode) - // { - // case dmRiveDDF::RiveModelDesc::BLEND_MODE_ALPHA: - // *src = dmGraphics::BLEND_FACTOR_ONE; - // *dst = dmGraphics::BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; - // break; - - // case dmRiveDDF::RiveModelDesc::BLEND_MODE_ADD: - // *src = dmGraphics::BLEND_FACTOR_ONE; - // *dst = dmGraphics::BLEND_FACTOR_ONE; - // break; - - // case dmRiveDDF::RiveModelDesc::BLEND_MODE_MULT: - // *src = dmGraphics::BLEND_FACTOR_DST_COLOR; - // *dst = dmGraphics::BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; - // break; - - // case dmRiveDDF::RiveModelDesc::BLEND_MODE_SCREEN: - // *src = dmGraphics::BLEND_FACTOR_ONE_MINUS_DST_COLOR; - // *dst = dmGraphics::BLEND_FACTOR_ONE; - // break; - - // default: - // dmLogError("Unknown blend mode: %d\n", blend_mode); - // assert(0); - // break; - // } - // } + static void GetBlendFactorsFromBlendMode(dmRiveDDF::RiveModelDesc::BlendMode blend_mode, dmGraphics::BlendFactor* src, dmGraphics::BlendFactor* dst) + { + switch (blend_mode) + { + case dmRiveDDF::RiveModelDesc::BLEND_MODE_ALPHA: + *src = dmGraphics::BLEND_FACTOR_ONE; + *dst = dmGraphics::BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; + break; + + case dmRiveDDF::RiveModelDesc::BLEND_MODE_ADD: + *src = dmGraphics::BLEND_FACTOR_ONE; + *dst = dmGraphics::BLEND_FACTOR_ONE; + break; + + case dmRiveDDF::RiveModelDesc::BLEND_MODE_MULT: + *src = dmGraphics::BLEND_FACTOR_DST_COLOR; + *dst = dmGraphics::BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; + break; + + case dmRiveDDF::RiveModelDesc::BLEND_MODE_SCREEN: + *src = dmGraphics::BLEND_FACTOR_ONE_MINUS_DST_COLOR; + *dst = dmGraphics::BLEND_FACTOR_ONE; + break; + + default: + dmLogError("Unknown blend mode: %d\n", blend_mode); + assert(0); + break; + } + } static void RenderBatchEnd(RiveWorld* world, dmRender::HRenderContext render_context) { if (world->m_RiveRenderContext) { + dmRiveCommands::ProcessMessages(); // Flush any draw() messages RenderEnd(world->m_RiveRenderContext); if (g_RenderBeginParams.m_DoFinalBlit) @@ -476,55 +549,25 @@ namespace dmRive } } - static inline rive::Fit DDFToRiveFit(dmRiveDDF::RiveModelDesc::Fit fit) - { - switch(fit) - { - case dmRiveDDF::RiveModelDesc::FIT_FILL: return rive::Fit::fill; - case dmRiveDDF::RiveModelDesc::FIT_CONTAIN: return rive::Fit::contain; - case dmRiveDDF::RiveModelDesc::FIT_COVER: return rive::Fit::cover; - case dmRiveDDF::RiveModelDesc::FIT_FIT_WIDTH: return rive::Fit::fitWidth; - case dmRiveDDF::RiveModelDesc::FIT_FIT_HEIGHT: return rive::Fit::fitHeight; - case dmRiveDDF::RiveModelDesc::FIT_NONE: return rive::Fit::none; - case dmRiveDDF::RiveModelDesc::FIT_SCALE_DOWN: return rive::Fit::scaleDown; - case dmRiveDDF::RiveModelDesc::FIT_LAYOUT: return rive::Fit::layout; - } - return rive::Fit::none; - } - - static inline rive::Alignment DDFToRiveAlignment(dmRiveDDF::RiveModelDesc::Alignment alignment) - { - switch(alignment) - { - case dmRiveDDF::RiveModelDesc::ALIGNMENT_TOP_LEFT: return rive::Alignment::topLeft; - case dmRiveDDF::RiveModelDesc::ALIGNMENT_TOP_CENTER: return rive::Alignment::topCenter; - case dmRiveDDF::RiveModelDesc::ALIGNMENT_TOP_RIGHT: return rive::Alignment::topRight; - case dmRiveDDF::RiveModelDesc::ALIGNMENT_CENTER_LEFT: return rive::Alignment::centerLeft; - case dmRiveDDF::RiveModelDesc::ALIGNMENT_CENTER: return rive::Alignment::center; - case dmRiveDDF::RiveModelDesc::ALIGNMENT_CENTER_RIGHT: return rive::Alignment::centerRight; - case dmRiveDDF::RiveModelDesc::ALIGNMENT_BOTTOM_LEFT: return rive::Alignment::bottomLeft; - case dmRiveDDF::RiveModelDesc::ALIGNMENT_BOTTOM_CENTER: return rive::Alignment::bottomCenter; - case dmRiveDDF::RiveModelDesc::ALIGNMENT_BOTTOM_RIGHT: return rive::Alignment::bottomRight; - } - return rive::Alignment::center; - } - static void RenderBatch(RiveWorld* world, dmRender::HRenderContext render_context, dmRender::RenderListEntry *buf, uint32_t* begin, uint32_t* end) { RiveComponent* first = (RiveComponent*) buf[*begin].m_UserData; dmRive::RiveSceneResource* scene_res = first->m_Resource->m_Scene; dmRive::RiveSceneData* data = (dmRive::RiveSceneData*) scene_res->m_Scene; - world->m_RiveRenderContext = data->m_RiveRenderContext; RenderBegin(world->m_RiveRenderContext, world->m_Ctx->m_Factory, g_RenderBeginParams); uint32_t width, height; GetDimensions(world->m_RiveRenderContext, &width, &height); + uint32_t window_height = dmGraphics::GetWindowHeight(world->m_Ctx->m_GraphicsContext); + rive::Mat2D viewTransform = GetViewTransform(world->m_RiveRenderContext, render_context); rive::Renderer* renderer = GetRiveRenderer(world->m_RiveRenderContext); - uint32_t window_height = dmGraphics::GetWindowHeight(world->m_Ctx->m_GraphicsContext); + float display_factor = g_DisplayFactor; + + rive::rcp queue = dmRiveCommands::GetCommandQueue(); for (uint32_t *i=begin;i!=end;i++) { @@ -536,49 +579,72 @@ namespace dmRive RiveModelResource* resource = c->m_Resource; dmRiveDDF::RiveModelDesc* ddf = resource->m_DDF; - renderer->save(); + // From command_buffer_example.cpp - rive::AABB bounds = c->m_ArtboardInstance->bounds(); + const rive::ArtboardHandle artboardHandle = c->m_Artboard; + rive::Fit fit = c->m_Fit; + rive::Alignment alignment = c->m_Alignment; + bool fullscreen = c->m_Fullscreen; - if (ddf->m_CoordinateSystem == dmRiveDDF::RiveModelDesc::COORDINATE_SYSTEM_FULLSCREEN) + rive::Mat2D transform; + if (fullscreen) { - // Apply the world matrix from the component to the artboard transform - rive::Mat2D transform; Mat4ToMat2D(c->m_World, transform); - - rive::Mat2D centerAdjustment = rive::Mat2D::fromTranslate(-bounds.width() / 2.0f, -bounds.height() / 2.0f); - rive::Mat2D scaleDpi = rive::Mat2D::fromScale(1,-1); - rive::Mat2D invertAdjustment = rive::Mat2D::fromScaleAndTranslation(g_DisplayFactor, -g_DisplayFactor, 0, window_height); - rive::Mat2D rendererTransform = invertAdjustment * viewTransform * transform * scaleDpi * centerAdjustment; - - renderer->transform(rendererTransform); - c->m_InverseRendererTransform = rendererTransform.invertOrIdentity(); } - else if (ddf->m_CoordinateSystem == dmRiveDDF::RiveModelDesc::COORDINATE_SYSTEM_RIVE) + + auto drawLoop = [artboardHandle, + renderer, + fullscreen, + fit, + alignment, + transform, + viewTransform, + width, + height, + window_height, + display_factor, + c](rive::DrawKey drawKey, rive::CommandServer* server) { - rive::Fit rive_fit = DDFToRiveFit(ddf->m_ArtboardFit); - rive::Alignment rive_align = DDFToRiveAlignment(ddf->m_ArtboardAlignment); + rive::ArtboardInstance* artboard = server->getArtboardInstance(artboardHandle); + if (artboard == nullptr) + { + return; + } - if (rive_fit == rive::Fit::layout) + rive::Factory* factory = server->factory(); + renderer->save(); + + rive::AABB bounds = artboard->bounds(); + + if (fullscreen) { - c->m_ArtboardInstance->width(width / g_DisplayFactor); - c->m_ArtboardInstance->height(height / g_DisplayFactor); + // Apply the world matrix from the component to the artboard transform + rive::Mat2D centerAdjustment = rive::Mat2D::fromTranslate(-bounds.width() / 2.0f, -bounds.height() / 2.0f); + rive::Mat2D scaleDpi = rive::Mat2D::fromScale(1,-1); + rive::Mat2D invertAdjustment = rive::Mat2D::fromScaleAndTranslation(display_factor, -display_factor, 0, window_height); + rive::Mat2D rendererTransform = invertAdjustment * viewTransform * transform * scaleDpi * centerAdjustment; + + renderer->transform(rendererTransform); + c->m_InverseRendererTransform = rendererTransform.invertOrIdentity(); } + else + { + if (fit == rive::Fit::layout) + { + artboard->width(width / display_factor); + artboard->height(height / display_factor); + } - rive::Mat2D rendererTransform = rive::computeAlignment(rive_fit, rive_align, rive::AABB(0, 0, width, height), bounds, g_DisplayFactor); - renderer->transform(rendererTransform); - c->m_InverseRendererTransform = rendererTransform.invertOrIdentity(); - } + rive::Mat2D rendererTransform = rive::computeAlignment(fit, alignment, rive::AABB(0, 0, width, height), bounds, display_factor); + renderer->transform(rendererTransform); + c->m_InverseRendererTransform = rendererTransform.invertOrIdentity(); + } - if (c->m_StateMachineInstance) { - c->m_StateMachineInstance->draw(renderer); - } else if (c->m_AnimationInstance) { - c->m_AnimationInstance->draw(renderer); - } else { - c->m_ArtboardInstance->draw(renderer); - } + artboard->draw(renderer); + renderer->restore(); + }; - renderer->restore(); + queue->draw(c->m_DrawKey, drawLoop); } } @@ -632,141 +698,12 @@ namespace dmRive component->m_AnimationIndex = 0xff; component->m_AnimationPlaybackRate = 1.0f; component->m_AnimationPlayback = dmGameObject::PLAYBACK_NONE; - - component->m_AnimationInstance.reset(); - component->m_StateMachineInstance.reset(); - component->m_StateMachineInputs.SetSize(0); - } - - static void CompRiveAnimationDoneCallback(RiveComponent* component) - { - assert(component->m_AnimationInstance); - - dmMessage::URL sender; - dmMessage::URL receiver = component->m_Listener; - if (!GetSender(component, &sender)) - { - dmLogError("Could not send animation_done to listener because of incomplete component."); - return; - } - - dmRive::RiveSceneData* data = (dmRive::RiveSceneData*) component->m_Resource->m_Scene->m_Scene; - - RiveArtboardIdList* id_list = FindArtboardIdList(component->m_ArtboardInstance.get(), data); - - dmRiveDDF::RiveAnimationDone message; - message.m_AnimationId = id_list->m_LinearAnimations[component->m_AnimationIndex]; - message.m_Playback = component->m_AnimationPlayback; - - if (component->m_Callback) - { - uint32_t id = component->m_CallbackId; - CompRiveRunCallback(component, dmRiveDDF::RiveAnimationDone::m_DDFDescriptor, (const char*)&message, &sender); - - // Make sure we're clearing the same callback as we ran above and not a new - // callback that was started from the original callback - if (id == component->m_CallbackId) - { - CompRiveClearCallback(component); - } - } - else - { - dmGameObject::Result result = dmGameObject::PostDDF(&message, &sender, &receiver, 0, true); - if (result != dmGameObject::RESULT_OK) - { - dmLogError("Could not send animation_done to listener: %d", result); - } - } - } - - static bool CompRiveHandleEventTrigger(RiveComponent* component, dmRiveDDF::RiveEventTrigger message) - { - dmMessage::URL sender; - dmMessage::URL receiver = component->m_Listener; - if (!GetSender(component, &sender)) - { - dmLogError("Could not send event_trigger to listener because of incomplete component."); - return false; - } - - if (component->m_Callback) - { - CompRiveRunCallback(component, dmRiveDDF::RiveEventTrigger::m_DDFDescriptor, (const char*)&message, &sender); - - // note that we are not clearing the callback here since multiple events can fire at - // different times during the playback - } - else - { - dmGameObject::Result result = dmGameObject::PostDDF(&message, &sender, &receiver, 0, true); - if (result != dmGameObject::RESULT_OK) - { - dmLogError("Could not send event_trigger to listener: %d", result); - return false; - } - } - return true; - } - - static void CompRiveEventTriggerCallback(RiveComponent* component, rive::Event* event) - { - dmRiveDDF::RiveEventTrigger event_message; - event_message.m_Name = event->name().c_str(); - event_message.m_Trigger = 0; - event_message.m_Text = ""; - event_message.m_Number = 0.0f; - if (!CompRiveHandleEventTrigger(component, event_message)) - { - return; - } - - // trigger event once per event property - for (auto child : event->children()) - { - if (child->is()) - { - if (!child->name().empty()) - { - dmRiveDDF::RiveEventTrigger property_message; - property_message.m_Name = child->name().c_str(); - property_message.m_Trigger = 0; - property_message.m_Text = ""; - property_message.m_Number = 0.0f; - switch (child->coreType()) - { - case rive::CustomPropertyBoolean::typeKey: - { - bool b = child->as()->propertyValue(); - property_message.m_Trigger = b; - break; - } - case rive::CustomPropertyString::typeKey: - { - const char* s = child->as()->propertyValue().c_str(); - property_message.m_Text = s; - break; - } - case rive::CustomPropertyNumber::typeKey: - { - float f = child->as()->propertyValue(); - property_message.m_Number = f; - break; - } - } - if (!CompRiveHandleEventTrigger(component, property_message)) - { - return; - } - } - } - } } dmGameObject::UpdateResult CompRiveUpdate(const dmGameObject::ComponentsUpdateParams& params, dmGameObject::ComponentsUpdateResult& update_result) { DM_PROFILE("RiveModel"); - RiveWorld* world = (RiveWorld*)params.m_World; + RiveWorld* world = (RiveWorld*)params.m_World; float dt = params.m_UpdateContext->m_DT; @@ -774,6 +711,8 @@ namespace dmRive const uint32_t count = components.Size(); DM_PROPERTY_ADD_U32(rmtp_RiveComponents, count); + rive::rcp queue = dmRiveCommands::GetCommandQueue(); + for (uint32_t i = 0; i < count; ++i) { RiveComponent& component = *components[i]; @@ -784,64 +723,10 @@ namespace dmRive continue; } - // RIVE UPDATE - dmRive::RiveSceneData* data = (dmRive::RiveSceneData*) component.m_Resource->m_Scene->m_Scene; - rive::File* f = data->m_File; - rive::Artboard* artboard = f->artboard(); - - if (!artboard) - { - component.m_Enabled = false; - continue; - } - //rive::AABB artboard_bounds = artboard->bounds(); - - if (component.m_StateMachineInstance) - { - size_t event_count = component.m_StateMachineInstance->reportedEventCount(); - for (size_t i = 0; i < event_count; i++) - { - rive::EventReport reported_event = component.m_StateMachineInstance->reportedEventAt(i); - rive::Event* event = reported_event.event(); - CompRiveEventTriggerCallback(&component, event); - } - - component.m_StateMachineInstance->advanceAndApply(dt * component.m_AnimationPlaybackRate); - } - else if (component.m_AnimationInstance) - { - component.m_AnimationInstance->advanceAndApply(dt * component.m_AnimationPlaybackRate); - - if (component.m_AnimationInstance->didLoop()) - { - bool did_finish = false; - switch(component.m_AnimationPlayback) - { - case dmGameObject::PLAYBACK_ONCE_FORWARD: - did_finish = true; - break; - case dmGameObject::PLAYBACK_ONCE_BACKWARD: - did_finish = true; - break; - case dmGameObject::PLAYBACK_ONCE_PINGPONG: - did_finish = component.m_AnimationInstance->direction() > 0; - break; - default:break; - } + queue->advanceStateMachine(component.m_StateMachine, dt * component.m_AnimationPlaybackRate); - if (did_finish) - { - CompRiveAnimationDoneCallback(&component); - CompRiveAnimationReset(&component); - } - } - } - else { - component.m_ArtboardInstance->advance(dt * component.m_AnimationPlaybackRate); - } - - if (component.m_Resource->m_CreateGoBones) - UpdateBones(&component); // after the artboard->advance(); + // if (component.m_Resource->m_CreateGoBones) + // UpdateBones(&component); // after the artboard->advance(); if (component.m_ReHash || (component.m_RenderConstants && dmGameSystem::AreRenderConstantsUpdated(component.m_RenderConstants))) { @@ -851,6 +736,8 @@ namespace dmRive component.m_DoRender = 1; } + dmRiveCommands::ProcessMessages(); // Update the command server + // If the child bones have been updated, we need to return true update_result.m_TransformsUpdated = false; @@ -962,156 +849,6 @@ namespace dmRive return -1; } - static rive::LinearAnimation* FindAnimation(rive::Artboard* artboard, dmRive::RiveSceneData* data, int* animation_index, dmhash_t anim_id) - { - RiveArtboardIdList* id_list = FindArtboardIdList(artboard, data); - if (!id_list) - { - return 0; - } - - int index = FindAnimationIndex(id_list->m_LinearAnimations.Begin(), id_list->m_LinearAnimations.Size(), anim_id); - if (index == -1) { - return 0; - } - *animation_index = index; - return artboard->animation(index); - } - - bool CompRivePlayAnimation(RiveComponent* component, dmRiveDDF::RivePlayAnimation* ddf, dmScript::LuaCallbackInfo* callback_info) - { - dmRive::RiveSceneData* data = (dmRive::RiveSceneData*) component->m_Resource->m_Scene->m_Scene; - dmhash_t anim_id = ddf->m_AnimationId; - dmGameObject::Playback playback_mode = (dmGameObject::Playback)ddf->m_Playback; - float offset = ddf->m_Offset; - float playback_rate = ddf->m_PlaybackRate; - - if (playback_mode == dmGameObject::PLAYBACK_NONE) - { - return true; - } - - int animation_index; - rive::LinearAnimation* animation = 0; - if (anim_id != 0) - { - animation = FindAnimation(component->m_ArtboardInstance.get(), data, &animation_index, anim_id); - - if (!animation) { - dmLogError("Failed to find animation '%s'", dmHashReverseSafe64(anim_id)); - return false; - } - } - else - { - animation_index = 0; - animation = component->m_ArtboardInstance->animation(animation_index); // Default animation - } - - CompRiveAnimationReset(component); - CompRiveClearCallback(component); - - rive::Loop loop_value = rive::Loop::oneShot; - int play_direction = 1; - float play_time = animation->startSeconds(); - float offset_value = animation->durationSeconds() * offset; - - switch(playback_mode) - { - case dmGameObject::PLAYBACK_ONCE_FORWARD: - loop_value = rive::Loop::oneShot; - break; - case dmGameObject::PLAYBACK_ONCE_BACKWARD: - loop_value = rive::Loop::oneShot; - play_direction = -1; - play_time = animation->endSeconds(); - offset_value = -offset_value; - break; - case dmGameObject::PLAYBACK_ONCE_PINGPONG: - loop_value = rive::Loop::pingPong; - break; - case dmGameObject::PLAYBACK_LOOP_FORWARD: - loop_value = rive::Loop::loop; - break; - case dmGameObject::PLAYBACK_LOOP_BACKWARD: - loop_value = rive::Loop::loop; - play_direction = -1; - play_time = animation->endSeconds(); - offset_value = -offset_value; - break; - case dmGameObject::PLAYBACK_LOOP_PINGPONG: - loop_value = rive::Loop::pingPong; - break; - default:break; - } - - component->m_Callback = callback_info; - component->m_CallbackId++; - component->m_AnimationIndex = animation_index; - component->m_AnimationPlaybackRate = playback_rate; - component->m_AnimationPlayback = playback_mode; - component->m_StateMachineInstance = nullptr; - component->m_AnimationInstance = component->m_ArtboardInstance->animationAt(animation_index); - component->m_AnimationInstance->inputCount(); - component->m_AnimationInstance->time(play_time + offset_value); - component->m_AnimationInstance->loopValue((int)loop_value); - component->m_AnimationInstance->direction(play_direction); - return true; - } - - bool CompRivePlayStateMachine(RiveComponent* component, dmRiveDDF::RivePlayAnimation* ddf, dmScript::LuaCallbackInfo* callback_info) - { - dmRive::RiveSceneData* data = (dmRive::RiveSceneData*) component->m_Resource->m_Scene->m_Scene; - dmhash_t anim_id = ddf->m_AnimationId; - float playback_rate = ddf->m_PlaybackRate; - - int default_state_machine_index = component->m_ArtboardInstance->defaultStateMachineIndex(); - int state_machine_index = -1; - if (anim_id != 0) - { - rive::StateMachine* state_machine = FindStateMachine(component->m_ArtboardInstance.get(), data, &state_machine_index, anim_id); - - if (!state_machine) { - dmLogError("Failed to play state machine with id '%s'", dmHashReverseSafe64(anim_id)); - return false; - } - } - else - { - state_machine_index = default_state_machine_index; - } - - CompRiveAnimationReset(component); - CompRiveClearCallback(component); - - // For now, we need to create a new state machine instance when playing a state machine, because of nested artboards+state machine events - InstantiateArtboard(component); - - component->m_CallbackId++; - component->m_Callback = callback_info; - component->m_AnimationInstance = nullptr; - component->m_AnimationPlaybackRate = playback_rate; - - component->m_StateMachineInstance = component->m_ArtboardInstance->stateMachineAt(state_machine_index); - - if (!component->m_StateMachineInstance.get()) - { - dmLogError("Failed to start state machine '%s' at index %d", dmHashReverseSafe64(anim_id), state_machine_index); - return false; - } - - if (component->m_CurrentViewModelInstanceRuntime != INVALID_HANDLE) - { - CompRiveSetViewModelInstanceRuntime(component, component->m_CurrentViewModelInstanceRuntime); - } - - // update the list of current state machine inputs - GetStateMachineInputNames(component->m_StateMachineInstance.get(), component->m_StateMachineInputs); - - component->m_StateMachineInstance->advanceAndApply(0); - return true; - } - dmGameObject::UpdateResult CompRiveOnMessage(const dmGameObject::ComponentOnMessageParams& params) { RiveWorld* world = (RiveWorld*)params.m_World; @@ -1124,34 +861,6 @@ namespace dmRive { component->m_Enabled = 0; } - else if (params.m_Message->m_Descriptor != 0x0) - { - if (params.m_Message->m_Id == dmRiveDDF::RivePlayAnimation::m_DDFDescriptor->m_NameHash) - { - dmRiveDDF::RivePlayAnimation* ddf = (dmRiveDDF::RivePlayAnimation*)params.m_Message->m_Data; - if (ddf->m_IsStateMachine) - { - bool result = CompRivePlayStateMachine(component, ddf, 0); - if (!result) - { - dmLogError("Couldn't play state machine named '%s'", dmHashReverseSafe64(ddf->m_AnimationId)); - } - } - else - { - bool result = CompRivePlayAnimation(component, ddf, 0); - if (!result) - { - dmLogError("Couldn't play animation named '%s'", dmHashReverseSafe64(ddf->m_AnimationId)); - } - } - } - else if (params.m_Message->m_Id == dmRiveDDF::RiveCancelAnimation::m_DDFDescriptor->m_NameHash) - { - CompRiveAnimationReset(component); - } - } - return dmGameObject::UPDATE_RESULT_OK; } @@ -1178,24 +887,14 @@ namespace dmRive RiveComponent* component = GetComponentFromIndex(world, *params.m_UserData); dmRive::RiveSceneData* data = (dmRive::RiveSceneData*) component->m_Resource->m_Scene->m_Scene; - RiveArtboardIdList* id_list = FindArtboardIdList(component->m_ArtboardInstance.get(), data); - - if (params.m_PropertyId == PROP_ANIMATION) + if (params.m_PropertyId == PROP_ARTBOARD) { - if (component->m_AnimationInstance && component->m_AnimationIndex < id_list->m_LinearAnimations.Size()) - { - out_value.m_Variant = dmGameObject::PropertyVar(id_list->m_LinearAnimations[component->m_AnimationIndex]); - } + out_value.m_Variant = dmGameObject::PropertyVar((dmhash_t)component->m_Artboard); return dmGameObject::PROPERTY_RESULT_OK; } - else if (params.m_PropertyId == PROP_CURSOR) + else if (params.m_PropertyId == PROP_STATE_MACHINE) { - if (component->m_AnimationInstance) - { - const rive::LinearAnimation* animation = component->m_AnimationInstance->animation(); - float cursor_value = (component->m_AnimationInstance->time() - animation->startSeconds()) / animation->durationSeconds(); - out_value.m_Variant = dmGameObject::PropertyVar(cursor_value); - } + out_value.m_Variant = dmGameObject::PropertyVar((dmhash_t)component->m_StateMachine); return dmGameObject::PROPERTY_RESULT_OK; } else if (params.m_PropertyId == PROP_PLAYBACK_RATE) @@ -1213,21 +912,9 @@ namespace dmRive RiveSceneData* resource = GetRiveResource(component, component->m_Resource); return dmGameSystem::GetResourceProperty(context->m_Factory, resource, out_value); } - else - { - if (component->m_StateMachineInstance) - { - int index = FindStateMachineInputIndex(component, params.m_PropertyId); - if (index >= 0) - { - return GetStateMachineInput(component, index, params, out_value); - } - } - } int32_t value_index = 0; dmGameObject::GetPropertyOptionsIndex(params.m_Options, 0, &value_index); - return dmGameSystem::GetMaterialConstant(GetMaterial(component, component->m_Resource), params.m_PropertyId, value_index, out_value, false, CompRiveGetConstantCallback, component); } @@ -1235,21 +922,9 @@ namespace dmRive { RiveWorld* world = (RiveWorld*)params.m_World; RiveComponent* component = world->m_Components.Get(*params.m_UserData); - if (params.m_PropertyId == PROP_CURSOR) - { - if (params.m_Value.m_Type != dmGameObject::PROPERTY_TYPE_NUMBER) - return dmGameObject::PROPERTY_RESULT_TYPE_MISMATCH; - - if (component->m_AnimationInstance) - { - const rive::LinearAnimation* animation = component->m_AnimationInstance->animation(); - float cursor = params.m_Value.m_Number * animation->durationSeconds() + animation->startSeconds(); - component->m_AnimationInstance->time(cursor); - } + rive::rcp queue = dmRiveCommands::GetCommandQueue(); - return dmGameObject::PROPERTY_RESULT_OK; - } - else if (params.m_PropertyId == PROP_PLAYBACK_RATE) + if (params.m_PropertyId == PROP_PLAYBACK_RATE) { if (params.m_Value.m_Type != dmGameObject::PROPERTY_TYPE_NUMBER) return dmGameObject::PROPERTY_RESULT_TYPE_MISMATCH; @@ -1263,15 +938,6 @@ namespace dmRive dmGameObject::PropertyResult res = dmGameSystem::SetResourceProperty(context->m_Factory, params.m_Value, MATERIAL_EXT_HASH, (void**)&component->m_Material); component->m_ReHash |= res == dmGameObject::PROPERTY_RESULT_OK; return res; - } else { - if (component->m_StateMachineInstance) - { - int index = FindStateMachineInputIndex(component, params.m_PropertyId); - if (index >= 0) - { - return SetStateMachineInput(component, index, params); - } - } } int32_t value_index = 0; dmGameObject::GetPropertyOptionsIndex(params.m_Options, 0, &value_index); @@ -1303,6 +969,9 @@ namespace dmRive static dmGameObject::Result ComponentTypeCreate(const dmGameObject::ComponentTypeCreateCtx* ctx, dmGameObject::ComponentType* type) { CompRiveContext* rivectx = new CompRiveContext; + rivectx->m_RiveRenderContext = dmRiveCommands::GetDefoldRenderContext(); + assert(rivectx->m_RiveRenderContext != 0); + rivectx->m_Factory = ctx->m_Factory; rivectx->m_GraphicsContext = *(dmGraphics::HContext*)ctx->m_Contexts.Get(dmHashString64("graphics")); rivectx->m_RenderContext = *(dmRender::HRenderContext*)ctx->m_Contexts.Get(dmHashString64("render")); @@ -1340,6 +1009,7 @@ namespace dmRive ComponentTypeSetDestroyFn(type, CompRiveDestroy); ComponentTypeSetAddToUpdateFn(type, CompRiveAddToUpdate); ComponentTypeSetUpdateFn(type, CompRiveUpdate); + // ComponentTypeSetLateUpdateFn(type, CompRiveLateUpdate); ComponentTypeSetRenderFn(type, CompRiveRender); ComponentTypeSetOnMessageFn(type, CompRiveOnMessage); // ComponentTypeSetOnInputFn(type, CompRiveOnInput); @@ -1359,140 +1029,147 @@ namespace dmRive return dmGameObject::RESULT_OK; } - static void DeleteBones(RiveComponent* component) - { - dmGameObject::HInstance rive_instance = component->m_Instance; - dmGameObject::HCollection collection = dmGameObject::GetCollection(rive_instance); + // static void DeleteBones(RiveComponent* component) + // { + // dmLogOnceError("MAWE Missing implementation; %s", __FUNCTION__); + + // // dmGameObject::HInstance rive_instance = component->m_Instance; + // // dmGameObject::HCollection collection = dmGameObject::GetCollection(rive_instance); + + // // uint32_t num_bones = component->m_BoneGOs.Size(); + // // for (uint32_t i = 0; i < num_bones; ++i) + // // { + // // dmGameObject::HInstance bone_instance = component->m_BoneGOs[i]; + // // if (bone_instance) + // // { + // // dmGameObject::Delete(collection, bone_instance, false); + // // } + // // } + // // component->m_BoneGOs.SetSize(0); + // } - uint32_t num_bones = component->m_BoneGOs.Size(); - for (uint32_t i = 0; i < num_bones; ++i) - { - dmGameObject::HInstance bone_instance = component->m_BoneGOs[i]; - if (bone_instance) - { - dmGameObject::Delete(collection, bone_instance, false); - } - } - component->m_BoneGOs.SetSize(0); - } + // static void UpdateBones(RiveComponent* component) + // { + // dmLogOnceError("MAWE Missing implementation; %s", __FUNCTION__); + // // rive::Artboard* artboard = component->m_Artboard.get(); - static void UpdateBones(RiveComponent* component) - { - rive::Artboard* artboard = component->m_ArtboardInstance.get(); + // // rive::AABB bounds = artboard->bounds(); + // // float cx = (bounds.maxX - bounds.minX) * 0.5f; + // // float cy = (bounds.maxY - bounds.minY) * 0.5f; - rive::AABB bounds = artboard->bounds(); - float cx = (bounds.maxX - bounds.minX) * 0.5f; - float cy = (bounds.maxY - bounds.minY) * 0.5f; + // // uint32_t num_bones = component->m_BoneGOs.Size(); + // // DM_PROPERTY_ADD_U32(rmtp_RiveBones, num_bones); - uint32_t num_bones = component->m_BoneGOs.Size(); - DM_PROPERTY_ADD_U32(rmtp_RiveBones, num_bones); + // // dmVMath::Point3 go_pos = dmGameObject::GetPosition(component->m_Instance); - for (uint32_t i = 0; i < num_bones; ++i) - { - dmGameObject::HInstance bone_instance = component->m_BoneGOs[i]; - rive::Bone* bone = component->m_Bones[i]; + // // for (uint32_t i = 0; i < num_bones; ++i) + // // { + // // dmGameObject::HInstance bone_instance = component->m_BoneGOs[i]; + // // rive::Bone* bone = component->m_Bones[i]; - const rive::Mat2D& rt = bone->worldTransform(); + // // const rive::Mat2D& rt = bone->worldTransform(); - dmVMath::Vector4 x_axis(rt.xx(), rt.xy(), 0, 0); - dmVMath::Vector4 y_axis(rt.yx(), rt.yy(), 0, 0); + // // dmVMath::Vector4 x_axis(rt.xx(), rt.xy(), 0, 0); + // // dmVMath::Vector4 y_axis(rt.yx(), rt.yy(), 0, 0); - float scale_x = length(x_axis); - float scale_y = length(y_axis); - dmVMath::Vector3 scale(scale_x, scale_y, 1); + // // float scale_x = length(x_axis); + // // float scale_y = length(y_axis); + // // dmVMath::Vector3 scale(scale_x, scale_y, 1); - float angle = atan2f(x_axis.getY(), x_axis.getX()); - Quat rotation = Quat::rotationZ(-angle); + // // float angle = atan2f(x_axis.getY(), x_axis.getX()); + // // Quat rotation = Quat::rotationZ(-angle); - // Since the Rive space is different, we need to flip the y axis - dmVMath::Vector3 pos(rt.tx() - cx, -rt.ty() + cy, 0); + // // // Since the Rive space is different, we need to flip the y axis + // // dmVMath::Vector3 pos(rt.tx() - cx, -rt.ty() + cy, 0); - dmVMath::Matrix4 world_transform(rotation, pos); + // // dmVMath::Matrix4 world_transform(rotation, pos); - dmTransform::Transform transform = dmTransform::ToTransform(world_transform); + // // dmTransform::Transform transform = dmTransform::ToTransform(world_transform); - dmGameObject::SetPosition(bone_instance, Point3(transform.GetTranslation())); - dmGameObject::SetRotation(bone_instance, transform.GetRotation()); - dmGameObject::SetScale(bone_instance, transform.GetScale()); - } - } + // // dmGameObject::SetPosition(bone_instance, Point3(transform.GetTranslation())); + // // dmGameObject::SetRotation(bone_instance, transform.GetRotation()); + // // dmGameObject::SetScale(bone_instance, transform.GetScale()); + // // } + // } - static bool CreateBones(RiveWorld* world, RiveComponent* component) - { - dmGameObject::HInstance rive_instance = component->m_Instance; - dmGameObject::HCollection collection = dmGameObject::GetCollection(rive_instance); + // static bool CreateBones(RiveWorld* world, RiveComponent* component) + // { + // dmLogOnceError("MAWE Missing implementation; %s", __FUNCTION__); - uint32_t num_bones = component->m_Bones.Size(); + // // dmGameObject::HInstance rive_instance = component->m_Instance; + // // dmGameObject::HCollection collection = dmGameObject::GetCollection(rive_instance); - component->m_BoneGOs.SetCapacity(num_bones); - component->m_BoneGOs.SetSize(num_bones); + // // uint32_t num_bones = component->m_Bones.Size(); - for (uint32_t i = 0; i < num_bones; ++i) - { - dmGameObject::HInstance bone_instance = dmGameObject::New(collection, 0x0); - if (bone_instance == 0x0) { - DeleteBones(component); - return false; - } + // // component->m_BoneGOs.SetCapacity(num_bones); + // // component->m_BoneGOs.SetSize(num_bones); - component->m_BoneGOs[i] = bone_instance; + // // for (uint32_t i = 0; i < num_bones; ++i) + // // { + // // dmGameObject::HInstance bone_instance = dmGameObject::New(collection, 0x0); + // // if (bone_instance == 0x0) { + // // DeleteBones(component); + // // return false; + // // } - uint32_t index = dmGameObject::AcquireInstanceIndex(collection); - if (index == dmGameObject::INVALID_INSTANCE_POOL_INDEX) - { - DeleteBones(component); - return false; - } + // // component->m_BoneGOs[i] = bone_instance; - dmhash_t id = dmGameObject::CreateInstanceId(); - dmGameObject::AssignInstanceIndex(index, bone_instance); + // // uint32_t index = dmGameObject::AcquireInstanceIndex(collection); + // // if (index == dmGameObject::INVALID_INSTANCE_POOL_INDEX) + // // { + // // DeleteBones(component); + // // return false; + // // } - dmGameObject::Result result = dmGameObject::SetIdentifier(collection, bone_instance, id); - if (dmGameObject::RESULT_OK != result) - { - DeleteBones(component); - return false; - } + // // dmhash_t id = dmGameObject::CreateInstanceId(); + // // dmGameObject::AssignInstanceIndex(index, bone_instance); - dmGameObject::SetBone(bone_instance, true); + // // dmGameObject::Result result = dmGameObject::SetIdentifier(collection, bone_instance, id); + // // if (dmGameObject::RESULT_OK != result) + // // { + // // DeleteBones(component); + // // return false; + // // } - // Since we're given the "world" coordinates from the rive bones, - // we don't really need a full hierarchy. So we use the actual game object as parent - dmGameObject::SetParent(bone_instance, rive_instance); - } + // // dmGameObject::SetBone(bone_instance, true); - return true; - } + // // // Since we're given the "world" coordinates from the rive bones, + // // // we don't really need a full hierarchy. So we use the actual game object as parent + // // dmGameObject::SetParent(bone_instance, rive_instance); + // // } + + // return true; + // } // ****************************************************************************** // SCRIPTING HELPER FUNCTIONS // ****************************************************************************** - bool CompRiveGetBoneID(RiveComponent* component, dmhash_t bone_name, dmhash_t* id) - { - uint32_t num_bones = component->m_Bones.Size(); - - // We need the arrays to be matching 1:1 (for lookup using the same indices) - if (num_bones == 0 || component->m_BoneGOs.Size() != num_bones) { - return false; - } + // bool CompRiveGetBoneID(RiveComponent* component, dmhash_t bone_name, dmhash_t* id) + // { + // uint32_t num_bones = component->m_Bones.Size(); - for (uint32_t i = 0; i < num_bones; ++i) - { - rive::Bone* bone = component->m_Bones[i]; + // // We need the arrays to be matching 1:1 (for lookup using the same indices) + // if (num_bones == 0 || component->m_BoneGOs.Size() != num_bones) { + // return false; + // } - // Getting bones by name is rare, so I think it is ok to do the hash now, as opposed to have an array in each of the components. - dmhash_t name_hash = dmHashString64(bone->name().c_str()); - if (bone_name == name_hash) - { - dmGameObject::HInstance bone_instance = component->m_BoneGOs[i]; - *id = dmGameObject::GetIdentifier(bone_instance); - return true; - } - } + // for (uint32_t i = 0; i < num_bones; ++i) + // { + // rive::Bone* bone = component->m_Bones[i]; + + // // Getting bones by name is rare, so I think it is ok to do the hash now, as opposed to have an array in each of the components. + // dmhash_t name_hash = dmHashString64(bone->name().c_str()); + // if (bone_name == name_hash) + // { + // dmGameObject::HInstance bone_instance = component->m_BoneGOs[i]; + // *id = dmGameObject::GetIdentifier(bone_instance); + // return true; + // } + // } - return false; - } + // return false; + // } float CompRiveGetDisplayScaleFactor() { @@ -1511,47 +1188,41 @@ namespace dmRive float normalized_y = 1 - (y / g_OriginalWindowHeight); rive::Vec2D p_local = component->m_InverseRendererTransform * rive::Vec2D(normalized_x * window_width, normalized_y * window_height); + //rive::Vec2D p_local = rive::Vec2D(normalized_x * window_width, normalized_y * window_height); return p_local; } - static inline rive::TextValueRun* GetTextRun(rive::ArtboardInstance* artboard, const char* name, const char* nested_artboard_path) + static void FillPointerEvent(RiveComponent* component, rive::CommandQueue::PointerEvent& event, rive::Vec2D& pos) { - if (nested_artboard_path) - { - auto nested = artboard->nestedArtboardAtPath(nested_artboard_path); - auto nested_instance = nested ? nested->artboardInstance() : 0; + dmGraphics::HContext graphics_context = dmGraphics::GetInstalledContext(); + float window_width = (float) dmGraphics::GetWindowWidth(graphics_context); + float window_height = (float) dmGraphics::GetWindowHeight(graphics_context); - if (nested_instance) - { - return nested_instance->find(name); - } - } - else - { - return artboard->find(name); - } - return 0; - } + event.fit = component->m_Fit; + event.alignment = component->m_Alignment; + event.screenBounds.x = window_width; + event.screenBounds.y = window_height; + event.position = pos; + event.scaleFactor = CompRiveGetDisplayScaleFactor(); - bool CompRiveSetTextRun(RiveComponent* component, const char* name, const char* text_run, const char* nested_artboard_path) - { - rive::TextValueRun* text_run_value = GetTextRun(component->m_ArtboardInstance.get(), name, nested_artboard_path); - if (!text_run_value) - { - return false; - } - text_run_value->text(text_run); - return true; + //printf(" FillEvent: w/h: %f, %f fit: %d align: %f,%f\n", window_width, window_height, (int)event.fit, event.alignment.x(), event.alignment.y()); } - const char* CompRiveGetTextRun(RiveComponent* component, const char* name, const char* nested_artboard_path) + void CompRivePointerAction(RiveComponent* component, dmRive::PointerAction action, float x, float y) { - rive::TextValueRun* text_run_value = GetTextRun(component->m_ArtboardInstance.get(), name, nested_artboard_path); - if (!text_run_value) + + rive::rcp queue = dmRiveCommands::GetCommandQueue(); + rive::Vec2D p = WorldToLocal(component, x, y); + rive::CommandQueue::PointerEvent event; + FillPointerEvent(component, event, p); + + switch(action) { - return 0; + case dmRive::PointerAction::POINTER_MOVE: queue->pointerMove(component->m_StateMachine, event); break; + case dmRive::PointerAction::POINTER_UP: queue->pointerUp(component->m_StateMachine, event); break; + case dmRive::PointerAction::POINTER_DOWN: queue->pointerDown(component->m_StateMachine, event); break; + case dmRive::PointerAction::POINTER_EXIT: queue->pointerExit(component->m_StateMachine, event); break; } - return text_run_value->text().c_str(); } void CompRiveDebugSetBlitMode(bool value) diff --git a/defold-rive/src/comp_rive.h b/defold-rive/src/comp_rive.h index 813a805c..be14ef9e 100644 --- a/defold-rive/src/comp_rive.h +++ b/defold-rive/src/comp_rive.h @@ -23,6 +23,8 @@ #include "rive_ddf.h" +#include // handles + namespace dmRive { ///////////////////////////////////////////////////////////////////////////////////// @@ -61,25 +63,31 @@ namespace dmRive // Get the game object identifier bool CompRiveGetBoneID(RiveComponent* component, dmhash_t bone_name, dmhash_t* id); - bool CompRivePlayStateMachine(RiveComponent* component, dmRiveDDF::RivePlayAnimation* ddf, dmScript::LuaCallbackInfo* callback_info); - bool CompRivePlayAnimation(RiveComponent* component, dmRiveDDF::RivePlayAnimation* ddf, dmScript::LuaCallbackInfo* callback_info); - const char* CompRiveGetTextRun(RiveComponent* component, const char* name, const char* nested_artboard_path); bool CompRiveSetTextRun(RiveComponent* component, const char* name, const char* text_run, const char* nested_artboard_path); float CompRiveGetDisplayScaleFactor(); void CompRiveDebugSetBlitMode(bool value); - // state machine impl - StateMachineInputData::Result CompRiveGetStateMachineInput(RiveComponent* component, const char* input_name, const char* nested_artboard_path, StateMachineInputData& data); - StateMachineInputData::Result CompRiveSetStateMachineInput(RiveComponent* component, const char* input_name, const char* nested_artboard_path, const StateMachineInputData& data); - void CompRivePointerMove(RiveComponent* component, float x, float y); - void CompRivePointerUp(RiveComponent* component, float x, float y); - void CompRivePointerDown(RiveComponent* component, float x, float y); + // Scripting + rive::FileHandle CompRiveGetFile(RiveComponent* component); + rive::ArtboardHandle CompRiveSetArtboard(RiveComponent* component, const char* name); + rive::ArtboardHandle CompRiveGetArtboard(RiveComponent* component); + rive::StateMachineHandle CompRiveSetStateMachine(RiveComponent* component, const char* name); + rive::StateMachineHandle CompRiveGetStateMachine(RiveComponent* component); + + rive::ViewModelInstanceHandle CompRiveSetViewModelInstance(RiveComponent* component, const char* name); + rive::ViewModelInstanceHandle CompRiveGetViewModelInstance(RiveComponent* component); + + enum PointerAction + { + POINTER_MOVE, + POINTER_DOWN, + POINTER_UP, + POINTER_EXIT, + }; - // bool CompRiveSetIKTargetInstance(RiveComponent* component, dmhash_t constraint_id, float mix, dmhash_t instance_id); - // bool CompRiveSetIKTargetPosition(RiveComponent* component, dmhash_t constraint_id, float mix, Vectormath::Aos::Point3 position); - // bool CompRiveResetIKTarget(RiveComponent* component, dmhash_t constraint_id); + void CompRivePointerAction(RiveComponent* component, PointerAction cmd, float x, float y); } #endif // DM_GAMESYS_COMP_RIVE_H diff --git a/defold-rive/src/comp_rive_databinding.cpp b/defold-rive/src/comp_rive_databinding.cpp deleted file mode 100644 index 6f5b4552..00000000 --- a/defold-rive/src/comp_rive_databinding.cpp +++ /dev/null @@ -1,408 +0,0 @@ -// Copyright 2020 The Defold Foundation -// Licensed under the Defold License version 1.0 (the "License"); you may not use -// this file except in compliance with the License. -// -// You may obtain a copy of the License, together with FAQs at -// https://www.defold.com/license -// -// Unless required by applicable law or agreed to in writing, software distributed -// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR -// CONDITIONS OF ANY KIND, either express or implied. See the License for the -// specific language governing permissions and limitations under the License. - -#if !defined(DM_RIVE_UNSUPPORTED) - -#include - -#include "comp_rive.h" -#include "comp_rive_private.h" -#include "res_rive_data.h" -#include "res_rive_scene.h" -#include "res_rive_model.h" - -#include -#include -#include -#include -#include -#include - -//debug -#include -#include - -namespace dmRive -{ - -#define CHECK_VMIR(VMIR, HANDLE) \ - if (!(VMIR)) { \ - dmLogError("%s:%d: No viewmodel runtime instance with handle '%u'", __FUNCTION__, __LINE__, (HANDLE)); \ - return false; \ - } - -#define CHECK_PROP_RESULT(PROP, TYPE, PATH) \ - if (!(PROP)) { \ - dmLogError("%s:%d: No property of type '%s', with path '%s'", __FUNCTION__, __LINE__, (TYPE), (PATH)); \ - return false; \ - } - -static rive::ViewModelInstanceRuntime* FromHandle(RiveComponent* component, uint32_t handle) -{ - rive::ViewModelInstanceRuntime** pvmir = component->m_ViewModelInstanceRuntimes.Get(handle); - return pvmir ? *pvmir : 0; -} - -static rive::ViewModelRuntime* FindViewModelRuntimeByHash(RiveComponent* component, dmhash_t name_hash) -{ - dmRive::RiveSceneData* data = (dmRive::RiveSceneData*) component->m_Resource->m_Scene->m_Scene; - rive::File* file = data->m_File; - - size_t n = file->viewModelCount(); - for (size_t i = 0; i < n; ++i) - { - rive::ViewModelRuntime* vmr = file->viewModelByIndex(i); - dmhash_t hash = dmHashString64(vmr->name().c_str()); - if (hash == name_hash) - return vmr; - } - return 0; -} - -static rive::ViewModelInstanceRuntime* CreateViewModelInstanceRuntimeByHash(RiveComponent* component, dmhash_t name_hash) -{ - dmRive::RiveSceneData* data = component->m_Resource->m_Scene->m_Scene; - rive::File* file = data->m_File; - - if (name_hash != 0) - { - rive::ViewModelRuntime* vmr = FindViewModelRuntimeByHash(component, name_hash); - return vmr ? vmr->createInstance().release() : 0; - } - - // Create a default view model instance - if (!component->m_ArtboardInstance) - return 0; - - rive::ViewModelRuntime* vmr = file->defaultArtboardViewModel(component->m_ArtboardInstance.get()); - if (vmr) - { - return vmr->createDefaultInstance().release(); - } - return 0; -} - -void SetViewModelInstanceRuntime(RiveComponent* component, rive::ViewModelInstanceRuntime* vmir) -{ - if (component->m_ArtboardInstance) - { - component->m_ArtboardInstance->bindViewModelInstance(vmir->instance()); - } - - if (component->m_StateMachineInstance) - { - component->m_StateMachineInstance->bindViewModelInstance(vmir->instance()); - } -} - -void DebugModelViews(RiveComponent* component) -{ - dmRive::RiveSceneData* data = (dmRive::RiveSceneData*) component->m_Resource->m_Scene->m_Scene; - - size_t num_viewmodels = data->m_File->viewModelCount(); - for (size_t i = 0; i < num_viewmodels; ++i) - { - rive::ViewModelRuntime* vmr = data->m_File->viewModelByIndex(i); - - dmLogInfo("NAME: '%s'\n", vmr->name().c_str()); - - size_t num_properties = vmr->propertyCount(); - std::vector pdatas = vmr->properties(); - for (size_t j = 0; j < num_properties; ++j) - { - rive::PropertyData& property = pdatas[j]; - dmLogInfo(" DATA: %d '%s'\n", (int)property.type, property.name.c_str()); - } - - size_t num_instances = vmr->instanceCount(); - dmLogInfo(" #instances: %u\n", (uint32_t)num_instances); - std::vector names = vmr->instanceNames(); - for (size_t j = 0; j < names.size(); ++j) - { - dmLogInfo(" INST: '%s'\n", names[j].c_str()); - } - } -} - -void DebugVMIR(rive::ViewModelInstanceRuntime* vmir) -{ - printf("NAME: '%s'\n", vmir->name().c_str()); - - size_t num_properties = vmir->propertyCount(); - std::vector pdatas = vmir->properties(); - for (size_t j = 0; j < num_properties; ++j) - { - rive::PropertyData& property = pdatas[j]; - printf(" DATA: %d '%s'\n", (int)property.type, property.name.c_str()); - } - - printf(" instance: '%s' %p\n", vmir->instance()->name().c_str(), vmir->instance().get()); -} - -// Scripting api + helpers - -bool CompRiveSetViewModelInstanceRuntime(RiveComponent* component, uint32_t handle) -{ - rive::ViewModelInstanceRuntime* vmir = FromHandle(component, handle); - CHECK_VMIR(vmir, handle); - - dmRive::SetViewModelInstanceRuntime(component, vmir); - component->m_CurrentViewModelInstanceRuntime = handle; - return true; -} - -uint32_t CompRiveGetViewModelInstanceRuntime(RiveComponent* component) -{ - return component->m_CurrentViewModelInstanceRuntime; -} - -uint32_t CompRiveCreateViewModelInstanceRuntime(RiveComponent* component, dmhash_t name_hash) -{ - uint32_t handle = dmRive::INVALID_HANDLE; - rive::ViewModelInstanceRuntime* vmir = dmRive::CreateViewModelInstanceRuntimeByHash(component, name_hash); - if (vmir) - { - handle = component->m_HandleCounter++; - - if (component->m_ViewModelInstanceRuntimes.Full()) - component->m_ViewModelInstanceRuntimes.OffsetCapacity(4); - component->m_ViewModelInstanceRuntimes.Put(handle, vmir); - } - else - { - dmLogError("Failed to create ViewModelInstance of name '%s'", dmHashReverseSafe64(name_hash)); - } - - //dmRive::DebugModelViews(component); - return handle; -} - -bool CompRiveDestroyViewModelInstanceRuntime(RiveComponent* component, uint32_t handle) -{ - rive::ViewModelInstanceRuntime* vmir = FromHandle(component, handle); - CHECK_VMIR(vmir, handle); - - // According to the Rive team, there is no need to free the pointer, as it belongs to runtime - component->m_ViewModelInstanceRuntimes.Erase(handle); - return true; -} - -// ************************************************************************************************************** -// PROPERTIES - -static rive::DataType GetDataType(rive::ViewModelInstanceRuntime* vmir, const char* path) -{ - rive::ViewModelInstanceValueRuntime* prop = vmir->property(path); - if (prop) - return prop->dataType(); - return rive::DataType::none; -}; - -bool GetPropertyDataType(RiveComponent* component, uint32_t handle, const char* path, rive::DataType* type) -{ - rive::ViewModelInstanceRuntime* vmir = FromHandle(component, handle); - if (!vmir) - { - return false; - } - - *type = GetDataType(vmir, path); - return *type != rive::DataType::none; -} - -bool CompRiveRuntimeSetPropertyBool(RiveComponent* component, uint32_t handle, const char* path, bool value) -{ - rive::ViewModelInstanceRuntime* vmir = FromHandle(component, handle); - CHECK_VMIR(vmir, handle); - - rive::ViewModelInstanceBooleanRuntime* prop = vmir->propertyBoolean(path); - CHECK_PROP_RESULT(prop, "boolean", path); - prop->value(value); - return true; -} - -bool CompRiveRuntimeSetPropertyF32(RiveComponent* component, uint32_t handle, const char* path, float value) -{ - rive::ViewModelInstanceRuntime* vmir = FromHandle(component, handle); - CHECK_VMIR(vmir, handle); - - rive::ViewModelInstanceNumberRuntime* prop = vmir->propertyNumber(path); - CHECK_PROP_RESULT(prop, "number", path); - if (!prop) - { - dmLogError("No property of type number, with path '%s'", path); - return false; - } - - prop->value(value); - return true; -} - -bool CompRiveRuntimeSetPropertyColor(RiveComponent* component, uint32_t handle, const char* path, dmVMath::Vector4* color) -{ - rive::ViewModelInstanceRuntime* vmir = FromHandle(component, handle); - CHECK_VMIR(vmir, handle); - - rive::ViewModelInstanceColorRuntime* prop = vmir->propertyColor(path); - CHECK_PROP_RESULT(prop, "color", path); - - prop->argb(255 * color->getW(), 255 * color->getX(), 255 * color->getY(), 255 * color->getZ()); - return true; -} - -bool CompRiveRuntimeSetPropertyString(RiveComponent* component, uint32_t handle, const char* path, const char* value) -{ - rive::ViewModelInstanceRuntime* vmir = FromHandle(component, handle); - CHECK_VMIR(vmir, handle); - - rive::ViewModelInstanceStringRuntime* prop = vmir->propertyString(path); - CHECK_PROP_RESULT(prop, "string", path); - - prop->value(value); - return true; -} - -bool CompRiveRuntimeSetPropertyEnum(RiveComponent* component, uint32_t handle, const char* path, const char* value) -{ - rive::ViewModelInstanceRuntime* vmir = FromHandle(component, handle); - CHECK_VMIR(vmir, handle); - - rive::ViewModelInstanceEnumRuntime* prop = vmir->propertyEnum(path); - CHECK_PROP_RESULT(prop, "enum", path); - - prop->value(value); - return true; -} - -bool CompRiveRuntimeSetPropertyTrigger(RiveComponent* component, uint32_t handle, const char* path) -{ - rive::ViewModelInstanceRuntime* vmir = FromHandle(component, handle); - CHECK_VMIR(vmir, handle); - - rive::ViewModelInstanceTriggerRuntime* prop = vmir->propertyTrigger(path); - CHECK_PROP_RESULT(prop, "trigger", path); - - prop->trigger(); - return true; -} - -bool CompRiveRuntimeSetPropertyImage(RiveComponent* component, uint32_t handle, const char* path, rive::RenderImage* image) -{ - rive::ViewModelInstanceRuntime* vmir = FromHandle(component, handle); - CHECK_VMIR(vmir, handle); - - rive::ViewModelInstanceAssetImageRuntime* prop = vmir->propertyImage(path); - CHECK_PROP_RESULT(prop, "image", path); - - prop->value(image); - if (image != nullptr) - { - image->unref(); // Release ownership after rive took its ref - } - return true; -} - -bool CompRiveRuntimeGetPropertyBool(RiveComponent* component, uint32_t handle, const char* path, bool* value) -{ - rive::ViewModelInstanceRuntime* vmir = FromHandle(component, handle); - CHECK_VMIR(vmir, handle); - - rive::ViewModelInstanceBooleanRuntime* prop = vmir->propertyBoolean(path); - CHECK_PROP_RESULT(prop, "boolean", path); - *value = prop->value(); - return true; -} - -bool CompRiveRuntimeGetPropertyF32(RiveComponent* component, uint32_t handle, const char* path, float* value) -{ - rive::ViewModelInstanceRuntime* vmir = FromHandle(component, handle); - CHECK_VMIR(vmir, handle); - - rive::ViewModelInstanceNumberRuntime* prop = vmir->propertyNumber(path); - CHECK_PROP_RESULT(prop, "number", path); - *value = prop->value(); - return true; -} - -bool CompRiveRuntimeGetPropertyColor(RiveComponent* component, uint32_t handle, const char* path, dmVMath::Vector4* color) -{ - rive::ViewModelInstanceRuntime* vmir = FromHandle(component, handle); - CHECK_VMIR(vmir, handle); - - rive::ViewModelInstanceColorRuntime* prop = vmir->propertyColor(path); - CHECK_PROP_RESULT(prop, "color", path); - - uint32_t argb = (uint32_t)prop->value(); - float a = ((argb >> 24) & 0xFF) / 255.0f; - float r = ((argb >> 16) & 0xFF) / 255.0f; - float g = ((argb >> 8) & 0xFF) / 255.0f; - float b = ((argb >> 0) & 0xFF) / 255.0f; - *color = dmVMath::Vector4(r, g, b, a); - return true; -} - -bool CompRiveRuntimeGetPropertyString(RiveComponent* component, uint32_t handle, const char* path, const char** value) -{ - rive::ViewModelInstanceRuntime* vmir = FromHandle(component, handle); - CHECK_VMIR(vmir, handle); - - rive::ViewModelInstanceStringRuntime* prop = vmir->propertyString(path); - CHECK_PROP_RESULT(prop, "string", path); - - *value = prop->value().c_str(); // the temp string is not for storage - return true; -} - -bool CompRiveRuntimeGetPropertyEnum(RiveComponent* component, uint32_t handle, const char* path, const char** value) -{ - rive::ViewModelInstanceRuntime* vmir = FromHandle(component, handle); - CHECK_VMIR(vmir, handle); - - rive::ViewModelInstanceEnumRuntime* prop = vmir->propertyEnum(path); - CHECK_PROP_RESULT(prop, "enum", path); - - *value = prop->value().c_str(); // the temp string is not for storage - return true; -} - -// LISTS - -bool CompRiveRuntimeListAddInstance(RiveComponent* component, uint32_t handle, const char* path, uint32_t instance) -{ - rive::ViewModelInstanceRuntime* vmir = FromHandle(component, handle); - CHECK_VMIR(vmir, handle); - - rive::ViewModelInstanceListRuntime* prop = vmir->propertyList(path); - CHECK_PROP_RESULT(prop, "list", path); - - rive::ViewModelInstanceRuntime* instance_vmir = FromHandle(component, instance); - prop->addInstance(instance_vmir); - return true; -} - -bool CompRiveRuntimeListRemoveInstance(RiveComponent* component, uint32_t handle, const char* path, uint32_t instance) -{ - rive::ViewModelInstanceRuntime* vmir = FromHandle(component, handle); - CHECK_VMIR(vmir, handle); - - rive::ViewModelInstanceListRuntime* prop = vmir->propertyList(path); - CHECK_PROP_RESULT(prop, "list", path); - - rive::ViewModelInstanceRuntime* instance_vmir = FromHandle(component, instance); - prop->removeInstance(instance_vmir); - return true; -} - - -} // namespace - -#endif // DM_RIVE_UNSUPPORTED diff --git a/defold-rive/src/comp_rive_private.h b/defold-rive/src/comp_rive_private.h index b1364d4f..52a37bb8 100644 --- a/defold-rive/src/comp_rive_private.h +++ b/defold-rive/src/comp_rive_private.h @@ -27,20 +27,8 @@ #include "rive_ddf.h" #include - -namespace rive -{ - class Artboard; - class ArtboardInstance; - class Bone; - class LinearAnimationInstance; - class StateMachine; - class StateMachineInstance; - class ViewModelInstance; - class ViewModelInstanceRuntime; - class RenderImage; - enum class DataType : unsigned int; -} +#include +#include namespace dmGameObject { @@ -70,23 +58,21 @@ namespace dmRive dmMessage::URL m_Listener; dmGameSystem::HComponentRenderConstants m_RenderConstants; dmGameSystem::MaterialResource* m_Material; - dmScript::LuaCallbackInfo* m_Callback; - uint32_t m_CallbackId; rive::Mat2D m_InverseRendererTransform; - std::unique_ptr m_ArtboardInstance; - std::unique_ptr m_AnimationInstance; - std::unique_ptr m_StateMachineInstance; - dmHashTable32 m_ViewModelInstanceRuntimes; - - dmArray> m_AllSMSInstances; + rive::ArtboardHandle m_Artboard; + rive::StateMachineHandle m_StateMachine; + rive::ViewModelInstanceHandle m_ViewModelInstance; + rive::DrawKey m_DrawKey; dmGameObject::Playback m_AnimationPlayback; float m_AnimationPlaybackRate; - dmArray m_Bones; - dmArray m_BoneGOs; - dmArray m_StateMachineInputs; // A list of the hashed names for the state machine inputs. Index corresponds 1:1 to the statemachine inputs + // dmArray m_Bones; + // dmArray m_BoneGOs; + + rive::Fit m_Fit; + rive::Alignment m_Alignment; uint32_t m_VertexCount; uint32_t m_IndexCount; @@ -99,55 +85,14 @@ namespace dmRive uint8_t m_DoRender : 1; uint8_t m_AddedToUpdate : 1; uint8_t m_ReHash : 1; + uint8_t m_Fullscreen : 1; }; static const uint32_t INVALID_HANDLE = 0xFFFFFFFF; // Math - rive::Vec2D WorldToLocal(RiveComponent* component, float x, float y); - - // animations - RiveArtboardIdList* FindArtboardIdList(rive::Artboard* artboard, dmRive::RiveSceneData* data); - int FindAnimationIndex(dmhash_t* entries, uint32_t num_entries, dmhash_t anim_id); - - // State machine - rive::StateMachine* FindStateMachine(rive::Artboard* artboard, dmRive::RiveSceneData* data, int* state_machine_index, dmhash_t anim_id); - int FindStateMachineInputIndex(RiveComponent* component, dmhash_t property_name); - void GetStateMachineInputNames(rive::StateMachineInstance* smi, dmArray& names); - - dmGameObject::PropertyResult SetStateMachineInput(RiveComponent* component, int index, const dmGameObject::ComponentSetPropertyParams& params); - dmGameObject::PropertyResult GetStateMachineInput(RiveComponent* component, int index, - const dmGameObject::ComponentGetPropertyParams& params, dmGameObject::PropertyDesc& out_value); - - // Data bindings - void DebugModelViews(RiveComponent* component); - - // script api - uint32_t CompRiveCreateViewModelInstanceRuntime(RiveComponent* component, dmhash_t name_hash); - bool CompRiveDestroyViewModelInstanceRuntime(RiveComponent* component, uint32_t handle); - bool CompRiveSetViewModelInstanceRuntime(RiveComponent* component, uint32_t handle); - uint32_t CompRiveGetViewModelInstanceRuntime(RiveComponent* component); - - bool GetPropertyDataType(RiveComponent* component, uint32_t handle, const char* path, rive::DataType* type); - - bool CompRiveRuntimeSetPropertyBool(RiveComponent* component, uint32_t handle, const char* path, bool value); - bool CompRiveRuntimeSetPropertyF32(RiveComponent* component, uint32_t handle, const char* path, float value); - bool CompRiveRuntimeSetPropertyColor(RiveComponent* component, uint32_t handle, const char* path, dmVMath::Vector4* color); - bool CompRiveRuntimeSetPropertyString(RiveComponent* component, uint32_t handle, const char* path, const char* value); - bool CompRiveRuntimeSetPropertyEnum(RiveComponent* component, uint32_t handle, const char* path, const char* value); - bool CompRiveRuntimeSetPropertyTrigger(RiveComponent* component, uint32_t handle, const char* path); - bool CompRiveRuntimeSetPropertyImage(RiveComponent* component, uint32_t handle, const char* path, rive::RenderImage* image); - - bool CompRiveRuntimeGetPropertyBool(RiveComponent* component, uint32_t handle, const char* path, bool* value); - bool CompRiveRuntimeGetPropertyF32(RiveComponent* component, uint32_t handle, const char* path, float* value); - bool CompRiveRuntimeGetPropertyColor(RiveComponent* component, uint32_t handle, const char* path, dmVMath::Vector4* color); - bool CompRiveRuntimeGetPropertyString(RiveComponent* component, uint32_t handle, const char* path, const char** value); - bool CompRiveRuntimeGetPropertyEnum(RiveComponent* component, uint32_t handle, const char* path, const char** value); - - bool CompRiveRuntimeListAddInstance(RiveComponent* component, uint32_t handle, const char* path, uint32_t instance); - bool CompRiveRuntimeListRemoveInstance(RiveComponent* component, uint32_t handle, const char* path, uint32_t instance); - - RiveSceneData* CompRiveGetRiveSceneData(RiveComponent* component); + rive::Vec2D WorldToLocal(RiveComponent* component, float x, float y); + RiveSceneData* CompRiveGetRiveSceneData(RiveComponent* component); } #endif //DM_COMP_RIVE_PRIVATE_H diff --git a/defold-rive/src/comp_rive_statemachine.cpp b/defold-rive/src/comp_rive_statemachine.cpp deleted file mode 100644 index cf9974f9..00000000 --- a/defold-rive/src/comp_rive_statemachine.cpp +++ /dev/null @@ -1,288 +0,0 @@ -// Copyright 2020 The Defold Foundation -// Licensed under the Defold License version 1.0 (the "License"); you may not use -// this file except in compliance with the License. -// -// You may obtain a copy of the License, together with FAQs at -// https://www.defold.com/license -// -// Unless required by applicable law or agreed to in writing, software distributed -// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR -// CONDITIONS OF ANY KIND, either express or implied. See the License for the -// specific language governing permissions and limitations under the License. - -#if !defined(DM_RIVE_UNSUPPORTED) - -#include "comp_rive.h" -#include "comp_rive_private.h" -#include "res_rive_data.h" - -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -namespace dmRive -{ - rive::StateMachine* FindStateMachine(rive::Artboard* artboard, dmRive::RiveSceneData* data, int* state_machine_index, dmhash_t anim_id) - { - RiveArtboardIdList* id_list = FindArtboardIdList(artboard, data); - if (!id_list) - { - return 0; - } - - int index = FindAnimationIndex(id_list->m_StateMachines.Begin(), id_list->m_StateMachines.Size(), anim_id); - if (index == -1) { - return 0; - } - *state_machine_index = index; - return artboard->stateMachine(index); - } - - int FindStateMachineInputIndex(RiveComponent* component, dmhash_t property_name) - { - uint32_t count = component->m_StateMachineInputs.Size(); - for (uint32_t i = 0; i < count; ++i) - { - if (component->m_StateMachineInputs[i] == property_name) - { - return (int)i; - } - } - return -1; - } - - void GetStateMachineInputNames(rive::StateMachineInstance* smi, dmArray& names) - { - uint32_t count = smi->inputCount(); - if (count > names.Capacity()) - { - names.SetCapacity(count); - } - names.SetSize(count); - - for (uint32_t i = 0; i < count; ++i) - { - const rive::SMIInput* input = smi->input(i); - names[i] = dmHashString64(input->name().c_str()); - } - } - - dmGameObject::PropertyResult SetStateMachineInput(RiveComponent* component, int index, const dmGameObject::ComponentSetPropertyParams& params) - { - const rive::StateMachine* state_machine = component->m_StateMachineInstance->stateMachine(); - const rive::StateMachineInput* input = state_machine->input(index); - rive::SMIInput* input_instance = component->m_StateMachineInstance->input(index); - - if (input->is()) - { - if (params.m_Value.m_Type != dmGameObject::PROPERTY_TYPE_BOOLEAN) - { - dmLogError("Found property %s of type trigger, but didn't receive a boolean", input->name().c_str()); - return dmGameObject::PROPERTY_RESULT_TYPE_MISMATCH; - } - - // The trigger can only respond to the value "true" - if (!params.m_Value.m_Bool) - { - dmLogError("Found property %s of type trigger, but didn't receive a boolean of true", input->name().c_str()); - return dmGameObject::PROPERTY_RESULT_TYPE_MISMATCH; - } - - rive::SMITrigger* trigger = (rive::SMITrigger*)input_instance; - trigger->fire(); - } - else if (input->is()) - { - if (params.m_Value.m_Type != dmGameObject::PROPERTY_TYPE_BOOLEAN) - { - dmLogError("Found property %s of type bool, but didn't receive a boolean", input->name().c_str()); - return dmGameObject::PROPERTY_RESULT_TYPE_MISMATCH; - } - - rive::SMIBool* v = (rive::SMIBool*)input_instance; - v->value(params.m_Value.m_Bool); - } - else if (input->is()) - { - if (params.m_Value.m_Type != dmGameObject::PROPERTY_TYPE_NUMBER) - { - dmLogError("Found property %s of type number, but didn't receive a number", input->name().c_str()); - return dmGameObject::PROPERTY_RESULT_TYPE_MISMATCH; - } - - rive::SMINumber* v = (rive::SMINumber*)input_instance; - v->value(params.m_Value.m_Number); - } - - return dmGameObject::PROPERTY_RESULT_OK; - } - - dmGameObject::PropertyResult GetStateMachineInput(RiveComponent* component, int index, - const dmGameObject::ComponentGetPropertyParams& params, dmGameObject::PropertyDesc& out_value) - { - const rive::StateMachine* state_machine = component->m_StateMachineInstance->stateMachine(); - const rive::StateMachineInput* input = state_machine->input(index); - rive::SMIInput* input_instance = component->m_StateMachineInstance->input(index); - - if (input->is()) - { - dmLogError("Cannot get value of input type trigger ( %s )", input->name().c_str()); - return dmGameObject::PROPERTY_RESULT_TYPE_MISMATCH; - } - else if (input->is()) - { - rive::SMIBool* v = (rive::SMIBool*)input_instance; - out_value.m_Variant = dmGameObject::PropertyVar(v->value()); - } - else if (input->is()) - { - rive::SMINumber* v = (rive::SMINumber*)input_instance; - out_value.m_Variant = dmGameObject::PropertyVar(v->value()); - } - - return dmGameObject::PROPERTY_RESULT_OK; - } - - - StateMachineInputData::Result CompRiveSetStateMachineInput(RiveComponent* component, const char* input_name, const char* nested_artboard_path, const StateMachineInputData& value) - { - rive::ArtboardInstance* artboard = component->m_ArtboardInstance.get(); - rive::SMIInput* input_instance = 0x0; - - if (nested_artboard_path) - { - input_instance = artboard->input(input_name, nested_artboard_path); - } - else - { - dmhash_t input_hash = dmHashString64(input_name); - int index = FindStateMachineInputIndex(component, input_hash); - if (index >= 0) - { - input_instance = component->m_StateMachineInstance->input(index); - } - } - - if (input_instance) - { - const rive::StateMachineInput* input = input_instance->input(); - - if (input->is()) - { - if (value.m_Type != StateMachineInputData::TYPE_BOOL) - { - return StateMachineInputData::RESULT_TYPE_MISMATCH; - } - rive::SMITrigger* trigger = (rive::SMITrigger*)input_instance; - trigger->fire(); - return StateMachineInputData::RESULT_OK; - } - else if (input->is()) - { - if (value.m_Type != StateMachineInputData::TYPE_BOOL) - { - return StateMachineInputData::RESULT_TYPE_MISMATCH; - } - rive::SMIBool* v = (rive::SMIBool*)input_instance; - v->value(value.m_BoolValue); - return StateMachineInputData::RESULT_OK; - } - else if (input->is()) - { - if (value.m_Type != StateMachineInputData::TYPE_NUMBER) - { - return StateMachineInputData::RESULT_TYPE_MISMATCH; - } - rive::SMINumber* v = (rive::SMINumber*)input_instance; - v->value(value.m_NumberValue); - return StateMachineInputData::RESULT_OK; - } - } - - return StateMachineInputData::RESULT_NOT_FOUND; - } - - StateMachineInputData::Result CompRiveGetStateMachineInput(RiveComponent* component, const char* input_name, const char* nested_artboard_path, StateMachineInputData& out_value) - { - rive::ArtboardInstance* artboard = component->m_ArtboardInstance.get(); - rive::SMIInput* input_instance = 0x0; - - if (nested_artboard_path) - { - input_instance = artboard->input(input_name, nested_artboard_path); - } - else - { - dmhash_t input_hash = dmHashString64(input_name); - int index = FindStateMachineInputIndex(component, input_hash); - if (index >= 0) - { - input_instance = component->m_StateMachineInstance->input(index); - } - } - - out_value.m_Type = StateMachineInputData::TYPE_INVALID; - - if (input_instance) - { - const rive::StateMachineInput* input = input_instance->input(); - - if (input->is()) - { - return StateMachineInputData::RESULT_TYPE_UNSUPPORTED; - } - else if (input->is()) - { - rive::SMIBool* v = (rive::SMIBool*)input_instance; - out_value.m_Type = StateMachineInputData::TYPE_BOOL; - out_value.m_BoolValue = v->value(); - return StateMachineInputData::RESULT_OK; - } - else if (input->is()) - { - rive::SMINumber* v = (rive::SMINumber*)input_instance; - out_value.m_Type = StateMachineInputData::TYPE_NUMBER; - out_value.m_NumberValue = v->value(); - return StateMachineInputData::RESULT_OK; - } - } - - return StateMachineInputData::RESULT_NOT_FOUND; - } - - void CompRivePointerMove(RiveComponent* component, float x, float y) - { - if (component->m_StateMachineInstance) - { - rive::Vec2D p = WorldToLocal(component, x, y); - component->m_StateMachineInstance->pointerMove(p); - } - } - - void CompRivePointerUp(RiveComponent* component, float x, float y) - { - if (component->m_StateMachineInstance) - { - rive::Vec2D p = WorldToLocal(component, x, y); - component->m_StateMachineInstance->pointerUp(p); - } - } - - void CompRivePointerDown(RiveComponent* component, float x, float y) - { - if (component->m_StateMachineInstance) - { - rive::Vec2D p = WorldToLocal(component, x, y); - component->m_StateMachineInstance->pointerDown(p); - } - } -} - -#endif diff --git a/defold-rive/src/extension.cpp b/defold-rive/src/extension.cpp index 2d739571..a88d22f3 100644 --- a/defold-rive/src/extension.cpp +++ b/defold-rive/src/extension.cpp @@ -1,12 +1,30 @@ +// Copyright 2020-2025 The Defold Foundation +// Licensed under the Defold License version 1.0 (the "License"); you may not use +// this file except in compliance with the License. +// +// You may obtain a copy of the License, together with FAQs at +// https://www.defold.com/license +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. #if defined(DM_UNSUPPORTED_PLATFORM) #error "This platform is not supported" #endif #include +#include #include + #include "script_rive.h" #include +#include "defold/renderer.h" + +#include + +dmRive::HRenderContext g_RenderContext = 0; static dmExtension::Result AppInitializeRive(dmExtension::AppParams* params) { @@ -15,25 +33,38 @@ static dmExtension::Result AppInitializeRive(dmExtension::AppParams* params) static dmExtension::Result InitializeRive(dmExtension::Params* params) { -#if !defined(DM_RIVE_UNSUPPORTED) + g_RenderContext = dmRive::NewRenderContext(); + assert(g_RenderContext != 0); + + dmRiveCommands::InitParams cmd_params; + cmd_params.m_UseThreads = true; // TODO: Use define and/or config flag + cmd_params.m_RenderContext = g_RenderContext; + cmd_params.m_Factory = dmRive::GetRiveFactory(g_RenderContext); + dmRiveCommands::Initialize(&cmd_params); + + // relies on the command queue for registering listeners dmResource::HFactory factory = dmExtension::GetContextAsType(params, "factory"); dmRive::ScriptRegister(params->m_L, factory); + dmLogInfo("Registered Rive extension: %s %s\n", RIVE_RUNTIME_DATE, RIVE_RUNTIME_SHA1); -#endif return dmExtension::RESULT_OK; } -static dmExtension::Result AppFinalizeRive(dmExtension::AppParams* params) +static dmExtension::Result FinalizeRive(dmExtension::Params* params) { + dmResource::HFactory factory = dmExtension::GetContextAsType(params, "factory"); + dmRive::ScriptUnregister(params->m_L, factory); + + dmRiveCommands::Finalize(); + + dmRive::DeleteRenderContext(g_RenderContext); + g_RenderContext = 0; + return dmExtension::RESULT_OK; } -static dmExtension::Result FinalizeRive(dmExtension::Params* params) +static dmExtension::Result AppFinalizeRive(dmExtension::AppParams* params) { -#if !defined(DM_RIVE_UNSUPPORTED) - dmResource::HFactory factory = dmExtension::GetContextAsType(params, "factory"); - dmRive::ScriptUnregister(params->m_L, factory); -#endif return dmExtension::RESULT_OK; } diff --git a/defold-rive/src/res_rive_data.cpp b/defold-rive/src/res_rive_data.cpp index ec99e41d..8bad084c 100644 --- a/defold-rive/src/res_rive_data.cpp +++ b/defold-rive/src/res_rive_data.cpp @@ -14,122 +14,69 @@ #include #include +#include #include -// Rive includes -#include -#include -#include -#include -#include -#include -#include - // Extension includes #include "res_rive_data.h" -#include "script_rive.h" // to set the render context +#include "script_rive.h" #include "defold/renderer.h" #include #include #include +#include + +#include +#include namespace dmRive { - static void SetupData(RiveSceneData* scene_data, rive::File* file, const char* path, HRenderContext rive_render_context) + static rive::FileHandle LoadFile(rive::rcp queue, const char* path, const void* data, uint32_t data_size, RiveSceneData* scene_data) { - scene_data->m_File = file; - scene_data->m_RiveRenderContext = rive_render_context; - scene_data->m_ArtboardDefault = scene_data->m_File->artboardDefault(); - if (!scene_data->m_ArtboardDefault.get()) - { - return; - } - - size_t artboard_count = scene_data->m_File->artboardCount(); - - scene_data->m_ArtboardIdLists.SetCapacity(artboard_count); - scene_data->m_ArtboardIdLists.SetSize(artboard_count); - - for (int i = 0; i < artboard_count; ++i) - { - rive::Artboard* artboard = scene_data->m_File->artboard(i); + // RIVE: Currently their api doesn't support passing the bytes directly, but require you to make a copy of it. + const uint8_t* _data = (const uint8_t*)data; + std::vector rive_data(_data, _data + data_size); - RiveArtboardIdList* id_list = new RiveArtboardIdList(); - scene_data->m_ArtboardIdLists[i] = id_list; - - id_list->m_ArtboardNameHash = dmHashString64(artboard->name().c_str()); - - // Setup state machine ID lists - uint32_t state_machine_count = (uint32_t)artboard->stateMachineCount(); - if (state_machine_count) - { - id_list->m_StateMachines.SetCapacity(state_machine_count); - id_list->m_StateMachines.SetSize(state_machine_count); - - for (int j = 0; j < state_machine_count; ++j) - { - rive::StateMachine* state_machine = artboard->stateMachine(j); - id_list->m_StateMachines[j] = dmHashString64(state_machine->name().c_str()); - } - } - - // Setup animation ID lists - uint32_t animation_count = (uint32_t)artboard->animationCount(); - if (animation_count) - { - id_list->m_LinearAnimations.SetCapacity(animation_count); - id_list->m_LinearAnimations.SetSize(animation_count); + rive::FileHandle file = queue->loadFile(rive_data, 0, (uint64_t)(uintptr_t)scene_data); + return file; + } - for (int j = 0; j < animation_count; ++j) - { - rive::LinearAnimation* animation = artboard->animation(j); - id_list->m_LinearAnimations[j] = dmHashString64(animation->name().c_str()); - } - } - } + static void SetupData(RiveSceneData* scene_data, rive::FileHandle file, const char* path, HRenderContext rive_render_context) + { + scene_data->m_PathHash = dmHashString64(path); + scene_data->m_File = file; + scene_data->m_RiveRenderContext = rive_render_context; } static dmResource::Result ResourceType_RiveData_Create(const dmResource::ResourceCreateParams* params) { HRenderContext render_context_res = (HRenderContext) params->m_Context; - rive::Factory* rive_factory = GetRiveFactory(render_context_res); + rive::rcp queue = dmRiveCommands::GetCommandQueue(); + rive::Factory* rive_factory = dmRiveCommands::GetFactory(); assert(rive_factory); - rive::Span data((const uint8_t*)params->m_Buffer, params->m_BufferSize); - - // Creates DefoldRenderImage with a hashed name for each image resource - rive::rcp atlas_resolver(new AtlasNameResolver(params->m_Factory, render_context_res)); - - rive::ImportResult result; - rive::rcp file = rive::File::import(data, - rive_factory, - &result, - atlas_resolver); - - if (result != rive::ImportResult::success) + RiveSceneData* scene_data = new RiveSceneData(); + scene_data->m_File = LoadFile(queue, params->m_Filename, params->m_Buffer, params->m_BufferSize, scene_data); + if (!scene_data->m_File) { - dmResource::SetResource(params->m_Resource, 0); + dmLogError("Failed to load '%s'", params->m_Filename); + delete scene_data; return dmResource::RESULT_INVALID_DATA; } - RiveSceneData* scene_data = new RiveSceneData(); - scene_data->m_FileAssets.Swap(atlas_resolver->GetAssets()); - SetupData(scene_data, file.release(), params->m_Filename, render_context_res); + SetupData(scene_data, scene_data->m_File, params->m_Filename, render_context_res); dmResource::SetResource(params->m_Resource, scene_data); - dmResource::SetResourceSize(params->m_Resource, 0); + dmResource::SetResourceSize(params->m_Resource, params->m_BufferSize); return dmResource::RESULT_OK; } static void DeleteData(RiveSceneData* scene_data) { - for (int i = 0; i < scene_data->m_ArtboardIdLists.Size(); ++i) - { - delete scene_data->m_ArtboardIdLists[i]; - } - delete scene_data->m_File; + rive::rcp queue = dmRiveCommands::GetCommandQueue(); + queue->deleteFile(scene_data->m_File); delete scene_data; } @@ -143,47 +90,38 @@ namespace dmRive static dmResource::Result ResourceType_RiveData_Recreate(const dmResource::ResourceRecreateParams* params) { HRenderContext render_context_res = (HRenderContext) params->m_Context; - rive::Span data((uint8_t*)params->m_Buffer, params->m_BufferSize); - - rive::Factory* rive_factory = GetRiveFactory(render_context_res); - - rive::rcp atlas_resolver(new AtlasNameResolver(params->m_Factory, render_context_res)); + rive::rcp queue = dmRiveCommands::GetCommandQueue(); - rive::ImportResult result; - rive::rcp file = rive::File::import(data, - rive_factory, - &result, - atlas_resolver); - - if (result != rive::ImportResult::success) + RiveSceneData* scene_data = new RiveSceneData(); + scene_data->m_File = LoadFile(queue, params->m_Filename, params->m_Buffer, params->m_BufferSize, scene_data); + if (!scene_data->m_File) { - // If we cannot load the new file, let's keep the old one + dmLogError("Failed to load '%s'", params->m_Filename); + delete scene_data; return dmResource::RESULT_INVALID_DATA; } RiveSceneData* old_data = (RiveSceneData*)dmResource::GetResource(params->m_Resource); - if (old_data != 0) - { - dmResource::SetResource(params->m_Resource, 0); - DeleteData(old_data); - } + assert(old_data != 0); - RiveSceneData* scene_data = new RiveSceneData(); + // We don't want to delete the old resource, as the pointer may be "live" + rive::FileHandle tmp_handle = scene_data->m_File; + scene_data->m_File = old_data->m_File; + old_data->m_File = tmp_handle; - scene_data->m_FileAssets.Swap(atlas_resolver->GetAssets()); - SetupData(scene_data, file.release(), params->m_Filename, render_context_res); + DeleteData(scene_data); - dmResource::SetResource(params->m_Resource, scene_data); - dmResource::SetResourceSize(params->m_Resource, 0); + SetupData(old_data, old_data->m_File, params->m_Filename, render_context_res); + + dmResource::SetResourceSize(params->m_Resource, params->m_BufferSize); return dmResource::RESULT_OK; } static ResourceResult RegisterResourceType_RiveData(HResourceTypeContext ctx, HResourceType type) { - HRenderContext rive_render_context = NewRenderContext(); - - ScriptSetRenderContext(rive_render_context); + HRenderContext rive_render_context = dmRiveCommands::GetDefoldRenderContext(); + assert(rive_render_context != 0); return (ResourceResult)dmResource::SetupType(ctx, type, @@ -198,112 +136,8 @@ namespace dmRive static ResourceResult DeregisterResourceType_RiveData(HResourceTypeContext ctx, HResourceType type) { - HRenderContext context = (HRenderContext)ResourceTypeGetContext(type); - ScriptSetRenderContext(0); - DeleteRenderContext(context); return RESOURCE_RESULT_OK; } - - // Scripting functions - - static rive::FileAsset* FindAsset(RiveSceneData* resource, const char* asset_name) - { - for (uint32_t i = 0; i < resource->m_FileAssets.Size(); ++i) - { - rive::FileAsset* _asset = resource->m_FileAssets[i]; - const std::string& name = _asset->name(); - const char* name_str = name.c_str(); - if (strcmp(asset_name, name_str) == 0) - return _asset; - } - return 0; - } - - dmResource::Result ResRiveDataSetAssetFromMemory(RiveSceneData* resource, const char* asset_name, void* payload, uint32_t payload_size) - { - rive::FileAsset* _asset = FindAsset(resource, asset_name); - if (!_asset) - { - dmLogError("Rive scene doesn't have asset named '%s'", asset_name); - return dmResource::RESULT_INVALID_DATA; - } - - if (_asset->is()) - { - rive::ImageAsset* asset = _asset->as(); - rive::rcp image = dmRive::LoadImageFromMemory(resource->m_RiveRenderContext, payload, payload_size); - if (!image) - { - dmLogError("Failed to load asset '%s' from payload.", asset_name); - return dmResource::RESULT_INVALID_DATA; - } - - asset->renderImage(image); - return dmResource::RESULT_OK; - } - else if (_asset->is()) - { - rive::FontAsset* asset = _asset->as(); - rive::rcp font = dmRive::LoadFontFromMemory(resource->m_RiveRenderContext, payload, payload_size); - if (!font) - { - dmLogError("Failed to load font asset '%s' from payload.", asset_name); - return dmResource::RESULT_INVALID_DATA; - } - - asset->font(font); - return dmResource::RESULT_OK; - } - - dmLogError("We currently don't support swapping the asset type of '%s'", asset_name); - return dmResource::RESULT_NOT_SUPPORTED; - } - - dmResource::Result ResRiveDataSetAsset(dmResource::HFactory factory, RiveSceneData* resource, const char* asset_name, const char* path) - { - rive::FileAsset* _asset = FindAsset(resource, asset_name); - if (!_asset) - { - dmLogError("Rive scene doesn't have asset named '%s'", asset_name); - return dmResource::RESULT_INVALID_DATA; - } - - if (_asset->is()) - { - rive::ImageAsset* asset = _asset->as(); - rive::rcp image = dmRive::LoadImageFromFactory(factory, resource->m_RiveRenderContext, path, false); - if (!image) - { - dmLogError("Failed to load asset '%s' with path '%s'", asset_name, path); - return dmResource::RESULT_INVALID_DATA; - } - - asset->renderImage(image); - return dmResource::RESULT_OK; - } - else if (_asset->is()) - { - rive::FontAsset* asset = _asset->as(); - rive::rcp font = dmRive::LoadFontFromFactory(factory, resource->m_RiveRenderContext, path); - if (!font) - { - dmLogError("Failed to load font asset '%s' with path '%s'", asset_name, path); - return dmResource::RESULT_INVALID_DATA; - } - - asset->font(font); - return dmResource::RESULT_OK; - } - - dmLogError("We currently don't support swapping the asset type of '%s'", asset_name); - return dmResource::RESULT_NOT_SUPPORTED; - } - - rive::RenderImage* ResRiveDataCreateRenderImage(dmResource::HFactory factory, RiveSceneData* resource, uint8_t* data, uint32_t data_length) - { - rive::rcp image = dmRive::LoadImageFromMemory(resource->m_RiveRenderContext, data, data_length); - return image.release(); - } } diff --git a/defold-rive/src/res_rive_data.h b/defold-rive/src/res_rive_data.h index 0332f8b7..3b79ac85 100644 --- a/defold-rive/src/res_rive_data.h +++ b/defold-rive/src/res_rive_data.h @@ -14,40 +14,19 @@ #define DM_RES_RIVE_DATA_H #include -#include -#include #include "defold/renderer.h" -namespace rive -{ - class File; - class FileAsset; - class ArtboardInstance; -} +#include +#include namespace dmRive { - struct RiveBone; - - struct RiveArtboardIdList - { - dmhash_t m_ArtboardNameHash; - dmArray m_LinearAnimations; - dmArray m_StateMachines; - }; - struct RiveSceneData { - rive::File* m_File; - HRenderContext m_RiveRenderContext; - std::unique_ptr m_ArtboardDefault; - dmArray m_ArtboardIdLists; - dmArray m_FileAssets; // For runtime swapping + dmhash_t m_PathHash; + rive::FileHandle m_File; + HRenderContext m_RiveRenderContext; }; - - dmResource::Result ResRiveDataSetAssetFromMemory(RiveSceneData* resource, const char* asset_name, void* payload, uint32_t payload_size); - dmResource::Result ResRiveDataSetAsset(dmResource::HFactory factory, RiveSceneData* resource, const char* asset_name, const char* path); - rive::RenderImage* ResRiveDataCreateRenderImage(dmResource::HFactory factory, RiveSceneData* resource, uint8_t* data, uint32_t data_length); } #endif // DM_RES_RIVE_DATA_H diff --git a/defold-rive/src/script_defold.cpp b/defold-rive/src/script_defold.cpp new file mode 100644 index 00000000..16ab9520 --- /dev/null +++ b/defold-rive/src/script_defold.cpp @@ -0,0 +1,102 @@ +// Copyright 2020 The Defold Foundation +// Licensed under the Defold License version 1.0 (the "License"); you may not use +// this file except in compliance with the License. +// +// You may obtain a copy of the License, together with FAQs at +// https://www.defold.com/license +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + + +#include + +namespace dmRive +{ + +int PushFromMetaMethods(lua_State* L, int obj_index, int key_index) +{ + if (lua_getmetatable(L, obj_index)) + { + lua_getfield(L, -1, "__methods"); + lua_pushvalue(L, key_index); + lua_rawget(L, -2); + lua_remove(L, -2); // pop methods table + lua_remove(L, -2); // pop metatable + return 1; + } + // No metatable or no __methods — push nil + lua_pushnil(L); + return 1; +} + + +// Helper: set functions from a luaL_reg array onto table at top of stack +static void SetFuncs(lua_State* L, const luaL_reg* funcs) +{ + if (!funcs) return; + for (const luaL_reg* r = funcs; r->name; ++r) + { + lua_pushcfunction(L, r->func); + lua_setfield(L, -2, r->name); + } +} + +void RegisterUserType(lua_State* L, const char* class_name, const char* metatable_name, const luaL_reg methods[], const luaL_reg meta[]) +{ + // Methods table + lua_newtable(L); + int methods_idx = lua_gettop(L); + SetFuncs(L, methods); + + // Metatable + luaL_newmetatable(L, metatable_name); + int mt = lua_gettop(L); + // Store methods table on metatable for generic __index helpers + lua_pushvalue(L, methods_idx); + lua_setfield(L, mt, "__methods"); + + // Apply/override metamethods from provided list + SetFuncs(L, meta); + + // pop metatable + lua_pop(L, 1); + + // Pop methods table + lua_pop(L, 1); +} + +static void* luaL_testudata(lua_State* L, int idx, const char* tname) +{ + void* p = lua_touserdata(L, idx); + if (p == NULL) + { + return NULL; + } + if (!lua_getmetatable(L, idx)) + { + return NULL; + } + luaL_getmetatable(L, tname); + int equal = lua_rawequal(L, -1, -2); + lua_pop(L, 2); + if (!equal) + { + return NULL; + } + return p; +} + +void* ToUserType(lua_State* L, int idx, const char* metatable_name) +{ + return luaL_testudata(L, idx, metatable_name); +} + +void* CheckUserType(lua_State* L, int idx, const char* metatable_name) +{ + return luaL_checkudata(L, idx, metatable_name); +} + +} diff --git a/defold-rive/src/script_defold.h b/defold-rive/src/script_defold.h new file mode 100644 index 00000000..a57e5202 --- /dev/null +++ b/defold-rive/src/script_defold.h @@ -0,0 +1,32 @@ +// Copyright 2020 The Defold Foundation +// Licensed under the Defold License version 1.0 (the "License"); you may not use +// this file except in compliance with the License. +// +// You may obtain a copy of the License, together with FAQs at +// https://www.defold.com/license +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef DM_GAMESYS_SCRIPT_DEFOLD_H +#define DM_GAMESYS_SCRIPT_DEFOLD_H + +extern "C" { + #include + #include +} + +namespace dmRive +{ + +void RegisterUserType(lua_State* L, const char* class_name, const char* metatable_name, const luaL_reg methods[], const luaL_reg meta[]); +void* ToUserType(lua_State* L, int user_data_index, const char* metatable_name); +void* CheckUserType(lua_State* L, int user_data_index, const char* metatable_name); + +// Helper to call methods from within an __index function +int PushFromMetaMethods(lua_State* L, int obj_index, int key_index); +} + +#endif // DM_GAMESYS_SCRIPT_DEFOLD_H diff --git a/defold-rive/src/script_rive.cpp b/defold-rive/src/script_rive.cpp index 4f25bf4d..faff9b38 100644 --- a/defold-rive/src/script_rive.cpp +++ b/defold-rive/src/script_rive.cpp @@ -12,13 +12,15 @@ #if !defined(DM_RIVE_UNSUPPORTED) -#include -#include -#include -#include - -#include -#include +/*# Rive model API documentation + * + * Functions and messages for interacting with the 'Rive' + * animation system. + * + * @document + * @name Rive + * @namespace rive + */ #include #include @@ -29,599 +31,529 @@ #include #include -#include -#include - #include "comp_rive.h" #include "comp_rive_private.h" #include "rive_ddf.h" #include "res_rive_data.h" +#include "script_rive.h" +#include "script_rive_cmd.h" +#include "script_rive_listeners.h" +#include "script_rive_handles.h" + +#include namespace dmRive { - static const char* RIVE_EXT = "rivc"; - static const dmhash_t RIVE_EXT_HASH = dmHashString64(RIVE_EXT); - - static dmResource::HFactory g_Factory = 0; - static dmRive::HRenderContext g_RenderContext = 0; - static rive::rcp g_FallbackFont; - - /*# Rive model API documentation - * - * Functions and messages for interacting with the 'Rive' - * animation system. - * - * @document - * @name Rive - * @namespace rive - */ - - /*# play an animation on a rive model - * Plays a specified animation on a rive model component with specified playback - * mode and parameters. - */ - static int RiveComp_PlayAnim(lua_State* L) - { - DM_LUA_STACK_CHECK(L, 0); - int top = lua_gettop(L); - - RiveComponent* component = 0; - dmScript::GetComponentFromLua(L, 1, dmRive::RIVE_MODEL_EXT, 0, (void**)&component, 0); - - dmRiveDDF::RivePlayAnimation ddf; - ddf.m_AnimationId = dmScript::CheckHashOrString(L, 2);; - ddf.m_Playback = luaL_checkinteger(L, 3); - ddf.m_Offset = 0.0; - ddf.m_PlaybackRate = 1.0; - ddf.m_IsStateMachine = false; - - if (top > 3) // table with args - { - if (lua_istable(L, 4)) - { - luaL_checktype(L, 4, LUA_TTABLE); - lua_pushvalue(L, 4); - - lua_getfield(L, -1, "offset"); - ddf.m_Offset = lua_isnil(L, -1) ? 0.0 : luaL_checknumber(L, -1); - lua_pop(L, 1); - - lua_getfield(L, -1, "playback_rate"); - ddf.m_PlaybackRate = lua_isnil(L, -1) ? 1.0 : luaL_checknumber(L, -1); - lua_pop(L, 1); - - lua_pop(L, 1); - } - } - - dmScript::LuaCallbackInfo* cbk = 0x0; - if (top > 4) // completed cb - { - if (lua_isfunction(L, 5)) - { - cbk = dmScript::CreateCallback(L, 5); - } - } - - if (!CompRivePlayAnimation(component, &ddf, cbk)) - { - if (cbk) - { - dmScript::DestroyCallback(cbk); - } - } - - return 0; - } - - /*# play a state machine on a rive model - * Plays a specified state machine on a rive model component with specified playback - * mode and parameters. - */ - static int RiveComp_PlayStateMachine(lua_State* L) - { - DM_LUA_STACK_CHECK(L, 0); - int top = lua_gettop(L); - - RiveComponent* component = 0; - dmScript::GetComponentFromLua(L, 1, dmRive::RIVE_MODEL_EXT, 0, (void**)&component, 0); - - dmRiveDDF::RivePlayAnimation ddf; - ddf.m_AnimationId = dmScript::CheckHashOrString(L, 2); - ddf.m_Playback = 0; - ddf.m_Offset = 0.0; - ddf.m_PlaybackRate = 1.0; - ddf.m_IsStateMachine = true; - - if (top > 2) // table with args - { - if (lua_istable(L, 3)) - { - luaL_checktype(L, 3, LUA_TTABLE); - lua_pushvalue(L, 3); - - lua_getfield(L, -1, "offset"); - ddf.m_Offset = lua_isnil(L, -1) ? 0.0 : luaL_checknumber(L, -1); - lua_pop(L, 1); - - lua_getfield(L, -1, "playback_rate"); - ddf.m_PlaybackRate = lua_isnil(L, -1) ? 1.0 : luaL_checknumber(L, -1); - lua_pop(L, 1); - - lua_pop(L, 1); - } - } - - dmScript::LuaCallbackInfo* cbk = 0x0; - if (top > 3) // completed cb - { - if (lua_isfunction(L, 4)) - { - cbk = dmScript::CreateCallback(L, 4); - } - } - - if (!CompRivePlayStateMachine(component, &ddf, cbk)) - { - if (cbk) - { - dmScript::DestroyCallback(cbk); - } - } - - return 0; - } - - // Can cancel both a single animation and a state machine playing - static int RiveComp_Cancel(lua_State* L) - { - DM_LUA_STACK_CHECK(L, 0); - - dmGameObject::HInstance instance = dmScript::CheckGOInstance(L); - - dmMessage::URL receiver; - dmMessage::URL sender; - dmScript::ResolveURL(L, 1, &receiver, &sender); - - dmRiveDDF::RiveCancelAnimation msg; - dmMessage::Post(&sender, &receiver, dmRiveDDF::RiveCancelAnimation::m_DDFDescriptor->m_NameHash, (uintptr_t)instance, 0, (uintptr_t)dmRiveDDF::RiveCancelAnimation::m_DDFDescriptor, &msg, sizeof(msg), 0); - return 0; - } - - /*# retrieve the game object corresponding to a rive artboard skeleton bone - * Returns the id of the game object that corresponds to a specified skeleton bone. - * The returned game object can be used for parenting and transform queries. - * This function has complexity `O(n)`, where `n` is the number of bones in the rive model skeleton. - * Game objects corresponding to a rive model skeleton bone can not be individually deleted. - * - * @name rive.get_go - * @param url [type:string|hash|url] the rive model to query - * @param bone_id [type:string|hash] id of the corresponding bone - * @return id [type:hash] id of the game object - * @examples - * - * The following examples assumes that the rive model has id "rivemodel". - * - * How to parent the game object of the calling script to the "right_hand" bone of the rive model in a player game object: - * - * ```lua - * function init(self) - * local parent = rive.get_go("player#rivemodel", "right_hand") - * msg.post(".", "set_parent", {parent_id = parent}) - * end - * ``` - */ - static int RiveComp_GetGO(lua_State* L) - { - DM_LUA_STACK_CHECK(L, 1); - - RiveComponent* component = 0; - dmScript::GetComponentFromLua(L, 1, dmRive::RIVE_MODEL_EXT, 0, (void**)&component, 0); - - dmhash_t bone_name = dmScript::CheckHashOrString(L, 2); - - dmhash_t instance_id = 0; - if (!CompRiveGetBoneID(component, bone_name, &instance_id)) { - return DM_LUA_ERROR("the bone '%s' could not be found", dmHashReverseSafe64(bone_name)); - } - - dmScript::PushHash(L, instance_id); - return 1; - } - - static int RiveComp_PointerMove(lua_State* L) - { - DM_LUA_STACK_CHECK(L, 0); - - RiveComponent* component = 0; - dmScript::GetComponentFromLua(L, 1, dmRive::RIVE_MODEL_EXT, 0, (void**)&component, 0); - lua_Number x = luaL_checknumber(L, 2); - lua_Number y = luaL_checknumber(L, 3); - - CompRivePointerMove(component, x, y); - - return 0; - } +static const char* RIVE_EXT = "rivc"; +static const dmhash_t RIVE_EXT_HASH = dmHashString64(RIVE_EXT); - static int RiveComp_PointerUp(lua_State* L) - { - DM_LUA_STACK_CHECK(L, 0); +static dmResource::HFactory g_Factory = 0; - RiveComponent* component = 0; - dmScript::GetComponentFromLua(L, 1, dmRive::RIVE_MODEL_EXT, 0, (void**)&component, 0); - lua_Number x = luaL_checknumber(L, 2); - lua_Number y = luaL_checknumber(L, 3); +FileListener g_FileListener; +ArtboardListener g_ArtboardListener; +StateMachineListener g_StateMachineListener; +ViewModelInstanceListener g_ViewModelInstanceListener; +RenderImageListener g_RenderImageListener; +AudioSourceListener g_AudioSourceListener; +FontListener g_FontListener; - CompRivePointerUp(component, x, y); +static int Script_PointerAction(lua_State* L, dmRive::PointerAction action) +{ + DM_LUA_STACK_CHECK(L, 0); - return 0; - } + RiveComponent* component = 0; + dmScript::GetComponentFromLua(L, 1, dmRive::RIVE_MODEL_EXT, 0, (void**)&component, 0); + lua_Number x = luaL_checknumber(L, 2); + lua_Number y = luaL_checknumber(L, 3); - static int RiveComp_PointerDown(lua_State* L) - { - DM_LUA_STACK_CHECK(L, 0); - - RiveComponent* component = 0; - dmScript::GetComponentFromLua(L, 1, dmRive::RIVE_MODEL_EXT, 0, (void**)&component, 0); - lua_Number x = luaL_checknumber(L, 2); - lua_Number y = luaL_checknumber(L, 3); + CompRivePointerAction(component, action, x, y); + return 0; +} - CompRivePointerDown(component, x, y); +/** + * Lua wrapper for pointer movement. + * @name rive.pointer_move(component, x, y) + * @param url [type: url] Component receiving the pointer move. + * @param x [type: number] Pointer x coordinate in component space. + * @param y [type: number] Pointer y coordinate in component space. + */ +static int Script_PointerMove(lua_State* L) +{ + return Script_PointerAction(L, dmRive::PointerAction::POINTER_MOVE); +} - return 0; - } +/** + * Lua wrapper for pointer up events. + * @name rive.pointer_up(component, x, y) + * @param url [type: url] Component receiving the pointer release. + * @param x [type: number] Pointer x coordinate. + * @param y [type: number] Pointer y coordinate. + */ +static int Script_PointerUp(lua_State* L) +{ + return Script_PointerAction(L, dmRive::PointerAction::POINTER_UP); +} +/** + * Lua wrapper for pointer down events. + * @name rive.pointer_down(component, x, y) + * @param url [type: url] Component receiving the pointer press. + * @param x [type: number] Pointer x coordinate. + * @param y [type: number] Pointer y coordinate. + */ +static int Script_PointerDown(lua_State* L) +{ + return Script_PointerAction(L, dmRive::PointerAction::POINTER_DOWN); +} - static int RiveComp_GetTextRun(lua_State* L) - { - DM_LUA_STACK_CHECK(L, 1); +/** + * Lua wrapper for pointer exit events. + * @name rive.pointer_exit(component, x, y) + * @param url [type: url] Component receiving the pointer leave. + * @param x [type: number] Pointer x coordinate. + * @param y [type: number] Pointer y coordinate. + */ +static int Script_PointerExit(lua_State* L) +{ + return Script_PointerAction(L, dmRive::PointerAction::POINTER_EXIT); +} - RiveComponent* component = 0; - dmScript::GetComponentFromLua(L, 1, dmRive::RIVE_MODEL_EXT, 0, (void**)&component, 0); +/** + * Returns the projection matrix in render coordinates. + * @name rive.get_projection_matrix() + * @return matrix [type: vmath.matrix4] Current projection matrix for the window. + */ +static int Script_GetProjectionMatrix(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 1); - const char* name = luaL_checkstring(L, 2); - const char* nested_artboard_path = 0; + dmGraphics::HContext graphics_context = dmGraphics::GetInstalledContext(); - if (lua_isstring(L, 3)) - { - nested_artboard_path = lua_tostring(L, 3); - } + float scale_factor = CompRiveGetDisplayScaleFactor(); + float right = (float) dmGraphics::GetWindowWidth(graphics_context) / scale_factor; + float top = (float) dmGraphics::GetWindowHeight(graphics_context) / scale_factor; - const char* text_run = CompRiveGetTextRun(component, name, nested_artboard_path); + dmScript::PushMatrix4(L, dmVMath::Matrix4::orthographic(0, right, 0, top, -1, 1)); - if (!text_run) - { - return DM_LUA_ERROR("The text-run '%s' could not be found.", name); - } + return 1; +} - lua_pushstring(L, text_run); - return 1; - } +// This is an "all bets are off" mode. +static int Script_DebugSetBlitMode(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 0); - static int RiveComp_SetTextRun(lua_State* L) + if (!lua_isboolean(L, 1)) { - DM_LUA_STACK_CHECK(L, 0); - - RiveComponent* component = 0; - dmScript::GetComponentFromLua(L, 1, dmRive::RIVE_MODEL_EXT, 0, (void**)&component, 0); - - const char* name = luaL_checkstring(L, 2); - const char* text_run = luaL_checkstring(L, 3); - const char* nested_artboard_path = 0; - - if (lua_isstring(L, 4)) - { - nested_artboard_path = lua_tostring(L, 4); - } - - if (!CompRiveSetTextRun(component, name, text_run, nested_artboard_path)) - { - return DM_LUA_ERROR("The text-run '%s' could not be found.", name); - } - - return 0; + return DM_LUA_ERROR("The first argument must be a boolean."); } + bool value = lua_toboolean(L, 1); + CompRiveDebugSetBlitMode(value); + return 0; +} - static int RiveComp_GetProjectionMatrix(lua_State* L) - { - DM_LUA_STACK_CHECK(L, 1); - - dmGraphics::HContext graphics_context = dmGraphics::GetInstalledContext(); - - float scale_factor = CompRiveGetDisplayScaleFactor(); - float right = (float) dmGraphics::GetWindowWidth(graphics_context) / scale_factor; - float top = (float) dmGraphics::GetWindowHeight(graphics_context) / scale_factor; - - dmScript::PushMatrix4(L, dmVMath::Matrix4::orthographic(0, right, 0, top, -1, 1)); +// **************************************************************************** - return 1; - } +template +static int SetListenerCallback(lua_State* L, LISTENER* listener) +{ + if (listener->m_Callback) + dmScript::DestroyCallback(listener->m_Callback); + listener->m_Callback = 0; - static int RiveComp_SetStateMachineInput(lua_State* L) + if (lua_isnil(L, 1)) { - DM_LUA_STACK_CHECK(L, 0); - - RiveComponent* component = 0; - dmScript::GetComponentFromLua(L, 1, dmRive::RIVE_MODEL_EXT, 0, (void**)&component, 0); - - const char* input_name = luaL_checkstring(L, 2); - const char* nested_artboard_path = 0; // optional - - StateMachineInputData data = {}; - - if (lua_isnumber(L, 3)) - { - data.m_Type = StateMachineInputData::TYPE_NUMBER; - data.m_NumberValue = lua_tonumber(L,3); - } - else if (lua_isboolean(L,3)) - { - data.m_Type = StateMachineInputData::TYPE_BOOL; - data.m_BoolValue = lua_toboolean(L,3); - } - else - { - return DM_LUA_ERROR("Cannot set input '%s' with an unsupported type.", input_name); - } - - if (lua_isstring(L, 4)) - { - nested_artboard_path = lua_tostring(L, 4); - } - - StateMachineInputData::Result res = CompRiveSetStateMachineInput(component, input_name, nested_artboard_path, data); - if (res != StateMachineInputData::RESULT_OK) - { - if (res == StateMachineInputData::RESULT_TYPE_MISMATCH) - { - return DM_LUA_ERROR("Type mismatch for input '%s'.", input_name); - } - assert(res == StateMachineInputData::RESULT_NOT_FOUND); - return DM_LUA_ERROR("The input '%s' could not be found (or an unknown error happened).", input_name); - } return 0; } - static int RiveComp_GetStateMachineInput(lua_State* L) - { - DM_LUA_STACK_CHECK(L, 1); - - RiveComponent* component = 0; - dmScript::GetComponentFromLua(L, 1, dmRive::RIVE_MODEL_EXT, 0, (void**)&component, 0); - - const char* input_name = luaL_checkstring(L, 2); - const char* nested_artboard_path = 0; // optional - - if (lua_isstring(L, 3)) - { - nested_artboard_path = lua_tostring(L, 3); - } - - StateMachineInputData data; - StateMachineInputData::Result res = CompRiveGetStateMachineInput(component, input_name, nested_artboard_path, data); - - if (res != StateMachineInputData::RESULT_OK) - { - if (res == StateMachineInputData::RESULT_TYPE_UNSUPPORTED) - { - return DM_LUA_ERROR("The input '%s' has an unsupported type.", input_name); - } - assert(res == StateMachineInputData::RESULT_NOT_FOUND); - return DM_LUA_ERROR("The input '%s' could not be found (or an unknown error happened).", input_name); - } - - switch(data.m_Type) - { - case StateMachineInputData::TYPE_BOOL: - lua_pushboolean(L, data.m_BoolValue); - break; - case StateMachineInputData::TYPE_NUMBER: - lua_pushnumber(L, data.m_NumberValue); - break; - case StateMachineInputData::TYPE_INVALID: - break; - } - return 1; - } + listener->m_Callback = dmScript::CreateCallback(L, 1); - static const char* GetString(lua_State* L, const char* key, uint32_t* out_length) + if (!dmScript::IsCallbackValid(listener->m_Callback)) { - const char* result = 0; - lua_getfield(L, -1, key); - if (lua_isstring(L, -1)) - { - size_t len = 0; - result = lua_tolstring(L, -1, &len); - if (out_length) - *out_length = (uint32_t)len; - } - lua_pop(L, 1); - return result; + listener->m_Callback = 0; + return luaL_error(L, "Failed to create callback"); } + return 0; +} - static int RiveComp_RivSwapAsset(lua_State* L) - { - DM_LUA_STACK_CHECK(L, 0); - - dmhash_t rivc_path_hash = dmScript::CheckHashOrString(L, 1); // path to .rivc - const char* riv_asset_name = luaL_checkstring(L, 2); // Name of asset inside the .riv file - - const char* path = 0; - const char* payload = 0; - uint32_t payload_size = 0; - - luaL_checktype(L, 3, LUA_TTABLE); - lua_pushvalue(L, 3); - - path = GetString(L, "path", 0); - payload = GetString(L, "payload", &payload_size); - - lua_pop(L, 1); - - if (path == 0 && (payload == 0 || payload_size == 0)) - { - return DM_LUA_ERROR("You must specify either a path or a payload"); - } - - // Temporarily get a reference to the file - dmRive::RiveSceneData* resource; - dmResource::Result r = dmResource::Get(g_Factory, rivc_path_hash, (void**)&resource); - if (dmResource::RESULT_OK != r) - { - return DM_LUA_ERROR("Resource was not found: '%s'", dmHashReverseSafe64(rivc_path_hash)); - } - - if (payload) - r = dmRive::ResRiveDataSetAssetFromMemory(resource, riv_asset_name, (void*)payload, payload_size); - else - r = dmRive::ResRiveDataSetAsset(g_Factory, resource, riv_asset_name, path); - - if (dmResource::RESULT_OK != r) - { - if (dmResource::RESULT_NOT_SUPPORTED == r) - { - return DM_LUA_ERROR("Asset type not supported: '%s'", riv_asset_name); - } - - if (payload) - { - return DM_LUA_ERROR("Failed to load payload for asset: '%s'", riv_asset_name); - } - else - { - return DM_LUA_ERROR("Failed to load asset: '%s' with path: '%s'", riv_asset_name, path); - } - } - - dmResource::Release(g_Factory, resource); - return 0; - } - - static rive::rcp RiveFontFallback(const rive::Unichar missing, - const uint32_t fallbackIndex, - const rive::Font* font) - { - if (g_FallbackFont) - return g_FallbackFont; - return 0; - } - - static int RiveComp_SetFontFallbackPath(lua_State* L) - { - DM_LUA_STACK_CHECK(L, 0); - - const char* path = luaL_checkstring(L, 1); - const char* resource = 0; - uint32_t resource_size = 0; +/** + * Sets or clears the global file listener callback. + * @name rive.set_file_listener(callback) + * @param callback [type:function(self, event, data)|nil] Callback invoked for file system events; pass nil to disable. + * + * `self` + * : [type:object] The calling script instance. + * + * `event` + * : [type: string] One of: + * - `onFileLoaded` + * - `onFileDeleted` + * - `onFileError` + * - `onArtboardsListed` + * - `onViewModelsListed` + * - `onViewModelInstanceNamesListed` + * - `onViewModelPropertiesListed` + * - `onViewModelEnumsListed` + * + * `data` + * : [type:table] Additional fields vary by event. Common keys include: + * - `file`: [type:handle] File handle involved in the event. + * - `viewModelName`: [type:string] View model name for the request, when applicable. + * - `instanceNames`: [type:table] Array of instance name strings. + * - `artboardNames`: [type:table] Array of artboard name strings. + * - `properties`: [type:table] Array of property metadata tables. + * - `enums`: [type:table] Array of enum definitions. + * - `error`: [type:string] Error message when a failure occurs. + */ +static int Script_SetFileListener(lua_State* L) +{ + return SetListenerCallback(L, &g_FileListener); +} - dmResource::Result r = dmResource::GetRaw(g_Factory, path, (void**)&resource, &resource_size); - if (dmResource::RESULT_OK != r) - { - return DM_LUA_ERROR("Resource was not found: '%s'", path); - } +/** + * Sets or clears the artboard listener callback. + * @name rive.set_artboard_listener(callback) + * @param callback [type:function(self, event, data)|nil] Callback invoked for artboard-related events; pass nil to disable. + * + * `self` + * : [type:object] The calling script instance. + * + * `event` + * : [type: string] One of: + * - `onArtboardError` + * - `onDefaultViewModelInfoReceived` + * - `onArtboardDeleted` + * - `onStateMachinesListed` + * + * `data` + * : [type:table] Additional data per event, typically: + * - `artboard`: [type:handle] Artboard handle involved. + * - `viewModelName`: [type:string] View model name for defaults (received event). + * - `instanceName`: [type:string] Instance name for defaults. + * - `stateMachineNames`: [type:table] Array of state machine name strings. + * - `error`: [type:string] Error message when an error event fires. + */ +static int Script_SetArtboardListener(lua_State* L) +{ + return SetListenerCallback(L, &g_ArtboardListener); +} - rive::Factory* factory = GetRiveFactory(g_RenderContext); - rive::rcp font = dmRive::LoadFontFromMemory(factory, (void*)resource, resource_size); +/** + * Sets or clears the state machine listener callback. + * @name rive.set_state_machine_listener(callback) + * @param callback [type:function(self, event, data)|nil] Callback invoked for state machine events; pass nil to disable. + * + * `self` + * : [type:object] The calling script instance. + * + * `event` + * : [type: string] One of: + * - `onStateMachineError` + * - `onStateMachineDeleted` + * - `onStateMachineSettled` + * + * `data` + * : [type:table] Event-specific details: + * - `stateMachine`: [type:handle] Active state machine handle. + * - `error`: [type:string] Error message when an error occurs. + */ +static int Script_SetStateMachineListener(lua_State* L) +{ + return SetListenerCallback(L, &g_StateMachineListener); +} - free((void*)resource); +/** + * Sets or clears the view model instance listener callback. + * @name rive.set_view_model_instance_listener(callback) + * @param callback [type:function(self, event, data)|nil] Callback invoked for view model instance events; pass nil to disable. + * + * `self` + * : [type:object] The calling script instance. + * + * `event` + * : [type: string] One of: + * - `onViewModelInstanceError` + * - `onViewModelDeleted` + * - `onViewModelDataReceived` + * - `onViewModelListSizeReceived` + * + * `data` + * : [type:table] Additional payload per event: + * - `viewModel`: [type:handle] Handle of the affected view model instance. + * - `error`: [type:string] Error description when an error fires. + * - `path`: [type:string] Path being inspected when list size arrives. + * - `size`: [type:number] List size value for list-size events. + */ +static int Script_SetViewModelInstanceListener(lua_State* L) +{ + return SetListenerCallback(L, &g_ViewModelInstanceListener); +} - if (!font) - { - return DM_LUA_ERROR("Failed to load font '%s' of size %u bytes!", path, resource_size); - } +/** + * Sets or clears the render image listener callback. + * @name rive.set_render_image_listener(callback) + * @param callback [type:function(self, event, data)|nil] Callback invoked for render image events; pass nil to disable. + * + * `self` + * : [type:object] The calling script instance. + * + * `event` + * : [type: string] One of: + * - `onRenderImageDecoded` + * - `onRenderImageError` + * - `onRenderImageDeleted` + * + * `data` + * : [type:table] Additional fields: + * - `renderImage`: [type:handle] Handle of the render image. + * - `error`: [type:string] Error message for the failure event. + */ +static int Script_SetRenderImageListener(lua_State* L) +{ + return SetListenerCallback(L, &g_RenderImageListener); +} - g_FallbackFont = font; - return 0; - } +/** + * Sets or clears the audio source listener callback. + * @name rive.set_audio_source_listener(callback) + * @param callback [type:function(self, event, data)|nil] Callback invoked for audio source events; pass nil to disable. + * + * `self` + * : [type:object] The calling script instance. + * + * `event` + * : [type: string] One of: + * - `onAudioSourceDecoded` + * - `onAudioSourceError` + * - `onAudioSourceDeleted` + * + * `data` + * : [type:table] Additional fields: + * - `audioSource`: [type:handle] Audio source handle for the event. + * - `error`: [type:string] Error message when provided. + */ +static int Script_SetAudioSourceListener(lua_State* L) +{ + return SetListenerCallback(L, &g_AudioSourceListener); +} - static int RiveComp_SetFontFallbackMemory(lua_State* L) - { - DM_LUA_STACK_CHECK(L, 0); +/** + * Sets or clears the font listener callback. + * @name rive.set_font_listener(callback) + * @param callback [type:function(self, event, data)|nil] Callback invoked for font events; pass nil to disable. + * + * `self` + * : [type:object] The calling script instance. + * + * `event` + * : [type: string] One of: + * - `onFontDecoded` + * - `onFontError` + * - `onFontDeleted` + * + * `data` + * : [type:table] Additional fields: + * - `font`: [type:handle] Font handle for the associated event. + * - `error`: [type:string] Error message for failure events. + */ +static int Script_SetFontListener(lua_State* L) +{ + return SetListenerCallback(L, &g_FontListener); +} - size_t payload_size = 0; - const char* payload = luaL_checklstring(L, 1, &payload_size); +// **************************************************************************** - rive::Factory* factory = GetRiveFactory(g_RenderContext); - rive::rcp font = dmRive::LoadFontFromMemory(factory, (void*)payload, payload_size); +/** + * Returns the Rive file handle tied to the component. + * @name rive.get_file(component) + * @param url [type: url] Component whose file handle to query. + * @return file_handle [type: FileHandle] Handle identifying the loaded Rive file. + */ +static int Script_GetFile(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 1); + RiveComponent* component = 0; + dmScript::GetComponentFromLua(L, 1, dmRive::RIVE_MODEL_EXT, 0, (void**)&component, 0); + dmRive::PushFileHandle(L, CompRiveGetFile(component)); + return 1; +} - if (!font) - { - return DM_LUA_ERROR("Failed to load font of size %u bytes!", (uint32_t)payload_size); - } +/** + * Switches the active artboard for the component. + * @name rive.set_artboard(component, name) + * @param url [type: url] Component using the artboard. + * @param name [type: string|nil] Name of the artboard to create and set. Pass nil to create a default artboard. + * @return artboard [type: ArtboardHandle] Old artboard handle + */ +static int Script_SetArtboard(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 1); + RiveComponent* component = 0; + dmScript::GetComponentFromLua(L, 1, dmRive::RIVE_MODEL_EXT, 0, (void**)&component, 0); + const char* name; + if (lua_isnil(L, 2)) + name = 0; + else + name = luaL_checkstring(L, 2); + rive::ArtboardHandle old_handle = CompRiveSetArtboard(component, name); + dmRive::PushArtboardHandle(L, old_handle); + return 1; +} - g_FallbackFont = font; - return 0; - } +/** + * Queries the current artboard handle for the component. + * @name rive.get_artboard(component) + * @param url [type: url] Component whose artboard handle to return. + * @return artboard [type: ArtboardHandle] Active artboard handle. + */ +static int Script_GetArtboard(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 1); + RiveComponent* component = 0; + dmScript::GetComponentFromLua(L, 1, dmRive::RIVE_MODEL_EXT, 0, (void**)&component, 0); + dmRive::PushArtboardHandle(L, CompRiveGetArtboard(component)); + return 1; +} - static int RiveComp_ClearFontFallback(lua_State* L) - { - DM_LUA_STACK_CHECK(L, 0); - g_FallbackFont.reset(); - return 0; - } +/** + * Selects a state machine by name on the component. + * @name rive.set_state_machine(component, name) + * @param url [type: url] Component owning the state machine. + * @param name [type: string|nil] Name of the state machine to create and set. Pass nil to create a default state machine. + * @return state_machine [type: StateMachineHandle] Old state machine handle + */ +static int Script_SetStateMachine(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 1); + RiveComponent* component = 0; + dmScript::GetComponentFromLua(L, 1, dmRive::RIVE_MODEL_EXT, 0, (void**)&component, 0); + const char* name; + if (lua_isnil(L, 2)) + name = 0; + else + name = luaL_checkstring(L, 2); + rive::StateMachineHandle old_handle = CompRiveSetStateMachine(component, name); + dmRive::PushStateMachineHandle(L, old_handle); + return 1; +} +/** + * Returns the active state machine handle for the component. + * @name rive.get_state_machine(component) + * @param url [type: url] Component whose active state machine to query. + * @return state_machine [type: StateMachineHandle] Current state machine handle. + */ +static int Script_GetStateMachine(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 1); + RiveComponent* component = 0; + dmScript::GetComponentFromLua(L, 1, dmRive::RIVE_MODEL_EXT, 0, (void**)&component, 0); + dmRive::PushStateMachineHandle(L, CompRiveGetStateMachine(component)); + return 1; +} - // This is an "all bets are off" mode. - static int RiveComp_DebugSetBlitMode(lua_State* L) - { - DM_LUA_STACK_CHECK(L, 0); - - if (!lua_isboolean(L, 1)) - { - return DM_LUA_ERROR("The first argument must be a boolean."); - } - bool value = lua_toboolean(L, 1); - CompRiveDebugSetBlitMode(value); - return 0; - } +/** + * Selects a view model instance by name. + * @name rive.set_view_model_instance(component, name) + * @param url [type: url] Component owning the view model instance. + * @param name [type: string] View model instance name to activate. + * @return success [type: boolean] True if the view model instance was activated. + * @return view_model_instance_handle [type: ViewModelInstanceHandle] Handle to the old view model instance. + */ +static int Script_SetViewModelInstance(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 1); + RiveComponent* component = 0; + dmScript::GetComponentFromLua(L, 1, dmRive::RIVE_MODEL_EXT, 0, (void**)&component, 0); + const char* name = luaL_checkstring(L, 2); + rive::ViewModelInstanceHandle old_handle = CompRiveSetViewModelInstance(component, name); + dmRive::PushViewModelInstanceHandle(L, old_handle); + return 1; +} - static const luaL_reg RIVE_FUNCTIONS[] = - { - {"play_anim", RiveComp_PlayAnim}, - {"play_state_machine", RiveComp_PlayStateMachine}, - {"cancel", RiveComp_Cancel}, - {"get_go", RiveComp_GetGO}, - {"pointer_move", RiveComp_PointerMove}, - {"pointer_up", RiveComp_PointerUp}, - {"pointer_down", RiveComp_PointerDown}, - {"set_text_run", RiveComp_SetTextRun}, - {"get_text_run", RiveComp_GetTextRun}, - {"get_projection_matrix", RiveComp_GetProjectionMatrix}, - {"get_state_machine_input", RiveComp_GetStateMachineInput}, - {"set_state_machine_input", RiveComp_SetStateMachineInput}, - {"debug_set_blit_mode", RiveComp_DebugSetBlitMode}, - - {"riv_swap_asset", RiveComp_RivSwapAsset}, - {"set_font_fallback_path", RiveComp_SetFontFallbackPath}, - {"set_font_fallback_memory",RiveComp_SetFontFallbackMemory}, - {"clear_font_fallback", RiveComp_ClearFontFallback}, - {0, 0} - }; - - extern void ScriptInitializeDataBinding(lua_State* L, dmResource::HFactory factory); - - void ScriptRegister(lua_State* L, dmResource::HFactory factory) - { - luaL_register(L, "rive", RIVE_FUNCTIONS); - ScriptInitializeDataBinding(L, factory); - lua_pop(L, 1); +/** + * Returns the handle of the currently bound view model instance. + * @name rive.get_view_model_instance(component) + * @param url [type: url] Component whose view model instance handle to query. + * @return view_model_instance_handle [type: ViewModelInstanceHandle] Handle for the active view model instance. + */ +static int Script_GetViewModelInstance(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 1); + RiveComponent* component = 0; + dmScript::GetComponentFromLua(L, 1, dmRive::RIVE_MODEL_EXT, 0, (void**)&component, 0); + dmRive::PushViewModelInstanceHandle(L, CompRiveGetViewModelInstance(component)); + return 1; +} - g_Factory = factory; - rive::Font::gFallbackProc = RiveFontFallback; - } +static const luaL_reg RIVE_FUNCTIONS[] = +{ + {"pointer_move", Script_PointerMove}, + {"pointer_up", Script_PointerUp}, + {"pointer_down", Script_PointerDown}, + {"pointer_exit", Script_PointerExit}, + {"get_projection_matrix", Script_GetProjectionMatrix}, + + {"set_file_listener", Script_SetFileListener}, + {"set_artboard_listener", Script_SetArtboardListener}, + {"set_state_machine_listener", Script_SetStateMachineListener}, + {"set_view_model_instance_listener",Script_SetViewModelInstanceListener}, + {"set_render_image_listener", Script_SetRenderImageListener}, + {"set_audio_source_listener", Script_SetAudioSourceListener}, + {"set_font_listener", Script_SetFontListener}, + + {"get_file", Script_GetFile}, + {"set_artboard", Script_SetArtboard}, + {"get_artboard", Script_GetArtboard}, + {"set_state_machine", Script_SetStateMachine}, + {"get_state_machine", Script_GetStateMachine}, + {"set_view_model_instance", Script_SetViewModelInstance}, + {"get_view_model_instance", Script_GetViewModelInstance}, + + // debug + {"debug_set_blit_mode", Script_DebugSetBlitMode}, + + {0, 0} +}; + +void ScriptRegister(lua_State* L, dmResource::HFactory factory) +{ + // Setup the global listeners + rive::rcp queue = dmRiveCommands::GetCommandQueue(); + queue->setGlobalFileListener(&g_FileListener); + queue->setGlobalArtboardListener(&g_ArtboardListener); + queue->setGlobalStateMachineListener(&g_StateMachineListener); + queue->setGlobalViewModelInstanceListener(&g_ViewModelInstanceListener); + queue->setGlobalRenderImageListener(&g_RenderImageListener); + queue->setGlobalAudioSourceListener(&g_AudioSourceListener); + queue->setGlobalFontListener(&g_FontListener); + + dmRive::RegisterScriptRiveHandles(L); + + luaL_register(L, "rive", RIVE_FUNCTIONS); + ScriptCmdRegister(L, factory); + lua_pop(L, 1); + + g_Factory = factory; +} - void ScriptUnregister(lua_State* L, dmResource::HFactory factory) - { - rive::Font::gFallbackProc = 0; - g_FallbackFont.reset(); - } +void ScriptUnregister(lua_State* L, dmResource::HFactory factory) +{ + rive::rcp queue = dmRiveCommands::GetCommandQueue(); + queue->setGlobalFileListener(0); + queue->setGlobalArtboardListener(0); + queue->setGlobalStateMachineListener(0); + queue->setGlobalViewModelInstanceListener(0); + queue->setGlobalRenderImageListener(0); + queue->setGlobalAudioSourceListener(0); + queue->setGlobalFontListener(0); + + ScriptCmdUnregister(L, factory); + g_Factory = 0; +} - void ScriptSetRenderContext(dmRive::HRenderContext rive_render_context) - { - g_RenderContext = rive_render_context; - } } #endif // DM_RIVE_UNSUPPORTED diff --git a/defold-rive/src/script_rive.h b/defold-rive/src/script_rive.h index 19dc2014..3a7adf61 100644 --- a/defold-rive/src/script_rive.h +++ b/defold-rive/src/script_rive.h @@ -14,17 +14,11 @@ #define DM_GAMESYS_SCRIPT_RIVE_H #include -#include - -// Extension includes -#include namespace dmRive { void ScriptRegister(lua_State* L, dmResource::HFactory factory); void ScriptUnregister(lua_State* L, dmResource::HFactory factory); - - void ScriptSetRenderContext(dmRive::HRenderContext rive_render_context); } #endif // DM_GAMESYS_SCRIPT_RIVE_H diff --git a/defold-rive/src/script_rive_cmd.cpp b/defold-rive/src/script_rive_cmd.cpp new file mode 100644 index 00000000..6e9e93ce --- /dev/null +++ b/defold-rive/src/script_rive_cmd.cpp @@ -0,0 +1,1459 @@ +// Copyright 2021 The Defold Foundation +// Licensed under the Defold License version 1.0 (the "License"); you may not use +// this file except in compliance with the License. +// +// You may obtain a copy of the License, together with FAQs at +// https://www.defold.com/license +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#if !defined(DM_RIVE_UNSUPPORTED) + +#include + +#include +#include +#include + +#include + +#include "script_rive_handles.h" +#include "script_rive_listeners.h" +#include "viewmodel_instance_registry.h" + +#include + +namespace dmRive +{ + +static dmResource::HFactory g_ResourceFactory = 0; + +static bool CheckBoolean(lua_State* L, int index) +{ + if (lua_isboolean(L, index)) + { + return lua_toboolean(L, index); + } + return luaL_error(L, "Argument %d must be a boolean", index); +} + +static void CheckStringOrArtboard(lua_State* L, int index, const char** string, rive::ArtboardHandle* handle) +{ + if (lua_type(L, index) == LUA_TSTRING) + *string = luaL_checkstring(L, index); + else + *handle = CheckArtboardHandle(L, index); +} + +static void AdjustViewModelListSize(rive::ViewModelInstanceHandle handle, const char* path, int32_t delta) +{ + ViewModelInstanceListener* listener = GetViewModelInstanceListener(handle); + if (!listener) + { + return; + } + dmhash_t path_hash = dmHashString64(path); + listener->AdjustListSize(path_hash, delta); +} + +static void SetViewModelInstanceCachedValue(rive::ViewModelInstanceHandle handle, const char* path, const rive::CommandQueue::ViewModelInstanceData& data) +{ + ViewModelInstanceListener* listener = GetViewModelInstanceListener(handle); + if (!listener) + { + return; + } + dmhash_t path_hash = dmHashString64(path); + listener->SetPropertyValue(path_hash, data); +} + +// ************************************************************************************************* + +/** + * Deletes an instantiated artboard. + * @name cmd.deleteArtboard(artboard_handle) + * @param artboard_handle [type: ArtboardHandle] Handle to the artboard that should be removed. + */ +static int Script_deleteArtboard(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 0); + rive::ArtboardHandle handle = CheckArtboardHandle(L, 1); + rive::rcp queue = dmRiveCommands::GetCommandQueue(); + queue->deleteArtboard(handle); + return 0; +} + +/** + * Returns the artboard handle created for the named view model. + * @name cmd.instantiateArtboardNamed(file_handle, viewmodel_name) + * @param file_handle [type: FileHandle] Handle to a previously loaded Rive file. + * @param viewmodel_name [type: string] Name of the view model to instantiate. + * @return artboard_handle [type: ArtboardHandle] Artboard handle created for the named view model. + */ +static int Script_instantiateArtboardNamed(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 1); + rive::FileHandle file = CheckFileHandle(L, 1); + const char* viewmodel_name = luaL_checkstring(L, 2); + + rive::rcp queue = dmRiveCommands::GetCommandQueue(); + rive::ArtboardHandle handle = queue->instantiateArtboardNamed(file, viewmodel_name); + + dmRive::PushArtboardHandle(L, handle); + return 1; +} + +/** + * Returns the default artboard handle for the file. + * @name cmd.instantiateDefaultArtboard(file_handle) + * @param file_handle [type: FileHandle] Handle to a previously loaded Rive file. + * @return artboard_handle [type: ArtboardHandle] Default artboard handle for the file. + */ +static int Script_instantiateDefaultArtboard(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 1); + rive::FileHandle file = CheckFileHandle(L, 1); + + rive::rcp queue = dmRiveCommands::GetCommandQueue(); + rive::ArtboardHandle handle = queue->instantiateDefaultArtboard(file); + + PushArtboardHandle(L, handle); + return 1; +} + +// ******************************************************************************************************* + +/** + * Creates the default state machine for an artboard. + * @name cmd.instantiateDefaultStateMachine(artboard_handle) + * @param artboard_handle [type: ArtboardHandle] Artboard used as the source for the state machine. + * @return state_machine_handle [type: StateMachineHandle] Handle referencing the created state machine. + */ +static int Script_instantiateDefaultStateMachine(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 1); + rive::ArtboardHandle artboard = CheckArtboardHandle(L, 1); + + rive::rcp queue = dmRiveCommands::GetCommandQueue(); + rive::StateMachineHandle handle = queue->instantiateDefaultStateMachine(artboard); + PushStateMachineHandle(L, handle); + return 1; +} + +/** + * Creates a named state machine for the provided artboard. + * @name cmd.instantiateStateMachineNamed(artboard_handle, name) + * @param artboard_handle [type: ArtboardHandle] Artboard where the state machine resides. + * @param name [type: string] Name to assign to the new state machine. + * @return state_machine_handle [type: StateMachineHandle] Handle referencing the created state machine. + */ +static int Script_instantiateStateMachineNamed(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 1); + rive::ArtboardHandle artboard = CheckArtboardHandle(L, 1); + const char* name = luaL_checkstring(L, 2); + + rive::rcp queue = dmRiveCommands::GetCommandQueue(); + rive::StateMachineHandle handle = queue->instantiateStateMachineNamed(artboard, name); + PushStateMachineHandle(L, handle); + return 1; +} + +/** + * Advances the state machine by the requested delta time. + * @name cmd.advanceStateMachine(state_machine_handle, delta) + * @param state_machine_handle [type: StateMachineHandle] State machine to advance. + * @param delta [type: number] Time in seconds to advance the state machine. + */ +static int Script_advanceStateMachine(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 0); + rive::StateMachineHandle state_machine = CheckStateMachineHandle(L, 1); + float dt = (float)luaL_checknumber(L, 2); + + rive::rcp queue = dmRiveCommands::GetCommandQueue(); + queue->advanceStateMachine(state_machine, dt); + return 0; +} + +/** + * Deletes a created state machine. + * @name cmd.deleteStateMachine(state_machine_handle) + * @param state_machine_handle [type: StateMachineHandle] Handle to the state machine to delete. + */ +static int Script_deleteStateMachine(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 0); + rive::StateMachineHandle handle = CheckStateMachineHandle(L, 1); + rive::rcp queue = dmRiveCommands::GetCommandQueue(); + queue->deleteStateMachine(handle); + return 0; +} + +// ******************************************************************************************************* + +/** + * Returns a blank view model instance handle for the given artboard or view model. + * @name cmd.instantiateBlankViewModelInstance(file_handle, artboard_or_viewmodel) + * @param file_handle [type: FileHandle] Handle to a previously loaded Rive file. + * @param artboard_or_viewmodel [type: ArtboardHandle|string] Artboard handle or view model name that identifies where to instantiate. + * @return view_model_instance_handle [type: ViewModelInstanceHandle] Blank view model instance handle. + */ +static int Script_instantiateBlankViewModelInstance(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 1); + rive::FileHandle file = CheckFileHandle(L, 1); + + if (lua_isnil(L, 2) || lua_isnone(L, 2)) + { + return luaL_error(L, "Argument #2 must be an integer (artboard handle) or a string (view model name)"); + } + + rive::ArtboardHandle artboard = 0; + const char* viewmodel_name = 0; + CheckStringOrArtboard(L, 2, &viewmodel_name, &artboard); + + rive::rcp queue = dmRiveCommands::GetCommandQueue(); + rive::ViewModelInstanceHandle handle = 0; + ViewModelInstanceListener* listener = new ViewModelInstanceListener(); + listener->SetAutoDeleteOnViewModelDeleted(true); + if (viewmodel_name) + handle = queue->instantiateBlankViewModelInstance(file, viewmodel_name, listener); + else + handle = queue->instantiateBlankViewModelInstance(file, artboard, listener); + + if (!handle) + { + delete listener; + } + else + { + RegisterViewModelInstanceListener(handle, listener); + if (viewmodel_name) + RequestViewModelInstanceProperties(file, handle, viewmodel_name); + else + RequestDefaultViewModelInstanceProperties(file, artboard, handle); + } + + PushViewModelInstanceHandle(L, handle); + return 1; +} + +/** + * Returns a default-populated view model instance handle for the given artboard or view model. + * @name cmd.instantiateDefaultViewModelInstance(file_handle, artboard_or_viewmodel) + * @param file_handle [type: FileHandle] Handle to a previously loaded Rive file. + * @param artboard_or_viewmodel [type: ArtboardHandle|string] Artboard handle or view model name the instance should derive from. + * @return view_model_instance_handle [type: ViewModelInstanceHandle] Default view model instance handle. + */ +static int Script_instantiateDefaultViewModelInstance(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 1); + rive::FileHandle file = CheckFileHandle(L, 1); + + if (lua_isnil(L, 2) || lua_isnone(L, 2)) + { + return luaL_error(L, "Argument #2 must be an integer (artboard handle) or a string (view model name)"); + } + + rive::ArtboardHandle artboard = 0; + const char* viewmodel_name = 0; + CheckStringOrArtboard(L, 2, &viewmodel_name, &artboard); + + rive::rcp queue = dmRiveCommands::GetCommandQueue(); + rive::ViewModelInstanceHandle handle = 0; + ViewModelInstanceListener* listener = new ViewModelInstanceListener(); + listener->SetAutoDeleteOnViewModelDeleted(true); + if (viewmodel_name) + handle = queue->instantiateDefaultViewModelInstance(file, viewmodel_name, listener); + else + handle = queue->instantiateDefaultViewModelInstance(file, artboard, listener); + + if (!handle) + { + delete listener; + } + else + { + RegisterViewModelInstanceListener(handle, listener); + if (viewmodel_name) + RequestViewModelInstanceProperties(file, handle, viewmodel_name); + else + RequestDefaultViewModelInstanceProperties(file, artboard, handle); + } + + PushViewModelInstanceHandle(L, handle); + return 1; +} + +/** + * Creates a named view model instance and returns its handle. + * @name cmd.instantiateViewModelInstanceNamed(file_handle, artboard_or_viewmodel, instance_name) + * @param file_handle [type: FileHandle] Handle to a previously loaded Rive file. + * @param artboard_or_viewmodel [type: ArtboardHandle|string] Artboard handle or view model name that will host the instance. + * @param instance_name [type: string] Name to assign to the new view model instance. + * @return view_model_instance_handle [type: ViewModelInstanceHandle] Named view model instance handle. + */ +static int Script_instantiateViewModelInstanceNamed(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 1); + rive::FileHandle file = CheckFileHandle(L, 1); + + if (lua_isnil(L, 2) || lua_isnone(L, 2)) + { + return luaL_error(L, "Argument #2 must be an integer (artboard handle) or a string (view model name)"); + } + + rive::ArtboardHandle artboard = 0; + const char* viewmodel_name = 0; + CheckStringOrArtboard(L, 2, &viewmodel_name, &artboard); + + const char* view_model_instance_name = luaL_checkstring(L, 3); + + rive::rcp queue = dmRiveCommands::GetCommandQueue(); + rive::ViewModelInstanceHandle handle = 0; + ViewModelInstanceListener* listener = new ViewModelInstanceListener(); + listener->SetAutoDeleteOnViewModelDeleted(true); + if (viewmodel_name) + handle = queue->instantiateViewModelInstanceNamed(file, viewmodel_name, view_model_instance_name, listener); + else + handle = queue->instantiateViewModelInstanceNamed(file, artboard, view_model_instance_name, listener); + + if (!handle) + { + delete listener; + } + else + { + RegisterViewModelInstanceListener(handle, listener); + if (viewmodel_name) + RequestViewModelInstanceProperties(file, handle, viewmodel_name); + else + RequestDefaultViewModelInstanceProperties(file, artboard, handle); + } + + PushViewModelInstanceHandle(L, handle); + return 1; +} + +/** + * Returns a handle to the nested view model at the given path. + * @name cmd.referenceNestedViewModelInstance(view_model_handle, path) + * @param view_model_handle [type: ViewModelInstanceHandle] Parent view model instance handle. + * @param path [type: string] Dot-delimited path to the nested view model. + * @return view_model_handle [type: ViewModelInstanceHandle] Handle to the nested view model. + */ +static int Script_referenceNestedViewModelInstance(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 1); + rive::ViewModelInstanceHandle handle = CheckViewModelInstanceHandle(L, 1); + const char* path = luaL_checkstring(L, 2); + + rive::rcp queue = dmRiveCommands::GetCommandQueue(); + ViewModelInstanceListener* listener = new ViewModelInstanceListener(); + listener->SetAutoDeleteOnViewModelDeleted(true); + handle = queue->referenceNestedViewModelInstance(handle, path, listener); + if (!handle) + { + delete listener; + } + else + { + RegisterViewModelInstanceListener(handle, listener); + } + PushViewModelInstanceHandle(L, handle); + return 1; +} + +/** + * Returns the handle for the list entry at the specified path and index. + * @name cmd.referenceListViewModelInstance(view_model_handle, path, index) + * @param view_model_handle [type: ViewModelInstanceHandle] Parent view model instance handle. + * @param path [type: string] Dot-delimited path to the list view model. + * @param index [type: number] Index within the list entry to reference. + * @return view_model_handle [type: ViewModelInstanceHandle] Handle to the referenced list entry. + */ +static int Script_referenceListViewModelInstance(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 1); + rive::ViewModelInstanceHandle handle = CheckViewModelInstanceHandle(L, 1); + const char* path = luaL_checkstring(L, 2); + int index = luaL_checkinteger(L, 3); + + rive::rcp queue = dmRiveCommands::GetCommandQueue(); + ViewModelInstanceListener* listener = new ViewModelInstanceListener(); + listener->SetAutoDeleteOnViewModelDeleted(true); + handle = queue->referenceListViewModelInstance(handle, path, index, listener); + if (!handle) + { + delete listener; + } + else + { + RegisterViewModelInstanceListener(handle, listener); + } + PushViewModelInstanceHandle(L, handle); + return 1; +} + +/** + * Replaces the nested view model at the given path with the supplied handle. + * @name cmd.setViewModelInstanceNestedViewModel(view_model_handle, path, nested_handle) + * @param view_model_handle [type: ViewModelInstanceHandle] View model instance whose nested child is updated. + * @param path [type: string] Path to the nested view model. + * @param nested_handle [type: ViewModelInstanceHandle] Handle of the nested view model to attach. + */ +static int Script_setViewModelInstanceNestedViewModel(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 0); + rive::ViewModelInstanceHandle handle = CheckViewModelInstanceHandle(L, 1); + rive::ViewModelInstanceHandle value = CheckViewModelInstanceHandle(L, 3); + const char* path = luaL_checkstring(L, 2); + + rive::rcp queue = dmRiveCommands::GetCommandQueue(); + queue->setViewModelInstanceNestedViewModel(handle, path, value); + return 0; +} + +/** + * Inserts a nested view model into the list at the given index. + * @name cmd.insertViewModelInstanceListViewModel(view_model_handle, path, nested_handle, index) + * @param view_model_handle [type: ViewModelInstanceHandle] View model instance owning the list. + * @param path [type: string] Path to the target list. + * @param nested_handle [type: ViewModelInstanceHandle] Handle of the view model to insert. + * @param index [type: number] Destination index for the insertion. + */ +static int Script_insertViewModelInstanceListViewModel(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 0); + rive::ViewModelInstanceHandle handle = CheckViewModelInstanceHandle(L, 1); + rive::ViewModelInstanceHandle value = CheckViewModelInstanceHandle(L, 3); + const char* path = luaL_checkstring(L, 2); + int index = luaL_checkinteger(L, 4); + + rive::rcp queue = dmRiveCommands::GetCommandQueue(); + queue->insertViewModelInstanceListViewModel(handle, path, value, index); + AdjustViewModelListSize(handle, path, 1); + return 0; +} + +/** + * Appends a nested view model to the list at the specified path. + * @name cmd.appendViewModelInstanceListViewModel(view_model_handle, path, nested_handle) + * @param view_model_handle [type: ViewModelInstanceHandle] View model instance owning the list. + * @param path [type: string] Path to the target list. + * @param nested_handle [type: ViewModelInstanceHandle] Handle of the view model to append. + */ +static int Script_appendViewModelInstanceListViewModel(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 0); + rive::ViewModelInstanceHandle handle = CheckViewModelInstanceHandle(L, 1); + rive::ViewModelInstanceHandle value = CheckViewModelInstanceHandle(L, 3); + const char* path = luaL_checkstring(L, 2); + + rive::rcp queue = dmRiveCommands::GetCommandQueue(); + queue->appendViewModelInstanceListViewModel(handle, path, value); + AdjustViewModelListSize(handle, path, 1); + return 0; +} + +/** + * Removes the entry at the supplied index from the nested list. + * @name cmd.removeViewModelInstanceListViewModelIndex(view_model_handle, path, index) + * @param view_model_handle [type: ViewModelInstanceHandle] View model instance owning the list. + * @param path [type: string] Path to the target list. + * @param index [type: number] Index of the entry to remove. + */ +static int Script_removeViewModelInstanceListViewModelIndex(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 0); + rive::ViewModelInstanceHandle handle = CheckViewModelInstanceHandle(L, 1); + const char* path = luaL_checkstring(L, 2); + int index = luaL_checkinteger(L, 3); + + rive::rcp queue = dmRiveCommands::GetCommandQueue(); + queue->removeViewModelInstanceListViewModel(handle, path, index, RIVE_NULL_HANDLE); + AdjustViewModelListSize(handle, path, -1); + return 0; +} + +/** + * Removes the specified nested view model from the list at the path. + * @name cmd.removeViewModelInstanceListViewModel(view_model_handle, path, nested_handle) + * @param view_model_handle [type: ViewModelInstanceHandle] View model instance owning the list. + * @param path [type: string] Path to the target list. + * @param nested_handle [type: ViewModelInstanceHandle] Handle of the view model to remove. + */ +static int Script_removeViewModelInstanceListViewModel(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 0); + rive::ViewModelInstanceHandle handle = CheckViewModelInstanceHandle(L, 1); + rive::ViewModelInstanceHandle value = CheckViewModelInstanceHandle(L, 3); + const char* path = luaL_checkstring(L, 2); + + rive::rcp queue = dmRiveCommands::GetCommandQueue(); + queue->removeViewModelInstanceListViewModel(handle, path, value); + AdjustViewModelListSize(handle, path, -1); + return 0; +} + +/** + * Deletes the view model instance handle. + * @name cmd.deleteViewModelInstance(view_model_handle) + * @param view_model_handle [type: ViewModelInstanceHandle] View model instance to delete. + */ +static int Script_deleteViewModelInstance(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 0); + rive::ViewModelInstanceHandle handle = CheckViewModelInstanceHandle(L, 1); + + rive::rcp queue = dmRiveCommands::GetCommandQueue(); + queue->deleteViewModelInstance(handle); + return 0; +} + +/** + * Swaps two entries in the nested list. + * @name cmd.swapViewModelInstanceListValues(view_model_handle, path, indexa, indexb) + * @param view_model_handle [type: ViewModelInstanceHandle] View model instance that owns the list. + * @param path [type: string] Path to the nested list. + * @param indexa [type: number] First index to swap. + * @param indexb [type: number] Second index to swap. + */ +static int Script_swapViewModelInstanceListValues(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 0); + rive::ViewModelInstanceHandle handle = CheckViewModelInstanceHandle(L, 1); + const char* path = luaL_checkstring(L, 2); + int indexa = luaL_checkinteger(L, 3); + int indexb = luaL_checkinteger(L, 4); + + rive::rcp queue = dmRiveCommands::GetCommandQueue(); + queue->swapViewModelInstanceListValues(handle, path, indexa, indexb); + return 0; +} + +// ***************************************************************************************** + + +/** + * Binds the state machine to the provided view model instance. + * @name cmd.bindViewModelInstance(state_machine_handle, view_model_handle) + * @param state_machine_handle [type: StateMachineHandle] State machine handle. + * @param view_model_handle [type: ViewModelInstanceHandle] View model instance handle. + */ +static int Script_bindViewModelInstance(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 0); + rive::StateMachineHandle state_machine = CheckStateMachineHandle(L, 1); + rive::ViewModelInstanceHandle view_model = CheckViewModelInstanceHandle(L, 2); + + rive::rcp queue = dmRiveCommands::GetCommandQueue(); + queue->bindViewModelInstance(state_machine, view_model); + return 0; +} + + +// ***************************************************************************************** + + +/** + * Fires a trigger on the view model instance. + * @name cmd.fireViewModelTrigger(view_model_handle, trigger_path) + * @param view_model_handle [type: ViewModelInstanceHandle] View model instance handle. + * @param trigger_path [type: string] Trigger path to activate. + */ +static int Script_fireViewModelTrigger(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 0); + rive::ViewModelInstanceHandle handle = CheckViewModelInstanceHandle(L, 1); + const char* path = luaL_checkstring(L, 2); + + rive::rcp queue = dmRiveCommands::GetCommandQueue(); + queue->fireViewModelTrigger(handle, path); + return 0; +} + +/** + * Updates the boolean property at the path. + * @name cmd.setViewModelInstanceBool(view_model_handle, path, value) + * @param view_model_handle [type: ViewModelInstanceHandle] View model instance handle. + * @param path [type: string] Path to the boolean property. + * @param value [type: boolean] New boolean value. + */ +static int Script_setViewModelInstanceBool(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 0); + rive::ViewModelInstanceHandle handle = CheckViewModelInstanceHandle(L, 1); + bool value = CheckBoolean(L, 3); + const char* path = luaL_checkstring(L, 2); + + rive::rcp queue = dmRiveCommands::GetCommandQueue(); + queue->setViewModelInstanceBool(handle, path, value); + rive::CommandQueue::ViewModelInstanceData data; + data.metaData.name = path; + data.metaData.type = rive::DataType::boolean; + data.stringValue.clear(); + data.boolValue = value; + SetViewModelInstanceCachedValue(handle, path, data); + return 0; +} + +/** + * Updates the numeric property at the path. + * @name cmd.setViewModelInstanceNumber(view_model_handle, path, value) + * @param view_model_handle [type: ViewModelInstanceHandle] View model instance handle. + * @param path [type: string] Path to the numeric property. + * @param value [type: number] New numeric value. + */ +static int Script_setViewModelInstanceNumber(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 0); + rive::ViewModelInstanceHandle handle = CheckViewModelInstanceHandle(L, 1); + lua_Number value = luaL_checknumber(L, 3); + const char* path = luaL_checkstring(L, 2); + + rive::rcp queue = dmRiveCommands::GetCommandQueue(); + queue->setViewModelInstanceNumber(handle, path, value); + rive::CommandQueue::ViewModelInstanceData data; + data.metaData.name = path; + data.metaData.type = rive::DataType::number; + data.stringValue.clear(); + data.numberValue = (float)value; + SetViewModelInstanceCachedValue(handle, path, data); + return 0; +} + +/** + * Updates the color property using the supplied vector. + * @name cmd.setViewModelInstanceColor(view_model_handle, path, vector4_color) + * @param view_model_handle [type: ViewModelInstanceHandle] View model instance handle. + * @param path [type: string] Path to the color property. + * @param vector4_color [type: vector4] Color encoded as a Defold Vector4 (WXYZ). + */ +static int Script_setViewModelInstanceColor(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 0); + rive::ViewModelInstanceHandle handle = CheckViewModelInstanceHandle(L, 1); + const char* path = luaL_checkstring(L, 2); + dmVMath::Vector4* color = dmScript::CheckVector4(L, 3); + rive::ColorInt value = rive::colorARGB(255 * color->getW(), 255 * color->getX(), 255 * color->getY(), 255 * color->getZ()); + + rive::rcp queue = dmRiveCommands::GetCommandQueue(); + queue->setViewModelInstanceColor(handle, path, value); + rive::CommandQueue::ViewModelInstanceData data; + data.metaData.name = path; + data.metaData.type = rive::DataType::color; + data.stringValue.clear(); + data.colorValue = value; + SetViewModelInstanceCachedValue(handle, path, data); + return 0; +} + +/** + * Updates the enum property at the path. + * @name cmd.setViewModelInstanceEnum(view_model_handle, path, enum_string) + * @param view_model_handle [type: ViewModelInstanceHandle] View model instance handle. + * @param path [type: string] Path to the enum property. + * @param enum_string [type: string] Enum name to select. + */ +static int Script_setViewModelInstanceEnum(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 0); + rive::ViewModelInstanceHandle handle = CheckViewModelInstanceHandle(L, 1); + const char* path = luaL_checkstring(L, 2); + const char* value = luaL_checkstring(L, 3); + + rive::rcp queue = dmRiveCommands::GetCommandQueue(); + queue->setViewModelInstanceEnum(handle, path, value); + rive::CommandQueue::ViewModelInstanceData data; + data.metaData.name = path; + data.metaData.type = rive::DataType::enumType; + data.stringValue = value; + data.numberValue = 0.0f; + SetViewModelInstanceCachedValue(handle, path, data); + return 0; +} + +/** + * Updates the string property at the path. + * @name cmd.setViewModelInstanceString(view_model_handle, path, string_value) + * @param view_model_handle [type: ViewModelInstanceHandle] View model instance handle. + * @param path [type: string] Path to the string property. + * @param string_value [type: string] New string value. + */ +static int Script_setViewModelInstanceString(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 0); + rive::ViewModelInstanceHandle handle = CheckViewModelInstanceHandle(L, 1); + const char* path = luaL_checkstring(L, 2); + const char* value = luaL_checkstring(L, 3); + + rive::rcp queue = dmRiveCommands::GetCommandQueue(); + queue->setViewModelInstanceString(handle, path, value); + rive::CommandQueue::ViewModelInstanceData data; + data.metaData.name = path; + data.metaData.type = rive::DataType::string; + data.stringValue = value; + data.numberValue = 0.0f; + SetViewModelInstanceCachedValue(handle, path, data); + return 0; +} + +/** + * Updates the image property at the path. + * @name cmd.setViewModelInstanceImage(view_model_handle, path, render_image_handle) + * @param view_model_handle [type: ViewModelInstanceHandle] View model instance handle. + * @param path [type: string] Path to the image property. + * @param render_image_handle [type: RenderImageHandle] Render image handle to assign. + */ +static int Script_setViewModelInstanceImage(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 0); + rive::ViewModelInstanceHandle handle = CheckViewModelInstanceHandle(L, 1); + rive::RenderImageHandle value = CheckRenderImageHandle(L, 3); + const char* path = luaL_checkstring(L, 2); + + rive::rcp queue = dmRiveCommands::GetCommandQueue(); + queue->setViewModelInstanceImage(handle, path, value); + return 0; +} + +/** + * Updates the artboard reference at the path. + * @name cmd.setViewModelInstanceArtboard(view_model_handle, path, artboard_handle) + * @param view_model_handle [type: ViewModelInstanceHandle] View model instance handle. + * @param path [type: string] Path to the artboard reference. + * @param artboard_handle [type: ArtboardHandle] Artboard handle to assign. + */ +static int Script_setViewModelInstanceArtboard(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 0); + rive::ViewModelInstanceHandle handle = CheckViewModelInstanceHandle(L, 1); + rive::ArtboardHandle value = CheckArtboardHandle(L, 3); + const char* path = luaL_checkstring(L, 2); + + rive::rcp queue = dmRiveCommands::GetCommandQueue(); + queue->setViewModelInstanceArtboard(handle, path, value); + return 0; +} + +static int Script_getViewModelInstanceData(lua_State* L, rive::DataType expected_type, bool allow_integer, rive::CommandQueue::ViewModelInstanceData* out_data) +{ + rive::ViewModelInstanceHandle handle = CheckViewModelInstanceHandle(L, 1); + const char* path = luaL_checkstring(L, 2); + ViewModelInstanceListener* listener = GetViewModelInstanceListener(handle); + if (!listener) + { + return luaL_error(L, "View model instance listener missing for property '%s'", path); + } + + dmhash_t path_hash = dmHashString64(path); + if (!listener->GetPropertyValue(path_hash, *out_data)) + { + return luaL_error(L, "View model property '%s' is not available. Ensure you requested or subscribed before reading.", path); + } + + if (out_data->metaData.type != expected_type && !(allow_integer && expected_type == rive::DataType::number && out_data->metaData.type == rive::DataType::integer)) + { + return luaL_error(L, "View model property '%s' has type %d, expected %d", path, (int)out_data->metaData.type, (int)expected_type); + } + return 0; +} + +static int Script_getViewModelInstanceListSizeValue(lua_State* L, size_t* out_size) +{ + rive::ViewModelInstanceHandle handle = CheckViewModelInstanceHandle(L, 1); + const char* path = luaL_checkstring(L, 2); + ViewModelInstanceListener* listener = GetViewModelInstanceListener(handle); + if (!listener) + { + return luaL_error(L, "View model instance listener missing for list property '%s'", path); + } + + dmhash_t path_hash = dmHashString64(path); + size_t size = 0; + if (!listener->GetListSize(path_hash, size)) + { + return luaL_error(L, "View model list property '%s' is not available. Ensure you requested or subscribed before reading.", path); + } + *out_size = size; + return 0; +} + +/** + * Returns the cached boolean property at the path. + * @name cmd.getViewModelInstanceBool(view_model_handle, path) + * @param view_model_handle [type: ViewModelInstanceHandle] View model instance handle. + * @param path [type: string] Path to the boolean property. + * @return value [type: boolean] Cached value. Raises error if unavailable. + */ +static int Script_getViewModelInstanceBool(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 1); + rive::CommandQueue::ViewModelInstanceData data; + if (Script_getViewModelInstanceData(L, rive::DataType::boolean, false, &data) != 0) + return 0; + + lua_pushboolean(L, data.boolValue); + return 1; +} + +/** + * Returns the cached numeric property at the path. + * @name cmd.getViewModelInstanceNumber(view_model_handle, path) + * @param view_model_handle [type: ViewModelInstanceHandle] View model instance handle. + * @param path [type: string] Path to the numeric property. + * @return value [type: number] Cached value. Raises error if unavailable. + */ +static int Script_getViewModelInstanceNumber(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 1); + rive::CommandQueue::ViewModelInstanceData data; + if (Script_getViewModelInstanceData(L, rive::DataType::number, true, &data) != 0) + return 0; + + lua_pushnumber(L, (lua_Number)data.numberValue); + return 1; +} + +/** + * Returns the cached color property at the path. + * @name cmd.getViewModelInstanceColor(view_model_handle, path) + * @param view_model_handle [type: ViewModelInstanceHandle] View model instance handle. + * @param path [type: string] Path to the color property. + * @return value [type: vector4] Cached color. Raises error if unavailable. + */ +static int Script_getViewModelInstanceColor(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 1); + rive::CommandQueue::ViewModelInstanceData data; + if (Script_getViewModelInstanceData(L, rive::DataType::color, false, &data) != 0) + return 0; + + float rgba[4]; + rive::UnpackColorToRGBA32F(data.colorValue, rgba); + dmVMath::Vector4 color(rgba[0], rgba[1], rgba[2], rgba[3]); + dmScript::PushVector4(L, color); + return 1; +} + +/** + * Returns the cached enum property at the path. + * @name cmd.getViewModelInstanceEnum(view_model_handle, path) + * @param view_model_handle [type: ViewModelInstanceHandle] View model instance handle. + * @param path [type: string] Path to the enum property. + * @return value [type: string] Cached enum name. Raises error if unavailable. + */ +static int Script_getViewModelInstanceEnum(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 1); + rive::CommandQueue::ViewModelInstanceData data; + if (Script_getViewModelInstanceData(L, rive::DataType::enumType, false, &data) != 0) + return 0; + + lua_pushstring(L, data.stringValue.c_str()); + return 1; +} + +/** + * Returns the cached string property at the path. + * @name cmd.getViewModelInstanceString(view_model_handle, path) + * @param view_model_handle [type: ViewModelInstanceHandle] View model instance handle. + * @param path [type: string] Path to the string property. + * @return value [type: string] Cached value. Raises error if unavailable. + */ +static int Script_getViewModelInstanceString(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 1); + rive::CommandQueue::ViewModelInstanceData data; + if (Script_getViewModelInstanceData(L, rive::DataType::string, false, &data) != 0) + return 0; + + lua_pushstring(L, data.stringValue.c_str()); + return 1; +} + +/** + * Returns the cached list size for the path. + * @name cmd.getViewModelInstanceListSize(view_model_handle, path) + * @param view_model_handle [type: ViewModelInstanceHandle] View model instance handle. + * @param path [type: string] Path to the list property. + * @return value [type: number] Cached list size. Raises error if unavailable. + */ +static int Script_getViewModelInstanceListSize(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 1); + size_t size = 0; + if (Script_getViewModelInstanceListSizeValue(L, &size) != 0) + return 0; + lua_pushinteger(L, (lua_Integer)size); + return 1; +} + +/** + * Subscribes for updates to the named property. + * @name cmd.subscribeToViewModelProperty(view_model_handle, path, data_type) + * @param view_model_handle [type: ViewModelInstanceHandle] View model instance handle. + * @param path [type: string] Path to the property to subscribe. + * @param data_type [type: enum] Data type identifier from rive::DataType. + */ +static int Script_subscribeToViewModelProperty(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 0); + rive::ViewModelInstanceHandle handle = CheckViewModelInstanceHandle(L, 1); + const char* path = luaL_checkstring(L, 2); + rive::DataType type = (rive::DataType)luaL_checkinteger(L, 3); + + rive::rcp queue = dmRiveCommands::GetCommandQueue(); + queue->subscribeToViewModelProperty(handle, path, type); + return 0; +} + +/** + * Cancels a previous subscription. + * @name cmd.unsubscribeToViewModelProperty(view_model_handle, path, data_type) + * @param view_model_handle [type: ViewModelInstanceHandle] View model instance handle. + * @param path [type: string] Path to the property. + * @param data_type [type: enum] Data type identifier previously subscribed. + */ +static int Script_unsubscribeToViewModelProperty(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 0); + rive::ViewModelInstanceHandle handle = CheckViewModelInstanceHandle(L, 1); + const char* path = luaL_checkstring(L, 2); + rive::DataType type = (rive::DataType)luaL_checkinteger(L, 3); + + rive::rcp queue = dmRiveCommands::GetCommandQueue(); + queue->unsubscribeToViewModelProperty(handle, path, type); + return 0; +} + +// ***************************************************************************************** + +/** + * Loads Rive bytes and returns a file handle. + * @name cmd.loadFile(riv_bytes) + * @param riv_bytes [type: string] Raw Rive file data. + * @return file_handle [type: FileHandle] Loaded Rive file handle. + */ +static int Script_loadFile(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 1); + size_t data_length = 0; + const uint8_t* data = (const uint8_t*)luaL_checklstring(L, 1, &data_length); + std::vector rivBytes(data, data + data_length); + + rive::rcp queue = dmRiveCommands::GetCommandQueue(); + rive::FileHandle file = queue->loadFile(rivBytes); + + PushFileHandle(L, file); + return 1; +} + +/** + * Deletes the runtime file handle. + * @name cmd.deleteFile(file_handle) + * @param file_handle [type: FileHandle] File handle to delete. + */ +static int Script_deleteFile(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 0); + rive::FileHandle handle = CheckFileHandle(L, 1); + rive::rcp queue = dmRiveCommands::GetCommandQueue(); + queue->deleteFile(handle); + return 0; +} + +/** + * Decodes image bytes and returns a render image handle. + * @name cmd.decodeImage(image_bytes) + * @param image_bytes [type: string] Encoded image data. + * @return render_image_handle [type: RenderImageHandle] Decoded render image handle. + */ +static int Script_decodeImage(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 1); + size_t data_length = 0; + const uint8_t* data = (const uint8_t*)luaL_checklstring(L, 1, &data_length); + std::vector encodedBytes(data, data + data_length); + + rive::rcp queue = dmRiveCommands::GetCommandQueue(); + rive::RenderImageHandle image = queue->decodeImage(encodedBytes); + + PushRenderImageHandle(L, image); + return 1; +} + +/** + * Deletes the render image handle. + * @name cmd.deleteImage(image_handle) + * @param image_handle [type: RenderImageHandle] Render image handle to delete. + */ +static int Script_deleteImage(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 0); + rive::RenderImageHandle handle = CheckRenderImageHandle(L, 1); + rive::rcp queue = dmRiveCommands::GetCommandQueue(); + queue->deleteImage(handle); + return 0; +} + +/** + * Decodes audio bytes and returns an audio source handle. + * @name cmd.decodeAudio(audio_bytes) + * @param audio_bytes [type: string] Encoded audio data. + * @return audio_handle [type: AudioSourceHandle] Decoded audio source handle. + */ +static int Script_decodeAudio(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 1); + size_t data_length = 0; + const uint8_t* data = (const uint8_t*)luaL_checklstring(L, 1, &data_length); + std::vector encodedBytes(data, data + data_length); + + rive::rcp queue = dmRiveCommands::GetCommandQueue(); + rive::AudioSourceHandle handle = queue->decodeAudio(encodedBytes); + + PushAudioSourceHandle(L, handle); + return 1; +} + +/** + * Deletes the audio source handle. + * @name cmd.deleteAudio(audio_handle) + * @param audio_handle [type: AudioSourceHandle] Audio source handle to delete. + */ +static int Script_deleteAudio(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 0); + rive::AudioSourceHandle handle = CheckAudioSourceHandle(L, 1); + rive::rcp queue = dmRiveCommands::GetCommandQueue(); + queue->deleteAudio(handle); + return 0; +} + +/** + * Decodes font bytes and returns a font handle. + * @name cmd.decodeFont(font_bytes) + * @param font_bytes [type: string] Encoded font data. + * @return font_handle [type: FontHandle] Decoded font handle. + */ +static int Script_decodeFont(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 1); + size_t data_length = 0; + const uint8_t* data = (const uint8_t*)luaL_checklstring(L, 1, &data_length); + std::vector encodedBytes(data, data + data_length); + + rive::rcp queue = dmRiveCommands::GetCommandQueue(); + rive::FontHandle handle = queue->decodeFont(encodedBytes); + + PushFontHandle(L, handle); + return 1; +} + +/** + * Deletes the font handle. + * @name cmd.deleteFont(font_handle) + * @param font_handle [type: FontHandle] Font handle to delete. + */ +static int Script_deleteFont(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 0); + rive::FontHandle handle = CheckFontHandle(L, 1); + rive::rcp queue = dmRiveCommands::GetCommandQueue(); + queue->deleteFont(handle); + return 0; +} + +/** + * Registers a global image asset. + * @name cmd.addGlobalImageAsset(asset_name, render_image_handle) + * @param asset_name [type: string] Name used to reference the global asset. + * @param render_image_handle [type: RenderImageHandle] Render image handle to register. + */ +static int Script_addGlobalImageAsset(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 0); + const char* name = luaL_checkstring(L, 1); + rive::RenderImageHandle handle = CheckRenderImageHandle(L, 2); + rive::rcp queue = dmRiveCommands::GetCommandQueue(); + queue->addGlobalImageAsset(name, handle); + return 0; +} + +/** + * Unregisters the named global image asset. + * @name cmd.removeGlobalImageAsset(asset_name) + * @param asset_name [type: string] Name of the asset to remove. + */ +static int Script_removeGlobalImageAsset(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 0); + const char* name = luaL_checkstring(L, 1); + rive::rcp queue = dmRiveCommands::GetCommandQueue(); + queue->removeGlobalImageAsset(name); + return 0; +} + +/** + * Registers a global font asset. + * @name cmd.addGlobalFontAsset(asset_name, font_handle) + * @param asset_name [type: string] Name used to reference the global font. + * @param font_handle [type: FontHandle] Font handle to register. + */ +static int Script_addGlobalFontAsset(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 0); + const char* name = luaL_checkstring(L, 1); + rive::FontHandle handle = CheckFontHandle(L, 2); + rive::rcp queue = dmRiveCommands::GetCommandQueue(); + queue->addGlobalFontAsset(name, handle); + return 0; +} + +/** + * Unregisters the named global font asset. + * @name cmd.removeGlobalFontAsset(asset_name) + * @param asset_name [type: string] Name of the font asset to remove. + */ +static int Script_removeGlobalFontAsset(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 0); + const char* name = luaL_checkstring(L, 1); + rive::rcp queue = dmRiveCommands::GetCommandQueue(); + queue->removeGlobalFontAsset(name); + return 0; +} + +/** + * Registers a global audio asset. + * @name cmd.addGlobalAudioAsset(asset_name, audio_handle) + * @param asset_name [type: string] Name used to reference the global audio. + * @param audio_handle [type: AudioSourceHandle] Audio source handle to register. + */ +static int Script_addGlobalAudioAsset(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 0); + const char* name = luaL_checkstring(L, 1); + rive::AudioSourceHandle handle = CheckAudioSourceHandle(L, 2); + rive::rcp queue = dmRiveCommands::GetCommandQueue(); + queue->addGlobalAudioAsset(name, handle); + return 0; +} + +/** + * Unregisters the named global audio asset. + * @name cmd.removeGlobalAudioAsset(asset_name) + * @param asset_name [type: string] Name of the audio asset to remove. + */ +static int Script_removeGlobalAudioAsset(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 0); + const char* name = luaL_checkstring(L, 1); + rive::rcp queue = dmRiveCommands::GetCommandQueue(); + queue->removeGlobalAudioAsset(name); + return 0; +} + +// ***************************************************************************************** + +/** + * Requests view model names for the file. + * @name cmd.requestViewModelNames(file_handle) + * @param file_handle [type: FileHandle] File handle whose view models to query. + */ +static int Script_requestViewModelNames(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 0); + rive::FileHandle file = CheckFileHandle(L, 1); + dmRiveCommands::GetCommandQueue()->requestViewModelNames(file); + return 0; +} + +/** + * Requests artboard names for the file. + * @name cmd.requestArtboardNames(file_handle) + * @param file_handle [type: FileHandle] File handle whose artboards to query. + */ +static int Script_requestArtboardNames(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 0); + rive::FileHandle file = CheckFileHandle(L, 1); + dmRiveCommands::GetCommandQueue()->requestArtboardNames(file); + return 0; +} + +/** + * Requests enum definitions for the file. + * @name cmd.requestViewModelEnums(file_handle) + * @param file_handle [type: FileHandle] File handle whose enums to query. + */ +static int Script_requestViewModelEnums(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 0); + rive::FileHandle file = CheckFileHandle(L, 1); + dmRiveCommands::GetCommandQueue()->requestViewModelEnums(file); + return 0; +} + +/** + * Requests property definitions for the view model. + * @name cmd.requestViewModelPropertyDefinitions(file_handle, viewmodel_name) + * @param file_handle [type: FileHandle] File handle whose view models to query. + * @param viewmodel_name [type: string] View model name whose property metadata to request. + */ +static int Script_requestViewModelPropertyDefinitions(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 0); + rive::FileHandle file = CheckFileHandle(L, 1); + const char* viewmodel_name = luaL_checkstring(L, 2); + dmRiveCommands::GetCommandQueue()->requestViewModelPropertyDefinitions(file, viewmodel_name); + return 0; +} + +/** + * Requests instance names for the view model. + * @name cmd.requestViewModelInstanceNames(file_handle, viewmodel_name) + * @param file_handle [type: FileHandle] File handle whose view models to query. + * @param viewmodel_name [type: string] View model name to inspect. + */ +static int Script_requestViewModelInstanceNames(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 0); + rive::FileHandle file = CheckFileHandle(L, 1); + const char* viewmodel_name = luaL_checkstring(L, 2); + dmRiveCommands::GetCommandQueue()->requestViewModelInstanceNames(file, viewmodel_name); + return 0; +} + +/** + * Requests the boolean value for the property. + * @name cmd.requestViewModelInstanceBool(instance_handle, property_name) + * @param instance_handle [type: ViewModelInstanceHandle] View model instance handle. + * @param property_name [type: string] Name of the bool property. + */ +static int Script_requestViewModelInstanceBool(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 0); + rive::ViewModelInstanceHandle handle = CheckViewModelInstanceHandle(L, 1); + const char* viewmodel_name = luaL_checkstring(L, 2); + dmRiveCommands::GetCommandQueue()->requestViewModelInstanceBool(handle, viewmodel_name); + return 0; +} + +/** + * Requests the numeric value for the property. + * @name cmd.requestViewModelInstanceNumber(instance_handle, property_name) + * @param instance_handle [type: ViewModelInstanceHandle] View model instance handle. + * @param property_name [type: string] Name of the numeric property. + */ +static int Script_requestViewModelInstanceNumber(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 0); + rive::ViewModelInstanceHandle handle = CheckViewModelInstanceHandle(L, 1); + const char* viewmodel_name = luaL_checkstring(L, 2); + dmRiveCommands::GetCommandQueue()->requestViewModelInstanceNumber(handle, viewmodel_name); + return 0; +} + +/** + * Requests the color value for the property. + * @name cmd.requestViewModelInstanceColor(instance_handle, property_name) + * @param instance_handle [type: ViewModelInstanceHandle] View model instance handle. + * @param property_name [type: string] Name of the color property. + */ +static int Script_requestViewModelInstanceColor(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 0); + rive::ViewModelInstanceHandle handle = CheckViewModelInstanceHandle(L, 1); + const char* viewmodel_name = luaL_checkstring(L, 2); + dmRiveCommands::GetCommandQueue()->requestViewModelInstanceColor(handle, viewmodel_name); + return 0; +} + +/** + * Requests the enum value for the property. + * @name cmd.requestViewModelInstanceEnum(instance_handle, property_name) + * @param instance_handle [type: ViewModelInstanceHandle] View model instance handle. + * @param property_name [type: string] Name of the enum property. + */ +static int Script_requestViewModelInstanceEnum(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 0); + rive::ViewModelInstanceHandle handle = CheckViewModelInstanceHandle(L, 1); + const char* viewmodel_name = luaL_checkstring(L, 2); + dmRiveCommands::GetCommandQueue()->requestViewModelInstanceEnum(handle, viewmodel_name); + return 0; +} + +/** + * Requests the string value for the property. + * @name cmd.requestViewModelInstanceString(instance_handle, property_name) + * @param instance_handle [type: ViewModelInstanceHandle] View model instance handle. + * @param property_name [type: string] Name of the string property. + */ +static int Script_requestViewModelInstanceString(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 0); + rive::ViewModelInstanceHandle handle = CheckViewModelInstanceHandle(L, 1); + const char* viewmodel_name = luaL_checkstring(L, 2); + dmRiveCommands::GetCommandQueue()->requestViewModelInstanceString(handle, viewmodel_name); + return 0; +} + +/** + * Requests the list size for the property. + * @name cmd.requestViewModelInstanceListSize(instance_handle, property_name) + * @param instance_handle [type: ViewModelInstanceHandle] View model instance handle. + * @param property_name [type: string] Name of the list property. + */ +static int Script_requestViewModelInstanceListSize(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 0); + rive::ViewModelInstanceHandle handle = CheckViewModelInstanceHandle(L, 1); + const char* viewmodel_name = luaL_checkstring(L, 2); + dmRiveCommands::GetCommandQueue()->requestViewModelInstanceListSize(handle, viewmodel_name); + return 0; +} + +/** + * Requests state machine names for the artboard. + * @name cmd.requestStateMachineNames(artboard_handle) + * @param artboard_handle [type: ArtboardHandle] Artboard handle to query. + */ +static int Script_requestStateMachineNames(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 0); + rive::ArtboardHandle artboard = CheckArtboardHandle(L, 1); + dmRiveCommands::GetCommandQueue()->requestStateMachineNames(artboard); + return 0; +} + +/** + * Requests metadata about the default view model or artboard. + * @name cmd.requestDefaultViewModelInfo(artboard_handle, file_handle) + * @param artboard_handle [type: ArtboardHandle] Artboard handle to query. + * @param file_handle [type: FileHandle] File handle providing metadata. + */ +static int Script_requestDefaultViewModelInfo(lua_State* L) +{ + DM_LUA_STACK_CHECK(L, 0); + rive::ArtboardHandle artboard = CheckArtboardHandle(L, 1); + rive::FileHandle file = CheckFileHandle(L, 2); + dmRiveCommands::GetCommandQueue()->requestDefaultViewModelInfo(artboard, file); + return 0; +} + +// ***************************************************************************************** + +static const luaL_reg RIVE_COMMAND_FUNCTIONS[] = +{ + {"instantiateArtboardNamed", Script_instantiateArtboardNamed}, + {"instantiateDefaultArtboard", Script_instantiateDefaultArtboard}, + {"deleteArtboard", Script_deleteArtboard}, + + {"instantiateStateMachineNamed", Script_instantiateStateMachineNamed}, + {"instantiateDefaultStateMachine", Script_instantiateDefaultStateMachine}, + {"advanceStateMachine", Script_advanceStateMachine}, + {"bindViewModelInstance", Script_bindViewModelInstance}, + {"deleteStateMachine", Script_deleteStateMachine}, + + {"instantiateBlankViewModelInstance", Script_instantiateBlankViewModelInstance}, + {"instantiateDefaultViewModelInstance", Script_instantiateDefaultViewModelInstance}, + {"instantiateViewModelInstanceNamed", Script_instantiateViewModelInstanceNamed}, + {"referenceNestedViewModelInstance", Script_referenceNestedViewModelInstance}, + {"referenceListViewModelInstance", Script_referenceListViewModelInstance}, + + {"setViewModelInstanceNestedViewModel", Script_setViewModelInstanceNestedViewModel}, + {"insertViewModelInstanceListViewModel", Script_insertViewModelInstanceListViewModel}, + {"appendViewModelInstanceListViewModel", Script_appendViewModelInstanceListViewModel}, + {"swapViewModelInstanceListValues", Script_swapViewModelInstanceListValues}, + + {"removeViewModelInstanceListViewModelIndex",Script_removeViewModelInstanceListViewModelIndex}, + {"removeViewModelInstanceListViewModel", Script_removeViewModelInstanceListViewModel}, + + {"deleteViewModelInstance", Script_deleteViewModelInstance}, + + {"fireViewModelTrigger", Script_fireViewModelTrigger}, + {"setViewModelInstanceBool", Script_setViewModelInstanceBool}, + {"setViewModelInstanceNumber", Script_setViewModelInstanceNumber}, + {"setViewModelInstanceColor", Script_setViewModelInstanceColor}, + {"setViewModelInstanceEnum", Script_setViewModelInstanceEnum}, + {"setViewModelInstanceString", Script_setViewModelInstanceString}, + {"setViewModelInstanceImage", Script_setViewModelInstanceImage}, + {"setViewModelInstanceArtboard",Script_setViewModelInstanceArtboard}, + + // BEGIN non-api + // NOTE: These aren't synchronous getters + // Nor are they matched by any api in the rive runtime. They are merely + // implemented on top of the subcription model, in order to get somewhat easier acess to the data. + {"getViewModelInstanceBool", Script_getViewModelInstanceBool}, + {"getViewModelInstanceNumber", Script_getViewModelInstanceNumber}, + {"getViewModelInstanceColor", Script_getViewModelInstanceColor}, + {"getViewModelInstanceEnum", Script_getViewModelInstanceEnum}, + {"getViewModelInstanceString", Script_getViewModelInstanceString}, + {"getViewModelInstanceListSize",Script_getViewModelInstanceListSize}, + // END non-api + + {"subscribeToViewModelProperty", Script_subscribeToViewModelProperty}, + {"unsubscribeToViewModelProperty", Script_unsubscribeToViewModelProperty}, + + {"loadFile", Script_loadFile}, + {"deleteFile", Script_deleteFile}, + {"decodeImage", Script_decodeImage}, + {"deleteImage", Script_deleteImage}, + {"decodeAudio", Script_decodeAudio}, + {"deleteAudio", Script_deleteAudio}, + {"decodeFont", Script_decodeFont}, + {"deleteFont", Script_deleteFont}, + + {"addGlobalImageAsset", Script_addGlobalImageAsset}, + {"removeGlobalImageAsset", Script_removeGlobalImageAsset}, + {"addGlobalAudioAsset", Script_addGlobalAudioAsset}, + {"removeGlobalAudioAsset", Script_removeGlobalAudioAsset}, + {"addGlobalFontAsset", Script_addGlobalFontAsset}, + {"removeGlobalFontAsset", Script_removeGlobalFontAsset}, + + {"requestViewModelNames", Script_requestViewModelNames}, + {"requestArtboardNames", Script_requestArtboardNames}, + {"requestViewModelEnums", Script_requestViewModelEnums}, + + {"requestViewModelPropertyDefinitions", Script_requestViewModelPropertyDefinitions}, + {"requestViewModelInstanceNames", Script_requestViewModelInstanceNames}, + {"requestViewModelInstanceBool", Script_requestViewModelInstanceBool}, + {"requestViewModelInstanceNumber", Script_requestViewModelInstanceNumber}, + {"requestViewModelInstanceColor", Script_requestViewModelInstanceColor}, + {"requestViewModelInstanceEnum", Script_requestViewModelInstanceEnum}, + {"requestViewModelInstanceString", Script_requestViewModelInstanceString}, + {"requestViewModelInstanceListSize", Script_requestViewModelInstanceListSize}, + + {"requestStateMachineNames", Script_requestStateMachineNames}, + {"requestDefaultViewModelInfo", Script_requestDefaultViewModelInfo}, + + {0, 0} +}; + +void ScriptCmdRegister(struct lua_State* L, dmResource::HFactory factory) +{ + lua_newtable(L); + luaL_register(L, 0, RIVE_COMMAND_FUNCTIONS); + lua_setfield(L, -2, "cmd"); + + g_ResourceFactory = factory; +} + +void ScriptCmdUnregister(struct lua_State* L, dmResource::HFactory factory) +{ + ClearViewModelInstancePropertyRequests(); + ClearViewModelInstanceListeners(); + g_ResourceFactory = 0; +} + +} // namespace dmRive + +#endif // DM_RIVE_UNSUPPORTED diff --git a/defold-rive/src/script_rive_cmd.h b/defold-rive/src/script_rive_cmd.h new file mode 100644 index 00000000..55ed6128 --- /dev/null +++ b/defold-rive/src/script_rive_cmd.h @@ -0,0 +1,25 @@ +// Copyright 2020 The Defold Foundation +// Licensed under the Defold License version 1.0 (the "License"); you may not use +// this file except in compliance with the License. +// +// You may obtain a copy of the License, together with FAQs at +// https://www.defold.com/license +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef DM_GAMESYS_SCRIPT_RIVE_CMD_H +#define DM_GAMESYS_SCRIPT_RIVE_CMD_H + +#include +#include + +namespace dmRive +{ + void ScriptCmdRegister(lua_State* L, dmResource::HFactory factory); + void ScriptCmdUnregister(lua_State* L, dmResource::HFactory factory); +} + +#endif // DM_GAMESYS_SCRIPT_RIVE_CMD_H diff --git a/defold-rive/src/script_rive_databinding.cpp b/defold-rive/src/script_rive_databinding.cpp deleted file mode 100644 index ea0af201..00000000 --- a/defold-rive/src/script_rive_databinding.cpp +++ /dev/null @@ -1,355 +0,0 @@ -// Copyright 2021 The Defold Foundation -// Licensed under the Defold License version 1.0 (the "License"); you may not use -// this file except in compliance with the License. -// -// You may obtain a copy of the License, together with FAQs at -// https://www.defold.com/license -// -// Unless required by applicable law or agreed to in writing, software distributed -// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR -// CONDITIONS OF ANY KIND, either express or implied. See the License for the -// specific language governing permissions and limitations under the License. - -#if !defined(DM_RIVE_UNSUPPORTED) - -#include - -#include -#include -#include - -#include "comp_rive.h" -#include "comp_rive_private.h" -#include "res_rive_data.h" - -#include - -namespace dmRive -{ - -static dmResource::HFactory g_ResourceFactory = 0; - -static int CreateViewModelInstanceRuntime(lua_State* L) -{ - DM_LUA_STACK_CHECK(L, 2); - - RiveComponent* component = 0; - dmScript::GetComponentFromLua(L, 1, dmRive::RIVE_MODEL_EXT, 0, (void**)&component, 0); - - dmhash_t name_hash = dmScript::CheckHashOrString(L, 2); - - uint32_t handle = dmRive::CompRiveCreateViewModelInstanceRuntime(component, name_hash); - if (handle != dmRive::INVALID_HANDLE) - { - lua_pushinteger(L, handle); - lua_pushnil(L); - } - else - { - lua_pushnil(L); - char msg[256]; - dmSnPrintf(msg, sizeof(msg), "Failed to create view model instance runtime with name '%s'", dmHashReverseSafe64(name_hash)); - lua_pushstring(L, msg); - } - return 2; -} - -static int DestroyViewModelInstanceRuntime(lua_State* L) -{ - DM_LUA_STACK_CHECK(L, 0); - - RiveComponent* component = 0; - dmScript::GetComponentFromLua(L, 1, dmRive::RIVE_MODEL_EXT, 0, (void**)&component, 0); - - uint32_t handle = luaL_checkinteger(L, 2); - dmRive::CompRiveDestroyViewModelInstanceRuntime(component, handle); - return 0; -} - -static int SetViewModelInstanceRuntime(lua_State* L) -{ - DM_LUA_STACK_CHECK(L, 0); - - RiveComponent* component = 0; - dmScript::GetComponentFromLua(L, 1, dmRive::RIVE_MODEL_EXT, 0, (void**)&component, 0); - - uint32_t handle = luaL_checkinteger(L, 2); - bool result = dmRive::CompRiveSetViewModelInstanceRuntime(component, handle); - if (!result) - { - return DM_LUA_ERROR("Could not set view model instance runtime with handle '%u'", handle); - } - - return 0; -} - -static int GetViewModelInstanceRuntime(lua_State* L) -{ - DM_LUA_STACK_CHECK(L, 1); - RiveComponent* component = 0; - dmScript::GetComponentFromLua(L, 1, dmRive::RIVE_MODEL_EXT, 0, (void**)&component, 0); - uint32_t handle = CompRiveGetViewModelInstanceRuntime(component); - lua_pushinteger(L, handle); - return 1; -} - -#define CHECK_BOOLEAN(PATH, INDEX) \ - if (!lua_isboolean(L, INDEX)) { \ - char buffer[1024]; \ - lua_pushvalue(L, INDEX); \ - dmSnPrintf(buffer, sizeof(buffer), "Property '%s' is not a boolean: '%s'", PATH, lua_tostring(L, -1)); \ - lua_pop(L, lua_gettop(L) - top); \ - return DM_LUA_ERROR("%s", buffer); \ - } - -#define CHECK_RESULT(RESULT, URL, PATH, INDEX) \ - if (!result) { \ - char buffer[1024]; \ - lua_pushvalue(L, INDEX); \ - dmSnPrintf(buffer, sizeof(buffer), "%s has no property with path '%s' that accepts type '%s' (value='%s')", dmScript::UrlToString(URL, buffer, sizeof(buffer)), PATH, lua_typename(L, lua_type(L, -1)), lua_tostring(L, -1)); \ - lua_pop(L, lua_gettop(L) - top); \ - return DM_LUA_ERROR("%s", buffer); \ - } - -static int SetProperties(lua_State* L) -{ - DM_LUA_STACK_CHECK(L, 0); - int top = lua_gettop(L); - - dmMessage::URL url; - RiveComponent* component = 0; - dmScript::GetComponentFromLua(L, 1, dmRive::RIVE_MODEL_EXT, 0, (void**)&component, &url); - - uint32_t handle = luaL_checkinteger(L, 2); - - luaL_checktype(L, 3, LUA_TTABLE); - lua_pushvalue(L, 3); - lua_pushnil(L); - while (lua_next(L, -2)) { - lua_pushvalue(L, -2); // Add a duplicate of the key, as we do a lua_tostring on it - - // Note: This is a "path", as it may refer to a property within a nested structure of view model instance runtimes - const char* path = lua_tostring(L, -1); // the key - - // Check what property type is wanted - rive::DataType data_type = rive::DataType::none; - bool result = dmRive::GetPropertyDataType(component, handle, path, &data_type); - CHECK_RESULT(result, &url, path, -2); - - if (data_type == rive::DataType::number) - { - float value = lua_tonumber(L, -2); - bool result = dmRive::CompRiveRuntimeSetPropertyF32(component, handle, path, value); - CHECK_RESULT(result, &url, path, -2); - } - else if (data_type == rive::DataType::string) - { - const char* value = luaL_checkstring(L, -2); - bool result = dmRive::CompRiveRuntimeSetPropertyString(component, handle, path, value); - CHECK_RESULT(result, &url, path, -2); - } - else if (data_type == rive::DataType::boolean) - { - CHECK_BOOLEAN(path, -2); - bool value = lua_toboolean(L, -2); - bool result = dmRive::CompRiveRuntimeSetPropertyBool(component, handle, path, value); - CHECK_RESULT(result, &url, path, -2); - } - else if (data_type == rive::DataType::trigger) - { - CHECK_BOOLEAN(path, -2); - bool value = lua_toboolean(L, -2); - // Trigger doesn't really use a value. - // But no value would mean nil, and nil would mean it's not in the table to begin with. - // So we use a bool as the "dummy" value! - // However, it would feel strange to trigger() on a false value :/ - if (value) - { - bool result = dmRive::CompRiveRuntimeSetPropertyTrigger(component, handle, path); - CHECK_RESULT(result, &url, path, -2); - } - } - else if (data_type == rive::DataType::color) - { - dmVMath::Vector4* color = dmScript::CheckVector4(L, -2); - bool result = dmRive::CompRiveRuntimeSetPropertyColor(component, handle, path, color); - CHECK_RESULT(result, &url, path, -2); - } - else if (data_type == rive::DataType::enumType) - { - const char* value = luaL_checkstring(L, -2); - result = dmRive::CompRiveRuntimeSetPropertyEnum(component, handle, path, value); - CHECK_RESULT(result, &url, path, -2); - } - else if (data_type == rive::DataType::assetImage) - { - size_t data_length = 0; - const char* data = luaL_checklstring(L, -2, &data_length); - - RiveSceneData* rive_scene_data = dmRive::CompRiveGetRiveSceneData(component); - rive::RenderImage* image = ResRiveDataCreateRenderImage(g_ResourceFactory, rive_scene_data, (uint8_t*)data, (uint32_t)data_length); - result = dmRive::CompRiveRuntimeSetPropertyImage(component, handle, path, image); - CHECK_RESULT(result, &url, path, -2); - } - else - { - dmLogWarning("Datatype %d is not yet supported (path: '%s')", (int)data_type, path); - } - - lua_pop(L, 2); - } - lua_pop(L, 1); - - return 0; -} - -static int GetProperty(lua_State* L) -{ - DM_LUA_STACK_CHECK(L, 1); - int top = lua_gettop(L); - - dmMessage::URL url; - RiveComponent* component = 0; - dmScript::GetComponentFromLua(L, 1, dmRive::RIVE_MODEL_EXT, 0, (void**)&component, &url); - - uint32_t handle = luaL_checkinteger(L, 2); - if (handle == dmRive::INVALID_HANDLE) - { - return DM_LUA_ERROR("Invalid handle: %d (x%08x) at index %d", (int)handle, handle, 2); - } - - // Note: This is a "path", as it may refer to a property within a nested structure of view model instance runtimes - const char* path = lua_tostring(L, 3); // the path - - rive::DataType data_type = rive::DataType::none; - bool result = dmRive::GetPropertyDataType(component, handle, path, &data_type); - CHECK_RESULT(result, &url, path, -2); - - if (data_type == rive::DataType::number) - { - float value = false; - bool result = dmRive::CompRiveRuntimeGetPropertyF32(component, handle, path, &value); - CHECK_RESULT(result, &url, path, -2); - lua_pushnumber(L, value); - } - else if (data_type == rive::DataType::string) - { - const char* value = 0; - bool result = dmRive::CompRiveRuntimeGetPropertyString(component, handle, path, &value); - CHECK_RESULT(result, &url, path, -2); - lua_pushstring(L, value); - } - else if (data_type == rive::DataType::boolean) - { - bool value = false; - bool result = dmRive::CompRiveRuntimeGetPropertyBool(component, handle, path, &value); - CHECK_RESULT(result, &url, path, -2); - lua_pushboolean(L, value); - } - else if (data_type == rive::DataType::enumType) - { - const char* value = 0; - bool result = dmRive::CompRiveRuntimeGetPropertyEnum(component, handle, path, &value); - CHECK_RESULT(result, &url, path, -2); - lua_pushstring(L, value); - } - else if(data_type == rive::DataType::color) - { - dmVMath::Vector4 value(-1, -1, -1, -1); - bool result = dmRive::CompRiveRuntimeGetPropertyColor(component, handle, path, &value); - CHECK_RESULT(result, &url, path, -2); - dmScript::PushVector4(L, value); - } - else if(data_type == rive::DataType::trigger) - { - return DM_LUA_ERROR("Cannot get trigger property value as it is input-only (path: '%s')", path); - } - else - { - return DM_LUA_ERROR("Getting data type %d is not supported (path: '%s')", (int)data_type, path); - } - return 1; -} - -static int ListAddInstanceInternal(lua_State* L, bool add, int index) -{ - DM_LUA_STACK_CHECK(L, 0); - - dmMessage::URL url; - RiveComponent* component = 0; - dmScript::GetComponentFromLua(L, 1, dmRive::RIVE_MODEL_EXT, 0, (void**)&component, &url); - - uint32_t handle = luaL_checkinteger(L, 2); - if (handle == dmRive::INVALID_HANDLE) - { - return DM_LUA_ERROR("Invalid handle: %d (x%08x) at index %d", (int)handle, handle, 2); - } - - // Note: This is a "path", as it may refer to a property within a nested structure of view model instance runtimes - const char* path = lua_tostring(L, 3); // the path - - uint32_t instance = luaL_checkinteger(L, 4); - if (instance == dmRive::INVALID_HANDLE) - { - return DM_LUA_ERROR("Invalid handle: %d (x%08x) at index %d", (int)instance, instance, 3); - } - - bool result; - if (add) - result = dmRive::CompRiveRuntimeListAddInstance(component, handle, path, instance); - else - result = dmRive::CompRiveRuntimeListRemoveInstance(component, handle, path, instance); - if (!result) - { - if (add) - { - return DM_LUA_ERROR("Failed adding instance to list %s", path); - } - else - { - return DM_LUA_ERROR("Failed removing instance from list %s", path); - } - } - - return 0; -} - -static int ListAddInstance(lua_State* L) -{ - return ListAddInstanceInternal(L, true, -1); -} - -static int ListRemoveInstance(lua_State* L) -{ - return ListAddInstanceInternal(L, false, -1); -} - - -#undef CHECK_RESULT -#undef CHECK_BOOLEAN - -static const luaL_reg RIVE_DATABIND_FUNCTIONS[] = -{ - {"create_view_model_instance_runtime", CreateViewModelInstanceRuntime}, - {"destroy_view_model_instance_runtime", DestroyViewModelInstanceRuntime}, - {"set_view_model_instance_runtime", SetViewModelInstanceRuntime}, - {"get_view_model_instance_runtime", GetViewModelInstanceRuntime}, - {"set_properties", SetProperties}, - {"get_property", GetProperty}, - {"list_add_instance", ListAddInstance}, - {"list_remove_instance", ListRemoveInstance}, - {0, 0} -}; - -void ScriptInitializeDataBinding(struct lua_State* L, dmResource::HFactory factory) -{ - lua_newtable(L); - luaL_register(L, 0, RIVE_DATABIND_FUNCTIONS); - lua_setfield(L, -2, "databind"); - - g_ResourceFactory = factory; -} - -} // namespace dmRive - -#endif // DM_RIVE_UNSUPPORTED diff --git a/defold-rive/src/script_rive_handles.cpp b/defold-rive/src/script_rive_handles.cpp new file mode 100644 index 00000000..71c6af69 --- /dev/null +++ b/defold-rive/src/script_rive_handles.cpp @@ -0,0 +1,193 @@ +// Copyright 2020 The Defold Foundation +// Licensed under the Defold License version 1.0 (the "License"); you may not use +// this file except in compliance with the License. +// +// You may obtain a copy of the License, together with FAQs at +// https://www.defold.com/license +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#include "script_defold.h" +#include "script_rive_handles.h" + +extern "C" +{ +#include +#include +} + +#include + +namespace dmRive +{ + struct HandleUserData + { + uint64_t m_Handle; + }; + + static const char* HandleTypeNames[] = { + "rive.FileHandle", + "rive.ArtboardHandle", + "rive.StateMachineHandle", + "rive.ViewModelInstanceHandle", + "rive.RenderImageHandle", + "rive.AudioSourceHandle", + "rive.FontHandle", + nullptr + }; + + static const char* IdentifyHandleType(lua_State* L, int index) + { + if (!lua_getmetatable(L, index)) + { + return nullptr; + } + int meta = lua_gettop(L); + for (const char** name = HandleTypeNames; *name; ++name) + { + luaL_getmetatable(L, *name); + if (lua_rawequal(L, -1, meta)) + { + lua_pop(L, 2); + return *name; + } + lua_pop(L, 1); + } + lua_pop(L, 1); + return nullptr; + } + + template + struct LuaHandle + { + static const char* s_TypeName; + + static int LuaGC(lua_State* L) + { + (void)Check(L, 1); + return 0; + } + + static int LuaToString(lua_State* L) + { + HandleT handle = Check(L, 1); + uint64_t value = (uint64_t)(uintptr_t)handle; + lua_pushfstring(L, "%s(%p)", s_TypeName, (void*)(uintptr_t)value); + return 1; + } + + static void Register(lua_State* L, const char* name) + { + s_TypeName = name; + static const luaL_reg meta[] = { + { "__gc", LuaGC }, + { "__tostring", LuaToString }, + { 0, 0 } + }; + RegisterUserType(L, name, name, 0, meta); + } + + static void Push(lua_State* L, HandleT handle) + { + HandleUserData* data = (HandleUserData*)lua_newuserdata(L, sizeof(HandleUserData)); + data->m_Handle = (uint64_t)(uintptr_t)handle; + luaL_getmetatable(L, s_TypeName); + lua_setmetatable(L, -2); + } + + static HandleT Check(lua_State* L, int index) + { + HandleUserData* data = (HandleUserData*)ToUserType(L, index, s_TypeName); + if (!data) + { + const char* actual = IdentifyHandleType(L, index); + if (actual) + { + luaL_error(L, "Argument %d expected %s, got %s", index, s_TypeName, actual); + } + luaL_typerror(L, index, s_TypeName); + } + return (HandleT)data->m_Handle; + } + }; + + template + const char* LuaHandle::s_TypeName = nullptr; + + void RegisterScriptRiveHandles(lua_State* L) + { + LuaHandle::Register(L, "rive.FileHandle"); + LuaHandle::Register(L, "rive.ArtboardHandle"); + LuaHandle::Register(L, "rive.StateMachineHandle"); + LuaHandle::Register(L, "rive.ViewModelInstanceHandle"); + LuaHandle::Register(L, "rive.RenderImageHandle"); + LuaHandle::Register(L, "rive.AudioSourceHandle"); + LuaHandle::Register(L, "rive.FontHandle"); + } + + void PushFileHandle(lua_State* L, rive::FileHandle handle) + { + LuaHandle::Push(L, handle); + } + rive::FileHandle CheckFileHandle(lua_State* L, int index) + { + return LuaHandle::Check(L, index); + } + + void PushArtboardHandle(lua_State* L, rive::ArtboardHandle handle) + { + LuaHandle::Push(L, handle); + } + rive::ArtboardHandle CheckArtboardHandle(lua_State* L, int index) + { + return LuaHandle::Check(L, index); + } + + void PushStateMachineHandle(lua_State* L, rive::StateMachineHandle handle) + { + LuaHandle::Push(L, handle); + } + rive::StateMachineHandle CheckStateMachineHandle(lua_State* L, int index) + { + return LuaHandle::Check(L, index); + } + + void PushViewModelInstanceHandle(lua_State* L, rive::ViewModelInstanceHandle handle) + { + LuaHandle::Push(L, handle); + } + rive::ViewModelInstanceHandle CheckViewModelInstanceHandle(lua_State* L, int index) + { + return LuaHandle::Check(L, index); + } + + void PushRenderImageHandle(lua_State* L, rive::RenderImageHandle handle) + { + LuaHandle::Push(L, handle); + } + rive::RenderImageHandle CheckRenderImageHandle(lua_State* L, int index) + { + return LuaHandle::Check(L, index); + } + + void PushAudioSourceHandle(lua_State* L, rive::AudioSourceHandle handle) + { + LuaHandle::Push(L, handle); + } + rive::AudioSourceHandle CheckAudioSourceHandle(lua_State* L, int index) + { + return LuaHandle::Check(L, index); + } + + void PushFontHandle(lua_State* L, rive::FontHandle handle) + { + LuaHandle::Push(L, handle); + } + rive::FontHandle CheckFontHandle(lua_State* L, int index) + { + return LuaHandle::Check(L, index); + } +} // namespace dmRive diff --git a/defold-rive/src/script_rive_handles.h b/defold-rive/src/script_rive_handles.h new file mode 100644 index 00000000..b0a6e935 --- /dev/null +++ b/defold-rive/src/script_rive_handles.h @@ -0,0 +1,46 @@ +// Copyright 2020 The Defold Foundation +// Licensed under the Defold License version 1.0 (the "License"); you may not use +// this file except in compliance with the License. +// +// You may obtain a copy of the License, together with FAQs at +// https://www.defold.com/license +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef DM_GAMESYS_SCRIPT_RIVE_HANDLES_H +#define DM_GAMESYS_SCRIPT_RIVE_HANDLES_H + +#include + +#include + +namespace dmRive +{ + void RegisterScriptRiveHandles(lua_State* L); + + void PushFileHandle(lua_State* L, rive::FileHandle handle); + rive::FileHandle CheckFileHandle(lua_State* L, int index); + + void PushArtboardHandle(lua_State* L, rive::ArtboardHandle handle); + rive::ArtboardHandle CheckArtboardHandle(lua_State* L, int index); + + void PushStateMachineHandle(lua_State* L, rive::StateMachineHandle handle); + rive::StateMachineHandle CheckStateMachineHandle(lua_State* L, int index); + + void PushViewModelInstanceHandle(lua_State* L, rive::ViewModelInstanceHandle handle); + rive::ViewModelInstanceHandle CheckViewModelInstanceHandle(lua_State* L, int index); + + void PushRenderImageHandle(lua_State* L, rive::RenderImageHandle handle); + rive::RenderImageHandle CheckRenderImageHandle(lua_State* L, int index); + + void PushAudioSourceHandle(lua_State* L, rive::AudioSourceHandle handle); + rive::AudioSourceHandle CheckAudioSourceHandle(lua_State* L, int index); + + void PushFontHandle(lua_State* L, rive::FontHandle handle); + rive::FontHandle CheckFontHandle(lua_State* L, int index); +} // namespace dmRive + +#endif // DM_GAMESYS_SCRIPT_RIVE_HANDLES_H diff --git a/defold-rive/src/script_rive_listeners.cpp b/defold-rive/src/script_rive_listeners.cpp new file mode 100644 index 00000000..3e347432 --- /dev/null +++ b/defold-rive/src/script_rive_listeners.cpp @@ -0,0 +1,735 @@ +// Copyright 2021 The Defold Foundation +// Licensed under the Defold License version 1.0 (the "License"); you may not use +// this file except in compliance with the License. +// +// You may obtain a copy of the License, together with FAQs at +// https://www.defold.com/license +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#if !defined(DM_RIVE_UNSUPPORTED) + +#include "res_rive_data.h" +#include "script_rive_listeners.h" +#include "viewmodel_instance_registry.h" +#include +#include +#include + +namespace dmRive +{ + +template +static void EnsureTableCapacity(dmHashTable& table, uint32_t capacity) +{ + if (table.Capacity() >= capacity) + { + return; + } + uint32_t grow = capacity - table.Capacity(); + table.OffsetCapacity((int32_t)grow); +} + +struct DeleteContext +{ +}; + +static void DeletePropertyValue(DeleteContext*, const dmhash_t*, rive::CommandQueue::ViewModelInstanceData** value) +{ + if (value && *value) + { + delete *value; + *value = 0; + } +} + +static void DeleteListSize(DeleteContext*, const dmhash_t*, size_t** value) +{ + if (value && *value) + { + delete *value; + *value = 0; + } +} + +struct ViewModelPropertyRequest +{ + rive::ViewModelInstanceHandle m_Instance; + rive::FileHandle m_File; +}; + +static dmMutex::HMutex g_ViewModelPropertyRequestMutex = 0; +static dmHashTable g_PendingDefaultViewModelRequests; +static dmHashTable g_PendingViewModelPropertyRequests; + +static void EnsureViewModelPropertyRequestCapacity(dmHashTable& table, uint32_t capacity) +{ + if (!g_ViewModelPropertyRequestMutex) + { + g_ViewModelPropertyRequestMutex = dmMutex::New(); + } + if (table.Capacity() >= capacity) + { + return; + } + uint32_t grow = capacity - table.Capacity(); + table.OffsetCapacity((int32_t)grow); +} + +static uint64_t RegisterViewModelPropertyRequest(dmHashTable& table, ViewModelPropertyRequest* request) +{ + EnsureViewModelPropertyRequestCapacity(table, 32); + DM_MUTEX_SCOPED_LOCK(g_ViewModelPropertyRequestMutex); + if (table.Full()) + { + EnsureViewModelPropertyRequestCapacity(table, table.Capacity() * 2); + } + uint64_t request_id = (uint64_t)(uintptr_t)request; + assert(table.Get(request_id) == 0); + table.Put(request_id, request); + return request_id; +} + +static ViewModelPropertyRequest* TakeViewModelPropertyRequest(dmHashTable& table, uint64_t request_id) +{ + EnsureViewModelPropertyRequestCapacity(table, 1); + DM_MUTEX_SCOPED_LOCK(g_ViewModelPropertyRequestMutex); + ViewModelPropertyRequest** entry = table.Get(request_id); + if (!entry) + { + return 0; + } + ViewModelPropertyRequest* request = *entry; + table.Erase(request_id); + return request; +} + +static bool SetupCallback(dmScript::LuaCallbackInfo* callback, dmhash_t id, uint64_t requestId) +{ + // TODO: Make sure this is run on the Lua thread! + + if (!callback) + return false; + + lua_State* L = dmScript::GetCallbackLuaContext(callback); + + if (!dmScript::SetupCallback(callback)) + { + return false; + } + + dmScript::PushHash(L, id); // the name of the callback function + lua_newtable(L); + + // TODO: Do we want/need to pass the request id (i.e. exposigin it through all the script api's?) + // lua_pushinteger(L, requestId); + // lua_setfield(L, -2, "requestId"); + + return true; +} + +static void InvokeCallback(lua_State* L, dmScript::LuaCallbackInfo* callback) +{ + // TODO: Make sure this is run on the Lua thread! + + // We assume that the SetupCallback was successful in adding [self, messageName, table] on the stack + dmScript::PCall(L, 3, 0); // self + # user arguments + dmScript::TeardownCallback(callback); +} + +static void PushStringArray(lua_State* L, const char* name, std::vector& array) +{ + uint32_t size = array.size(); + + // An empty table is already on the stack + // Create a new one for the artboard names + lua_createtable(L, size, 0); + + for (uint32_t i = 0; i < size; ++i) + { + lua_pushstring(L, array[i].c_str()); + lua_rawseti(L, -2, i+1); + } + + lua_setfield(L, -2, name); +} + +static void PushPropertiesArray(lua_State* L, const char* name, std::vector& properties) +{ + uint32_t size = properties.size(); + + // An empty table is already on the stack + // Create a new one for the artboard names + lua_createtable(L, size, 0); + + for (uint32_t i = 0; i < size; ++i) + { + lua_createtable(L, 0, 0); + + lua_pushstring(L, properties[i].name.c_str()); + lua_setfield(L, -2, "name"); + + lua_pushstring(L, properties[i].metaData.c_str()); + lua_setfield(L, -2, "metaData"); + + lua_pushinteger(L, (int)properties[i].type); + lua_setfield(L, -2, "type"); + + lua_rawseti(L, -2, i+1); + } + + lua_setfield(L, -2, name); +} + +static void PushEnumsArray(lua_State* L, const char* name, std::vector& enums) +{ + uint32_t size = enums.size(); + + // An empty table is already on the stack + // Create a new one for the artboard names + lua_createtable(L, size, 0); + + for (uint32_t i = 0; i < size; ++i) + { + lua_createtable(L, 0, 0); + + lua_pushstring(L, enums[i].name.c_str()); + lua_setfield(L, -2, "name"); + + PushStringArray(L, "enumerants", enums[i].enumerants); + + lua_rawseti(L, -2, i+1); + } + + lua_setfield(L, -2, name); +} + +void RequestViewModelInstanceProperties(rive::FileHandle file, rive::ViewModelInstanceHandle instance, const char* viewmodel_name) +{ + if (instance == RIVE_NULL_HANDLE || viewmodel_name == 0 || viewmodel_name[0] == '\0') + { + return; + } + + ViewModelPropertyRequest* request = new ViewModelPropertyRequest(); + request->m_Instance = instance; + request->m_File = file; + uint64_t request_id = RegisterViewModelPropertyRequest(g_PendingViewModelPropertyRequests, request); + dmRiveCommands::GetCommandQueue()->requestViewModelPropertyDefinitions(file, viewmodel_name, request_id); +} + +void RequestDefaultViewModelInstanceProperties(rive::FileHandle file, rive::ArtboardHandle artboard, rive::ViewModelInstanceHandle instance) +{ + if (instance == RIVE_NULL_HANDLE || artboard == RIVE_NULL_HANDLE) + { + return; + } + + ViewModelPropertyRequest* request = new ViewModelPropertyRequest(); + request->m_Instance = instance; + request->m_File = file; + uint64_t request_id = RegisterViewModelPropertyRequest(g_PendingDefaultViewModelRequests, request); + dmRiveCommands::GetCommandQueue()->requestDefaultViewModelInfo(artboard, file, request_id); +} + + +struct DeleteRequestContext +{ +}; + +static void DeleteRequest(DeleteRequestContext*, const uint64_t*, ViewModelPropertyRequest** request) +{ + if (request && *request) + { + delete *request; + *request = 0; + } +} + +void ClearViewModelInstancePropertyRequests() +{ + EnsureViewModelPropertyRequestCapacity(g_PendingDefaultViewModelRequests, 1); + EnsureViewModelPropertyRequestCapacity(g_PendingViewModelPropertyRequests, 1); + DM_MUTEX_SCOPED_LOCK(g_ViewModelPropertyRequestMutex); + + DeleteRequestContext context; + g_PendingDefaultViewModelRequests.Iterate(DeleteRequest, &context); + g_PendingDefaultViewModelRequests.Clear(); + g_PendingViewModelPropertyRequests.Iterate(DeleteRequest, &context); + g_PendingViewModelPropertyRequests.Clear(); +} + +// ****************************************************************************************************************************** + +void FileListener::onArtboardsListed(const rive::FileHandle fileHandle, uint64_t requestId, std::vector artboardNames) +{ + static dmhash_t id = dmHashString64("onArtboardsListed"); + + if (m_Callback && SetupCallback(m_Callback, id, requestId)) + { + lua_State* L = dmScript::GetCallbackLuaContext(m_Callback); + PushStringArray(L, "artboardNames", artboardNames); + InvokeCallback(L, m_Callback); + } +} + +void FileListener::onFileError(const rive::FileHandle, uint64_t requestId, std::string error) +{ + RiveSceneData* scene = (RiveSceneData*)requestId; + if (scene) + { + dmLogError("%s: %s", dmHashReverseSafe64(scene->m_PathHash), error.c_str()); + } + else + { + dmLogError("%s", error.c_str()); + } + + static dmhash_t id = dmHashString64("onFileError"); + if (m_Callback && SetupCallback(m_Callback, id, requestId)) + { + lua_State* L = dmScript::GetCallbackLuaContext(m_Callback); + + lua_pushstring(L, error.c_str()); + lua_setfield(L, -2, "artboardNames"); + + InvokeCallback(L, m_Callback); + } +} + +void FileListener::onFileDeleted(const rive::FileHandle file, uint64_t requestId) +{ + static dmhash_t id = dmHashString64("onFileDeleted"); + if (m_Callback && SetupCallback(m_Callback, id, requestId)) + { + lua_State* L = dmScript::GetCallbackLuaContext(m_Callback); + + lua_pushinteger(L, (int)(uintptr_t)file); + lua_setfield(L, -2, "file"); + + InvokeCallback(L, m_Callback); + } +} + +void FileListener::onFileLoaded(const rive::FileHandle file, uint64_t requestId) +{ + static dmhash_t id = dmHashString64("onFileLoaded"); + if (m_Callback && SetupCallback(m_Callback, id, requestId)) + { + lua_State* L = dmScript::GetCallbackLuaContext(m_Callback); + + lua_pushinteger(L, (int)(uintptr_t)file); + lua_setfield(L, -2, "file"); + + InvokeCallback(L, m_Callback); + } +} + +void FileListener::onViewModelsListed(const rive::FileHandle file, uint64_t requestId, std::vector viewModelNames) +{ + static dmhash_t id = dmHashString64("onViewModelsListed"); + if (m_Callback && SetupCallback(m_Callback, id, requestId)) + { + lua_State* L = dmScript::GetCallbackLuaContext(m_Callback); + + lua_pushinteger(L, (int)(uintptr_t)file); + lua_setfield(L, -2, "file"); + PushStringArray(L, "viewModelNames", viewModelNames); + + InvokeCallback(L, m_Callback); + } +} + +void FileListener::onViewModelInstanceNamesListed(const rive::FileHandle file, uint64_t requestId, std::string viewModelName, std::vector instanceNames) +{ + static dmhash_t id = dmHashString64("onViewModelInstanceNamesListed"); + if (m_Callback && SetupCallback(m_Callback, id, requestId)) + { + lua_State* L = dmScript::GetCallbackLuaContext(m_Callback); + + lua_pushinteger(L, (int)(uintptr_t)file); + lua_setfield(L, -2, "file"); + + lua_pushstring(L, viewModelName.c_str()); + lua_setfield(L, -2, "viewModelName"); + + PushStringArray(L, "instanceNames", instanceNames); + + InvokeCallback(L, m_Callback); + } +} + +void FileListener::onViewModelPropertiesListed(const rive::FileHandle file, uint64_t requestId, std::string viewModelName, std::vector properties) +{ + ViewModelPropertyRequest* request = TakeViewModelPropertyRequest(g_PendingViewModelPropertyRequests, requestId); + if (request) + { + ViewModelInstanceListener* listener = GetViewModelInstanceListener(request->m_Instance); + rive::rcp queue = dmRiveCommands::GetCommandQueue(); + for (uint32_t i = 0; i < properties.size(); ++i) + { + const ViewModelPropertyData& property = properties[i]; + queue->subscribeToViewModelProperty(request->m_Instance, property.name, property.type); + switch (property.type) + { + case rive::DataType::boolean: + queue->requestViewModelInstanceBool(request->m_Instance, property.name); + break; + case rive::DataType::number: + case rive::DataType::integer: + queue->requestViewModelInstanceNumber(request->m_Instance, property.name); + break; + case rive::DataType::color: + queue->requestViewModelInstanceColor(request->m_Instance, property.name); + break; + case rive::DataType::enumType: + queue->requestViewModelInstanceEnum(request->m_Instance, property.name); + break; + case rive::DataType::string: + queue->requestViewModelInstanceString(request->m_Instance, property.name); + break; + case rive::DataType::list: + if (listener) + listener->EnsureListSize(dmHashString64(property.name.c_str()), 0); + queue->requestViewModelInstanceListSize(request->m_Instance, property.name); + break; + default: + break; + } + } + delete request; + } + + static dmhash_t id = dmHashString64("onViewModelPropertiesListed"); + if (m_Callback && SetupCallback(m_Callback, id, requestId)) + { + lua_State* L = dmScript::GetCallbackLuaContext(m_Callback); + + lua_pushinteger(L, (int)(uintptr_t)file); + lua_setfield(L, -2, "file"); + + lua_pushstring(L, viewModelName.c_str()); + lua_setfield(L, -2, "viewModelName"); + + PushPropertiesArray(L, "properties", properties); + + InvokeCallback(L, m_Callback); + } +} + +void FileListener::onViewModelEnumsListed(const rive::FileHandle file, uint64_t requestId, std::vector enums) +{ + static dmhash_t id = dmHashString64("onViewModelEnumsListed"); + if (m_Callback && SetupCallback(m_Callback, id, requestId)) + { + lua_State* L = dmScript::GetCallbackLuaContext(m_Callback); + + lua_pushinteger(L, (int)(uintptr_t)file); + lua_setfield(L, -2, "file"); + + PushEnumsArray(L, "enums", enums); + + InvokeCallback(L, m_Callback); + } +} + +// ****************************************************************************************************************************** + +void RenderImageListener::onRenderImageDecoded(const rive::RenderImageHandle, uint64_t requestId) +{ + +} +void RenderImageListener::onRenderImageError(const rive::RenderImageHandle, uint64_t requestId, std::string error) +{ + +} +void RenderImageListener::onRenderImageDeleted(const rive::RenderImageHandle, uint64_t requestId) +{ + +} + +// ****************************************************************************************************************************** + +void AudioSourceListener::onAudioSourceDecoded(const rive::AudioSourceHandle, uint64_t requestId) +{ + +} +void AudioSourceListener::onAudioSourceError(const rive::AudioSourceHandle, uint64_t requestId, std::string error) +{ + +} +void AudioSourceListener::onAudioSourceDeleted(const rive::AudioSourceHandle, uint64_t requestId) +{ + +} + +// ****************************************************************************************************************************** + + +void FontListener::onFontDecoded(const rive::FontHandle, uint64_t requestId) +{ + +} +void FontListener::onFontError(const rive::FontHandle, uint64_t requestId, std::string error) +{ + +} +void FontListener::onFontDeleted(const rive::FontHandle, uint64_t requestId) +{ + +} + +// ****************************************************************************************************************************** + +void ArtboardListener::onArtboardError(const rive::ArtboardHandle, uint64_t requestId, std::string error) +{ + +} +void ArtboardListener::onDefaultViewModelInfoReceived(const rive::ArtboardHandle, uint64_t requestId, std::string viewModelName, std::string instanceName) +{ + ViewModelPropertyRequest* request = TakeViewModelPropertyRequest(g_PendingDefaultViewModelRequests, requestId); + if (request) + { + RequestViewModelInstanceProperties(request->m_File, request->m_Instance, viewModelName.c_str()); + delete request; + } +} +void ArtboardListener::onArtboardDeleted(const rive::ArtboardHandle, uint64_t requestId) +{ + +} +void ArtboardListener::onStateMachinesListed(const rive::ArtboardHandle, uint64_t requestId, std::vector stateMachineNames) +{ + +} + +// ****************************************************************************************************************************** + +ViewModelInstanceListener::ViewModelInstanceListener() + : m_Callback(0) + , m_Mutex(dmMutex::New()) + , m_DeleteOnViewModelDeleted(false) +{ +} + +ViewModelInstanceListener::~ViewModelInstanceListener() +{ + { + DM_MUTEX_OPTIONAL_SCOPED_LOCK(m_Mutex); + DeleteContext context; + m_PropertyValues.Iterate(DeletePropertyValue, &context); + m_PropertyValues.Clear(); + m_ListSizes.Iterate(DeleteListSize, &context); + m_ListSizes.Clear(); + } + if (m_Mutex) + { + dmMutex::Delete(m_Mutex); + m_Mutex = 0; + } +} + +void ViewModelInstanceListener::SetAutoDeleteOnViewModelDeleted(bool value) +{ + m_DeleteOnViewModelDeleted = value; +} + +bool ViewModelInstanceListener::SetPropertyValue(dmhash_t path_hash, const rive::CommandQueue::ViewModelInstanceData& data) +{ + DM_MUTEX_OPTIONAL_SCOPED_LOCK(m_Mutex); + if (m_PropertyValues.Capacity() == 0) + { + EnsureTableCapacity(m_PropertyValues, 32); + } + else if (m_PropertyValues.Full()) + { + EnsureTableCapacity(m_PropertyValues, m_PropertyValues.Capacity() * 2); + } + + rive::CommandQueue::ViewModelInstanceData** entry = m_PropertyValues.Get(path_hash); + if (entry && *entry) + { + **entry = data; + return true; + } + + rive::CommandQueue::ViewModelInstanceData* copy = new rive::CommandQueue::ViewModelInstanceData(data); + m_PropertyValues.Put(path_hash, copy); + return true; +} + +bool ViewModelInstanceListener::GetPropertyValue(dmhash_t path_hash, rive::CommandQueue::ViewModelInstanceData& out) const +{ + DM_MUTEX_OPTIONAL_SCOPED_LOCK(m_Mutex); + if (m_PropertyValues.Capacity() == 0) + { + return false; + } + + rive::CommandQueue::ViewModelInstanceData* const* entry = m_PropertyValues.Get(path_hash); + if (entry && *entry) + { + out = **entry; + return true; + } + return false; +} + +bool ViewModelInstanceListener::GetListSize(dmhash_t path_hash, size_t& out) const +{ + DM_MUTEX_OPTIONAL_SCOPED_LOCK(m_Mutex); + if (m_ListSizes.Capacity() == 0) + { + return false; + } + + size_t* const* entry = m_ListSizes.Get(path_hash); + if (entry && *entry) + { + out = **entry; + return true; + } + return false; +} + +bool ViewModelInstanceListener::AdjustListSize(dmhash_t path_hash, int32_t delta) +{ + DM_MUTEX_OPTIONAL_SCOPED_LOCK(m_Mutex); + if (m_ListSizes.Capacity() == 0) + { + EnsureTableCapacity(m_ListSizes, 16); + } + else if (m_ListSizes.Full()) + { + EnsureTableCapacity(m_ListSizes, m_ListSizes.Capacity() * 2); + } + + size_t* const* entry = m_ListSizes.Get(path_hash); + if (!entry || !*entry) + { + size_t* copy = new size_t(0); + m_ListSizes.Put(path_hash, copy); + entry = m_ListSizes.Get(path_hash); + if (!entry || !*entry) + { + return false; + } + } + + int64_t new_size = (int64_t)**entry + (int64_t)delta; + if (new_size < 0) + { + new_size = 0; + } + **entry = (size_t)new_size; + return true; +} + +bool ViewModelInstanceListener::EnsureListSize(dmhash_t path_hash, size_t value) +{ + DM_MUTEX_OPTIONAL_SCOPED_LOCK(m_Mutex); + if (m_ListSizes.Capacity() == 0) + { + EnsureTableCapacity(m_ListSizes, 16); + } + else if (m_ListSizes.Full()) + { + EnsureTableCapacity(m_ListSizes, m_ListSizes.Capacity() * 2); + } + + size_t** entry = m_ListSizes.Get(path_hash); + if (entry && *entry) + { + return true; + } + + size_t* copy = new size_t(value); + m_ListSizes.Put(path_hash, copy); + return true; +} + +void ViewModelInstanceListener::onViewModelInstanceError(const rive::ViewModelInstanceHandle, uint64_t requestId, std::string error) +{ + +} + +void ViewModelInstanceListener::onViewModelDeleted(const rive::ViewModelInstanceHandle handle, uint64_t requestId) +{ + UnregisterViewModelInstanceListener(handle); + if (m_DeleteOnViewModelDeleted) + { + delete this; + } +} + +void ViewModelInstanceListener::onViewModelDataReceived(const rive::ViewModelInstanceHandle, uint64_t requestId, rive::CommandQueue::ViewModelInstanceData data) +{ + dmhash_t path_hash = dmHashString64(data.metaData.name.c_str()); + DM_MUTEX_OPTIONAL_SCOPED_LOCK(m_Mutex); + if (m_PropertyValues.Capacity() == 0) + { + EnsureTableCapacity(m_PropertyValues, 32); + } + else if (m_PropertyValues.Full()) + { + EnsureTableCapacity(m_PropertyValues, m_PropertyValues.Capacity() * 2); + } + + rive::CommandQueue::ViewModelInstanceData** entry = m_PropertyValues.Get(path_hash); + if (entry && *entry) + { + **entry = data; + } + else + { + rive::CommandQueue::ViewModelInstanceData* copy = new rive::CommandQueue::ViewModelInstanceData(data); + m_PropertyValues.Put(path_hash, copy); + } +} + +void ViewModelInstanceListener::onViewModelListSizeReceived(const rive::ViewModelInstanceHandle, uint64_t requestId, std::string path, size_t size) +{ + dmhash_t path_hash = dmHashString64(path.c_str()); + DM_MUTEX_OPTIONAL_SCOPED_LOCK(m_Mutex); + if (m_ListSizes.Capacity() == 0) + { + EnsureTableCapacity(m_ListSizes, 16); + } + else if (m_ListSizes.Full()) + { + EnsureTableCapacity(m_ListSizes, m_ListSizes.Capacity() * 2); + } + + size_t** entry = m_ListSizes.Get(path_hash); + if (entry && *entry) + { + **entry = size; + } + else + { + size_t* copy = new size_t(size); + m_ListSizes.Put(path_hash, copy); + } +} + +// ****************************************************************************************************************************** + +void StateMachineListener::onStateMachineError(const rive::StateMachineHandle, uint64_t requestId, std::string error) +{ + +} +void StateMachineListener::onStateMachineDeleted(const rive::StateMachineHandle, uint64_t requestId) +{ + +} +void StateMachineListener::onStateMachineSettled(const rive::StateMachineHandle, uint64_t requestId) +{ + +} + +} // namespace dmRive + +#endif // DM_RIVE_UNSUPPORTED diff --git a/defold-rive/src/script_rive_listeners.h b/defold-rive/src/script_rive_listeners.h new file mode 100644 index 00000000..3740d1df --- /dev/null +++ b/defold-rive/src/script_rive_listeners.h @@ -0,0 +1,119 @@ +// Copyright 2020 The Defold Foundation +// Licensed under the Defold License version 1.0 (the "License"); you may not use +// this file except in compliance with the License. +// +// You may obtain a copy of the License, together with FAQs at +// https://www.defold.com/license +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef DM_GAMESYS_SCRIPT_RIVE_LISTENERS_H +#define DM_GAMESYS_SCRIPT_RIVE_LISTENERS_H + +#include + +#include +#include +#include +#include + +#include +#include + +namespace dmRive +{ + +class FileListener : public rive::CommandQueue::FileListener +{ +public: + virtual void onArtboardsListed(const rive::FileHandle fileHandle, uint64_t requestId, std::vector artboardNames) override; + virtual void onFileError(const rive::FileHandle, uint64_t requestId, std::string error) override; + virtual void onFileDeleted(const rive::FileHandle, uint64_t requestId) override; + virtual void onFileLoaded(const rive::FileHandle, uint64_t requestId) override; + virtual void onViewModelsListed(const rive::FileHandle, uint64_t requestId, std::vector viewModelNames) override; + virtual void onViewModelInstanceNamesListed(const rive::FileHandle, uint64_t requestId, std::string viewModelName, std::vector instanceNames) override; + virtual void onViewModelPropertiesListed( const rive::FileHandle, uint64_t requestId, std::string viewModelName, std::vector properties) override; + virtual void onViewModelEnumsListed(const rive::FileHandle, uint64_t requestId, std::vector enums) override; + + dmScript::LuaCallbackInfo* m_Callback; +}; + +class RenderImageListener : public rive::CommandQueue::RenderImageListener +{ +public: + virtual void onRenderImageDecoded(const rive::RenderImageHandle, uint64_t requestId) override; + virtual void onRenderImageError(const rive::RenderImageHandle, uint64_t requestId, std::string error) override; + virtual void onRenderImageDeleted(const rive::RenderImageHandle, uint64_t requestId) override; + dmScript::LuaCallbackInfo* m_Callback; +}; + +class AudioSourceListener : public rive::CommandQueue::AudioSourceListener +{ +public: + virtual void onAudioSourceDecoded(const rive::AudioSourceHandle, uint64_t requestId) override; + virtual void onAudioSourceError(const rive::AudioSourceHandle, uint64_t requestId, std::string error) override; + virtual void onAudioSourceDeleted(const rive::AudioSourceHandle, uint64_t requestId) override; + dmScript::LuaCallbackInfo* m_Callback; +}; + +class FontListener : public rive::CommandQueue::FontListener +{ +public: + virtual void onFontDecoded(const rive::FontHandle, uint64_t requestId) override; + virtual void onFontError(const rive::FontHandle, uint64_t requestId, std::string error) override; + virtual void onFontDeleted(const rive::FontHandle, uint64_t requestId) override; + dmScript::LuaCallbackInfo* m_Callback; +}; + +class ArtboardListener : public rive::CommandQueue::ArtboardListener +{ +public: + virtual void onArtboardError(const rive::ArtboardHandle, uint64_t requestId, std::string error) override; + virtual void onDefaultViewModelInfoReceived(const rive::ArtboardHandle, uint64_t requestId, std::string viewModelName, std::string instanceName) override; + virtual void onArtboardDeleted(const rive::ArtboardHandle, uint64_t requestId) override; + virtual void onStateMachinesListed(const rive::ArtboardHandle, uint64_t requestId, std::vector stateMachineNames) override; + dmScript::LuaCallbackInfo* m_Callback; +}; + +class ViewModelInstanceListener : public rive::CommandQueue::ViewModelInstanceListener +{ +public: + ViewModelInstanceListener(); + ~ViewModelInstanceListener(); + void SetAutoDeleteOnViewModelDeleted(bool value); + bool SetPropertyValue(dmhash_t path_hash, const rive::CommandQueue::ViewModelInstanceData& data); + bool GetPropertyValue(dmhash_t path_hash, rive::CommandQueue::ViewModelInstanceData& out) const; + bool GetListSize(dmhash_t path_hash, size_t& out) const; + bool AdjustListSize(dmhash_t path_hash, int32_t delta); + bool EnsureListSize(dmhash_t path_hash, size_t value); + virtual void onViewModelInstanceError(const rive::ViewModelInstanceHandle, uint64_t requestId, std::string error) override; + virtual void onViewModelDeleted(const rive::ViewModelInstanceHandle, uint64_t requestId) override; + virtual void onViewModelDataReceived(const rive::ViewModelInstanceHandle, uint64_t requestId, rive::CommandQueue::ViewModelInstanceData) override; + virtual void onViewModelListSizeReceived(const rive::ViewModelInstanceHandle, uint64_t requestId, std::string path, size_t size) override; + dmScript::LuaCallbackInfo* m_Callback; +private: + dmMutex::HMutex m_Mutex; + bool m_DeleteOnViewModelDeleted; + dmHashTable m_PropertyValues; + dmHashTable m_ListSizes; +}; + +class StateMachineListener : public rive::CommandQueue::StateMachineListener +{ +public: + virtual void onStateMachineError(const rive::StateMachineHandle, uint64_t requestId, std::string error) override; + virtual void onStateMachineDeleted(const rive::StateMachineHandle, uint64_t requestId) override; + virtual void onStateMachineSettled(const rive::StateMachineHandle, uint64_t requestId) override; + dmScript::LuaCallbackInfo* m_Callback; +}; + +void RequestViewModelInstanceProperties(rive::FileHandle file, rive::ViewModelInstanceHandle instance, const char* viewmodel_name); +void RequestDefaultViewModelInstanceProperties(rive::FileHandle file, rive::ArtboardHandle artboard, rive::ViewModelInstanceHandle instance); +void ClearViewModelInstancePropertyRequests(); + +} // namespace + +#endif // DM_GAMESYS_SCRIPT_RIVE_LISTENERS_H diff --git a/defold-rive/src/viewmodel_instance_registry.cpp b/defold-rive/src/viewmodel_instance_registry.cpp new file mode 100644 index 00000000..e80aafd7 --- /dev/null +++ b/defold-rive/src/viewmodel_instance_registry.cpp @@ -0,0 +1,124 @@ +// Copyright 2026 The Defold Foundation +// Licensed under the Defold License version 1.0 (the "License"); you may not use +// this file except in compliance with the License. +// +// You may obtain a copy of the License, together with FAQs at +// https://www.defold.com/license +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#include "viewmodel_instance_registry.h" + +#include + +#include + +#include +#include + +#include "script_rive_listeners.h" + +namespace dmRive +{ + namespace + { + typedef uint64_t RegistryKey; + + dmMutex::HMutex g_ViewModelRegistryMutex = 0; + dmHashTable g_ViewModelInstanceListeners; + + struct DeleteRegistryContext + { + }; + + RegistryKey ToRegistryKey(rive::ViewModelInstanceHandle handle) + { + return (RegistryKey)(uintptr_t)handle; + } + + void DeleteRegistryEntry(DeleteRegistryContext*, const RegistryKey*, ViewModelInstanceListener** v) + { + if (v && *v) + { + delete *v; + *v = 0; + } + } + + void EnsureRegistryCapacity(uint32_t capacity) + { + if (!g_ViewModelRegistryMutex) + { + g_ViewModelRegistryMutex = dmMutex::New(); + } + uint32_t current_capacity = g_ViewModelInstanceListeners.Capacity(); + if (current_capacity >= capacity) + { + return; + } + uint32_t grow = capacity - current_capacity; + g_ViewModelInstanceListeners.OffsetCapacity((int32_t)grow); + } + } // namespace + + void RegisterViewModelInstanceListener(rive::ViewModelInstanceHandle handle, ViewModelInstanceListener* listener) + { + if (handle == RIVE_NULL_HANDLE || listener == 0) + { + return; + } + + EnsureRegistryCapacity(32); + DM_MUTEX_SCOPED_LOCK(g_ViewModelRegistryMutex); + if (g_ViewModelInstanceListeners.Full()) + { + EnsureRegistryCapacity(g_ViewModelInstanceListeners.Capacity() * 2); + } + const RegistryKey key = ToRegistryKey(handle); + assert(g_ViewModelInstanceListeners.Get(key) == 0); + g_ViewModelInstanceListeners.Put(key, listener); + } + + ViewModelInstanceListener* GetViewModelInstanceListener(rive::ViewModelInstanceHandle handle) + { + if (handle == RIVE_NULL_HANDLE) + { + return 0; + } + + EnsureRegistryCapacity(1); + DM_MUTEX_SCOPED_LOCK(g_ViewModelRegistryMutex); + const RegistryKey key = ToRegistryKey(handle); + ViewModelInstanceListener** entry = g_ViewModelInstanceListeners.Get(key); + return entry ? *entry : 0; + } + + void UnregisterViewModelInstanceListener(rive::ViewModelInstanceHandle handle) + { + if (handle == RIVE_NULL_HANDLE) + { + return; + } + + EnsureRegistryCapacity(1); + DM_MUTEX_SCOPED_LOCK(g_ViewModelRegistryMutex); + const RegistryKey key = ToRegistryKey(handle); + if (g_ViewModelInstanceListeners.Get(key)) + { + g_ViewModelInstanceListeners.Erase(key); + } + } + + void ClearViewModelInstanceListeners() + { + EnsureRegistryCapacity(1); + DM_MUTEX_SCOPED_LOCK(g_ViewModelRegistryMutex); + DeleteRegistryContext context; + g_ViewModelInstanceListeners.Iterate(DeleteRegistryEntry, &context); + g_ViewModelInstanceListeners.Clear(); + } + +} // namespace dmRive diff --git a/defold-rive/src/viewmodel_instance_registry.h b/defold-rive/src/viewmodel_instance_registry.h new file mode 100644 index 00000000..059995eb --- /dev/null +++ b/defold-rive/src/viewmodel_instance_registry.h @@ -0,0 +1,28 @@ +// Copyright 2026 The Defold Foundation +// Licensed under the Defold License version 1.0 (the "License"); you may not use +// this file except in compliance with the License. +// +// You may obtain a copy of the License, together with FAQs at +// https://www.defold.com/license +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#ifndef DM_RIVE_VIEWMODEL_INSTANCE_REGISTRY_H +#define DM_RIVE_VIEWMODEL_INSTANCE_REGISTRY_H + +#include + +namespace dmRive +{ + class ViewModelInstanceListener; + + void RegisterViewModelInstanceListener(rive::ViewModelInstanceHandle handle, ViewModelInstanceListener* listener); + ViewModelInstanceListener* GetViewModelInstanceListener(rive::ViewModelInstanceHandle handle); + void UnregisterViewModelInstanceListener(rive::ViewModelInstanceHandle handle); + void ClearViewModelInstanceListeners(); +} // namespace dmRive + +#endif // DM_RIVE_VIEWMODEL_INSTANCE_REGISTRY_H diff --git a/game.project b/game.project index 7e2e7bf6..06ba01db 100644 --- a/game.project +++ b/game.project @@ -18,7 +18,7 @@ package = com.defold.extensionrive [project] title = extension-rive bundle_exclude_resources = utils -custom_resources = /main/outofband/walle,/main/outofband/font,/main/fontfallback/LemonBrushArabicPersonalUseOnly-Regular-5277260.ttf +custom_resources = /main/outofband/font dependencies#0 = https://github.com/andsve/dirtylarry/archive/master.zip [library] diff --git a/main/bones/bone.go b/main/bones/bone.go deleted file mode 100644 index dbcdb1a4..00000000 --- a/main/bones/bone.go +++ /dev/null @@ -1,20 +0,0 @@ -embedded_components { - id: "sprite" - type: "sprite" - data: "tile_set: \"/assets/main.atlas\"\n" - "default_animation: \"bone\"\n" - "material: \"/builtins/materials/sprite.material\"\n" - "blend_mode: BLEND_MODE_ALPHA\n" - "" - position { - x: 0.0 - y: 0.0 - z: 0.0 - } - rotation { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } -} diff --git a/main/bones/bones.collection b/main/bones/bones.collection deleted file mode 100644 index 78bdcb58..00000000 --- a/main/bones/bones.collection +++ /dev/null @@ -1,38 +0,0 @@ -name: "bones" -instances { - id: "back" - prototype: "/main/menu/back.go" -} -scale_along_z: 0 -embedded_instances { - id: "go" - data: "components {\n" - " id: \"bones\"\n" - " component: \"/main/bones/bones.script\"\n" - "}\n" - "embedded_components {\n" - " id: \"rivemodel\"\n" - " type: \"rivemodel\"\n" - " data: \"scene: \\\"/main/bones/marty.rivescene\\\"\\n" - "default_animation: \\\"Animation1\\\"\\n" - "material: \\\"/defold-rive/assets/rivemodel.material\\\"\\n" - "create_go_bones: true\\n" - "artboard: \\\"New Artboard\\\"\\n" - "\"\n" - "}\n" - "embedded_components {\n" - " id: \"bonefactory\"\n" - " type: \"factory\"\n" - " data: \"prototype: \\\"/main/bones/bone.go\\\"\\n" - "\"\n" - "}\n" - "" - position { - x: 486.0 - y: 271.726 - } - scale3 { - x: 0.5 - y: 0.5 - } -} diff --git a/main/bones/bones.script b/main/bones/bones.script deleted file mode 100644 index 2996f1e1..00000000 --- a/main/bones/bones.script +++ /dev/null @@ -1,45 +0,0 @@ -function init(self) - -- Marty (fixed) - local names = {"hips","Belly","Chest","Neck","Head","Hair","Arm_right","Forearm_right","Hand_right","Arm_left","Forearm_left","Hand_left","Pocket_rignt","Pocket_left","Hoverboard","Shoe_right_foot","Shoe_right","Root Bone R","Shoe_left_foot","Shoe_left","Root Bone L","Hoverboard_ctrl"} - --local names = {"Neck", "hips"} - - --rive.cancel("#rivemodel") - - local pos = go.get_position() - - for _, name in ipairs(names) do - local bone_go = rive.get_go("#rivemodel", name) - - local id = factory.create("#bonefactory", vmath.vector3(0,0,0)) - go.set_parent(id, bone_go, false) - end - - --go.animate(".", "position.x", go.PLAYBACK_LOOP_PINGPONG, pos.x + 100, go.EASING_LINEAR, 2) -end - -function update(self, dt) -end - -function on_message(self, message_id, message, sender) - -- Add message-handling code here - -- Learn more: https://defold.com/manuals/message-passing/ - -- Remove this function if not needed -end - -function on_input(self, action_id, action) - -- Add input-handling code here. The game object this script is attached to - -- must have acquired input focus: - -- - -- msg.post(".", "acquire_input_focus") - -- - -- All mapped input bindings will be received. Mouse and touch input will - -- be received regardless of where on the screen it happened. - -- Learn more: https://defold.com/manuals/input/ - -- Remove this function if not needed -end - -function on_reload(self) - -- Add reload-handling code here - -- Learn more: https://defold.com/manuals/hot-reload/ - -- Remove this function if not needed -end diff --git a/main/bones/marty.riv b/main/bones/marty.riv deleted file mode 100644 index a3b7b249..00000000 Binary files a/main/bones/marty.riv and /dev/null differ diff --git a/main/bones/marty.rivescene b/main/bones/marty.rivescene deleted file mode 100644 index 7abc8f26..00000000 --- a/main/bones/marty.rivescene +++ /dev/null @@ -1,2 +0,0 @@ -scene: "/main/bones/marty.riv" -atlas: "/defold-rive/assets/empty.atlas" diff --git a/main/car/car.collection b/main/car/car.collection deleted file mode 100644 index 65ec9567..00000000 --- a/main/car/car.collection +++ /dev/null @@ -1,35 +0,0 @@ -name: "main" -instances { - id: "back" - prototype: "/main/menu/back.go" -} -scale_along_z: 0 -embedded_instances { - id: "go" - data: "components {\n" - " id: \"test\"\n" - " component: \"/assets/test.rivemodel\"\n" - "}\n" - "components {\n" - " id: \"main\"\n" - " component: \"/main/car/car.script\"\n" - "}\n" - "" - position { - x: 337.7781 - y: 268.6295 - } - scale3 { - x: 0.41 - y: 0.41 - z: 0.82 - } -} -embedded_instances { - id: "gui" - data: "components {\n" - " id: \"gui\"\n" - " component: \"/main/car/gui.gui\"\n" - "}\n" - "" -} diff --git a/main/car/car.riv b/main/car/car.riv deleted file mode 100644 index 81202cb8..00000000 Binary files a/main/car/car.riv and /dev/null differ diff --git a/main/car/car.rivemodel b/main/car/car.rivemodel deleted file mode 100644 index 478174ff..00000000 --- a/main/car/car.rivemodel +++ /dev/null @@ -1,5 +0,0 @@ -scene: "/main/car/car.rivescene" -default_animation: "idle" -material: "/defold-rive/assets/rivemodel.material" -blend_mode: BLEND_MODE_ALPHA -default_state_machine: "" diff --git a/main/car/car.rivescene b/main/car/car.rivescene deleted file mode 100644 index f13b4cd8..00000000 --- a/main/car/car.rivescene +++ /dev/null @@ -1,2 +0,0 @@ -scene: "/assets/rive/off_road_car.riv" -atlas: "/defold-rive/assets/empty.atlas" diff --git a/main/car/car.script b/main/car/car.script deleted file mode 100644 index 1d27e473..00000000 --- a/main/car/car.script +++ /dev/null @@ -1,44 +0,0 @@ -function init(self) - self.v = 0 - self.url = msg.url("#test") - self.anim = 0 - self.cursor = 0 - self.playback = go.PLAYBACK_LOOP_FORWARD -end - -function update(self, dt) - self.v = self.v + dt - self.anim = go.get(self.url, "animation") - self.cursor = go.get(self.url, "cursor") - - if self.anim ~= 0 then - msg.post("/gui#gui", "animation_cursor", { cursor = self.cursor }) - end -end - -local function anim_done(self, message_id, message, sender) - if message_id == hash("rive_animation_done") then - msg.post("/gui#gui", "animation_complete") - end -end - -function play_animation(self, anim, playback, offset, rate) - if anim ~= self.anim or playback ~= self.playback then - print("Playing animation: " .. tostring(anim) .. " with playback mode " .. tostring(playback) .. ", rate " .. tostring(rate) .. ", offset " .. tostring(offset)) - self.anim = anim - self.playback = playback - rive.play_anim(self.url, self.anim, self.playback, { offset = offset, playback_rate = rate }, anim_done) - end -end - -function on_message(self, message_id, message) - if message_id == hash("MSG_CANCEL") then - rive.cancel(self.url) - elseif message_id == hash("MSG_PLAY") then - play_animation(self, message.animation, message.playback, message.offset, message.rate) - elseif message_id == hash("MSG_PLAYBACK_RATE") then - go.set(self.url, "playback_rate", message.rate) - elseif message_id == hash("MSG_PLAYBACK_CURSOR") then - go.set(self.url, "cursor", message.cursor) - end -end \ No newline at end of file diff --git a/main/car/gui.gui b/main/car/gui.gui deleted file mode 100644 index 0c07d6e2..00000000 --- a/main/car/gui.gui +++ /dev/null @@ -1,3178 +0,0 @@ -script: "/main/car/gui.gui_script" -background_color { - x: 0.0 - y: 0.0 - z: 0.0 - w: 0.0 -} -nodes { - position { - x: 0.0 - y: -101.0 - z: 0.0 - w: 1.0 - } - rotation { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - scale { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - size { - x: 200.0 - y: 100.0 - z: 0.0 - w: 1.0 - } - color { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - type: TYPE_BOX - blend_mode: BLEND_MODE_ALPHA - texture: "" - id: "controls" - xanchor: XANCHOR_NONE - yanchor: YANCHOR_NONE - pivot: PIVOT_CENTER - adjust_mode: ADJUST_MODE_FIT - layer: "" - inherit_alpha: true - slice9 { - x: 0.0 - y: 0.0 - z: 0.0 - w: 0.0 - } - clipping_mode: CLIPPING_MODE_NONE - clipping_visible: true - clipping_inverted: false - alpha: 1.0 - template_node_child: false - size_mode: SIZE_MODE_AUTO - custom_type: 0 - enabled: true - visible: false - material: "" -} -nodes { - position { - x: 757.0 - y: 251.0 - z: 0.0 - w: 1.0 - } - rotation { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - scale { - x: 0.5 - y: 0.5 - z: 1.0 - w: 1.0 - } - size { - x: 200.0 - y: 100.0 - z: 0.0 - w: 1.0 - } - color { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - type: TYPE_TEMPLATE - id: "playback_offset" - parent: "controls" - layer: "" - inherit_alpha: true - alpha: 1.0 - template: "/dirtylarry/slider.gui" - template_node_child: false - custom_type: 0 - enabled: true -} -nodes { - position { - x: -32.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - rotation { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - scale { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - size { - x: 364.0 - y: 32.0 - z: 0.0 - w: 1.0 - } - color { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - type: TYPE_BOX - blend_mode: BLEND_MODE_ALPHA - texture: "" - id: "playback_offset/larrysafearea" - xanchor: XANCHOR_NONE - yanchor: YANCHOR_NONE - pivot: PIVOT_W - adjust_mode: ADJUST_MODE_FIT - parent: "playback_offset" - layer: "" - inherit_alpha: true - slice9 { - x: 0.0 - y: 0.0 - z: 0.0 - w: 0.0 - } - clipping_mode: CLIPPING_MODE_NONE - clipping_visible: true - clipping_inverted: false - alpha: 0.0 - template_node_child: true - size_mode: SIZE_MODE_MANUAL - custom_type: 0 - enabled: true - visible: true - material: "" -} -nodes { - position { - x: 32.0 - y: -4.0 - z: 0.0 - w: 1.0 - } - rotation { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - scale { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - size { - x: 300.0 - y: 32.0 - z: 0.0 - w: 1.0 - } - color { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - type: TYPE_BOX - blend_mode: BLEND_MODE_ALPHA - texture: "dirtylarry/button_pressed" - id: "playback_offset/larryslider" - xanchor: XANCHOR_NONE - yanchor: YANCHOR_NONE - pivot: PIVOT_W - adjust_mode: ADJUST_MODE_FIT - parent: "playback_offset/larrysafearea" - layer: "" - inherit_alpha: false - slice9 { - x: 16.0 - y: 16.0 - z: 16.0 - w: 16.0 - } - clipping_mode: CLIPPING_MODE_NONE - clipping_visible: true - clipping_inverted: false - alpha: 1.0 - template_node_child: true - size_mode: SIZE_MODE_MANUAL - custom_type: 0 - enabled: true - visible: true - material: "" -} -nodes { - position { - x: 134.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - rotation { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - scale { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - size { - x: 32.0 - y: 32.0 - z: 0.0 - w: 1.0 - } - color { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - type: TYPE_BOX - blend_mode: BLEND_MODE_ALPHA - texture: "dirtylarry/radio_checked_normal" - id: "playback_offset/larrycursor" - xanchor: XANCHOR_NONE - yanchor: YANCHOR_NONE - pivot: PIVOT_W - adjust_mode: ADJUST_MODE_FIT - parent: "playback_offset/larryslider" - layer: "" - inherit_alpha: true - slice9 { - x: 0.0 - y: 0.0 - z: 0.0 - w: 0.0 - } - clipping_mode: CLIPPING_MODE_NONE - clipping_visible: true - clipping_inverted: false - alpha: 1.0 - template_node_child: true - size_mode: SIZE_MODE_MANUAL - custom_type: 0 - enabled: true - visible: true - material: "" -} -nodes { - position { - x: 306.0 - y: 3.0 - z: 0.0 - w: 1.0 - } - rotation { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - scale { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - size { - x: 200.0 - y: 42.0 - z: 0.0 - w: 1.0 - } - color { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - type: TYPE_TEXT - blend_mode: BLEND_MODE_ALPHA - text: "0.5" - font: "larryfont" - id: "playback_offset/larryvalue" - xanchor: XANCHOR_NONE - yanchor: YANCHOR_NONE - pivot: PIVOT_W - outline { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - shadow { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - adjust_mode: ADJUST_MODE_FIT - line_break: false - parent: "playback_offset/larryslider" - layer: "" - inherit_alpha: true - alpha: 1.0 - outline_alpha: 1.0 - shadow_alpha: 1.0 - template_node_child: true - text_leading: 1.0 - text_tracking: 0.0 - custom_type: 0 - enabled: true - visible: true - material: "" -} -nodes { - position { - x: -8.0 - y: 3.0 - z: 0.0 - w: 1.0 - } - rotation { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - scale { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - size { - x: 200.0 - y: 42.0 - z: 0.0 - w: 1.0 - } - color { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - type: TYPE_TEXT - blend_mode: BLEND_MODE_ALPHA - text: "Playback Offset\n" - "" - font: "larryfont" - id: "playback_offset/larrylabel" - xanchor: XANCHOR_NONE - yanchor: YANCHOR_NONE - pivot: PIVOT_E - outline { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - shadow { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - adjust_mode: ADJUST_MODE_FIT - line_break: false - parent: "playback_offset/larryslider" - layer: "" - inherit_alpha: true - alpha: 1.0 - outline_alpha: 1.0 - shadow_alpha: 1.0 - overridden_fields: 8 - template_node_child: true - text_leading: 1.0 - text_tracking: 0.0 - custom_type: 0 - enabled: true - visible: true - material: "" -} -nodes { - position { - x: 757.0 - y: 282.0 - z: 0.0 - w: 1.0 - } - rotation { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - scale { - x: 0.5 - y: 0.5 - z: 0.5 - w: 1.0 - } - size { - x: 200.0 - y: 100.0 - z: 0.0 - w: 1.0 - } - color { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - type: TYPE_TEMPLATE - id: "playback_rate" - parent: "controls" - layer: "" - inherit_alpha: true - alpha: 1.0 - template: "/dirtylarry/slider.gui" - template_node_child: false - custom_type: 0 - enabled: true -} -nodes { - position { - x: -32.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - rotation { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - scale { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - size { - x: 364.0 - y: 32.0 - z: 0.0 - w: 1.0 - } - color { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - type: TYPE_BOX - blend_mode: BLEND_MODE_ALPHA - texture: "" - id: "playback_rate/larrysafearea" - xanchor: XANCHOR_NONE - yanchor: YANCHOR_NONE - pivot: PIVOT_W - adjust_mode: ADJUST_MODE_FIT - parent: "playback_rate" - layer: "" - inherit_alpha: true - slice9 { - x: 0.0 - y: 0.0 - z: 0.0 - w: 0.0 - } - clipping_mode: CLIPPING_MODE_NONE - clipping_visible: true - clipping_inverted: false - alpha: 0.0 - template_node_child: true - size_mode: SIZE_MODE_MANUAL - custom_type: 0 - enabled: true - visible: true - material: "" -} -nodes { - position { - x: 32.0 - y: -4.0 - z: 0.0 - w: 1.0 - } - rotation { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - scale { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - size { - x: 300.0 - y: 32.0 - z: 0.0 - w: 1.0 - } - color { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - type: TYPE_BOX - blend_mode: BLEND_MODE_ALPHA - texture: "dirtylarry/button_pressed" - id: "playback_rate/larryslider" - xanchor: XANCHOR_NONE - yanchor: YANCHOR_NONE - pivot: PIVOT_W - adjust_mode: ADJUST_MODE_FIT - parent: "playback_rate/larrysafearea" - layer: "" - inherit_alpha: false - slice9 { - x: 16.0 - y: 16.0 - z: 16.0 - w: 16.0 - } - clipping_mode: CLIPPING_MODE_NONE - clipping_visible: true - clipping_inverted: false - alpha: 1.0 - template_node_child: true - size_mode: SIZE_MODE_MANUAL - custom_type: 0 - enabled: true - visible: true - material: "" -} -nodes { - position { - x: 134.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - rotation { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - scale { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - size { - x: 32.0 - y: 32.0 - z: 0.0 - w: 1.0 - } - color { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - type: TYPE_BOX - blend_mode: BLEND_MODE_ALPHA - texture: "dirtylarry/radio_checked_normal" - id: "playback_rate/larrycursor" - xanchor: XANCHOR_NONE - yanchor: YANCHOR_NONE - pivot: PIVOT_W - adjust_mode: ADJUST_MODE_FIT - parent: "playback_rate/larryslider" - layer: "" - inherit_alpha: true - slice9 { - x: 0.0 - y: 0.0 - z: 0.0 - w: 0.0 - } - clipping_mode: CLIPPING_MODE_NONE - clipping_visible: true - clipping_inverted: false - alpha: 1.0 - template_node_child: true - size_mode: SIZE_MODE_MANUAL - custom_type: 0 - enabled: true - visible: true - material: "" -} -nodes { - position { - x: 306.0 - y: 3.0 - z: 0.0 - w: 1.0 - } - rotation { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - scale { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - size { - x: 200.0 - y: 42.0 - z: 0.0 - w: 1.0 - } - color { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - type: TYPE_TEXT - blend_mode: BLEND_MODE_ALPHA - text: "0.5" - font: "larryfont" - id: "playback_rate/larryvalue" - xanchor: XANCHOR_NONE - yanchor: YANCHOR_NONE - pivot: PIVOT_W - outline { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - shadow { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - adjust_mode: ADJUST_MODE_FIT - line_break: false - parent: "playback_rate/larryslider" - layer: "" - inherit_alpha: true - alpha: 1.0 - outline_alpha: 1.0 - shadow_alpha: 1.0 - template_node_child: true - text_leading: 1.0 - text_tracking: 0.0 - custom_type: 0 - enabled: true - visible: true - material: "" -} -nodes { - position { - x: -8.0 - y: 3.0 - z: 0.0 - w: 1.0 - } - rotation { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - scale { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - size { - x: 200.0 - y: 42.0 - z: 0.0 - w: 1.0 - } - color { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - type: TYPE_TEXT - blend_mode: BLEND_MODE_ALPHA - text: "Playback Rate" - font: "larryfont" - id: "playback_rate/larrylabel" - xanchor: XANCHOR_NONE - yanchor: YANCHOR_NONE - pivot: PIVOT_E - outline { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - shadow { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - adjust_mode: ADJUST_MODE_FIT - line_break: false - parent: "playback_rate/larryslider" - layer: "" - inherit_alpha: true - alpha: 1.0 - outline_alpha: 1.0 - shadow_alpha: 1.0 - overridden_fields: 8 - template_node_child: true - text_leading: 1.0 - text_tracking: 0.0 - custom_type: 0 - enabled: true - visible: true - material: "" -} -nodes { - position { - x: 612.0 - y: 613.0 - z: 0.0 - w: 1.0 - } - rotation { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - scale { - x: 0.5 - y: 0.5 - z: 1.0 - w: 1.0 - } - size { - x: 200.0 - y: 100.0 - z: 0.0 - w: 1.0 - } - color { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - type: TYPE_TEMPLATE - id: "playback_once_forward" - parent: "controls" - layer: "" - inherit_alpha: true - alpha: 1.0 - template: "/dirtylarry/radio_label.gui" - template_node_child: false - custom_type: 0 - enabled: true -} -nodes { - position { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - rotation { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - scale { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - size { - x: 64.0 - y: 68.0 - z: 0.0 - w: 1.0 - } - color { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - type: TYPE_BOX - blend_mode: BLEND_MODE_ALPHA - texture: "radio/radio_normal" - id: "playback_once_forward/larryradio" - xanchor: XANCHOR_NONE - yanchor: YANCHOR_NONE - pivot: PIVOT_CENTER - adjust_mode: ADJUST_MODE_FIT - parent: "playback_once_forward" - layer: "" - inherit_alpha: true - slice9 { - x: 0.0 - y: 0.0 - z: 0.0 - w: 0.0 - } - clipping_mode: CLIPPING_MODE_NONE - clipping_visible: true - clipping_inverted: false - alpha: 1.0 - template_node_child: true - size_mode: SIZE_MODE_MANUAL - custom_type: 0 - enabled: true - visible: true - material: "" -} -nodes { - position { - x: 46.0 - y: 3.0 - z: 0.0 - w: 1.0 - } - rotation { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - scale { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - size { - x: 200.0 - y: 50.0 - z: 0.0 - w: 1.0 - } - color { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - type: TYPE_TEXT - blend_mode: BLEND_MODE_ALPHA - text: "Once Forward\n" - "" - font: "larryfont" - id: "playback_once_forward/larrylabel" - xanchor: XANCHOR_NONE - yanchor: YANCHOR_NONE - pivot: PIVOT_W - outline { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - shadow { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - adjust_mode: ADJUST_MODE_FIT - line_break: false - parent: "playback_once_forward" - layer: "" - inherit_alpha: true - alpha: 1.0 - outline_alpha: 1.0 - shadow_alpha: 1.0 - overridden_fields: 8 - template_node_child: true - text_leading: 1.0 - text_tracking: 0.0 - custom_type: 0 - enabled: true - visible: true - material: "" -} -nodes { - position { - x: 612.0 - y: 492.0 - z: 0.0 - w: 1.0 - } - rotation { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - scale { - x: 0.5 - y: 0.5 - z: 1.0 - w: 1.0 - } - size { - x: 200.0 - y: 100.0 - z: 0.0 - w: 1.0 - } - color { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - type: TYPE_TEMPLATE - id: "playback_loop_forward" - parent: "controls" - layer: "" - inherit_alpha: true - alpha: 1.0 - template: "/dirtylarry/radio_label.gui" - template_node_child: false - custom_type: 0 - enabled: true -} -nodes { - position { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - rotation { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - scale { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - size { - x: 64.0 - y: 68.0 - z: 0.0 - w: 1.0 - } - color { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - type: TYPE_BOX - blend_mode: BLEND_MODE_ALPHA - texture: "radio/radio_normal" - id: "playback_loop_forward/larryradio" - xanchor: XANCHOR_NONE - yanchor: YANCHOR_NONE - pivot: PIVOT_CENTER - adjust_mode: ADJUST_MODE_FIT - parent: "playback_loop_forward" - layer: "" - inherit_alpha: true - slice9 { - x: 0.0 - y: 0.0 - z: 0.0 - w: 0.0 - } - clipping_mode: CLIPPING_MODE_NONE - clipping_visible: true - clipping_inverted: false - alpha: 1.0 - template_node_child: true - size_mode: SIZE_MODE_MANUAL - custom_type: 0 - enabled: true - visible: true - material: "" -} -nodes { - position { - x: 46.0 - y: 3.0 - z: 0.0 - w: 1.0 - } - rotation { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - scale { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - size { - x: 200.0 - y: 50.0 - z: 0.0 - w: 1.0 - } - color { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - type: TYPE_TEXT - blend_mode: BLEND_MODE_ALPHA - text: "Loop Forward\n" - "" - font: "larryfont" - id: "playback_loop_forward/larrylabel" - xanchor: XANCHOR_NONE - yanchor: YANCHOR_NONE - pivot: PIVOT_W - outline { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - shadow { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - adjust_mode: ADJUST_MODE_FIT - line_break: false - parent: "playback_loop_forward" - layer: "" - inherit_alpha: true - alpha: 1.0 - outline_alpha: 1.0 - shadow_alpha: 1.0 - overridden_fields: 8 - template_node_child: true - text_leading: 1.0 - text_tracking: 0.0 - custom_type: 0 - enabled: true - visible: true - material: "" -} -nodes { - position { - x: 612.0 - y: 573.0 - z: 0.0 - w: 1.0 - } - rotation { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - scale { - x: 0.5 - y: 0.5 - z: 1.0 - w: 1.0 - } - size { - x: 200.0 - y: 100.0 - z: 0.0 - w: 1.0 - } - color { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - type: TYPE_TEMPLATE - id: "playback_once_backward" - parent: "controls" - layer: "" - inherit_alpha: true - alpha: 1.0 - template: "/dirtylarry/radio_label.gui" - template_node_child: false - custom_type: 0 - enabled: true -} -nodes { - position { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - rotation { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - scale { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - size { - x: 64.0 - y: 68.0 - z: 0.0 - w: 1.0 - } - color { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - type: TYPE_BOX - blend_mode: BLEND_MODE_ALPHA - texture: "radio/radio_normal" - id: "playback_once_backward/larryradio" - xanchor: XANCHOR_NONE - yanchor: YANCHOR_NONE - pivot: PIVOT_CENTER - adjust_mode: ADJUST_MODE_FIT - parent: "playback_once_backward" - layer: "" - inherit_alpha: true - slice9 { - x: 0.0 - y: 0.0 - z: 0.0 - w: 0.0 - } - clipping_mode: CLIPPING_MODE_NONE - clipping_visible: true - clipping_inverted: false - alpha: 1.0 - template_node_child: true - size_mode: SIZE_MODE_MANUAL - custom_type: 0 - enabled: true - visible: true - material: "" -} -nodes { - position { - x: 46.0 - y: 3.0 - z: 0.0 - w: 1.0 - } - rotation { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - scale { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - size { - x: 200.0 - y: 50.0 - z: 0.0 - w: 1.0 - } - color { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - type: TYPE_TEXT - blend_mode: BLEND_MODE_ALPHA - text: "Once Backward" - font: "larryfont" - id: "playback_once_backward/larrylabel" - xanchor: XANCHOR_NONE - yanchor: YANCHOR_NONE - pivot: PIVOT_W - outline { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - shadow { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - adjust_mode: ADJUST_MODE_FIT - line_break: false - parent: "playback_once_backward" - layer: "" - inherit_alpha: true - alpha: 1.0 - outline_alpha: 1.0 - shadow_alpha: 1.0 - overridden_fields: 8 - template_node_child: true - text_leading: 1.0 - text_tracking: 0.0 - custom_type: 0 - enabled: true - visible: true - material: "" -} -nodes { - position { - x: 612.0 - y: 532.0 - z: 0.0 - w: 1.0 - } - rotation { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - scale { - x: 0.5 - y: 0.5 - z: 1.0 - w: 1.0 - } - size { - x: 200.0 - y: 100.0 - z: 0.0 - w: 1.0 - } - color { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - type: TYPE_TEMPLATE - id: "playback_once_pingpong" - parent: "controls" - layer: "" - inherit_alpha: true - alpha: 1.0 - template: "/dirtylarry/radio_label.gui" - template_node_child: false - custom_type: 0 - enabled: true -} -nodes { - position { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - rotation { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - scale { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - size { - x: 64.0 - y: 68.0 - z: 0.0 - w: 1.0 - } - color { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - type: TYPE_BOX - blend_mode: BLEND_MODE_ALPHA - texture: "radio/radio_normal" - id: "playback_once_pingpong/larryradio" - xanchor: XANCHOR_NONE - yanchor: YANCHOR_NONE - pivot: PIVOT_CENTER - adjust_mode: ADJUST_MODE_FIT - parent: "playback_once_pingpong" - layer: "" - inherit_alpha: true - slice9 { - x: 0.0 - y: 0.0 - z: 0.0 - w: 0.0 - } - clipping_mode: CLIPPING_MODE_NONE - clipping_visible: true - clipping_inverted: false - alpha: 1.0 - template_node_child: true - size_mode: SIZE_MODE_MANUAL - custom_type: 0 - enabled: true - visible: true - material: "" -} -nodes { - position { - x: 46.0 - y: 3.0 - z: 0.0 - w: 1.0 - } - rotation { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - scale { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - size { - x: 200.0 - y: 50.0 - z: 0.0 - w: 1.0 - } - color { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - type: TYPE_TEXT - blend_mode: BLEND_MODE_ALPHA - text: "Once Ping Pong\n" - "" - font: "larryfont" - id: "playback_once_pingpong/larrylabel" - xanchor: XANCHOR_NONE - yanchor: YANCHOR_NONE - pivot: PIVOT_W - outline { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - shadow { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - adjust_mode: ADJUST_MODE_FIT - line_break: false - parent: "playback_once_pingpong" - layer: "" - inherit_alpha: true - alpha: 1.0 - outline_alpha: 1.0 - shadow_alpha: 1.0 - overridden_fields: 8 - template_node_child: true - text_leading: 1.0 - text_tracking: 0.0 - custom_type: 0 - enabled: true - visible: true - material: "" -} -nodes { - position { - x: 871.0 - y: 329.0 - z: 0.0 - w: 1.0 - } - rotation { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - scale { - x: 0.5 - y: 0.5 - z: 1.0 - w: 1.0 - } - size { - x: 200.0 - y: 100.0 - z: 0.0 - w: 1.0 - } - color { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - type: TYPE_TEMPLATE - id: "animation_broken" - parent: "controls" - layer: "" - inherit_alpha: true - alpha: 1.0 - template: "/dirtylarry/button.gui" - template_node_child: false - custom_type: 0 - enabled: true -} -nodes { - position { - x: -16.0 - y: 5.0 - z: 0.0 - w: 1.0 - } - rotation { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - scale { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - size { - x: 350.0 - y: 88.0 - z: 0.0 - w: 1.0 - } - color { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - type: TYPE_BOX - blend_mode: BLEND_MODE_ALPHA - texture: "button/button_normal" - id: "animation_broken/larrybutton" - xanchor: XANCHOR_NONE - yanchor: YANCHOR_NONE - pivot: PIVOT_CENTER - adjust_mode: ADJUST_MODE_FIT - parent: "animation_broken" - layer: "" - inherit_alpha: true - slice9 { - x: 32.0 - y: 32.0 - z: 32.0 - w: 32.0 - } - clipping_mode: CLIPPING_MODE_NONE - clipping_visible: true - clipping_inverted: false - alpha: 1.0 - overridden_fields: 1 - overridden_fields: 4 - template_node_child: true - size_mode: SIZE_MODE_MANUAL - custom_type: 0 - enabled: true - visible: true - material: "" -} -nodes { - position { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - rotation { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - scale { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - size { - x: 200.0 - y: 100.0 - z: 0.0 - w: 1.0 - } - color { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - type: TYPE_TEXT - blend_mode: BLEND_MODE_ALPHA - text: "Broken\n" - "" - font: "larryfont" - id: "animation_broken/larrylabel" - xanchor: XANCHOR_NONE - yanchor: YANCHOR_NONE - pivot: PIVOT_CENTER - outline { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - shadow { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - adjust_mode: ADJUST_MODE_FIT - line_break: false - parent: "animation_broken/larrybutton" - layer: "" - inherit_alpha: true - alpha: 1.0 - outline_alpha: 1.0 - shadow_alpha: 1.0 - overridden_fields: 8 - template_node_child: true - text_leading: 1.0 - text_tracking: 0.0 - custom_type: 0 - enabled: true - visible: true - material: "" -} -nodes { - position { - x: 870.0 - y: 496.0 - z: 0.0 - w: 1.0 - } - rotation { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - scale { - x: 0.5 - y: 0.5 - z: 1.0 - w: 1.0 - } - size { - x: 200.0 - y: 100.0 - z: 0.0 - w: 1.0 - } - color { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - type: TYPE_TEMPLATE - id: "animation_idle" - parent: "controls" - layer: "" - inherit_alpha: true - alpha: 1.0 - template: "/dirtylarry/button.gui" - template_node_child: false - custom_type: 0 - enabled: true -} -nodes { - position { - x: -16.0 - y: 5.0 - z: 0.0 - w: 1.0 - } - rotation { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - scale { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - size { - x: 350.0 - y: 88.0 - z: 0.0 - w: 1.0 - } - color { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - type: TYPE_BOX - blend_mode: BLEND_MODE_ALPHA - texture: "button/button_normal" - id: "animation_idle/larrybutton" - xanchor: XANCHOR_NONE - yanchor: YANCHOR_NONE - pivot: PIVOT_CENTER - adjust_mode: ADJUST_MODE_FIT - parent: "animation_idle" - layer: "" - inherit_alpha: true - slice9 { - x: 32.0 - y: 32.0 - z: 32.0 - w: 32.0 - } - clipping_mode: CLIPPING_MODE_NONE - clipping_visible: true - clipping_inverted: false - alpha: 1.0 - overridden_fields: 1 - overridden_fields: 4 - template_node_child: true - size_mode: SIZE_MODE_MANUAL - custom_type: 0 - enabled: true - visible: true - material: "" -} -nodes { - position { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - rotation { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - scale { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - size { - x: 200.0 - y: 100.0 - z: 0.0 - w: 1.0 - } - color { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - type: TYPE_TEXT - blend_mode: BLEND_MODE_ALPHA - text: "Idle" - font: "larryfont" - id: "animation_idle/larrylabel" - xanchor: XANCHOR_NONE - yanchor: YANCHOR_NONE - pivot: PIVOT_CENTER - outline { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - shadow { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - adjust_mode: ADJUST_MODE_FIT - line_break: false - parent: "animation_idle/larrybutton" - layer: "" - inherit_alpha: true - alpha: 1.0 - outline_alpha: 1.0 - shadow_alpha: 1.0 - overridden_fields: 8 - template_node_child: true - text_leading: 1.0 - text_tracking: 0.0 - custom_type: 0 - enabled: true - visible: true - material: "" -} -nodes { - position { - x: 870.0 - y: 605.0 - z: 0.0 - w: 1.0 - } - rotation { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - scale { - x: 0.5 - y: 0.5 - z: 1.0 - w: 1.0 - } - size { - x: 200.0 - y: 100.0 - z: 0.0 - w: 1.0 - } - color { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - type: TYPE_TEMPLATE - id: "animation_label" - parent: "controls" - layer: "" - inherit_alpha: true - alpha: 1.0 - template: "/dirtylarry/button.gui" - template_node_child: false - custom_type: 0 - enabled: true -} -nodes { - position { - x: -16.0 - y: 5.0 - z: 0.0 - w: 1.0 - } - rotation { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - scale { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - size { - x: 350.0 - y: 88.0 - z: 0.0 - w: 1.0 - } - color { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - type: TYPE_BOX - blend_mode: BLEND_MODE_ALPHA - texture: "button/button_normal" - id: "animation_label/larrybutton" - xanchor: XANCHOR_NONE - yanchor: YANCHOR_NONE - pivot: PIVOT_CENTER - adjust_mode: ADJUST_MODE_FIT - parent: "animation_label" - layer: "" - inherit_alpha: true - slice9 { - x: 32.0 - y: 32.0 - z: 32.0 - w: 32.0 - } - clipping_mode: CLIPPING_MODE_NONE - clipping_visible: true - clipping_inverted: false - alpha: 1.0 - overridden_fields: 1 - overridden_fields: 4 - template_node_child: true - size_mode: SIZE_MODE_MANUAL - custom_type: 0 - enabled: true - visible: true - material: "" -} -nodes { - position { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - rotation { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - scale { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - size { - x: 200.0 - y: 100.0 - z: 0.0 - w: 1.0 - } - color { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - type: TYPE_TEXT - blend_mode: BLEND_MODE_ALPHA - text: "Animation:" - font: "larryfont" - id: "animation_label/larrylabel" - xanchor: XANCHOR_NONE - yanchor: YANCHOR_NONE - pivot: PIVOT_CENTER - outline { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - shadow { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - adjust_mode: ADJUST_MODE_FIT - line_break: false - parent: "animation_label/larrybutton" - layer: "" - inherit_alpha: true - alpha: 1.0 - outline_alpha: 1.0 - shadow_alpha: 1.0 - overridden_fields: 8 - template_node_child: true - text_leading: 1.0 - text_tracking: 0.0 - custom_type: 0 - enabled: true - visible: true - material: "" -} -nodes { - position { - x: 871.0 - y: 384.0 - z: 0.0 - w: 1.0 - } - rotation { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - scale { - x: 0.5 - y: 0.5 - z: 1.0 - w: 1.0 - } - size { - x: 200.0 - y: 100.0 - z: 0.0 - w: 1.0 - } - color { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - type: TYPE_TEMPLATE - id: "animation_windshield_wipers" - parent: "controls" - layer: "" - inherit_alpha: true - alpha: 1.0 - template: "/dirtylarry/button.gui" - template_node_child: false - custom_type: 0 - enabled: true -} -nodes { - position { - x: -16.0 - y: 5.0 - z: 0.0 - w: 1.0 - } - rotation { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - scale { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - size { - x: 350.0 - y: 88.0 - z: 0.0 - w: 1.0 - } - color { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - type: TYPE_BOX - blend_mode: BLEND_MODE_ALPHA - texture: "button/button_normal" - id: "animation_windshield_wipers/larrybutton" - xanchor: XANCHOR_NONE - yanchor: YANCHOR_NONE - pivot: PIVOT_CENTER - adjust_mode: ADJUST_MODE_FIT - parent: "animation_windshield_wipers" - layer: "" - inherit_alpha: true - slice9 { - x: 32.0 - y: 32.0 - z: 32.0 - w: 32.0 - } - clipping_mode: CLIPPING_MODE_NONE - clipping_visible: true - clipping_inverted: false - alpha: 1.0 - overridden_fields: 1 - overridden_fields: 4 - template_node_child: true - size_mode: SIZE_MODE_MANUAL - custom_type: 0 - enabled: true - visible: true - material: "" -} -nodes { - position { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - rotation { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - scale { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - size { - x: 200.0 - y: 100.0 - z: 0.0 - w: 1.0 - } - color { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - type: TYPE_TEXT - blend_mode: BLEND_MODE_ALPHA - text: "Windshield Wipers\n" - "" - font: "larryfont" - id: "animation_windshield_wipers/larrylabel" - xanchor: XANCHOR_NONE - yanchor: YANCHOR_NONE - pivot: PIVOT_CENTER - outline { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - shadow { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - adjust_mode: ADJUST_MODE_FIT - line_break: false - parent: "animation_windshield_wipers/larrybutton" - layer: "" - inherit_alpha: true - alpha: 1.0 - outline_alpha: 1.0 - shadow_alpha: 1.0 - overridden_fields: 8 - template_node_child: true - text_leading: 1.0 - text_tracking: 0.0 - custom_type: 0 - enabled: true - visible: true - material: "" -} -nodes { - position { - x: 757.0 - y: 222.0 - z: 0.0 - w: 1.0 - } - rotation { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - scale { - x: 0.5 - y: 0.5 - z: 1.0 - w: 1.0 - } - size { - x: 200.0 - y: 100.0 - z: 0.0 - w: 1.0 - } - color { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - type: TYPE_TEMPLATE - id: "playback_cursor" - parent: "controls" - layer: "" - inherit_alpha: true - alpha: 1.0 - template: "/dirtylarry/slider.gui" - template_node_child: false - custom_type: 0 - enabled: true -} -nodes { - position { - x: -32.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - rotation { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - scale { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - size { - x: 364.0 - y: 32.0 - z: 0.0 - w: 1.0 - } - color { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - type: TYPE_BOX - blend_mode: BLEND_MODE_ALPHA - texture: "" - id: "playback_cursor/larrysafearea" - xanchor: XANCHOR_NONE - yanchor: YANCHOR_NONE - pivot: PIVOT_W - adjust_mode: ADJUST_MODE_FIT - parent: "playback_cursor" - layer: "" - inherit_alpha: true - slice9 { - x: 0.0 - y: 0.0 - z: 0.0 - w: 0.0 - } - clipping_mode: CLIPPING_MODE_NONE - clipping_visible: true - clipping_inverted: false - alpha: 0.0 - template_node_child: true - size_mode: SIZE_MODE_MANUAL - custom_type: 0 - enabled: true - visible: true - material: "" -} -nodes { - position { - x: 32.0 - y: -4.0 - z: 0.0 - w: 1.0 - } - rotation { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - scale { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - size { - x: 300.0 - y: 32.0 - z: 0.0 - w: 1.0 - } - color { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - type: TYPE_BOX - blend_mode: BLEND_MODE_ALPHA - texture: "dirtylarry/button_pressed" - id: "playback_cursor/larryslider" - xanchor: XANCHOR_NONE - yanchor: YANCHOR_NONE - pivot: PIVOT_W - adjust_mode: ADJUST_MODE_FIT - parent: "playback_cursor/larrysafearea" - layer: "" - inherit_alpha: false - slice9 { - x: 16.0 - y: 16.0 - z: 16.0 - w: 16.0 - } - clipping_mode: CLIPPING_MODE_NONE - clipping_visible: true - clipping_inverted: false - alpha: 1.0 - template_node_child: true - size_mode: SIZE_MODE_MANUAL - custom_type: 0 - enabled: true - visible: true - material: "" -} -nodes { - position { - x: 134.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - rotation { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - scale { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - size { - x: 32.0 - y: 32.0 - z: 0.0 - w: 1.0 - } - color { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - type: TYPE_BOX - blend_mode: BLEND_MODE_ALPHA - texture: "dirtylarry/radio_checked_normal" - id: "playback_cursor/larrycursor" - xanchor: XANCHOR_NONE - yanchor: YANCHOR_NONE - pivot: PIVOT_W - adjust_mode: ADJUST_MODE_FIT - parent: "playback_cursor/larryslider" - layer: "" - inherit_alpha: true - slice9 { - x: 0.0 - y: 0.0 - z: 0.0 - w: 0.0 - } - clipping_mode: CLIPPING_MODE_NONE - clipping_visible: true - clipping_inverted: false - alpha: 1.0 - template_node_child: true - size_mode: SIZE_MODE_MANUAL - custom_type: 0 - enabled: true - visible: true - material: "" -} -nodes { - position { - x: 306.0 - y: 3.0 - z: 0.0 - w: 1.0 - } - rotation { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - scale { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - size { - x: 200.0 - y: 42.0 - z: 0.0 - w: 1.0 - } - color { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - type: TYPE_TEXT - blend_mode: BLEND_MODE_ALPHA - text: "0.5" - font: "larryfont" - id: "playback_cursor/larryvalue" - xanchor: XANCHOR_NONE - yanchor: YANCHOR_NONE - pivot: PIVOT_W - outline { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - shadow { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - adjust_mode: ADJUST_MODE_FIT - line_break: false - parent: "playback_cursor/larryslider" - layer: "" - inherit_alpha: true - alpha: 1.0 - outline_alpha: 1.0 - shadow_alpha: 1.0 - template_node_child: true - text_leading: 1.0 - text_tracking: 0.0 - custom_type: 0 - enabled: true - visible: true - material: "" -} -nodes { - position { - x: -8.0 - y: 3.0 - z: 0.0 - w: 1.0 - } - rotation { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - scale { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - size { - x: 200.0 - y: 42.0 - z: 0.0 - w: 1.0 - } - color { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - type: TYPE_TEXT - blend_mode: BLEND_MODE_ALPHA - text: "Playback Cursor" - font: "larryfont" - id: "playback_cursor/larrylabel" - xanchor: XANCHOR_NONE - yanchor: YANCHOR_NONE - pivot: PIVOT_E - outline { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - shadow { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - adjust_mode: ADJUST_MODE_FIT - line_break: false - parent: "playback_cursor/larryslider" - layer: "" - inherit_alpha: true - alpha: 1.0 - outline_alpha: 1.0 - shadow_alpha: 1.0 - overridden_fields: 8 - template_node_child: true - text_leading: 1.0 - text_tracking: 0.0 - custom_type: 0 - enabled: true - visible: true - material: "" -} -nodes { - position { - x: 870.0 - y: 551.0 - z: 0.0 - w: 1.0 - } - rotation { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - scale { - x: 0.5 - y: 0.5 - z: 1.0 - w: 1.0 - } - size { - x: 200.0 - y: 100.0 - z: 0.0 - w: 1.0 - } - color { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - type: TYPE_TEMPLATE - id: "animation_cancel" - parent: "controls" - layer: "" - inherit_alpha: true - alpha: 1.0 - template: "/dirtylarry/button.gui" - template_node_child: false - custom_type: 0 - enabled: true -} -nodes { - position { - x: -16.0 - y: 5.0 - z: 0.0 - w: 1.0 - } - rotation { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - scale { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - size { - x: 350.0 - y: 88.0 - z: 0.0 - w: 1.0 - } - color { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - type: TYPE_BOX - blend_mode: BLEND_MODE_ALPHA - texture: "button/button_normal" - id: "animation_cancel/larrybutton" - xanchor: XANCHOR_NONE - yanchor: YANCHOR_NONE - pivot: PIVOT_CENTER - adjust_mode: ADJUST_MODE_FIT - parent: "animation_cancel" - layer: "" - inherit_alpha: true - slice9 { - x: 32.0 - y: 32.0 - z: 32.0 - w: 32.0 - } - clipping_mode: CLIPPING_MODE_NONE - clipping_visible: true - clipping_inverted: false - alpha: 1.0 - overridden_fields: 1 - overridden_fields: 4 - template_node_child: true - size_mode: SIZE_MODE_MANUAL - custom_type: 0 - enabled: true - visible: true - material: "" -} -nodes { - position { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - rotation { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - scale { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - size { - x: 200.0 - y: 100.0 - z: 0.0 - w: 1.0 - } - color { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - type: TYPE_TEXT - blend_mode: BLEND_MODE_ALPHA - text: "Stop" - font: "larryfont" - id: "animation_cancel/larrylabel" - xanchor: XANCHOR_NONE - yanchor: YANCHOR_NONE - pivot: PIVOT_CENTER - outline { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - shadow { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - adjust_mode: ADJUST_MODE_FIT - line_break: false - parent: "animation_cancel/larrybutton" - layer: "" - inherit_alpha: true - alpha: 1.0 - outline_alpha: 1.0 - shadow_alpha: 1.0 - overridden_fields: 8 - template_node_child: true - text_leading: 1.0 - text_tracking: 0.0 - custom_type: 0 - enabled: true - visible: true - material: "" -} -nodes { - position { - x: 870.0 - y: 439.0 - z: 0.0 - w: 1.0 - } - rotation { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - scale { - x: 0.5 - y: 0.5 - z: 1.0 - w: 1.0 - } - size { - x: 200.0 - y: 100.0 - z: 0.0 - w: 1.0 - } - color { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - type: TYPE_TEMPLATE - id: "animation_bouncing" - parent: "controls" - layer: "" - inherit_alpha: true - alpha: 1.0 - template: "/dirtylarry/button.gui" - template_node_child: false - custom_type: 0 - enabled: true -} -nodes { - position { - x: -16.0 - y: 5.0 - z: 0.0 - w: 1.0 - } - rotation { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - scale { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - size { - x: 350.0 - y: 88.0 - z: 0.0 - w: 1.0 - } - color { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - type: TYPE_BOX - blend_mode: BLEND_MODE_ALPHA - texture: "button/button_normal" - id: "animation_bouncing/larrybutton" - xanchor: XANCHOR_NONE - yanchor: YANCHOR_NONE - pivot: PIVOT_CENTER - adjust_mode: ADJUST_MODE_FIT - parent: "animation_bouncing" - layer: "" - inherit_alpha: true - slice9 { - x: 32.0 - y: 32.0 - z: 32.0 - w: 32.0 - } - clipping_mode: CLIPPING_MODE_NONE - clipping_visible: true - clipping_inverted: false - alpha: 1.0 - overridden_fields: 1 - overridden_fields: 4 - template_node_child: true - size_mode: SIZE_MODE_MANUAL - custom_type: 0 - enabled: true - visible: true - material: "" -} -nodes { - position { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - rotation { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - scale { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - size { - x: 200.0 - y: 100.0 - z: 0.0 - w: 1.0 - } - color { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - type: TYPE_TEXT - blend_mode: BLEND_MODE_ALPHA - text: "Bouncing\n" - "" - font: "larryfont" - id: "animation_bouncing/larrylabel" - xanchor: XANCHOR_NONE - yanchor: YANCHOR_NONE - pivot: PIVOT_CENTER - outline { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - shadow { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - adjust_mode: ADJUST_MODE_FIT - line_break: false - parent: "animation_bouncing/larrybutton" - layer: "" - inherit_alpha: true - alpha: 1.0 - outline_alpha: 1.0 - shadow_alpha: 1.0 - overridden_fields: 8 - template_node_child: true - text_leading: 1.0 - text_tracking: 0.0 - custom_type: 0 - enabled: true - visible: true - material: "" -} -nodes { - position { - x: 612.0 - y: 412.0 - z: 0.0 - w: 1.0 - } - rotation { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - scale { - x: 0.5 - y: 0.5 - z: 1.0 - w: 1.0 - } - size { - x: 200.0 - y: 100.0 - z: 0.0 - w: 1.0 - } - color { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - type: TYPE_TEMPLATE - id: "playback_loop_pingpong" - parent: "controls" - layer: "" - inherit_alpha: true - alpha: 1.0 - template: "/dirtylarry/radio_label.gui" - template_node_child: false - custom_type: 0 - enabled: true -} -nodes { - position { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - rotation { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - scale { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - size { - x: 64.0 - y: 68.0 - z: 0.0 - w: 1.0 - } - color { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - type: TYPE_BOX - blend_mode: BLEND_MODE_ALPHA - texture: "radio/radio_normal" - id: "playback_loop_pingpong/larryradio" - xanchor: XANCHOR_NONE - yanchor: YANCHOR_NONE - pivot: PIVOT_CENTER - adjust_mode: ADJUST_MODE_FIT - parent: "playback_loop_pingpong" - layer: "" - inherit_alpha: true - slice9 { - x: 0.0 - y: 0.0 - z: 0.0 - w: 0.0 - } - clipping_mode: CLIPPING_MODE_NONE - clipping_visible: true - clipping_inverted: false - alpha: 1.0 - template_node_child: true - size_mode: SIZE_MODE_MANUAL - custom_type: 0 - enabled: true - visible: true - material: "" -} -nodes { - position { - x: 46.0 - y: 3.0 - z: 0.0 - w: 1.0 - } - rotation { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - scale { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - size { - x: 200.0 - y: 50.0 - z: 0.0 - w: 1.0 - } - color { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - type: TYPE_TEXT - blend_mode: BLEND_MODE_ALPHA - text: "Loop Ping Pong\n" - "" - font: "larryfont" - id: "playback_loop_pingpong/larrylabel" - xanchor: XANCHOR_NONE - yanchor: YANCHOR_NONE - pivot: PIVOT_W - outline { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - shadow { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - adjust_mode: ADJUST_MODE_FIT - line_break: false - parent: "playback_loop_pingpong" - layer: "" - inherit_alpha: true - alpha: 1.0 - outline_alpha: 1.0 - shadow_alpha: 1.0 - overridden_fields: 8 - template_node_child: true - text_leading: 1.0 - text_tracking: 0.0 - custom_type: 0 - enabled: true - visible: true - material: "" -} -nodes { - position { - x: 612.0 - y: 452.0 - z: 0.0 - w: 1.0 - } - rotation { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - scale { - x: 0.5 - y: 0.5 - z: 1.0 - w: 1.0 - } - size { - x: 200.0 - y: 100.0 - z: 0.0 - w: 1.0 - } - color { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - type: TYPE_TEMPLATE - id: "playback_loop_backward" - parent: "controls" - layer: "" - inherit_alpha: true - alpha: 1.0 - template: "/dirtylarry/radio_label.gui" - template_node_child: false - custom_type: 0 - enabled: true -} -nodes { - position { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - rotation { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - scale { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - size { - x: 64.0 - y: 68.0 - z: 0.0 - w: 1.0 - } - color { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - type: TYPE_BOX - blend_mode: BLEND_MODE_ALPHA - texture: "radio/radio_normal" - id: "playback_loop_backward/larryradio" - xanchor: XANCHOR_NONE - yanchor: YANCHOR_NONE - pivot: PIVOT_CENTER - adjust_mode: ADJUST_MODE_FIT - parent: "playback_loop_backward" - layer: "" - inherit_alpha: true - slice9 { - x: 0.0 - y: 0.0 - z: 0.0 - w: 0.0 - } - clipping_mode: CLIPPING_MODE_NONE - clipping_visible: true - clipping_inverted: false - alpha: 1.0 - template_node_child: true - size_mode: SIZE_MODE_MANUAL - custom_type: 0 - enabled: true - visible: true - material: "" -} -nodes { - position { - x: 46.0 - y: 3.0 - z: 0.0 - w: 1.0 - } - rotation { - x: 0.0 - y: 0.0 - z: 0.0 - w: 1.0 - } - scale { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - size { - x: 200.0 - y: 50.0 - z: 0.0 - w: 1.0 - } - color { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - type: TYPE_TEXT - blend_mode: BLEND_MODE_ALPHA - text: "Loop Backward" - font: "larryfont" - id: "playback_loop_backward/larrylabel" - xanchor: XANCHOR_NONE - yanchor: YANCHOR_NONE - pivot: PIVOT_W - outline { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - shadow { - x: 1.0 - y: 1.0 - z: 1.0 - w: 1.0 - } - adjust_mode: ADJUST_MODE_FIT - line_break: false - parent: "playback_loop_backward" - layer: "" - inherit_alpha: true - alpha: 1.0 - outline_alpha: 1.0 - shadow_alpha: 1.0 - overridden_fields: 8 - template_node_child: true - text_leading: 1.0 - text_tracking: 0.0 - custom_type: 0 - enabled: true - visible: true - material: "" -} -material: "/builtins/materials/gui.material" -adjust_reference: ADJUST_REFERENCE_PARENT -max_nodes: 512 \ No newline at end of file diff --git a/main/car/gui.gui_script b/main/car/gui.gui_script deleted file mode 100644 index de375376..00000000 --- a/main/car/gui.gui_script +++ /dev/null @@ -1,107 +0,0 @@ -local dirtylarry = require "dirtylarry/dirtylarry" - -function init(self) - msg.post(".", "acquire_input_focus") - - self.playback_rate = 1.0 - self.playback_offset = 0.0 - self.playback_cursor = 0.0 - self.playback_value = go.PLAYBACK_LOOP_FORWARD - self.animation_label = gui.get_node("animation_label/larrylabel") - self.cursor_node = gui.get_node("playback_cursor/larrysafearea") - - clear_animation_label(self) -end - -function animation_play(self, anim) - self.anim = anim - gui.set_text(self.animation_label, "Animation: " .. anim) - msg.post("/go#main", "MSG_PLAY", { - animation = hash(anim), - playback = self.playback_value, - rate = self.playback_rate, - offset = self.playback_offset, - }) -end - -function animation_set_rate(self, rate) - msg.post("/go#main", "MSG_PLAYBACK_RATE", { - rate = self.playback_rate, - }) -end - -function animation_set_cursor(self, cursor) - msg.post("/go#main", "MSG_PLAYBACK_CURSOR", { - cursor = self.playback_cursor, - }) -end - -function clear_animation_label(self) - gui.set_text(self.animation_label, "Animation: none") -end - -function animation_cancel(self) - clear_animation_label(self) - msg.post("/go#main", "MSG_CANCEL") -end - -function on_input(self, action_id, action) - local playback_value = self.playback_value - local playback_rate = self.playback_rate - local playback_cursor = self.playback_cursor - - self.playback_value = dirtylarry:radio("playback_once_forward", action_id, action, go.PLAYBACK_ONCE_FORWARD, self.playback_value) - self.playback_value = dirtylarry:radio("playback_once_backward", action_id, action, go.PLAYBACK_ONCE_BACKWARD, self.playback_value) - self.playback_value = dirtylarry:radio("playback_once_pingpong", action_id, action, go.PLAYBACK_ONCE_PINGPONG, self.playback_value) - self.playback_value = dirtylarry:radio("playback_loop_forward", action_id, action, go.PLAYBACK_LOOP_FORWARD, self.playback_value) - self.playback_value = dirtylarry:radio("playback_loop_backward", action_id, action, go.PLAYBACK_LOOP_BACKWARD, self.playback_value) - self.playback_value = dirtylarry:radio("playback_loop_pingpong", action_id, action, go.PLAYBACK_LOOP_PINGPONG, self.playback_value) - - self.playback_rate = dirtylarry:slider("playback_rate", action_id, action, 0.0, 1.0, self.playback_rate) - self.playback_offset = dirtylarry:slider("playback_offset", action_id, action, 0.0, 1.0, self.playback_offset) - self.playback_cursor = dirtylarry:slider("playback_cursor", action_id, action, 0.0, 1.0, self.playback_cursor) - - if self.playback_value ~= playback_value and self.anim ~= nil then - animation_play(self, self.anim) - elseif self.playback_rate ~= playback_rate and self.anim ~= nil then - animation_set_rate(self, self.playback_rate) - end - - if self.playback_cursor ~= playback_cursor and self.anim ~= nil then - animation_set_cursor(self, self.playback_cursor) - end - - dirtylarry:button("animation_cancel", action_id, action, function () - animation_cancel(self) - end) - - dirtylarry:button("animation_idle", action_id, action, function () - animation_play(self, "idle") - end) - - dirtylarry:button("animation_bouncing", action_id, action, function () - animation_play(self, "bouncing") - end) - - dirtylarry:button("animation_windshield_wipers", action_id, action, function () - animation_play(self, "windshield_wipers") - end) - - dirtylarry:button("animation_broken", action_id, action, function () - animation_play(self, "broken") - end) -end - -function on_message(self, message_id, message) - if message_id == hash("animation_complete") then - clear_animation_label(self) - elseif message_id == hash("animation_cursor") then - if dirtylarry.active_node ~= nil and gui.get_id(dirtylarry.active_node) == gui.get_id(self.cursor_node) then - return - end - - self.playback_cursor = message.cursor - self.playback_cursor = dirtylarry:slider("playback_cursor", hash("touch"), { x = -1, y = -1 }, 0.0, 1.0, self.playback_cursor) - end -end - diff --git a/main/cursor/cursor.collection b/main/cursor/cursor.collection deleted file mode 100644 index 4260a650..00000000 --- a/main/cursor/cursor.collection +++ /dev/null @@ -1,31 +0,0 @@ -name: "cursor" -instances { - id: "back" - prototype: "/main/menu/back.go" -} -scale_along_z: 0 -embedded_instances { - id: "go" - data: "components {\n" - " id: \"cursor\"\n" - " component: \"/main/cursor/cursor.script\"\n" - "}\n" - "embedded_components {\n" - " id: \"rivemodel\"\n" - " type: \"rivemodel\"\n" - " data: \"scene: \\\"/main/cursor/emoji.rivescene\\\"\\n" - "default_animation: \\\"Dart_board\\\"\\n" - "material: \\\"/defold-rive/assets/rivemodel.material\\\"\\n" - "artboard: \\\"New Artboard\\\"\\n" - "\"\n" - "}\n" - "" - position { - x: 480.0 - y: 323.0 - } - scale3 { - x: 0.5 - y: 0.5 - } -} diff --git a/main/cursor/cursor.script b/main/cursor/cursor.script deleted file mode 100644 index e8090efb..00000000 --- a/main/cursor/cursor.script +++ /dev/null @@ -1,9 +0,0 @@ -function init(self) - self.url = "#rivemodel" - go.set(self.url, "cursor", 1.0) - go.animate(self.url, "cursor", go.PLAYBACK_LOOP_FORWARD, 0.0, go.EASING_LINEAR, 1) -end - -function update(self, dt) - print("cursor", go.get(self.url, "cursor")) -end diff --git a/main/cursor/emoji.riv b/main/cursor/emoji.riv deleted file mode 100644 index 2d3bdb36..00000000 Binary files a/main/cursor/emoji.riv and /dev/null differ diff --git a/main/cursor/emoji.rivescene b/main/cursor/emoji.rivescene deleted file mode 100644 index 2ca2aeb9..00000000 --- a/main/cursor/emoji.rivescene +++ /dev/null @@ -1,2 +0,0 @@ -scene: "/main/cursor/emoji.riv" -atlas: "/defold-rive/assets/empty.atlas" diff --git a/main/databind/14447-40690-game-achievement-badge-data-binding.riv b/main/databind/14447-40690-game-achievement-badge-data-binding.riv new file mode 100644 index 00000000..364d9621 Binary files /dev/null and b/main/databind/14447-40690-game-achievement-badge-data-binding.riv differ diff --git a/main/databind/achievement.rivescene b/main/databind/achievement.rivescene new file mode 100644 index 00000000..7b89d127 --- /dev/null +++ b/main/databind/achievement.rivescene @@ -0,0 +1,2 @@ +scene: "/main/databind/14447-40690-game-achievement-badge-data-binding.riv" +atlas: "/defold-rive/assets/empty.atlas" diff --git a/main/databind/calc.riv b/main/databind/calc.riv deleted file mode 100644 index 9fa7e3d2..00000000 Binary files a/main/databind/calc.riv and /dev/null differ diff --git a/main/databind/calc.rivemodel b/main/databind/calc.rivemodel deleted file mode 100644 index d70d1a77..00000000 --- a/main/databind/calc.rivemodel +++ /dev/null @@ -1,5 +0,0 @@ -scene: "/main/databind/calc.rivescene" -default_animation: "" -material: "/defold-rive/assets/rivemodel.material" -default_state_machine: "State Machine 1" -artboard: "Artboard" diff --git a/main/databind/calc.rivescene b/main/databind/calc.rivescene deleted file mode 100644 index 6510d441..00000000 --- a/main/databind/calc.rivescene +++ /dev/null @@ -1,2 +0,0 @@ -scene: "/main/databind/calc.riv" -atlas: "/defold-rive/assets/empty.atlas" diff --git a/main/databind/databind.collection b/main/databind/databind.collection index 379d5315..19535069 100644 --- a/main/databind/databind.collection +++ b/main/databind/databind.collection @@ -11,21 +11,19 @@ embedded_instances { " component: \"/main/databind/databind.script\"\n" "}\n" "embedded_components {\n" - " id: \"modal\"\n" + " id: \"rivemodel\"\n" " type: \"rivemodel\"\n" - " data: \"scene: \\\"/main/databind/modal.rivescene\\\"\\n" - "default_animation: \\\"textVariation1\\\"\\n" + " data: \"scene: \\\"/main/databind/achievement.rivescene\\\"\\n" + "default_animation: \\\"\\\"\\n" "material: \\\"/defold-rive/assets/rivemodel.material\\\"\\n" - "default_state_machine: \\\"State Machine 1\\\"\\n" - "artboard: \\\"Main\\\"\\n" + "coordinate_system: COORDINATE_SYSTEM_RIVE\\n" + "artboard_fit: FIT_CONTAIN\\n" + "blit_material: \\\"/defold-rive/assets/shader-library/rivemodel_blit.material\\\"\\n" "\"\n" "}\n" "" position { - x: 477.7781 + x: 476.77814 y: 268.6295 } - scale3 { - z: 0.82 - } } diff --git a/main/databind/databind.script b/main/databind/databind.script index 1f332131..660df311 100644 --- a/main/databind/databind.script +++ b/main/databind/databind.script @@ -1,27 +1,101 @@ --- CREDITS: --- calc.riv is from user https://rive.app/@thefloridianfreak/: --- https://rive.app/marketplace/19344-36364-supplied-mutliplication/ --- dynamic_modal.riv by https://rive.app/@drawsgood/: --- https://rive.app/marketplace/19673-37017-dynamic-modal/ +local function set_listeners(self) + rive.set_file_listener(function (self, message_id, message) + pprint("FILE LISTENER", message_id, message) + end) + + rive.set_artboard_listener(function (self, message_id, message) + pprint("ARTBOARD LISTENER", message_id, message) + end) + + rive.set_state_machine_listener(function (self, message_id, message) + pprint("STATEMACHINE LISTENER", message_id, message) + end) + + rive.set_view_model_instance_listener(function (self, message_id, message) + pprint("VIEWMODEL LISTENER", message_id, message) + end) + + rive.set_render_image_listener(function (self, message_id, message) + pprint("RENDERIMAGE LISTENER", message_id, message) + end) + + rive.set_audio_source_listener(function (self, message_id, message) + pprint("AUDIO SOURCE LISTENER", message_id, message) + end) + + rive.set_font_listener(function (self, message_id, message) + pprint("FONT LISTENER", message_id, message) + end) +end function init(self) msg.post(".", "acquire_input_focus") - self.url = msg.url("#modal") - self.handle, err = rive.databind.create_view_model_instance_runtime(self.url, "") --select the first view model - if self.handle == nil then - print("ERROR:", err) - return - end - rive.databind.set_view_model_instance_runtime(self.url, self.handle) - - self.data = { - menuIsOpen = true, - accentColor = vmath.vector4(1,1,0,1), - modalCopy = "Hello Folks! This is a test of data bindings between the Rive scene and the Defold Game Engine!", - } - rive.databind.set_properties(self.url, self.handle, self.data) + set_listeners(self) + + self.url = msg.url("#rivemodel") + self.file = rive.get_file(self.url) + self.artboard = rive.get_artboard(self.url) + self.state_machine = rive.get_state_machine(self.url) + self.view_model = rive.get_view_model_instance(self.url) + + -- debugging + rive.cmd.requestViewModelNames(self.file) + rive.cmd.requestArtboardNames(self.file) + rive.cmd.requestViewModelEnums(self.file) + rive.cmd.requestStateMachineNames(self.artboard) + rive.cmd.requestDefaultViewModelInfo(self.artboard, self.file) + + rive.cmd.requestViewModelInstanceNames(self.file, "vmAchiementBadge") + rive.cmd.requestViewModelPropertyDefinitions(self.file, "vmAchiementBadge") + + rive.cmd.requestViewModelInstanceNames(self.file, "vmBadge") + rive.cmd.requestViewModelPropertyDefinitions(self.file, "vmBadge") + + rive.cmd.requestViewModelInstanceNames(self.file, "vmRating") + rive.cmd.requestViewModelPropertyDefinitions(self.file, "vmRating") + + -- end debugging + + -- {"requestViewModelNames", Script_requestViewModelNames}, + -- {"requestArtboardNames", Script_requestArtboardNames}, + -- {"requestViewModelEnums", Script_requestViewModelEnums}, + + -- {"requestViewModelPropertyDefinitions", Script_requestViewModelPropertyDefinitions}, + -- {"requestViewModelInstanceNames", Script_requestViewModelInstanceNames}, + -- {"requestViewModelInstanceBool", Script_requestViewModelInstanceBool}, + -- {"requestViewModelInstanceNumber", Script_requestViewModelInstanceNumber}, + -- {"requestViewModelInstanceColor", Script_requestViewModelInstanceColor}, + -- {"requestViewModelInstanceEnum", Script_requestViewModelInstanceEnum}, + -- {"requestViewModelInstanceString", Script_requestViewModelInstanceString}, + -- {"requestViewModelInstanceListSize", Script_requestViewModelInstanceListSize}, + + -- {"requestStateMachineNames", Script_requestStateMachineNames}, + -- {"requestDefaultViewModelInfo", Script_requestDefaultViewModelInfo}, + + + --self.view_model = rive.cmd.instantiateDefaultViewModelInstance(self.file, self.artboard) + self.view_model_flag = rive.cmd.referenceNestedViewModelInstance(self.view_model, "property of vmFlag") + self.view_model_badge = rive.cmd.referenceNestedViewModelInstance(self.view_model, "property of vmBadge") + self.view_model_rating = rive.cmd.referenceNestedViewModelInstance(self.view_model, "property of vmRating") + + -- no need to bind, as it has "autobind" selected + --rive.cmd.bindViewModelInstance(self.state_machine, self.view_model) + + self.isOpen = true + rive.cmd.setViewModelInstanceBool(self.view_model, "isOpen", self.isOpen) + + rive.cmd.setViewModelInstanceString(self.view_model_flag, "flagText", "DEFOLD!") + rive.cmd.setViewModelInstanceColor(self.view_model_flag, "flagColor", vmath.vector4(0.5, 0.3, 0.4, 1.0)) + + -- 1 = "Froggo", + -- 2 = "Skulla", + -- 3 = "Castela" + rive.cmd.setViewModelInstanceEnum(self.view_model_badge, "badgeDesign", "Castela") + + rive.cmd.setViewModelInstanceNumber(self.view_model_rating, "stars", 5) + end function final(self) @@ -44,12 +118,16 @@ end function on_input(self, action_id, action) if not action_id or action_id == hash("touch") then + if action.pressed then - rive.pointer_down(self.url, action.x, action.y) + self.isOpen = not self.isOpen + rive.cmd.setViewModelInstanceBool(self.view_model, "isOpen", self.isOpen) + + --rive.pointer_down(self.url, action.x, action.y) elseif action.released then - rive.pointer_up(self.url, action.x, action.y) + --rive.pointer_up(self.url, action.x, action.y) else - rive.pointer_move(self.url, action.x, action.y) + --rive.pointer_move(self.url, action.x, action.y) end end end diff --git a/main/databind/dynamic_modal.riv b/main/databind/dynamic_modal.riv deleted file mode 100644 index 80693114..00000000 Binary files a/main/databind/dynamic_modal.riv and /dev/null differ diff --git a/main/databind/loader_example_for_data_binding.riv b/main/databind/loader_example_for_data_binding.riv deleted file mode 100644 index 37dd6992..00000000 Binary files a/main/databind/loader_example_for_data_binding.riv and /dev/null differ diff --git a/main/databind/modal.rivescene b/main/databind/modal.rivescene deleted file mode 100644 index ed98c0b6..00000000 --- a/main/databind/modal.rivescene +++ /dev/null @@ -1,2 +0,0 @@ -scene: "/main/databind/dynamic_modal.riv" -atlas: "/defold-rive/assets/empty.atlas" diff --git a/main/databind/progress.rivescene b/main/databind/progress.rivescene deleted file mode 100644 index aeed2c0f..00000000 --- a/main/databind/progress.rivescene +++ /dev/null @@ -1,2 +0,0 @@ -scene: "/main/databind/loader_example_for_data_binding.riv" -atlas: "/defold-rive/assets/empty.atlas" diff --git a/main/egg/egg.rivemodel b/main/egg/egg.rivemodel index 9cb37666..d82de30e 100644 --- a/main/egg/egg.rivemodel +++ b/main/egg/egg.rivemodel @@ -1,5 +1,4 @@ scene: "/main/egg/egg.rivescene" -default_animation: "Motion" +default_animation: "" material: "/defold-rive/assets/rivemodel.material" -default_state_machine: "State Machine 1" -artboard: "Artboard 3" +blit_material: "/defold-rive/assets/shader-library/rivemodel_blit.material" diff --git a/main/fighting-game/fighter_game_player_v6_events.riv b/main/fighting-game/fighter_game_player_v6_events.riv deleted file mode 100644 index 78b495a1..00000000 Binary files a/main/fighting-game/fighter_game_player_v6_events.riv and /dev/null differ diff --git a/main/fighting-game/fighting-game.collection b/main/fighting-game/fighting-game.collection deleted file mode 100644 index 963332fe..00000000 --- a/main/fighting-game/fighting-game.collection +++ /dev/null @@ -1,34 +0,0 @@ -name: "grimley" -instances { - id: "back" - prototype: "/main/menu/back.go" -} -scale_along_z: 0 -embedded_instances { - id: "go" - data: "components {\n" - " id: \"fighting-game\"\n" - " component: \"/main/fighting-game/fighting-game.script\"\n" - "}\n" - "embedded_components {\n" - " id: \"model\"\n" - " type: \"rivemodel\"\n" - " data: \"scene: \\\"/main/fighting-game/fighting-game.rivescene\\\"\\n" - "default_animation: \\\"\\\"\\n" - "material: \\\"/defold-rive/assets/rivemodel.material\\\"\\n" - "default_state_machine: \\\"State Machine 1\\\"\\n" - "artboard: \\\"Main\\\"\\n" - "coordinate_system: COORDINATE_SYSTEM_RIVE\\n" - "artboard_fit: FIT_CONTAIN\\n" - "\"\n" - "}\n" - "" - position { - x: 480.0 - y: 270.0 - } - scale3 { - x: 0.5 - y: 0.5 - } -} diff --git a/main/fighting-game/fighting-game.rivescene b/main/fighting-game/fighting-game.rivescene deleted file mode 100644 index 0ac5f6ab..00000000 --- a/main/fighting-game/fighting-game.rivescene +++ /dev/null @@ -1,2 +0,0 @@ -scene: "/main/fighting-game/fighter_game_player_v6_events.riv" -atlas: "/defold-rive/assets/empty.atlas" diff --git a/main/fighting-game/fighting-game.script b/main/fighting-game/fighting-game.script deleted file mode 100644 index ee26a8c7..00000000 --- a/main/fighting-game/fighting-game.script +++ /dev/null @@ -1,42 +0,0 @@ -function init(self) - msg.post(".", "acquire_input_focus") - - rive.play_state_machine("#model", "State Machine 1", nil, function(self, message_id, message) - print("play_state_machine", message_id) - pprint(message) - end) - - self.player_one_selection = { x = 1, y = 0, selected = false } - self.player_two_selection = { x = 5, y = 0, selected = false } -end - -function clamp_and_get_selection(self, sel) - if sel.x < 1 then sel.x = 1 - elseif sel.x > 5 then sel.x = 5 end - if sel.y < 0 then sel.y = 0 - elseif sel.y > 1 then sel.y = 1 end - return sel.y * 5 + sel.x -end - -function on_input(self, action_id, action) - - if action.released then - if action_id == hash("right") then - self.player_one_selection.x = self.player_one_selection.x + 1 - go.set("#model", "player1MasterSelection", clamp_and_get_selection(self, self.player_one_selection)) - elseif action_id == hash("left") then - self.player_one_selection.x = self.player_one_selection.x - 1 - go.set("#model", "player1MasterSelection", clamp_and_get_selection(self, self.player_one_selection)) - elseif action_id == hash("down") then - self.player_one_selection.y = self.player_one_selection.y + 1 - go.set("#model", "player1MasterSelection", clamp_and_get_selection(self, self.player_one_selection)) - elseif action_id == hash("up") then - self.player_one_selection.y = self.player_one_selection.y - 1 - go.set("#model", "player1MasterSelection", clamp_and_get_selection(self, self.player_one_selection)) - elseif action_id == hash("enter") then - self.player_one_selection.selected = not self.player_one_selection.selected - go.set("#model", "player1MasterConfirmed", self.player_one_selection.selected) - end - end -end - \ No newline at end of file diff --git a/main/ghost/ghost.collection b/main/ghost/ghost.collection index e94c24b1..35089a26 100644 --- a/main/ghost/ghost.collection +++ b/main/ghost/ghost.collection @@ -20,6 +20,7 @@ embedded_instances { "artboard: \\\"New Artboard\\\"\\n" "coordinate_system: COORDINATE_SYSTEM_RIVE\\n" "artboard_fit: FIT_CONTAIN\\n" + "blit_material: \\\"/defold-rive/assets/shader-library/rivemodel_blit.material\\\"\\n" "\"\n" "}\n" "" diff --git a/main/grimley/4951-10031-grimley.riv b/main/grimley/4951-10031-grimley.riv index 383db7f5..621c1a9b 100644 Binary files a/main/grimley/4951-10031-grimley.riv and b/main/grimley/4951-10031-grimley.riv differ diff --git a/main/grimley/grimley.script b/main/grimley/grimley.script index ac27116a..72497cc9 100644 --- a/main/grimley/grimley.script +++ b/main/grimley/grimley.script @@ -1,6 +1,30 @@ +local function rive_listener(self, message_name, data) + pprint("file:", message_name, data) +end + function init(self) - --rive.play_anim("#model", "panelAnimateOn", go.PLAYBACK_LOOP_FORWARD) - --rive.play_anim("#model", "cape", go.PLAYBACK_LOOP_FORWARD) - --rive.play_anim("#model", "bodyLoop", go.PLAYBACK_LOOP_FORWARD) - + msg.post(".", "acquire_input_focus") + + rive.set_file_listener(rive_listener) + + self.url = "#model" + + local artboard = rive.get_artboard(self.url) + local state_machine = rive.get_state_machine(self.url) + print("artboard:", artboard) + print("state_machine:", state_machine) +end + +function on_input(self, action_id, action) + if hash("touch") == action_id then + -- rive.set_artboard("#model", "Dwarf_SeparateExport.psd") + -- rive.set_state_machine("#model", "State Machine 1") + if action.pressed then + rive.pointer_down(self.url, action.x, action.y) + elseif action.released then + rive.pointer_up(self.url, action.x, action.y) + end + elseif action_id == nil then -- mouse move + rive.pointer_move(self.url, action.x, action.y) + end end diff --git a/main/lights/lights.collection b/main/lights/lights.collection deleted file mode 100644 index 6c6747a3..00000000 --- a/main/lights/lights.collection +++ /dev/null @@ -1,22 +0,0 @@ -name: "lights" -instances { - id: "back" - prototype: "/main/menu/back.go" -} -scale_along_z: 0 -embedded_instances { - id: "go" - data: "components {\n" - " id: \"lights\"\n" - " component: \"/main/lights/lights.rivemodel\"\n" - "}\n" - "" - position { - x: 480.0 - y: 270.0 - } - scale3 { - x: 2.0 - y: 2.0 - } -} diff --git a/main/lights/lights.riv b/main/lights/lights.riv deleted file mode 100644 index 637b61ff..00000000 Binary files a/main/lights/lights.riv and /dev/null differ diff --git a/main/lights/lights.rivemodel b/main/lights/lights.rivemodel deleted file mode 100644 index 06628fb7..00000000 --- a/main/lights/lights.rivemodel +++ /dev/null @@ -1,6 +0,0 @@ -scene: "/main/lights/lights.rivescene" -default_animation: "Animation 1" -material: "/defold-rive/assets/rivemodel.material" -artboard: "Lights" -coordinate_system: COORDINATE_SYSTEM_RIVE -artboard_fit: FIT_CONTAIN diff --git a/main/lights/lights.rivescene b/main/lights/lights.rivescene deleted file mode 100644 index df47a872..00000000 --- a/main/lights/lights.rivescene +++ /dev/null @@ -1,2 +0,0 @@ -scene: "/main/lights/lights.riv" -atlas: "/defold-rive/assets/empty.atlas" diff --git a/main/loader.collection b/main/loader.collection index 7b1e2261..4c0e0f56 100644 --- a/main/loader.collection +++ b/main/loader.collection @@ -7,36 +7,12 @@ embedded_instances { " component: \"/main/loader.script\"\n" "}\n" "embedded_components {\n" - " id: \"bones\"\n" - " type: \"collectionproxy\"\n" - " data: \"collection: \\\"/main/bones/bones.collection\\\"\\n" - "\"\n" - "}\n" - "embedded_components {\n" - " id: \"car\"\n" - " type: \"collectionproxy\"\n" - " data: \"collection: \\\"/main/car/car.collection\\\"\\n" - "\"\n" - "}\n" - "embedded_components {\n" - " id: \"cursor\"\n" - " type: \"collectionproxy\"\n" - " data: \"collection: \\\"/main/cursor/cursor.collection\\\"\\n" - "\"\n" - "}\n" - "embedded_components {\n" " id: \"menu\"\n" " type: \"collectionproxy\"\n" " data: \"collection: \\\"/main/menu/menu.collection\\\"\\n" "\"\n" "}\n" "embedded_components {\n" - " id: \"lights\"\n" - " type: \"collectionproxy\"\n" - " data: \"collection: \\\"/main/lights/lights.collection\\\"\\n" - "\"\n" - "}\n" - "embedded_components {\n" " id: \"state\"\n" " type: \"collectionproxy\"\n" " data: \"collection: \\\"/main/statemachine/statemachine.collection\\\"\\n" @@ -79,12 +55,6 @@ embedded_instances { "\"\n" "}\n" "embedded_components {\n" - " id: \"fighting-game\"\n" - " type: \"collectionproxy\"\n" - " data: \"collection: \\\"/main/fighting-game/fighting-game.collection\\\"\\n" - "\"\n" - "}\n" - "embedded_components {\n" " id: \"ghost\"\n" " type: \"collectionproxy\"\n" " data: \"collection: \\\"/main/ghost/ghost.collection\\\"\\n" diff --git a/main/menu/menu.gui_script b/main/menu/menu.gui_script index fe73d434..7db0b12a 100644 --- a/main/menu/menu.gui_script +++ b/main/menu/menu.gui_script @@ -2,18 +2,11 @@ local dirtylarry = require "dirtylarry/dirtylarry" local BUTTONS = { - { id = "car", text = "Car" }, - { id = "lights", text = "Lights" }, - --{ id = "bones", text = "Bones" }, - --{ id = "cursor", text = "Cursor" }, - --{ id = "state", text = "State" }, { id = "sophiahud", text = "Sophia" }, { id = "scifihud", text = "Sci-fi" }, - --{ id = "circleui", text = "Circle UI" }, { id = "takethis", text = "Take This!" }, { id = "ac2", text = "Assassin" }, { id = "grimley", text = "Grimley" }, - { id = "fighting-game", text = "Fight!" }, { id = "ghost", text = "Ghost UI" }, { id = "layout", text = "Layouts" }, { id = "egg", text = "Feathering" }, diff --git a/main/outofband/font/referencedfontassets.riv b/main/outofband/font/referencedfontassets.riv deleted file mode 100644 index ee3e5233..00000000 Binary files a/main/outofband/font/referencedfontassets.riv and /dev/null differ diff --git a/main/outofband/font/referencedfontassets.rivemodel b/main/outofband/font/referencedfontassets.rivemodel deleted file mode 100644 index 520f44e4..00000000 --- a/main/outofband/font/referencedfontassets.rivemodel +++ /dev/null @@ -1,6 +0,0 @@ -scene: "/main/outofband/font/referencedfontassets.rivescene" -default_animation: "" -material: "/defold-rive/assets/rivemodel.material" -artboard: "Main" -coordinate_system: COORDINATE_SYSTEM_RIVE -artboard_fit: FIT_LAYOUT diff --git a/main/outofband/font/referencedfontassets.rivescene b/main/outofband/font/referencedfontassets.rivescene deleted file mode 100644 index 037dc2df..00000000 --- a/main/outofband/font/referencedfontassets.rivescene +++ /dev/null @@ -1,2 +0,0 @@ -scene: "/main/outofband/font/referencedfontassets.riv" -atlas: "/defold-rive/assets/empty.atlas" diff --git a/main/outofband/out-of-band.rivescene b/main/outofband/out-of-band.rivescene new file mode 100644 index 00000000..6ac957cc --- /dev/null +++ b/main/outofband/out-of-band.rivescene @@ -0,0 +1,2 @@ +scene: "/main/outofband/out-of-band/out-of-band-assets.riv" +atlas: "/defold-rive/assets/empty.atlas" diff --git a/main/outofband/font/Inter-referenced.ttf b/main/outofband/out-of-band/MyFont-4228759.ttf similarity index 100% rename from main/outofband/font/Inter-referenced.ttf rename to main/outofband/out-of-band/MyFont-4228759.ttf diff --git a/main/outofband/out-of-band/logo-ver-outline-white-320-5143474.png b/main/outofband/out-of-band/logo-ver-outline-white-320-5143474.png new file mode 100644 index 00000000..ec6507f1 Binary files /dev/null and b/main/outofband/out-of-band/logo-ver-outline-white-320-5143474.png differ diff --git a/main/outofband/font/referencedfontassets.rev b/main/outofband/out-of-band/out-of-band-assets.rev similarity index 98% rename from main/outofband/font/referencedfontassets.rev rename to main/outofband/out-of-band/out-of-band-assets.rev index 9603f709..3b9ad65d 100644 Binary files a/main/outofband/font/referencedfontassets.rev and b/main/outofband/out-of-band/out-of-band-assets.rev differ diff --git a/main/outofband/out-of-band/out-of-band-assets.riv b/main/outofband/out-of-band/out-of-band-assets.riv new file mode 100644 index 00000000..72ddb0dd Binary files /dev/null and b/main/outofband/out-of-band/out-of-band-assets.riv differ diff --git a/main/outofband/outofband-assets.collection b/main/outofband/outofband-assets.collection new file mode 100644 index 00000000..03973bf9 --- /dev/null +++ b/main/outofband/outofband-assets.collection @@ -0,0 +1,10 @@ +name: "outofband_assets" +scale_along_z: 0 +embedded_instances { + id: "assets" + data: "components {\n" + " id: \"outofband_assets\"\n" + " component: \"/main/outofband/outofband_assets.script\"\n" + "}\n" + "" +} diff --git a/main/outofband/outofband-scene.collection b/main/outofband/outofband-scene.collection new file mode 100644 index 00000000..7458eaf5 --- /dev/null +++ b/main/outofband/outofband-scene.collection @@ -0,0 +1,28 @@ +name: "outofband_scene" +scale_along_z: 0 +embedded_instances { + id: "go_rive" + data: "components {\n" + " id: \"outofband_scene\"\n" + " component: \"/main/outofband/outofband_scene.script\"\n" + "}\n" + "embedded_components {\n" + " id: \"rivemodel\"\n" + " type: \"rivemodel\"\n" + " data: \"scene: \\\"/main/outofband/out-of-band.rivescene\\\"\\n" + "default_animation: \\\"\\\"\\n" + "material: \\\"/defold-rive/assets/rivemodel.material\\\"\\n" + "blit_material: \\\"/defold-rive/assets/shader-library/rivemodel_blit.material\\\"\\n" + "\"\n" + "}\n" + "" + position { + x: 456.745 + y: 259.97 + } + scale3 { + x: 0.372221 + y: 0.368649 + z: 0.66 + } +} diff --git a/main/outofband/outofband.collection b/main/outofband/outofband.collection index f4ba9296..b11f132e 100644 --- a/main/outofband/outofband.collection +++ b/main/outofband/outofband.collection @@ -8,54 +8,23 @@ embedded_instances { "}\n" "" } -embedded_instances { - id: "go_rive" - data: "embedded_components {\n" - " id: \"rivemodel\"\n" - " type: \"rivemodel\"\n" - " data: \"scene: \\\"/main/outofband/walle.rivescene\\\"\\n" - "default_animation: \\\"Animation 1\\\"\\n" - "material: \\\"/defold-rive/assets/rivemodel.material\\\"\\n" - "artboard: \\\"New Artboard\\\"\\n" - "\"\n" - "}\n" - "" - position { - x: 471.745 - y: 251.97 - } - scale3 { - x: 0.66 - y: 0.66 - z: 0.66 - } -} embedded_instances { id: "go" data: "components {\n" " id: \"outofband\"\n" " component: \"/main/outofband/outofband.script\"\n" "}\n" - "" -} -embedded_instances { - id: "go_rive2" - data: "components {\n" - " id: \"referencedfontassets\"\n" - " component: \"/main/outofband/font/referencedfontassets.rivemodel\"\n" - " position {\n" - " x: 466.66666\n" - " }\n" + "embedded_components {\n" + " id: \"outofband_assets\"\n" + " type: \"collectionproxy\"\n" + " data: \"collection: \\\"/main/outofband/outofband-assets.collection\\\"\\n" + "\"\n" + "}\n" + "embedded_components {\n" + " id: \"outofband_scene\"\n" + " type: \"collectionproxy\"\n" + " data: \"collection: \\\"/main/outofband/outofband-scene.collection\\\"\\n" + "\"\n" "}\n" "" - position { - x: 471.745 - y: 251.97 - z: -0.1 - } - scale3 { - x: 0.66 - y: 0.66 - z: 0.66 - } } diff --git a/main/outofband/outofband.script b/main/outofband/outofband.script index 99444b6a..bdb89f0a 100644 --- a/main/outofband/outofband.script +++ b/main/outofband/outofband.script @@ -1,114 +1,35 @@ --- There are multiple ways to load an image file. --- Note however that, for the "Runtime Asset Swap" feature, it's necessary to provide the raw data as-is. --- * Load it via our resource system: --- * Path needs to be mounted (archive file, zip file, loose file) --- * Included as "custom resource" --- * Download using http, to a specific path available to the resource system --- * Pass the payload directly to the rive.riv_swap_asset() function - -local URL = "https://defold.com/images/logo/defold/logo_with_text/logo-ver-classic-white-160.png" -local URL_FONT = "http://themes.googleusercontent.com/static/fonts/abeezee/v1/JYPhMn-3Xw-JGuyB-fEdNA.ttf" - --- path to your dlc files. For simplicity, this is at the root of the project -local DLC_FILES = "." - - -local RIVE_MODEL = "go_rive#rivemodel" -local RIVE_MODEL_2 = "go_rive2#referencedfontassets" - -local function swap_assets(self) - local rivc = go.get(RIVE_MODEL, "rive_file") - rive.riv_swap_asset(rivc, "eve.png", { path = "/main/outofband/walle/eve.png" }) - -- no idea why they named it jpg - rive.riv_swap_asset(rivc, "walle.jpg", { path = "/main/outofband/walle/walle.png" }) -end - +local ASSETS_PROXY = "#outofband_assets" +local SCENE_PROXY = "#outofband_scene" +local ASSETS_SCRIPT = "outofband_assets:/assets#outofband_assets" function init(self) - local mount_name = "files" - local mount_found = false - for _,mount in pairs(liveupdate.get_mounts()) do - if mount.name == mount_name then - mount_found = true - end - end - - if mount_found then - swap_assets(self) - else - liveupdate.add_mount(mount_name, DLC_FILES, 10, function () - print("LU: mounted file provider!") - swap_assets(self) - - -- Note that there is also the option to save the file to a specific path - -- If no path is specified, it will be stored in the http cache - timer.delay(1.0, false, function() - print("HTTP REQUEST", URL) - http.request(URL, "GET", function (self, _id, response) - print(" response.status:", response.status) - if response.status == 200 or response.status == 206 or response.status == 304 then - local rivc = go.get(RIVE_MODEL, "rive_file") - rive.riv_swap_asset(rivc, "walle.jpg", { payload = response.response }) - end - end) - end) - end) - end - rive.riv_swap_asset(go.get(RIVE_MODEL_2, "rive_file"), "Inter", { path = "/main/outofband/font/Inter-referenced.ttf" }) - timer.delay(1.0, false, function() - print("HTTP REQUEST", URL_FONT) - http.request(URL_FONT, "GET", function (self, _id, response) - print(" response.status:", response.status) - if response.status == 200 or response.status == 206 or response.status == 304 then - local rivc = go.get(RIVE_MODEL_2, "rive_file") - rive.riv_swap_asset(rivc, "Inter", { payload = response.response }) - end - end) - end) -end - -function final(self) - -- Add finalization code here - -- Learn more: https://defold.com/manuals/script/ - -- Remove this function if not needed -end + self.assets_proxy = msg.url(ASSETS_PROXY) + self.scene_proxy = msg.url(SCENE_PROXY) + self.scene_loading = false -function update(self, dt) - -- Add update code here - -- Learn more: https://defold.com/manuals/script/ - -- Remove this function if not needed -end - -function fixed_update(self, dt) - -- This function is called if 'Fixed Update Frequency' is enabled in the Engine section of game.project - -- Can be coupled with fixed updates of the physics simulation if 'Use Fixed Timestep' is enabled in - -- Physics section of game.project - -- Add update code here - -- Learn more: https://defold.com/manuals/script/ - -- Remove this function if not needed + msg.post(ASSETS_PROXY, "async_load") end function on_message(self, message_id, message, sender) - -- Add message-handling code here - -- Learn more: https://defold.com/manuals/message-passing/ - -- Remove this function if not needed -end - -function on_input(self, action_id, action) - -- Add input-handling code here. The game object this script is attached to - -- must have acquired input focus: - -- - -- msg.post(".", "acquire_input_focus") - -- - -- All mapped input bindings will be received. Mouse and touch input will - -- be received regardless of where on the screen it happened. - -- Learn more: https://defold.com/manuals/input/ - -- Remove this function if not needed -end - -function on_reload(self) - -- Add reload-handling code here - -- Learn more: https://defold.com/manuals/hot-reload/ - -- Remove this function if not needed + if message_id == hash("proxy_loaded") then + if sender == self.assets_proxy then + msg.post(sender, "init") + msg.post(sender, "enable") + timer.delay(0, false, function() + msg.post(ASSETS_SCRIPT, "set_host", { host = msg.url() }) + end) + elseif sender == self.scene_proxy then + msg.post(sender, "init") + msg.post(sender, "enable") + else + msg.post(sender, "init") + msg.post(sender, "enable") + end + elseif message_id == hash("assets_initialized") then + if not self.scene_loading then + self.scene_loading = true + msg.post(SCENE_PROXY, "async_load") + end + end end diff --git a/main/outofband/outofband_assets.script b/main/outofband/outofband_assets.script new file mode 100644 index 00000000..f42eb2b8 --- /dev/null +++ b/main/outofband/outofband_assets.script @@ -0,0 +1,77 @@ +-- There are multiple ways to load an image file. +-- When loading assets via the rive command queue api feature, it's necessary to provide the raw data as-is. +-- * Load it via our resource system: +-- * Path needs to be mounted (archive file, zip file, loose file) +-- * Included as "custom resource" +-- * Download using http, to a specific path available to the resource system +-- * Pass the payload directly to the rive.riv_swap_asset() function + +-- Example from: https://codesandbox.io/p/sandbox/objective-cohen-sqwh9q +-- See static fonts from https://gist.github.com/dotJoel/7326331 +local URL_IMAGE = "https://defold.com/images/logo/defold/logo/logo-ver-classic-white-320.png" +local URL_FONT = "http://themes.googleusercontent.com/static/fonts/abeezee/v1/JYPhMn-3Xw-JGuyB-fEdNA.ttf" + +-- path to your dlc files. For simplicity, this is at the root of the project +local DLC_FILES = "." + +local function maybe_ready(self) + if self.host_url and self.image_done and self.font_done and not self.ready_sent then + self.ready_sent = true + msg.post(self.host_url, "assets_initialized") + end +end + +function init(self) + self.ready_sent = false + self.image_done = false + self.font_done = false + local mount_name = "files" + local mount_found = false + + if mount_found then + self.image_done = true + maybe_ready(self) + else + liveupdate.add_mount(mount_name, DLC_FILES, 10, function () + -- Note that there is also the option to save the file to a specific path + -- If no path is specified, it will be stored in the http cache + http.request(URL_IMAGE, "GET", function (self, _id, response) + --print(" response.status:", response.status) + self.image_done = true + if response.status == 200 or response.status == 206 or response.status == 304 then + local image = rive.cmd.decodeImage(response.response) + local image_filename = "logo-ver-outline-white-320-5143474" + + rive.cmd.addGlobalImageAsset(image_filename, image) + print("ADDED IMAGE", image_filename, "from", response.url) + else + print("Failed to load", response.url) + print("STATUS:", response.status) + end + maybe_ready(self) + end) + end) + end + + http.request(URL_FONT, "GET", function (self, _id, response) + --print(" response.status:", response.status) + self.font_done = true + if response.status == 200 or response.status == 206 or response.status == 304 then + local font = rive.cmd.decodeFont(response.response) + local font_filename = "MyFont-4228759" + rive.cmd.addGlobalFontAsset(font_filename, font) + print("ADDED FONT", font_filename, "from", response.url) + else + print("Failed to load", response.url) + print("STATUS:", response.status) + end + maybe_ready(self) + end) +end + +function on_message(self, message_id, message) + if message_id == hash("set_host") then + self.host_url = message.host + maybe_ready(self) + end +end diff --git a/main/outofband/outofband_scene.script b/main/outofband/outofband_scene.script new file mode 100644 index 00000000..4a47e3af --- /dev/null +++ b/main/outofband/outofband_scene.script @@ -0,0 +1,42 @@ +local RIVE_MODEL = "#rivemodel" + +local function setup_scene(self) + self.url = RIVE_MODEL + self.file = rive.get_file(self.url) + self.artboard = rive.get_artboard(self.url) + self.state_machine = rive.get_state_machine(self.url) + self.view_model = rive.get_view_model_instance(self.url) +-- +-- -- debugging +-- rive.cmd.requestViewModelNames(self.file) +-- rive.cmd.requestArtboardNames(self.file) +-- rive.cmd.requestViewModelEnums(self.file) +-- rive.cmd.requestStateMachineNames(self.artboard) +-- rive.cmd.requestDefaultViewModelInfo(self.artboard, self.file) +-- +-- rive.cmd.requestViewModelInstanceNames(self.file, "vmMain") +-- rive.cmd.requestViewModelPropertyDefinitions(self.file, "vmMain") +-- + -- end debugging + + rive.cmd.setViewModelInstanceString(self.view_model, "Text", "DEFOLD GAME ENGINE!") + print("SCENE INITIALIZED") +end + +function init(self) + msg.post(".", "acquire_input_focus") + + setup_scene(self) +end + +function on_input(self, action_id, action) + if not action_id or action_id == hash("touch") then + if action.pressed then + rive.pointer_down(RIVE_MODEL, action.x, action.y) + elseif action.released then + rive.pointer_up(RIVE_MODEL, action.x, action.y) + else + rive.pointer_move(RIVE_MODEL, action.x, action.y) + end + end +end diff --git a/main/outofband/walle.atlas b/main/outofband/walle.atlas deleted file mode 100644 index 7ffd1a1d..00000000 --- a/main/outofband/walle.atlas +++ /dev/null @@ -1,7 +0,0 @@ -images { - image: "/main/outofband/walle/eve.png" -} -images { - image: "/main/outofband/walle/walle.png" -} -extrude_borders: 2 diff --git a/main/outofband/walle.rivescene b/main/outofband/walle.rivescene deleted file mode 100644 index 65995919..00000000 --- a/main/outofband/walle.rivescene +++ /dev/null @@ -1,2 +0,0 @@ -scene: "/main/outofband/walle/walle.riv" -atlas: "/main/outofband/walle.atlas" diff --git a/main/outofband/walle/eve.png b/main/outofband/walle/eve.png deleted file mode 100644 index 221934aa..00000000 Binary files a/main/outofband/walle/eve.png and /dev/null differ diff --git a/main/outofband/walle/walle.png b/main/outofband/walle/walle.png deleted file mode 100644 index a3ac8ee2..00000000 Binary files a/main/outofband/walle/walle.png and /dev/null differ diff --git a/main/outofband/walle/walle.riv b/main/outofband/walle/walle.riv deleted file mode 100644 index bd0edff4..00000000 Binary files a/main/outofband/walle/walle.riv and /dev/null differ diff --git a/main/scifihud/scifihud.collection b/main/scifihud/scifihud.collection index 52b23241..20507256 100644 --- a/main/scifihud/scifihud.collection +++ b/main/scifihud/scifihud.collection @@ -20,6 +20,7 @@ embedded_instances { "artboard: \\\"Main Artboard SciFi_Game_HUD_Scope\\\"\\n" "coordinate_system: COORDINATE_SYSTEM_RIVE\\n" "artboard_fit: FIT_CONTAIN\\n" + "blit_material: \\\"/defold-rive/assets/shader-library/rivemodel_blit.material\\\"\\n" "\"\n" "}\n" "" diff --git a/main/scifihud/scifihud.script b/main/scifihud/scifihud.script index 459a7256..4c5886f3 100644 --- a/main/scifihud/scifihud.script +++ b/main/scifihud/scifihud.script @@ -1,32 +1,34 @@ function init(self) msg.post(".", "acquire_input_focus") - go.set("#hud", "Health", 100) - go.set("#hud", "Shield", 100) - go.animate("#hud", "Health", go.PLAYBACK_LOOP_PINGPONG, 0, go.EASING_INOUTQUAD, 5) - go.animate("#hud", "Shield", go.PLAYBACK_LOOP_PINGPONG, 0, go.EASING_INOUTQUAD, 4, 2) + + self.url = "#hud" + self.file = rive.get_file(self.url) + self.artboard = rive.get_artboard(self.url) + self.state_machine = rive.get_state_machine(self.url) + self.view_model = rive.get_view_model_instance(self.url) end function on_input(self, action_id, action) if action_id == hash("touch") then if action.pressed then - go.set("#hud", "Fire", true) + --rive.cmd.fireViewModelTrigger(self.view_model, "Fire") elseif action.released then - go.set("#hud", "Fire", false) + --go.set(self.url, "Fire", false) end elseif action_id == hash("1") then if action.pressed then print("weapon 1") - go.set("#hud", "Weapon ID", -1) + --go.set(self.url, "Weapon ID", -1) end elseif action_id == hash("2") then if action.pressed then print("weapon 2") - go.set("#hud", "Weapon ID", 0) + --go.set(self.url, "Weapon ID", 0) end elseif action_id == hash("3") then if action.pressed then print("weapon 3") - go.set("#hud", "Weapon ID", 1) + --go.set(self.url, "Weapon ID", 1) end end end diff --git a/main/scrollist/scrollist.script b/main/scrollist/scrollist.script index b4bd41db..d4faa438 100644 --- a/main/scrollist/scrollist.script +++ b/main/scrollist/scrollist.script @@ -8,7 +8,7 @@ local function set_border(self, index, border) end local thumb = self.thumbs[index+1] - rive.databind.set_properties(self.rive_url, thumb, {Border = border}) + rive.cmd.setViewModelInstanceNumber(thumb, "Border", border) end local function set_selected_thumb(self, index) @@ -18,7 +18,9 @@ local function set_selected_thumb(self, index) set_border(self, prev_index, 0) set_border(self, self.thumb_index, 10) - rive.databind.set_properties(self.rive_url, self.modelViewInstanceRuntime, {["List Index"] = self.thumb_index}) + rive.cmd.setViewModelInstanceNumber(self.view_model, "List Index", self.thumb_index) + + print("List Index:", rive.cmd.getViewModelInstanceNumber(self.view_model, "List Index")) end local function move_left(self) @@ -38,18 +40,30 @@ local function move_right(self) end local function create_thumb(self, image_data) - local thumb, errThumb = rive.databind.create_view_model_instance_runtime(self.rive_url, "ThumbVM") - if thumb == nil then - print("Failed to create view model instance:", errThumb) - return + local viewmodel_name = "ThumbVM" + local thumb = rive.cmd.instantiateDefaultViewModelInstance(self.file, viewmodel_name) + if thumb == 0 then + print("Failed to create view model instance:", viewmodel_name) + return end + + print("thumb:", thumb) table.insert(self.thumbs, thumb) - rive.databind.set_properties(self.rive_url, thumb, {Border = 10, Image = image_data}) - -- add the thumb last in the list - rive.databind.list_add_instance(self.rive_url, self.modelViewInstanceRuntime, self.list_path, thumb) + local image = rive.cmd.decodeImage(image_data) + print("image:", image) + + rive.cmd.setViewModelInstanceNumber(thumb, "Border", 10) + rive.cmd.setViewModelInstanceImage(thumb, "Image", image) - -- update borders + -- -- add the thumb last in the list + rive.cmd.appendViewModelInstanceListViewModel(self.view_model, self.list_path, thumb) + + -- The list size is manually modified/incremented in the appendViewModelInstanceListViewModel et.al + -- This allows us to get an accurate number directly after + print("List Size: ", rive.cmd.getViewModelInstanceListSize(self.view_model, self.list_path)) + + -- -- update borders set_selected_thumb(self, #self.thumbs - 1) end @@ -67,49 +81,58 @@ end local function request_thumb(self, url) print("Requesting new thumb") - download_image(self.modelViewInstanceRuntime, url) + download_image(self.view_model, url) end local function remove_thumb(self, index) print("Removing thumb at index", index) local thumb = table.remove(self.thumbs, index) - rive.databind.list_remove_instance(self.rive_url, self.modelViewInstanceRuntime, self.list_path, thumb) + rive.cmd.removeViewModelInstanceListViewModel(self.view_model, self.list_path, thumb) set_selected_thumb(self, index-1) end --- Rive events trigger when the splash intro starts and ends and is used to not allow input while the intro is playing. --- The logic is done within the rive statemachine. -local function rive_event_handler(self, message_id, message) - print("received event", message.name) + +local function rive_listener(self, message_name, data) + pprint("file:", message_name, data) end -- Get input focus then start the splash statemachine. function init(self) msg.post(".", "acquire_input_focus") + --rive.set_file_listener(rive_listener) + + self.list_path = 'Thumbs' + self.thumbs = {} self.rive_url = msg.url("#scrollist") - self.modelViewInstanceRuntime = rive.databind.get_view_model_instance_runtime(self.rive_url) + self.file = rive.get_file(self.rive_url) + self.artboard = rive.get_artboard(self.rive_url) + self.state_machine = rive.get_state_machine(self.rive_url) + print("file:", self.file) + print("artboard:", self.artboard) + print("state_machine:", self.state_machine) - if self.modelViewInstanceRuntime == 0xFFFFFFFF then - -- auto_bind == false - local main, errMain = rive.databind.create_view_model_instance_runtime(self.rive_url, "MainVM") - if main == nil then - print("Failed to create view model instance:", errMain) - return - end + --self.view_model = rive.cmd.instantiateDefaultViewModelInstance(self.file, self.artboard) + self.view_model = rive.cmd.instantiateBlankViewModelInstance(self.file, self.artboard) + print("viewmodelinstance", self.view_model) - self.modelViewInstanceRuntime = main - rive.databind.set_view_model_instance_runtime(self.rive_url, self.modelViewInstanceRuntime) - end + rive.cmd.bindViewModelInstance(self.state_machine, self.view_model) - self.list_path = 'Thumbs' - self.thumb_index = rive.databind.get_property(self.rive_url, self.modelViewInstanceRuntime, "List Index") - self.thumbs = {} + self.thumb_index = 0 + -- self.thumb_index = rive.databind.get_property(self.rive_url, self.view_model, "List Index") + + -- NOTE: due to the asynchronous message order, the list size won't be available until + -- two frames from this one. the dmRiveCommands::ProcessMessages() is run in Update(), which means + -- the commands above gets processed, which trigger the listeners, which pushes our list size request to the queue. + -- And so the list size isn't handled after the second dmRiveCommands::ProcessMessages(), which happens _after_ two frames. + --print("Initial List Size:", rive.cmd.getViewModelInstanceListSize(self.view_model, self.list_path)) + --print("Initial List Index:", rive.cmd.getViewModelInstanceNumber(self.view_model, "List Index")) for i=1,MAX_NUM_THUMBS do request_thumb(self, URL) end + end diff --git a/utils/buildscripts/build_android.sh b/utils/buildscripts/build_android.sh index 28251d5c..3e5ae475 100755 --- a/utils/buildscripts/build_android.sh +++ b/utils/buildscripts/build_android.sh @@ -15,6 +15,9 @@ set -euo pipefail shopt -s nullglob +# Script location (used for shared helper scripts). +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" + # Treat the current working directory as the repo root (do not depend on script location). ROOT_DIR="$(pwd -P)" SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" diff --git a/utils/buildscripts/build_darwin.sh b/utils/buildscripts/build_darwin.sh index 4a29e705..ed434682 100755 --- a/utils/buildscripts/build_darwin.sh +++ b/utils/buildscripts/build_darwin.sh @@ -21,6 +21,9 @@ set -euo pipefail shopt -s nullglob +# Script location (used for shared helper scripts). +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" + # Treat the current working directory as the repo root (do not depend on script location). ROOT_DIR="$(pwd -P)" SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" diff --git a/utils/buildscripts/build_emscripten.sh b/utils/buildscripts/build_emscripten.sh index 9b2f830e..e2acbc15 100755 --- a/utils/buildscripts/build_emscripten.sh +++ b/utils/buildscripts/build_emscripten.sh @@ -18,6 +18,9 @@ set -euo pipefail shopt -s nullglob +# Script location (used for shared helper scripts). +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" + # Treat the current working directory as the repo root (do not depend on script location). ROOT_DIR="$(pwd -P)" SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" diff --git a/utils/buildscripts/build_linux.sh b/utils/buildscripts/build_linux.sh index 0a334733..09055e60 100755 --- a/utils/buildscripts/build_linux.sh +++ b/utils/buildscripts/build_linux.sh @@ -16,6 +16,9 @@ set -euo pipefail shopt -s nullglob +# Script location (used for shared helper scripts). +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" + # Treat the current working directory as the repo root (do not depend on script location). ROOT_DIR="$(pwd -P)" SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" diff --git a/utils/update_script_api.py b/utils/update_script_api.py new file mode 100644 index 00000000..46e5d904 --- /dev/null +++ b/utils/update_script_api.py @@ -0,0 +1,397 @@ +#!/usr/bin/env python3 +""" +Generate `defold-rive/api/rive.script_api` from the Doxygen documentation inside +`defold-rive/src/script_rive.cpp` and `defold-rive/src/script_rive_cmd.cpp`. +""" + +import re +from pathlib import Path +from typing import Dict, List, Optional, Set + + +INPUT_RIVE = Path("defold-rive/src/script_rive.cpp") +INPUT_CMD = Path("defold-rive/src/script_rive_cmd.cpp") +OUTPUT_API = Path("defold-rive/api/rive.script_api") + + +TYPE_ALIAS_BY_NAME = { + "file_handle": "FileHandle", + "artboard_handle": "ArtboardHandle", + "state_machine_handle": "StateMachineHandle", + "view_model_handle": "ViewModelInstanceHandle", + "view_model_instance_handle": "ViewModelInstanceHandle", + "nested_handle": "ViewModelInstanceHandle", + "image_handle": "RenderImageHandle", + "render_image_handle": "RenderImageHandle", + "audio_handle": "AudioSourceHandle", + "font_handle": "FontHandle", +} + +TYPE_ALIAS_BY_BULLET = { + "file": "FileHandle", + "artboard": "ArtboardHandle", + "stateMachine": "StateMachineHandle", + "viewModel": "ViewModelInstanceHandle", + "renderImage": "RenderImageHandle", + "audioSource": "AudioSourceHandle", + "font": "FontHandle", + "viewModelInstance": "ViewModelInstanceHandle", +} + + +def parse_documentation(text: str) -> Dict[str, dict]: + result = {} + pattern = re.compile(r"/\*\*((?:.|\n)*?)\*/\s+static int (Script_\w+)\(", re.MULTILINE) + + for match in pattern.finditer(text): + doc_text = match.group(1) + script_name = match.group(2) + result[script_name] = parse_doc_block(doc_text) + return result + + +def parse_doc_block(doc_text: str) -> dict: + lines = [] + for raw in doc_text.splitlines(): + raw = raw.strip() + if raw.startswith("*"): + raw = raw[1:].lstrip() + lines.append(raw) + description_lines = [] + params = [] + returns = [] + names: List[str] = [] + desc_done = False + i = 0 + while i < len(lines): + line = lines[i] + if line.startswith("@name"): + names_line = line[len("@name"):].strip() + parts = [part.strip() for part in names_line.split("/") if part.strip()] + names.extend(parts) + desc_done = True + i += 1 + continue + if line.startswith("@param"): + desc_done = True + param = parse_typed_line(line[len("@param") :].strip()) + i += 1 + extra = [] + while i < len(lines) and not lines[i].startswith("@"): + extra.append(lines[i]) + i += 1 + param["extras"] = extra + params.append(param) + continue + if line.startswith("@return"): + desc_done = True + returns.append(parse_typed_line(line[len("@return") :].strip())) + i += 1 + continue + if not desc_done and line: + description_lines.append(line) + i += 1 + description = " ".join(description_lines).strip() + for param in params: + apply_type_alias(param) + if param["name"] == "callback": + nested = parse_callback_sections(param.get("extras", [])) + if nested: + param["parameters"] = nested + elif param.get("extras"): + # drop extras for non-callback parameters + pass + for ret in returns: + apply_type_alias(ret) + return { + "description": description, + "parameters": params, + "returns": returns, + "names": names, + } + + +def parse_typed_line(text: str) -> dict: + match = re.match(r"(\w+)\s+\[type:\s*([^\]]+)\]\s*(.*)", text) + if not match: + raise SystemExit(f"Could not parse typed line: {text!r}") + name = match.group(1) + type_text = match.group(2).strip() + desc = match.group(3).strip() + return {"name": name, "type": type_text, "desc": desc} + + +def apply_type_alias(entry: dict): + name = entry["name"] + type_text = entry["type"] + alias = TYPE_ALIAS_BY_NAME.get(name) + if alias: + entry["type"] = alias + return + if type_text in TYPE_ALIAS_BY_NAME: + entry["type"] = TYPE_ALIAS_BY_NAME[type_text] + return + entry["type"] = TYPE_ALIAS_BY_NAME.get(type_text, type_text) + + +def parse_callback_sections(lines: List[str]) -> List[dict]: + sections = [] + current: Optional[dict] = None + bullets: List[str] = [] + for raw in lines: + text = raw.strip() + if not text: + continue + if text.startswith("`") and "`" in text[1:]: + if current: + current["bullets"] = bullets + bullets = [] + key = text.strip("`").strip() + current = {"name": key, "type": "", "desc": "", "bullets": []} + sections.append(current) + continue + if text.startswith(":"): + match = re.match(r":\s*\[type:\s*([^\]]+)\]\s*(.*)", text) + if match and current: + current["type"] = match.group(1).strip() + current["desc"] = match.group(2).strip() + continue + if text.startswith("-"): + bullets.append(text[1:].strip()) + continue + if current: + current["bullets"] = bullets + result = [] + for section in sections: + entry = { + "name": section["name"], + "type": section["type"] or "string", + "desc": section["desc"], + } + if entry["name"] == "event" and section["bullets"]: + extras = ", ".join(b.strip("` ") for b in section["bullets"]) + entry["desc"] = f"{entry['desc']} {extras}".strip() + if entry["name"] == "data" and section["bullets"]: + subparams = [] + for bullet in section["bullets"]: + match = re.match(r"`?([^`]+)`?:\s*\[type:\s*([^\]]+)\]\s*(.*)", bullet) + if not match: + continue + child_name = match.group(1).strip() + child_type = match.group(2).strip() + child_desc = match.group(3).strip() + child_type = TYPE_ALIAS_BY_BULLET.get(child_name, child_type) + subparams.append( + {"name": child_name, "type": child_type, "desc": child_desc} + ) + if subparams: + entry["parameters"] = subparams + result.append(entry) + return result + + +def parse_registration(text: str, symbol: str) -> List[tuple]: + pattern = re.compile( + rf"static const luaL_reg {symbol}\[\]\s*=\s*\{{(.*?)\}};", re.S + ) + match = pattern.search(text) + if not match: + raise SystemExit(f"Registration table {symbol} not found.") + content = match.group(1) + funcs = [] + for item in re.findall(r'\{"([^"]+)",\s*Script_(\w+)\}', content): + funcs.append(item) + return funcs + + +def write_members(members: List[dict]) -> List[str]: + lines = [] + first = True + for member in members: + if not first: + lines.append("") + lines.append("#*****************************************************************************************************") + first = False + lines.append("") + lines.append(f" - name: {member['name']}") + lines.append(" type: function") + lines.append(f" desc: {member['desc']}") + if member.get("parameters"): + lines.append(" parameters:") + for param in member["parameters"]: + lines.extend(format_param(param, 6)) + if member.get("returns"): + lines.append(" return:") + for ret in member["returns"]: + lines.extend( + [ + " - name: {}".format(ret["name"]), + " type: {}".format(ret["type"]), + " desc: {}".format(ret["desc"]), + ] + ) + return lines + + +def format_param(param: dict, indent: int) -> List[str]: + lines = [] + lines.append(" " * indent + f"- name: {param['name']}") + lines.append(" " * (indent + 2) + f"type: {param['type']}") + lines.append(" " * (indent + 2) + f"desc: {param['desc']}") + if param.get("parameters"): + lines.append(" " * (indent + 2) + "parameters:") + for child in param["parameters"]: + lines.extend(format_param(child, indent + 4)) + return lines + + +def build_api(): + rive_text = INPUT_RIVE.read_text() + cmd_text = INPUT_CMD.read_text() + rive_docs = parse_documentation(rive_text) + cmd_docs = parse_documentation(cmd_text) + rive_order = parse_registration(rive_text, "RIVE_FUNCTIONS") + cmd_order = parse_registration(cmd_text, "RIVE_COMMAND_FUNCTIONS") + + rive_members = [] + for lua_name, script_name in rive_order: + doc = rive_docs.get(f"Script_{script_name}") + if not doc: + continue + rive_members.append( + { + "name": lua_name, + "desc": doc["description"] or lua_name, + "parameters": doc["parameters"], + "returns": doc["returns"], + } + ) + + cmd_members = [] + for lua_name, script_name in cmd_order: + doc = cmd_docs.get(f"Script_{script_name}") + if not doc: + continue + cmd_members.append( + { + "name": lua_name, + "desc": doc["description"] or lua_name, + "parameters": doc["parameters"], + "returns": doc["returns"], + } + ) + + lines = [] + lines.append("- name: rive") + lines.append(" type: table") + lines.append(' desc: Rive animation helpers exposed to Lua scripts') + lines.append("") + lines.append(" members:") + lines.extend(write_members(rive_members)) + lines.append("") + lines.append("#*****************************************************************************************************") + lines.append("") + lines.append("- name: rive.cmd") + lines.append(" type: table") + lines.append(" desc: Command queue helpers for interacting with the Rive runtime") + lines.append("") + lines.append(" members:") + lines.extend(write_members(cmd_members)) + lines.append("") + + OUTPUT_API.write_text("\n".join(lines).strip() + "\n") + update_lua_annotations(rive_members, cmd_members) + + +def gather_handle_aliases(members: List[dict]) -> Set[str]: + aliases: Set[str] = set() + for member in members: + for entry in member.get("parameters", []) + member.get("returns", []): + type_name = entry["type"] + if type_name in TYPE_ALIAS_BY_NAME.values(): + aliases.add(type_name) + return aliases + + +def update_lua_annotations(rive_members: List[dict], cmd_members: List[dict]): + lua_lines = [ + "-- Auto generated from utils/update_script_api.py", + "-- WARNING: Do not edit manually.", + "", + "--[[", + "Rive API documentation", + "Functions and constants for interacting with Rive models", + "--]]", + "", + "---@meta", + "---@diagnostic disable: lowercase-global", + "---@diagnostic disable: missing-return", + "---@diagnostic disable: duplicate-doc-param", + "---@diagnostic disable: duplicate-set-field", + "---@diagnostic disable: args-after-dots", + "", + ] + handles = sorted(gather_handle_aliases(rive_members + cmd_members)) + for alias in handles: + lua_lines.append(f"---@alias {alias} integer") + if handles: + lua_lines.append("") + + lua_lines.append("--- @class rive") + lua_lines.append("rive = {}") + lua_lines.append("") + for member in rive_members: + lua_lines.extend(format_lua_function("rive", member)) + lua_lines.append("--- @class rive.cmd") + lua_lines.append("rive.cmd = {}") + lua_lines.append("") + for member in cmd_members: + lua_lines.extend(format_lua_function("rive.cmd", member)) + + OUTPUT_API.parent.mkdir(parents=True, exist_ok=True) + lua_file = OUTPUT_API.with_suffix(".lua") + lua_file.write_text("\n".join(lua_lines).strip() + "\n") + + +def format_lua_function(namespace: str, member: dict) -> List[str]: + lines = [] + lines.append(f"--- {member['desc']}") + for param in member.get("parameters", []): + type_text = param["type"] + optional = False + if "|nil" in type_text: + type_text = type_text.replace("|nil", "") + optional = True + type_text = type_text.strip() + if param.get("parameters"): + inner = ", ".join(entry["name"] for entry in param["parameters"]) + lua_type = f"fun({inner})" + elif "function" in type_text: + lua_type = "function" + else: + lua_type = type_text + param_name = f"{param['name']}?" if optional else param['name'] + lines.append(f"---@param {param_name} {lua_type} {param['desc']}") + if param.get("parameters"): + lines.extend(format_callback_subparams(param["name"], param["parameters"])) + for ret in member.get("returns", []): + lines.append(f"---@return {ret['type']} {ret['name']} {ret['desc']}") + params = ", ".join(param["name"] for param in member.get("parameters", [])) + func_ns = namespace + lines.append(f"function {func_ns}.{member['name']}({params}) end") + lines.append("") + return lines + + +def format_callback_subparams(base: str, entries: List[dict]) -> List[str]: + lines = [] + for entry in entries: + child_name = f"{base}_{entry['name']}" + lines.append(f"---@param {child_name} {entry['type']} {entry['desc']}") + if entry.get("parameters"): + lines.extend(format_callback_subparams(child_name, entry["parameters"])) + return lines + + +if __name__ == "__main__": + build_api() diff --git a/utils/viewer/CMakeLists.txt b/utils/viewer/CMakeLists.txt new file mode 100644 index 00000000..40f53c47 --- /dev/null +++ b/utils/viewer/CMakeLists.txt @@ -0,0 +1,166 @@ +cmake_minimum_required(VERSION 3.16) +project(viewer LANGUAGES CXX) + +set(DYNAMO_HOME $ENV{DYNAMO_HOME}) +if(NOT DYNAMO_HOME) + message(FATAL_ERROR "DYNAMO_HOME environment variable is required") +endif() + +if(NOT TARGET_PLATFORM) + set(TARGET_PLATFORM $ENV{TARGET_PLATFORM}) +endif() + +if(NOT TARGET_PLATFORM) + message(FATAL_ERROR "TARGET_PLATFORM must be supplied as a CMake variable or environment variable") +endif() + +if(${TARGET_PLATFORM} MATCHES "macos") + enable_language(OBJCXX) +endif() + +option(WITH_ASAN "Build viewer with Address Sanitizer" OFF) + +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Release CACHE STRING "Build type" FORCE) +endif() + +file(REAL_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../.." EXTENSION_ROOT) + +set(ASAN_SUPPORTED FALSE) +if(CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU") + set(ASAN_SUPPORTED TRUE) +endif() +if(WITH_ASAN AND NOT ASAN_SUPPORTED) + message(WARNING "Address Sanitizer is not supported for ${CMAKE_CXX_COMPILER_ID}; disabling") + set(WITH_ASAN OFF) +endif() +set(VIEWER_SHADER_SOURCE "${CMAKE_CURRENT_SOURCE_DIR}/shaders.cpp") + +if(${TARGET_PLATFORM} MATCHES "macos") + set(RIVE_BLIT_SHADER_VP "${EXTENSION_ROOT}/defold-rive/assets/shader-library/rivemodel_blit.vp") + set(RIVE_BLIT_SHADER_FP "${EXTENSION_ROOT}/defold-rive/assets/shader-library/rivemodel_blit.fp") + set(RIVE_BLIT_SHADER_OUT "${CMAKE_CURRENT_BINARY_DIR}/shaders.cpp") + set(RIVE_SHADER_GEN_SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/gen_shaders.py") + + set(BOB_PATH $ENV{BOB}) + if(NOT BOB_PATH) + if(DEFINED ENV{DYNAMO_HOME}) + set(BOB_PATH "$ENV{DYNAMO_HOME}/share/java/bob.jar") + else() + set(BOB_PATH "${EXTENSION_ROOT}/bob.jar") + endif() + endif() + + add_custom_command( + OUTPUT "${RIVE_BLIT_SHADER_OUT}" + COMMAND python3 "${RIVE_SHADER_GEN_SCRIPT}" + --bob "${BOB_PATH}" + --platform "${TARGET_PLATFORM}" + --content-root "${EXTENSION_ROOT}" + --vertex "${RIVE_BLIT_SHADER_VP}" + --fragment "${RIVE_BLIT_SHADER_FP}" + --output "${RIVE_BLIT_SHADER_OUT}" + DEPENDS + "${RIVE_SHADER_GEN_SCRIPT}" + "${RIVE_BLIT_SHADER_VP}" + "${RIVE_BLIT_SHADER_FP}" + VERBATIM + ) + + set(VIEWER_SHADER_SOURCE "${RIVE_BLIT_SHADER_OUT}") +endif() + +add_executable(viewer + "${CMAKE_CURRENT_SOURCE_DIR}/viewer.cpp" + "${VIEWER_SHADER_SOURCE}" +) + +file(GLOB RIVE_COMMON_SRC + "${EXTENSION_ROOT}/defold-rive/commonsrc/*.cpp" + "${EXTENSION_ROOT}/defold-rive/commonsrc/renderer/*.cpp" +) +target_sources(viewer PRIVATE ${RIVE_COMMON_SRC}) + +target_include_directories(viewer PRIVATE + "${EXTENSION_ROOT}/defold-rive/include" + "${DYNAMO_HOME}/sdk/include/${TARGET_PLATFORM}" + "${DYNAMO_HOME}/sdk/include" + "${DYNAMO_HOME}/include/${TARGET_PLATFORM}" + "${DYNAMO_HOME}/include" + "${DYNAMO_HOME}/ext/include/${TARGET_PLATFORM}" + "${DYNAMO_HOME}/ext/include" +) + +target_link_directories(viewer PRIVATE + "${DYNAMO_HOME}/lib/${TARGET_PLATFORM}" + "${DYNAMO_HOME}/ext/lib/${TARGET_PLATFORM}" +) + +if(${TARGET_PLATFORM} MATCHES "macos") + target_link_directories(viewer PRIVATE "${EXTENSION_ROOT}/defold-rive/lib/arm64-osx") +else() + target_link_directories(viewer PRIVATE "${EXTENSION_ROOT}/defold-rive/lib/{TARGET_PLATFORM}") +endif() + +set(TARGET_LIBS + dlib + ddf + profile_null + platform + graphics + + rive + rive_decoders + rive_harfbuzz + rive_pls_renderer + rive_sheenbidi + rive_yoga + libjpeg + libpng + libwebp + miniaudio +) + +if(${TARGET_PLATFORM} MATCHES "macos") + file(GLOB RIVE_COMMON_OBJCXX_SRC + "${EXTENSION_ROOT}/defold-rive/commonsrc/renderer/*.mm" + ) + target_sources(viewer PRIVATE ${RIVE_COMMON_OBJCXX_SRC}) + + list(APPEND TARGET_LIBS + platform_vulkan + graphics_vulkan + glfw3 + MoltenVK + ) + target_link_libraries(viewer PRIVATE + "-framework CoreGraphics" + "-framework CoreFoundation" + "-framework IOKit" + "-framework IOSurface" + "-framework AppKit" + "-framework Metal" + "-framework QuartzCore") + + target_compile_definitions(viewer PRIVATE DM_PLATFORM_MACOS VIEWER_USE_SPIRV=1) +endif() + +target_link_libraries(viewer PRIVATE ${TARGET_LIBS}) + +target_compile_options(viewer PRIVATE + $<$:-O2> + $<$:-g> + $<$>:-O0> + $<$>:-g> +) +if(WITH_ASAN) + target_compile_options(viewer PRIVATE -fsanitize=address) + target_link_options(viewer PRIVATE -fsanitize=address) +endif() + +target_compile_definitions(viewer PRIVATE DLIB_LOG_DOMAIN=\"VIEWER\") + +set_target_properties(viewer PROPERTIES + CXX_STANDARD 17 + CXX_STANDARD_REQUIRED ON +) diff --git a/utils/viewer/README.md b/utils/viewer/README.md new file mode 100644 index 00000000..e69de29b diff --git a/utils/viewer/build.sh b/utils/viewer/build.sh new file mode 100755 index 00000000..9dd32a94 --- /dev/null +++ b/utils/viewer/build.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" + +WITH_ASAN=OFF +ARGS=() + +while [[ $# -gt 0 ]]; do + case "$1" in + --with-asan) + WITH_ASAN=ON + shift + ;; + *) + ARGS+=("$1") + shift + ;; + esac +done + +TARGET_PLATFORM="${ARGS[0]:-${TARGET_PLATFORM:-}}" + +if [ -z "${TARGET_PLATFORM}" ]; then + echo "Usage: $0 [--with-asan] " + exit 1 +fi + +ALLOWED_PLATFORMS=("arm64-macos" "x86_64-win32" "x86_64-linux") +if [[ ! " ${ALLOWED_PLATFORMS[*]} " =~ " ${TARGET_PLATFORM} " ]]; then + echo "Unsupported target platform: ${TARGET_PLATFORM}" + echo "Supported platforms: ${ALLOWED_PLATFORMS[*]}" + exit 1 +fi + +BUILD_DIR="${SCRIPT_DIR}/build/${TARGET_PLATFORM}" +mkdir -p "${BUILD_DIR}" + +cmake -S "${SCRIPT_DIR}" -B "${BUILD_DIR}" -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DTARGET_PLATFORM="${TARGET_PLATFORM}" \ + -DWITH_ASAN=${WITH_ASAN} +cmake --build "${BUILD_DIR}" --target viewer diff --git a/utils/viewer/gen_shaders.py b/utils/viewer/gen_shaders.py new file mode 100644 index 00000000..fbcb2897 --- /dev/null +++ b/utils/viewer/gen_shaders.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python3 +import argparse +import os +import pathlib +import shlex +import subprocess +import sys +import tempfile + + +def resolve_bob_path(cli_bob): + if cli_bob: + return cli_bob + env_bob = os.environ.get("BOB") + if env_bob: + return env_bob + dynamo_home = os.environ.get("DYNAMO_HOME") + if dynamo_home: + return os.path.join(dynamo_home, "share", "java", "bob.jar") + return "" + + +def write_cpp(output_path, spc_bytes, source_paths, command_line): + output_path.parent.mkdir(parents=True, exist_ok=True) + with output_path.open("w", encoding="ascii") as out: + out.write("// Generated by gen_shaders.py. Do not edit.\n") + out.write("// Command: {}\n".format(command_line)) + out.write("// Sources:\n") + for path in source_paths: + out.write("// {}\n".format(path)) + out.write("\n") + out.write("#include \n") + out.write("#include \n") + out.write("#include \n") + out.write("#include \n") + out.write("\n") + out.write("static uint8_t RIVEMODEL_BLIT_SPC[] = {\n") + for i in range(0, len(spc_bytes), 12): + chunk = spc_bytes[i : i + 12] + line = ", ".join("0x{:02x}".format(b) for b in chunk) + out.write(" {},\n".format(line)) + out.write("};\n") + out.write("static const uint32_t RIVEMODEL_BLIT_SPC_SIZE = sizeof(RIVEMODEL_BLIT_SPC);\n") + out.write("\n") + out.write("namespace dmGraphics\n") + out.write("{\n") + out.write(" ShaderDesc* CreateRiveModelBlitShaderDesc()\n") + out.write(" {\n") + out.write(" static ShaderDesc* shader_desc = 0;\n") + out.write(" if (!shader_desc)\n") + out.write(" {\n") + out.write(" void* msg = 0;\n") + out.write(" dmDDF::Result result = dmDDF::LoadMessage(RIVEMODEL_BLIT_SPC,\n") + out.write(" RIVEMODEL_BLIT_SPC_SIZE,\n") + out.write(" ShaderDesc::m_DDFDescriptor,\n") + out.write(" &msg);\n") + out.write(" if (result != dmDDF::RESULT_OK)\n") + out.write(" {\n") + out.write(" return 0;\n") + out.write(" }\n") + out.write(" shader_desc = (ShaderDesc*) msg;\n") + out.write(" }\n") + out.write(" return shader_desc;\n") + out.write(" }\n") + out.write("}\n") + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--bob", default="") + parser.add_argument("--platform", required=True) + parser.add_argument("--content-root", required=True) + parser.add_argument("--vertex", required=True) + parser.add_argument("--fragment", required=True) + parser.add_argument("--output", required=True) + args = parser.parse_args() + + bob_path = resolve_bob_path(args.bob) + if not bob_path or not os.path.isfile(bob_path): + print("bob.jar not found. Set --bob, BOB, or DYNAMO_HOME.", file=sys.stderr) + return 1 + + content_root = pathlib.Path(args.content_root).resolve() + vertex_path = pathlib.Path(args.vertex).resolve() + fragment_path = pathlib.Path(args.fragment).resolve() + output_path = pathlib.Path(args.output).absolute() + + if not vertex_path.is_file() or not fragment_path.is_file(): + print("Shader inputs not found.", file=sys.stderr) + return 1 + + with tempfile.TemporaryDirectory() as tmpdir: + spc_path = pathlib.Path(tmpdir) / "rivemodel_blit.spc" + cmd = [ + "java", + "-classpath", + bob_path, + "com.dynamo.bob.pipeline.ShaderProgramBuilder", + str(vertex_path), + str(fragment_path), + str(spc_path), + args.platform, + str(content_root), + ] + cmd_str = " ".join(shlex.quote(part) for part in cmd) + subprocess.check_call(cmd) + + spc_bytes = spc_path.read_bytes() + write_cpp(output_path, spc_bytes, [vertex_path, fragment_path], cmd_str) + + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/utils/viewer/viewer.cpp b/utils/viewer/viewer.cpp new file mode 100644 index 00000000..f797c819 --- /dev/null +++ b/utils/viewer/viewer.cpp @@ -0,0 +1,626 @@ +// Copyright 2020-2025 The Defold Foundation +// Copyright 2014-2020 King +// Copyright 2009-2014 Ragnar Svensson, Christian Murray +// Licensed under the Defold License version 1.0 (the "License"); you may not use +// this file except in compliance with the License. +// +// You may obtain a copy of the License, together with FAQs at +// https://www.defold.com/license +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +// Creating a small app test for initializing an running a small graphics app + +#include +#include +#include +#include + +#include +#include +#include +#include + + +#include +#include // LogParams +#include // JobThread +#include // ContextParams + +#include +#include + +namespace dmGraphics +{ + ShaderDesc* CreateRiveModelBlitShaderDesc(); +} + +enum UpdateResult +{ + RESULT_OK = 0, + RESULT_REBOOT = 1, + RESULT_EXIT = -1, +}; + +typedef void* (*EngineCreateFn)(int argc, char** argv); +typedef void (*EngineDestroyFn)(void* engine); +typedef UpdateResult (*EngineUpdateFn)(void* engine); +typedef void (*EngineGetResultFn)(void* engine, int* run_action, int* exit_code, int* argc, char*** argv); + + +static const char* s_RiveFilePath = 0; + +static bool ReadFile(const char* path, std::vector& out) +{ + FILE* f = fopen(path, "rb"); + if (!f) + { + fprintf(stderr, "Failed to open '%s'\n", path); + return false; + } + + if (fseek(f, 0, SEEK_END) != 0) + { + fclose(f); + return false; + } + long size = ftell(f); + if (size < 0) + { + fclose(f); + return false; + } + rewind(f); + + out.resize(size); + if (size > 0) + { + size_t read = fread(out.data(), 1, size, f); + if (read != (size_t)size) + { + fclose(f); + return false; + } + } + + fclose(f); + return true; +} + +struct AppCtx +{ + int m_Created; + int m_Destroyed; +}; + +struct EngineCtx +{ + int m_WasCreated; + int m_WasRun; + int m_WasDestroyed; + int m_WasResultCalled; + int m_Running; + bool m_WindowClosed; + + dmJobThread::HContext m_JobThread; + + dmPlatform::HWindow m_Window; + dmGraphics::HContext m_GraphicsContext; + + dmGraphics::HVertexBuffer m_BlitToBackbufferVertexBuffer; + dmGraphics::HVertexDeclaration m_VertexDeclaration; + dmGraphics::HProgram m_BlitProgram; + dmGraphics::HTexture m_Texture; + dmGraphics::HUniformLocation m_SamplerLocation; + + // Rive related + dmRive::HRenderContext m_RenderContext; + + rive::FileHandle m_File; + rive::ArtboardHandle m_Artboard; + rive::StateMachineHandle m_StateMachine; + rive::ViewModelInstanceHandle m_ViewModelInstance; + rive::DrawKey m_DrawKey; +}; + +struct RunLoopParams +{ + int m_Argc; + char** m_Argv; + + void* m_AppCtx; + void (*m_AppCreate)(void* ctx); + void (*m_AppDestroy)(void* ctx); + + EngineCreateFn m_EngineCreate; + EngineDestroyFn m_EngineDestroy; + EngineUpdateFn m_EngineUpdate; + EngineGetResultFn m_EngineGetResult; +}; + +static int RunLoop(const RunLoopParams* params) +{ + if (params->m_AppCreate) + params->m_AppCreate(params->m_AppCtx); + + int argc = params->m_Argc; + char** argv = params->m_Argv; + int exit_code = 0; + void* engine = 0; + UpdateResult result = RESULT_OK; + while (RESULT_OK == result) + { + if (engine == 0) + { + engine = params->m_EngineCreate(argc, argv); + if (!engine) + { + exit_code = 1; + break; + } + } + + result = params->m_EngineUpdate(engine); + + if (RESULT_OK != result) + { + int run_action = 0; + params->m_EngineGetResult(engine, &run_action, &exit_code, &argc, &argv); + + params->m_EngineDestroy(engine); + engine = 0; + + if (RESULT_REBOOT == result) + { + // allows us to reboot + result = RESULT_OK; + } + } + } + + if (params->m_AppDestroy) + params->m_AppDestroy(params->m_AppCtx); + + return exit_code; +} + +static void AppCreate(void* _ctx) +{ + dmLog::LogParams params; + dmLog::LogInitialize(¶ms); + + AppCtx* ctx = (AppCtx*)_ctx; + ctx->m_Created++; +} + +static void AppDestroy(void* _ctx) +{ + AppCtx* ctx = (AppCtx*)_ctx; + ctx->m_Destroyed++; +} + +static dmGraphics::HUniformLocation FindUniformLocation(dmGraphics::HProgram program, const char* name) +{ + uint32_t uniform_count = dmGraphics::GetUniformCount(program); + for (uint32_t i = 0; i < uniform_count; ++i) + { + dmGraphics::Uniform uniform; + dmGraphics::GetUniform(program, i, &uniform); + if (uniform.m_Name && strcmp(uniform.m_Name, name) == 0) + { + return uniform.m_Location; + } + } + + return dmGraphics::INVALID_UNIFORM_LOCATION; +} + +static void DrawFullscreenQuad(EngineCtx* engine, dmGraphics::HTexture texture) +{ + if (engine->m_BlitProgram == 0 || engine->m_BlitProgram == dmGraphics::INVALID_PROGRAM_HANDLE || texture == 0) + { + return; + } + + dmGraphics::HContext context = engine->m_GraphicsContext; + uint32_t window_width = dmGraphics::GetWindowWidth(context); + uint32_t window_height = dmGraphics::GetWindowHeight(context); + dmGraphics::SetViewport(context, 0, 0, window_width, window_height); + dmGraphics::EnableProgram(context, engine->m_BlitProgram); + dmGraphics::EnableVertexDeclaration(context, engine->m_VertexDeclaration, 0, 0, engine->m_BlitProgram); + dmGraphics::EnableVertexBuffer(context, engine->m_BlitToBackbufferVertexBuffer, 0); + + dmGraphics::EnableTexture(context, 0, 0, texture); + if (engine->m_SamplerLocation != dmGraphics::INVALID_UNIFORM_LOCATION) + { + dmGraphics::SetSampler(context, engine->m_SamplerLocation, 0); + } + + dmGraphics::Draw(context, dmGraphics::PRIMITIVE_TRIANGLES, 0, 6, 1); + + dmGraphics::DisableTexture(context, 0, texture); + dmGraphics::DisableVertexBuffer(context, engine->m_BlitToBackbufferVertexBuffer); + dmGraphics::DisableVertexDeclaration(context, engine->m_VertexDeclaration); + dmGraphics::DisableProgram(context); +} + +static bool OnWindowClose(void* user_data) +{ + EngineCtx* engine = (EngineCtx*) user_data; + engine->m_WindowClosed = 1; + return true; +} + +static void* EngineCreate(int argc, char** argv) +{ + EngineCtx* engine = new EngineCtx; + memset(engine, 0, sizeof(*engine)); + engine->m_Window = dmPlatform::NewWindow(); + + dmJobThread::JobThreadCreationParams job_params = {0}; + engine->m_JobThread = dmJobThread::Create(job_params); + + dmPlatform::WindowParams window_params = {}; + window_params.m_Width = 512; + window_params.m_Height = 512; + window_params.m_Title = "Rive Viewer App"; + + window_params.m_GraphicsApi = dmPlatform::PLATFORM_GRAPHICS_API_VULKAN; + window_params.m_CloseCallback = OnWindowClose; + window_params.m_CloseCallbackUserData = (void*) engine; + + if (dmGraphics::GetInstalledAdapterFamily() == dmGraphics::ADAPTER_FAMILY_OPENGL) + { + window_params.m_GraphicsApi = dmPlatform::PLATFORM_GRAPHICS_API_OPENGL; + } + else if (dmGraphics::GetInstalledAdapterFamily() == dmGraphics::ADAPTER_FAMILY_OPENGLES) + { + window_params.m_GraphicsApi = dmPlatform::PLATFORM_GRAPHICS_API_OPENGLES; + } + else if (dmGraphics::GetInstalledAdapterFamily() == dmGraphics::ADAPTER_FAMILY_DIRECTX) + { + window_params.m_GraphicsApi = dmPlatform::PLATFORM_GRAPHICS_API_DIRECTX; + } + + dmPlatform::OpenWindow(engine->m_Window, window_params); + dmPlatform::ShowWindow(engine->m_Window); + + dmGraphics::ContextParams graphics_context_params = {}; + graphics_context_params.m_DefaultTextureMinFilter = dmGraphics::TEXTURE_FILTER_LINEAR_MIPMAP_NEAREST; + graphics_context_params.m_DefaultTextureMagFilter = dmGraphics::TEXTURE_FILTER_LINEAR_MIPMAP_NEAREST; + graphics_context_params.m_VerifyGraphicsCalls = 1; + graphics_context_params.m_UseValidationLayers = 1; + graphics_context_params.m_Window = engine->m_Window; + graphics_context_params.m_Width = 512; + graphics_context_params.m_Height = 512; + graphics_context_params.m_JobThread = engine->m_JobThread; + + engine->m_GraphicsContext = dmGraphics::NewContext(graphics_context_params); + + engine->m_WasCreated++; + engine->m_Running = 1; + + // dmRender::RenderContextParams render_params; + // render_params.m_ScriptContext = 0; + // render_params.m_SystemFontMap = 0; + // render_params.m_ShaderProgramDesc = 0; + // render_params.m_MaxRenderTypes = 16; + // render_params.m_MaxInstances = 2048; + // render_params.m_MaxRenderTargets = 32; + // render_params.m_ShaderProgramDescSize = 0; + // render_params.m_MaxCharacters = 2048 * 4; + // render_params.m_MaxBatches = 128; + // render_params.m_CommandBufferSize = 1024; + // render_params.m_MaxDebugVertexCount = 0; + // engine->m_RenderListContext = dmRender::NewRenderContext(engine->m_GraphicsContext, render_params); + + // Graphics + + float bottom = 0.0f; + float top = 1.0f; + + // Flip texture coordinates on y axis for OpenGL for the final blit: + if (dmGraphics::GetInstalledAdapterFamily() != dmGraphics::ADAPTER_FAMILY_OPENGL) + { + top = 0.0f; + bottom = 1.0f; + } + + const float vertex_data[] = { + -1.0f, -1.0f, 0.0f, bottom, // Bottom-left corner + 1.0f, -1.0f, 1.0f, bottom, // Bottom-right corner + -1.0f, 1.0f, 0.0f, top, // Top-left corner + 1.0f, -1.0f, 1.0f, bottom, // Bottom-right corner + 1.0f, 1.0f, 1.0f, top, // Top-right corner + -1.0f, 1.0f, 0.0f, top // Top-left corner + }; + + engine->m_BlitToBackbufferVertexBuffer = dmGraphics::NewVertexBuffer(engine->m_GraphicsContext, sizeof(vertex_data), (void*) vertex_data, dmGraphics::BUFFER_USAGE_STATIC_DRAW); + + dmGraphics::HVertexStreamDeclaration stream_declaration_vertex = dmGraphics::NewVertexStreamDeclaration(engine->m_GraphicsContext); + dmGraphics::AddVertexStream(stream_declaration_vertex, "position", 2, dmGraphics::TYPE_FLOAT, false); + dmGraphics::AddVertexStream(stream_declaration_vertex, "texcoord0", 2, dmGraphics::TYPE_FLOAT, false); + engine->m_VertexDeclaration = dmGraphics::NewVertexDeclaration(engine->m_GraphicsContext, stream_declaration_vertex); + + dmGraphics::ShaderDesc* shader_desc = dmGraphics::CreateRiveModelBlitShaderDesc(); + char program_error[512] = {}; + engine->m_BlitProgram = dmGraphics::NewProgram(engine->m_GraphicsContext, + shader_desc, + program_error, + sizeof(program_error)); + engine->m_SamplerLocation = dmGraphics::INVALID_UNIFORM_LOCATION; + if (engine->m_BlitProgram == dmGraphics::INVALID_PROGRAM_HANDLE || engine->m_BlitProgram == 0) + { + dmLogError("Failed to create blit program: %s", program_error); + engine->m_BlitProgram = dmGraphics::INVALID_PROGRAM_HANDLE; + } + else + { + engine->m_SamplerLocation = FindUniformLocation(engine->m_BlitProgram, "texture_sampler"); + } + + // Rive + engine->m_RenderContext = dmRive::NewRenderContext(); + + dmRiveCommands::InitParams cmd_params; + cmd_params.m_RenderContext = engine->m_RenderContext; + cmd_params.m_Factory = dmRive::GetRiveFactory(engine->m_RenderContext); + dmRiveCommands::Initialize(&cmd_params); + + if (s_RiveFilePath) + { + printf("Loading '%s'\n", s_RiveFilePath); + + rive::rcp queue = dmRiveCommands::GetCommandQueue(); + std::vector bytes; + if (ReadFile(s_RiveFilePath, bytes)) + { + engine->m_File = queue->loadFile(bytes); + + if (engine->m_File) + { + printf("Loaded file\n"); + + engine->m_Artboard = queue->instantiateDefaultArtboard(engine->m_File); + if (engine->m_Artboard) + { + printf("Created default artboard\n"); + + engine->m_StateMachine = queue->instantiateDefaultStateMachine(engine->m_Artboard); + if (engine->m_StateMachine) + { + printf("Created default state machine\n"); + + engine->m_ViewModelInstance = queue->instantiateDefaultViewModelInstance(engine->m_File, engine->m_Artboard); + if (engine->m_ViewModelInstance) + { + printf("Created default view model instance\n"); + + queue->bindViewModelInstance(engine->m_StateMachine, engine->m_ViewModelInstance); + } + } + } + } + } + + engine->m_DrawKey = queue->createDrawKey(); + } + + return engine; +} + +static void EngineDestroy(void* _engine) +{ + EngineCtx* engine = (EngineCtx*)_engine; + + dmRiveCommands::Finalize(); + dmRive::DeleteRenderContext(engine->m_RenderContext); + + dmJobThread::Destroy(engine->m_JobThread); + + dmGraphics::DeleteVertexBuffer(engine->m_BlitToBackbufferVertexBuffer); + dmGraphics::DeleteVertexDeclaration(engine->m_VertexDeclaration); + if (engine->m_BlitProgram != 0 && engine->m_BlitProgram != dmGraphics::INVALID_PROGRAM_HANDLE) + { + dmGraphics::DeleteProgram(engine->m_GraphicsContext, engine->m_BlitProgram); + } + + dmGraphics::CloseWindow(engine->m_GraphicsContext); + dmGraphics::DeleteContext(engine->m_GraphicsContext); + dmGraphics::Finalize(); + + engine->m_WasDestroyed++; + + delete engine; +} + +static void UpdateRiveScene(EngineCtx* engine) +{ + rive::rcp queue = dmRiveCommands::GetCommandQueue(); + + static uint64_t last_update = dmTime::GetMonotonicTime(); + uint64_t time = dmTime::GetMonotonicTime(); + float dt = (time - last_update) / 1000000.0f; + last_update = time; + queue->advanceStateMachine(engine->m_StateMachine, dt); +} + +static void DrawRiveScene(EngineCtx* engine) +{ + rive::rcp queue = dmRiveCommands::GetCommandQueue(); + + rive::Renderer* renderer = dmRive::GetRiveRenderer(engine->m_RenderContext); + + const rive::ArtboardHandle artboardHandle = engine->m_Artboard; + static rive::Fit s_fit = rive::Fit::contain; + static rive::Alignment s_alignment= rive::Alignment::center; + + rive::Fit fit = s_fit; + rive::Alignment alignment = s_alignment; + + uint32_t width = dmGraphics::GetWindowWidth(engine->m_GraphicsContext); + uint32_t height = dmGraphics::GetWindowHeight(engine->m_GraphicsContext); + float display_factor = dmGraphics::GetDisplayScaleFactor(engine->m_GraphicsContext); + + auto drawLoop = [artboardHandle, + renderer, + fit, + alignment, + width, + height, + display_factor](rive::DrawKey drawKey, rive::CommandServer* server) + { + rive::ArtboardInstance* artboard = server->getArtboardInstance(artboardHandle); + if (artboard == nullptr) + { + return; + } + + rive::Factory* factory = server->factory(); + // Draw the .riv. + renderer->save(); + + rive::AABB bounds = artboard->bounds(); + + bool fullscreen = false; + if (fullscreen) + { + // // Apply the world matrix from the component to the artboard transform + // rive::Mat2D transform = rive::Mat2D::fromTranslate(width / 2.0f, height / 2.0f); + // rive::Mat2D centerAdjustment = rive::Mat2D::fromTranslate(-bounds.width() / 2.0f, -bounds.height() / 2.0f); + // rive::Mat2D scaleDpi = rive::Mat2D::fromScale(1,-1); + // rive::Mat2D invertAdjustment = rive::Mat2D::fromScaleAndTranslation(display_factor, -display_factor, 0, window_height); + // rive::Mat2D rendererTransform = invertAdjustment * viewTransform * transform * scaleDpi * centerAdjustment; + + // renderer->transform(rendererTransform); + // For making input work nicely + //c->m_InverseRendererTransform = rendererTransform.invertOrIdentity(); + } + else + { + if (fit == rive::Fit::layout) + { + artboard->width(width / display_factor); + artboard->height(height / display_factor); + } + + rive::Mat2D rendererTransform = rive::computeAlignment(fit, alignment, rive::AABB(0, 0, width, height), bounds, display_factor); + renderer->transform(rendererTransform); + // For making input work nicely + //c->m_InverseRendererTransform = rendererTransform.invertOrIdentity(); + } + + artboard->draw(renderer); + renderer->restore(); + }; + + queue->draw(engine->m_DrawKey, drawLoop); +} + +static UpdateResult EngineUpdate(void* _engine) +{ + EngineCtx* engine = (EngineCtx*)_engine; + engine->m_WasRun++; + uint64_t t = dmTime::GetMonotonicTime(); + + if (!engine->m_Running) + { + return RESULT_EXIT; + } + + dmJobThread::Update(engine->m_JobThread, 0); // Flush any graphics jobs + + dmPlatform::PollEvents(engine->m_Window); + + if (engine->m_WindowClosed) + { + return RESULT_EXIT; + } + + UpdateRiveScene(engine); + + { + rive::rcp queue = dmRiveCommands::GetCommandQueue(); + + // engine->m_Factory + dmRive::RenderBeginParams render_params; + render_params.m_DoFinalBlit = true; + render_params.m_BackbufferSamples = 0; + dmRive::RenderBegin(engine->m_RenderContext, 0, render_params); + + DrawRiveScene(engine); + + dmRiveCommands::ProcessMessages(); // Making sure any draw call is processed + dmRive::RenderEnd(engine->m_RenderContext); + } + + dmGraphics::BeginFrame(engine->m_GraphicsContext); + dmGraphics::Clear(engine->m_GraphicsContext, dmGraphics::BUFFER_TYPE_COLOR0_BIT, + 0.0f, 0.0f, 0.0f, 0.0f, + 1.0f, 0); + + DrawFullscreenQuad(engine, dmRive::GetBackingTexture(engine->m_RenderContext)); + + dmGraphics::Flip(engine->m_GraphicsContext); + + return RESULT_OK; +} + +static void EngineGetResult(void* _engine, int* run_action, int* exit_code, int* argc, char*** argv) +{ + EngineCtx* ctx = (EngineCtx*)_engine; + ctx->m_WasResultCalled++; +} + +#if defined(__APPLE__) || defined(__linux__) +extern "C" void GraphicsAdapterVulkan(); +#else +extern "C" void GraphicsAdapterOpenGL(); +#endif + +static void dmExportedSymbols() +{ +#if defined(__APPLE__) || defined(__linux__) + GraphicsAdapterVulkan(); +#else + GraphicsAdapterOpenGL(); +#endif +} + +int main(int argc, char **argv) +{ + dmExportedSymbols(); + dmGraphics::InstallAdapter(); + + if (argc > 1) + { + const char* path = argv[argc-1]; + size_t len = strlen(path); + if (len > 4 && strcmp(path + len - 4, ".riv") == 0) + { + s_RiveFilePath = path; + } + else + { + fprintf(stderr, "Must speficy a .riv path"); + return 1; + } + } + + AppCtx ctx; + memset(&ctx, 0, sizeof(ctx)); + + RunLoopParams params; + params.m_AppCtx = &ctx; + params.m_AppCreate = AppCreate; + params.m_AppDestroy = AppDestroy; + params.m_EngineCreate = EngineCreate; + params.m_EngineDestroy = EngineDestroy; + params.m_EngineUpdate = EngineUpdate; + params.m_EngineGetResult = EngineGetResult; + + return RunLoop(¶ms); +}