| /* |
| * Copyright 2022 Rive |
| */ |
| |
| #include "rive/animation/linear_animation_instance.hpp" |
| #include "rive/animation/state_machine_instance.hpp" |
| #include "rive/animation/state_machine_input_instance.hpp" |
| #include "rive/animation/state_machine_number.hpp" |
| #include "rive/animation/state_machine_bool.hpp" |
| #include "rive/animation/state_machine_trigger.hpp" |
| #include "rive/artboard.hpp" |
| #include "rive/file.hpp" |
| #include "rive/layout.hpp" |
| #include "rive/math/aabb.hpp" |
| #include "cg_skia_factory.hpp" |
| #include "skia_renderer.hpp" |
| #include "viewer_content.hpp" |
| |
| #include "../src/render_counter.hpp" |
| |
| // ImGui wants raw pointers to names, but our public API returns |
| // names as strings (by value), so we cache these names each time we |
| // load a file |
| std::vector<std::string> animationNames; |
| std::vector<std::string> stateMachineNames; |
| |
| constexpr int REQUEST_DEFAULT_SCENE = -1; |
| |
| #include <time.h> |
| double GetSecondsToday() { |
| time_t m_time; |
| time(&m_time); |
| struct tm tstruct; |
| gmtime_r(&m_time, &tstruct); |
| |
| int hours = tstruct.tm_hour - 4; |
| if (hours < 0) { |
| hours += 12; |
| } else if (hours >= 12) { |
| hours -= 12; |
| } |
| |
| auto secs = (double)hours * 60 * 60 + |
| (double)tstruct.tm_min * 60 + |
| (double)tstruct.tm_sec; |
| // printf("%d %d %d\n", tstruct.tm_sec, tstruct.tm_min, hours); |
| // printf("%g %g %g\n", secs, secs/60, secs/60/60); |
| return secs; |
| } |
| |
| // We hold onto the file's bytes for the lifetime of the file, in case we want |
| // to change animations or state-machines, we just rebuild the rive::File from |
| // it. |
| std::vector<uint8_t> fileBytes; |
| |
| |
| static void loadNames(const rive::Artboard* ab) { |
| animationNames.clear(); |
| stateMachineNames.clear(); |
| if (ab) { |
| for (size_t i = 0; i < ab->animationCount(); ++i) { |
| animationNames.push_back(ab->animationNameAt(i)); |
| } |
| for (size_t i = 0; i < ab->stateMachineCount(); ++i) { |
| stateMachineNames.push_back(ab->stateMachineNameAt(i)); |
| } |
| } |
| } |
| |
| class SceneContent : public ViewerContent { |
| std::string m_filename; |
| std::unique_ptr<rive::File> m_file; |
| |
| std::unique_ptr<rive::ArtboardInstance> m_artboardInstance; |
| std::unique_ptr<rive::Scene> m_currentScene; |
| int m_animationIndex = 0; |
| int m_stateMachineIndex = -1; |
| |
| int m_width = 0, m_height = 0; |
| rive::Vec2D m_lastPointer; |
| rive::Mat2D m_inverseViewTransform; |
| |
| void initStateMachine(int index) { |
| m_stateMachineIndex = -1; |
| m_animationIndex = -1; |
| m_currentScene = nullptr; |
| m_artboardInstance = nullptr; |
| |
| m_artboardInstance = m_file->artboardDefault(); |
| m_artboardInstance->advance(0.0f); |
| loadNames(m_artboardInstance.get()); |
| |
| if (index < 0) { |
| m_currentScene = m_artboardInstance->defaultStateMachine(); |
| index = m_artboardInstance->defaultStateMachineIndex(); |
| } |
| if (!m_currentScene) { |
| if (index >= m_artboardInstance->stateMachineCount()) { |
| index = 0; |
| } |
| m_currentScene = m_artboardInstance->stateMachineAt(index); |
| } |
| if (!m_currentScene) { |
| index = -1; |
| m_currentScene = m_artboardInstance->animationAt(0); |
| m_animationIndex = 0; |
| } |
| m_stateMachineIndex = index; |
| |
| if (m_currentScene) { |
| m_currentScene->inputCount(); |
| } |
| |
| rive::RenderCounter::globalCounter().dump("After loading file"); |
| } |
| |
| void initAnimation(int index) { |
| m_stateMachineIndex = -1; |
| m_animationIndex = -1; |
| m_currentScene = nullptr; |
| m_artboardInstance = nullptr; |
| |
| m_artboardInstance = m_file->artboardDefault(); |
| m_artboardInstance->advance(0.0f); |
| loadNames(m_artboardInstance.get()); |
| |
| if (index >= 0 && index < m_artboardInstance->animationCount()) { |
| m_animationIndex = index; |
| m_currentScene = m_artboardInstance->animationAt(index); |
| m_currentScene->inputCount(); |
| } |
| |
| rive::RenderCounter::globalCounter().dump("After loading file"); |
| } |
| |
| public: |
| SceneContent(const char filename[], std::unique_ptr<rive::File> file) : |
| m_filename(filename), |
| m_file(std::move(file)) |
| { |
| initStateMachine(REQUEST_DEFAULT_SCENE); |
| } |
| |
| ~SceneContent() override { |
| rive::RenderCounter::globalCounter().dump("After deleting file"); |
| } |
| |
| void handlePointerMove(float x, float y) override { |
| m_lastPointer = m_inverseViewTransform * rive::Vec2D(x, y); |
| if (m_currentScene) { |
| m_currentScene->pointerMove(m_lastPointer); |
| } |
| } |
| void handlePointerDown() override { |
| if (m_currentScene) { |
| m_currentScene->pointerDown(m_lastPointer); |
| } |
| } |
| void handlePointerUp() override { |
| if (m_currentScene) { |
| m_currentScene->pointerUp(m_lastPointer); |
| } |
| } |
| |
| void handleResize(int width, int height) override { |
| m_width = width; |
| m_height = height; |
| } |
| |
| void handleDraw(SkCanvas* canvas, double elapsed) override { |
| |
| if (m_currentScene) { |
| // See if we can "set the time" e.g. clock statemachine |
| if (auto num = m_currentScene->getNumber("isTime")) { |
| num->value(GetSecondsToday()/60/60); |
| } |
| |
| m_currentScene->advanceAndApply(elapsed); |
| |
| rive::SkiaRenderer renderer(canvas); |
| renderer.save(); |
| |
| auto viewTransform = rive::computeAlignment(rive::Fit::contain, |
| rive::Alignment::center, |
| rive::AABB(0, 0, m_width, m_height), |
| m_currentScene->bounds()); |
| renderer.transform(viewTransform); |
| // Store the inverse view so we can later go from screen to world. |
| m_inverseViewTransform = viewTransform.invertOrIdentity(); |
| // post_mouse_event(artboard.get(), canvas->getTotalMatrix()); |
| |
| m_currentScene->draw(&renderer); |
| renderer.restore(); |
| } |
| } |
| |
| void handleImgui() override { |
| if (m_artboardInstance != nullptr) { |
| ImGui::Begin(m_filename.c_str(), nullptr); |
| if (ImGui::ListBox( |
| "Animations", |
| &m_animationIndex, |
| [](void* data, int index, const char** name) { |
| *name = animationNames[index].c_str(); |
| return true; |
| }, |
| m_artboardInstance.get(), |
| animationNames.size(), |
| 4)) |
| { |
| m_stateMachineIndex = -1; |
| initAnimation(m_animationIndex); |
| } |
| if (ImGui::ListBox( |
| "State Machines", |
| &m_stateMachineIndex, |
| [](void* data, int index, const char** name) { |
| *name = stateMachineNames[index].c_str(); |
| return true; |
| }, |
| m_artboardInstance.get(), |
| stateMachineNames.size(), |
| 4)) |
| { |
| m_animationIndex = -1; |
| initStateMachine(m_stateMachineIndex); |
| } |
| if (m_currentScene != nullptr) { |
| |
| ImGui::Columns(2); |
| ImGui::SetColumnWidth(0, ImGui::GetWindowWidth() * 0.6666); |
| |
| for (int i = 0; i < m_currentScene->inputCount(); i++) { |
| auto inputInstance = m_currentScene->input(i); |
| |
| if (inputInstance->input()->is<rive::StateMachineNumber>()) { |
| // ImGui requires names as id's, use ## to hide the |
| // label but still give it an id. |
| char label[256]; |
| snprintf(label, 256, "##%u", i); |
| |
| auto number = static_cast<rive::SMINumber*>(inputInstance); |
| float v = number->value(); |
| ImGui::InputFloat(label, &v, 1.0f, 2.0f, "%.3f"); |
| number->value(v); |
| ImGui::NextColumn(); |
| } else if (inputInstance->input()->is<rive::StateMachineTrigger>()) { |
| // ImGui requires names as id's, use ## to hide the |
| // label but still give it an id. |
| char label[256]; |
| snprintf(label, 256, "Fire##%u", i); |
| if (ImGui::Button(label)) { |
| auto trigger = static_cast<rive::SMITrigger*>(inputInstance); |
| trigger->fire(); |
| } |
| ImGui::NextColumn(); |
| } else if (inputInstance->input()->is<rive::StateMachineBool>()) { |
| // ImGui requires names as id's, use ## to hide the |
| // label but still give it an id. |
| char label[256]; |
| snprintf(label, 256, "##%u", i); |
| auto boolInput = static_cast<rive::SMIBool*>(inputInstance); |
| bool value = boolInput->value(); |
| |
| ImGui::Checkbox(label, &value); |
| boolInput->value(value); |
| ImGui::NextColumn(); |
| } |
| ImGui::Text("%s", inputInstance->input()->name().c_str()); |
| ImGui::NextColumn(); |
| } |
| |
| ImGui::Columns(1); |
| } |
| ImGui::End(); |
| |
| } else { |
| ImGui::Text("Drop a .riv file to preview."); |
| } |
| } |
| }; |
| |
| rive::CGSkiaFactory skiaFactory; |
| |
| std::unique_ptr<ViewerContent> ViewerContent::Scene(const char filename[]) { |
| auto bytes = LoadFile(filename); |
| if (auto file = rive::File::import(rive::toSpan(bytes), &skiaFactory)) { |
| return std::make_unique<SceneContent>(filename, std::move(file)); |
| } |
| return nullptr; |
| } |