| |
| // Makes ure gl3w is included before glfw3 |
| #include "GL/gl3w.h" |
| |
| #define SK_GL |
| #include "GLFW/glfw3.h" |
| |
| #include "GrBackendSurface.h" |
| #include "GrDirectContext.h" |
| #include "SkCanvas.h" |
| #include "SkColorSpace.h" |
| #include "SkSurface.h" |
| #include "SkTypes.h" |
| #include "gl/GrGLInterface.h" |
| |
| #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 "skia_factory.hpp" |
| #include "skia_renderer.hpp" |
| |
| #include "imgui/backends/imgui_impl_glfw.h" |
| #include "imgui/backends/imgui_impl_opengl3.h" |
| |
| #include <cmath> |
| #include <stdio.h> |
| |
| rive::SkiaFactory skiaFactory; |
| |
| std::string filename; |
| std::unique_ptr<rive::File> currentFile; |
| std::unique_ptr<rive::ArtboardInstance> artboardInstance; |
| std::unique_ptr<rive::Scene> currentScene; |
| |
| // 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; |
| |
| // 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; |
| |
| int animationIndex = 0; |
| int stateMachineIndex = -1; |
| |
| 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)); |
| } |
| } |
| } |
| |
| void initStateMachine(int index) { |
| stateMachineIndex = index; |
| animationIndex = -1; |
| assert(fileBytes.size() != 0); |
| auto file = rive::File::import(rive::toSpan(fileBytes), &skiaFactory); |
| if (!file) { |
| fileBytes.clear(); |
| fprintf(stderr, "failed to import file\n"); |
| return; |
| } |
| currentScene = nullptr; |
| artboardInstance = nullptr; |
| |
| currentFile = std::move(file); |
| artboardInstance = currentFile->artboardDefault(); |
| artboardInstance->advance(0.0f); |
| loadNames(artboardInstance.get()); |
| |
| if (index >= 0 && index < artboardInstance->stateMachineCount()) { |
| currentScene = artboardInstance->stateMachineAt(index); |
| currentScene->inputCount(); |
| } |
| } |
| |
| void initAnimation(int index) { |
| animationIndex = index; |
| stateMachineIndex = -1; |
| assert(fileBytes.size() != 0); |
| auto file = rive::File::import(rive::toSpan(fileBytes), &skiaFactory); |
| if (!file) { |
| fileBytes.clear(); |
| fprintf(stderr, "failed to import file\n"); |
| return; |
| } |
| currentScene = nullptr; |
| artboardInstance = nullptr; |
| |
| currentFile = std::move(file); |
| artboardInstance = currentFile->artboardDefault(); |
| artboardInstance->advance(0.0f); |
| loadNames(artboardInstance.get()); |
| |
| if (index >= 0 && index < artboardInstance->animationCount()) { |
| currentScene = artboardInstance->animationAt(index); |
| currentScene->inputCount(); |
| } |
| } |
| |
| rive::Mat2D gInverseViewTransform; |
| rive::Vec2D lastWorldMouse; |
| static void glfwCursorPosCallback(GLFWwindow* window, double x, double y) { |
| float xscale, yscale; |
| glfwGetWindowContentScale(window, &xscale, &yscale); |
| lastWorldMouse = gInverseViewTransform * rive::Vec2D(x * xscale, y * yscale); |
| if (currentScene) { |
| currentScene->pointerMove(lastWorldMouse); |
| } |
| } |
| void glfwMouseButtonCallback(GLFWwindow* window, int button, int action, int mods) { |
| if (currentScene) { |
| switch (action) { |
| case GLFW_PRESS: |
| currentScene->pointerDown(lastWorldMouse); |
| break; |
| case GLFW_RELEASE: |
| currentScene->pointerUp(lastWorldMouse); |
| break; |
| } |
| } |
| } |
| |
| void glfwErrorCallback(int error, const char* description) { puts(description); } |
| |
| void glfwDropCallback(GLFWwindow* window, int count, const char** paths) { |
| // Just get the last dropped file for now... |
| filename = paths[count - 1]; |
| |
| FILE* fp = fopen(filename.c_str(), "rb"); |
| fseek(fp, 0, SEEK_END); |
| size_t size = ftell(fp); |
| fseek(fp, 0, SEEK_SET); |
| fileBytes.resize(size); |
| if (fread(fileBytes.data(), 1, size, fp) != size) { |
| fileBytes.clear(); |
| fprintf(stderr, "failed to read all of %s\n", filename.c_str()); |
| return; |
| } |
| initAnimation(0); |
| } |
| |
| int main() { |
| if (!glfwInit()) { |
| fprintf(stderr, "Failed to initialize glfw.\n"); |
| return 1; |
| } |
| glfwSetErrorCallback(glfwErrorCallback); |
| |
| glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); |
| glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2); |
| GLFWwindow* window = glfwCreateWindow(1280, 720, "Rive Viewer", NULL, NULL); |
| if (window == nullptr) { |
| fprintf(stderr, "Failed to make window or GL.\n"); |
| glfwTerminate(); |
| return 1; |
| } |
| |
| glfwSetDropCallback(window, glfwDropCallback); |
| glfwSetCursorPosCallback(window, glfwCursorPosCallback); |
| glfwSetMouseButtonCallback(window, glfwMouseButtonCallback); |
| glfwMakeContextCurrent(window); |
| if (gl3wInit() != 0) { |
| fprintf(stderr, "Failed to make initialize gl3w.\n"); |
| glfwTerminate(); |
| return 1; |
| } |
| // Enable VSYNC. |
| glfwSwapInterval(1); |
| |
| // Setup ImGui |
| ImGui::CreateContext(); |
| ImGuiIO& io = ImGui::GetIO(); |
| (void)io; |
| |
| ImGui::StyleColorsDark(); |
| ImGui_ImplGlfw_InitForOpenGL(window, true); |
| ImGui_ImplOpenGL3_Init("#version 150"); |
| io.Fonts->AddFontDefault(); |
| |
| // Setup Skia |
| GrContextOptions options; |
| sk_sp<GrDirectContext> context = GrDirectContext::MakeGL(nullptr, options); |
| GrGLFramebufferInfo framebufferInfo; |
| framebufferInfo.fFBOID = 0; |
| framebufferInfo.fFormat = GL_RGBA8; |
| |
| sk_sp<SkSurface> surface; |
| SkCanvas* canvas = nullptr; |
| |
| // Render loop. |
| int width = 0, height = 0; |
| int lastScreenWidth = 0, lastScreenHeight = 0; |
| double lastTime = glfwGetTime(); |
| while (!glfwWindowShouldClose(window)) { |
| glfwGetFramebufferSize(window, &width, &height); |
| |
| // Update surface. |
| if (!surface || width != lastScreenWidth || height != lastScreenHeight) { |
| lastScreenWidth = width; |
| lastScreenHeight = height; |
| |
| SkColorType colorType = |
| kRGBA_8888_SkColorType; // GrColorTypeToSkColorType(GrPixelConfigToColorType(kRGBA_8888_GrPixelConfig)); |
| // |
| // if (kRGBA_8888_GrPixelConfig == kSkia8888_GrPixelConfig) |
| // { |
| // colorType = kRGBA_8888_SkColorType; |
| // } |
| // else |
| // { |
| // colorType = kBGRA_8888_SkColorType; |
| // } |
| |
| GrBackendRenderTarget backendRenderTarget(width, |
| height, |
| 0, // sample count |
| 0, // stencil bits |
| framebufferInfo); |
| |
| surface = SkSurface::MakeFromBackendRenderTarget(context.get(), |
| backendRenderTarget, |
| kBottomLeft_GrSurfaceOrigin, |
| colorType, |
| nullptr, |
| nullptr); |
| if (!surface) { |
| fprintf(stderr, "Failed to create Skia surface\n"); |
| return 1; |
| } |
| canvas = surface->getCanvas(); |
| } |
| |
| double time = glfwGetTime(); |
| float elapsed = (float)(time - lastTime); |
| lastTime = time; |
| |
| // Clear screen. |
| SkPaint paint; |
| paint.setColor(SK_ColorDKGRAY); |
| canvas->drawPaint(paint); |
| |
| if (currentScene) { |
| currentScene->advanceAndApply(elapsed); |
| |
| rive::SkiaRenderer renderer(canvas); |
| renderer.save(); |
| |
| auto viewTransform = rive::computeAlignment(rive::Fit::contain, |
| rive::Alignment::center, |
| rive::AABB(0, 0, width, height), |
| currentScene->bounds()); |
| renderer.transform(viewTransform); |
| // Store the inverse view so we can later go from screen to world. |
| gInverseViewTransform = viewTransform.invertOrIdentity(); |
| // post_mouse_event(artboard.get(), canvas->getTotalMatrix()); |
| |
| currentScene->draw(&renderer); |
| renderer.restore(); |
| } |
| context->flush(); |
| |
| ImGui_ImplOpenGL3_NewFrame(); |
| ImGui_ImplGlfw_NewFrame(); |
| ImGui::NewFrame(); |
| |
| if (artboardInstance != nullptr) { |
| ImGui::Begin(filename.c_str(), nullptr); |
| if (ImGui::ListBox( |
| "Animations", |
| &animationIndex, |
| [](void* data, int index, const char** name) { |
| *name = animationNames[index].c_str(); |
| return true; |
| }, |
| artboardInstance.get(), |
| animationNames.size(), |
| 4)) |
| { |
| stateMachineIndex = -1; |
| initAnimation(animationIndex); |
| } |
| if (ImGui::ListBox( |
| "State Machines", |
| &stateMachineIndex, |
| [](void* data, int index, const char** name) { |
| *name = stateMachineNames[index].c_str(); |
| return true; |
| }, |
| artboardInstance.get(), |
| stateMachineNames.size(), |
| 4)) |
| { |
| animationIndex = -1; |
| initStateMachine(stateMachineIndex); |
| } |
| if (currentScene != nullptr) { |
| |
| ImGui::Columns(2); |
| ImGui::SetColumnWidth(0, ImGui::GetWindowWidth() * 0.6666); |
| |
| for (int i = 0; i < currentScene->inputCount(); i++) { |
| auto inputInstance = 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."); |
| } |
| |
| ImGui::Render(); |
| ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); |
| |
| glfwSwapBuffers(window); |
| glfwPollEvents(); |
| } |
| |
| // Cleanup Skia. |
| surface = nullptr; |
| context = nullptr; |
| |
| ImGui_ImplGlfw_Shutdown(); |
| |
| // Cleanup GLFW. |
| glfwDestroyWindow(window); |
| glfwTerminate(); |
| |
| return 0; |
| } |