From 2da6bca17120d4f121c08bdbcec288af00731462 Mon Sep 17 00:00:00 2001 From: Dom Bellizzi Date: Thu, 3 Jul 2025 21:10:58 +0000 Subject: [PATCH] Add event handler extraction support to rive code generator This enhancement enables the rive code generator to extract event handlers from state machines, addressing a limitation where only inputs were extracted but events that could be fired back to the host application were missing. ## Changes Made ### C++ Code Generator (src/main.cpp): - Added headers for event system: rive/event.hpp, rive/animation/listener_fire_event.hpp - Added EventInfo struct to hold event information - Added state_machine_events field to ArtboardData struct - Implemented get_state_machine_events_from_artboard() function that: - Iterates through state machine listeners - Checks for ListenerFireEvent actions - Resolves event IDs to Event objects - Extracts unique event names per state machine - Integrated event extraction into template generation logic ### JSON Template (templates/json_template.mustache): - Added "events" section within each state machine - Events are formatted as key-value pairs: "eventCamelCase": "eventName" - Maintains backward compatibility with existing JSON structure ## API Usage The implementation uses the correct Rive API pattern: 1. StateMachine->listener(i) to access listeners 2. ListenerAction->is() to check action type 3. ListenerFireEvent->eventId() to get event ID 4. ArtboardInstance->resolve(eventId) to get Event object 5. Event->name() to extract event name ## Example Output Before (only inputs): ```json "stateMachines": { "monsterV1": { "inputs": { ... } } } ``` After (inputs + events): ```json "stateMachines": { "monsterV1": { "inputs": { ... }, "events": { "monstertapped": "monsterTapped", "enteropenmouth": "enterOpenMouth", "exitopenmouth": "exitOpenMouth" } } } ``` ## Testing Tested with multiple .riv files including complex state machines with events. The enhancement correctly extracts events while maintaining all existing functionality for inputs, animations, and other data. Resolves the limitation where event handlers like "monsterTapped" were present in Rive files but not accessible in generated code. --- src/main.cpp | 96 +++++++++++++++++++++++++++++++- templates/json_template.mustache | 5 ++ 2 files changed, 100 insertions(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index 4198713..6e6e131 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -14,11 +14,16 @@ #include "rive/generated/animation/state_machine_bool_base.hpp" #include "rive/generated/animation/state_machine_number_base.hpp" #include "rive/generated/animation/state_machine_trigger_base.hpp" +#include "rive/animation/state_machine_listener.hpp" +#include "rive/animation/listener_action.hpp" +#include "rive/animation/listener_fire_event.hpp" +#include "rive/event.hpp" #include "utils/no_op_factory.hpp" #include #include #include #include +#include #include "default_template.h" const std::string generated_file_name = "rive_generated"; @@ -43,6 +48,10 @@ struct InputInfo { std::string default_value; }; +struct EventInfo { + std::string name; +}; + struct TextValueRunInfo { std::string name; std::string default_value; @@ -71,6 +80,7 @@ struct ArtboardData std::string artboard_kebab_case; std::vector animations; std::vector>> state_machines; + std::vector>> state_machine_events; std::vector text_value_runs; std::vector nested_text_value_runs; }; @@ -284,6 +294,64 @@ std::vector>> get_state_machines_f return state_machines; } +std::vector>> get_state_machine_events_from_artboard(rive::ArtboardInstance *artboard) +{ + std::vector>> state_machine_events; + auto stateMachineCount = artboard->stateMachineCount(); + + for (int i = 0; i < stateMachineCount; i++) + { + auto stateMachineInstance = artboard->stateMachineAt(i); + const auto* stateMachine = stateMachineInstance->stateMachine(); + std::string state_machine_name = stateMachineInstance->name(); + + // Use a set to store unique event names within this state machine + std::set eventNames; + + // Iterate through each listener in the state machine template + for (size_t listenerIndex = 0; listenerIndex < stateMachine->listenerCount(); ++listenerIndex) + { + const auto* listener = stateMachine->listener(listenerIndex); + + // Check each action on this listener + for (size_t actionIndex = 0; actionIndex < listener->actionCount(); ++actionIndex) + { + const auto* action = listener->action(actionIndex); + + // Check if this action is a fire event action + if (action->is()) + { + const auto* fireEvent = action->as(); + if (fireEvent && fireEvent->eventId() != 0) + { + // Resolve the event ID to get the actual event object + auto eventObject = artboard->resolve(fireEvent->eventId()); + if (eventObject && eventObject->is()) + { + const auto* event = eventObject->as(); + if (event && !event->name().empty()) + { + eventNames.insert(event->name()); + } + } + } + } + } + } + + // Convert the set to a vector of EventInfo + std::vector events; + for (const auto& eventName : eventNames) + { + events.push_back({eventName}); + } + + state_machine_events.emplace_back(state_machine_name, events); + } + + return state_machine_events; +} + std::vector find_riv_files(const std::string &path) { std::vector riv_files; @@ -461,10 +529,11 @@ std::optional process_riv_file(const std::string &rive_file_path) std::vector animations = get_animations_from_artboard(artboard.get()); std::vector>> state_machines = get_state_machines_from_artboard(artboard.get()); + std::vector>> state_machine_events = get_state_machine_events_from_artboard(artboard.get()); std::vector text_value_runs = get_text_value_runs_from_artboard(artboard.get()); std::vector nested_text_value_runs = get_nested_text_value_run_paths_from_artboard(artboard.get()); - file_data.artboards.push_back({artboard_name, artboard_pascal_case, artboard_camel_case, artboard_snake_case, artboard_kebab_case, animations, state_machines, text_value_runs, nested_text_value_runs}); + file_data.artboards.push_back({artboard_name, artboard_pascal_case, artboard_camel_case, artboard_snake_case, artboard_kebab_case, animations, state_machines, state_machine_events, text_value_runs, nested_text_value_runs}); } return file_data; @@ -650,6 +719,31 @@ int main(int argc, char *argv[]) } state_machine_data["inputs"] = inputs; + // Find the corresponding events for this state machine + std::unordered_set usedEventNames; + std::vector events; + for (const auto &state_machine_event : artboard.state_machine_events) + { + if (state_machine_event.first == state_machine.first) // Match by state machine name + { + for (size_t event_index = 0; event_index < state_machine_event.second.size(); event_index++) + { + const auto &event = state_machine_event.second[event_index]; + kainjow::mustache::data event_data; + auto unique_name = makeUnique(event.name, usedEventNames); + event_data["event_name"] = event.name; + event_data["event_camel_case"] = toCamelCase(unique_name); + event_data["event_pascal_case"] = toPascalCase(unique_name); + event_data["event_snake_case"] = toSnakeCase(unique_name); + event_data["event_kebab_case"] = toKebabCase(unique_name); + event_data["last"] = (event_index == state_machine_event.second.size() - 1); + events.push_back(event_data); + } + break; // Found the matching state machine, no need to continue + } + } + state_machine_data["events"] = events; + state_machines.push_back(state_machine_data); } artboard_data["state_machines"] = state_machines; diff --git a/templates/json_template.mustache b/templates/json_template.mustache index 3466888..cfdbfee 100644 --- a/templates/json_template.mustache +++ b/templates/json_template.mustache @@ -47,6 +47,11 @@ "defaultValue": "{{input_default_value}}" }{{^last}},{{/last}} {{/inputs}} + }, + "events": { + {{#events}} + "{{event_camel_case}}": "{{event_name}}"{{^last}},{{/last}} + {{/events}} } }{{^last}},{{/last}} {{/state_machines}}