2 // Makes ure gl3w is included before glfw3
6 #include "GLFW/glfw3.h"
8 #include "GrBackendSurface.h"
9 #include "GrDirectContext.h"
11 #include "SkColorSpace.h"
12 #include "SkSurface.h"
14 #include "gl/GrGLInterface.h"
16 #include "rive/animation/linear_animation_instance.hpp"
17 #include "rive/animation/state_machine_instance.hpp"
18 #include "rive/animation/state_machine_input_instance.hpp"
19 #include "rive/animation/state_machine_number.hpp"
20 #include "rive/animation/state_machine_bool.hpp"
21 #include "rive/animation/state_machine_trigger.hpp"
22 #include "rive/artboard.hpp"
23 #include "rive/file.hpp"
24 #include "rive/layout.hpp"
25 #include "rive/math/aabb.hpp"
26 #include "skia_factory.hpp"
27 #include "skia_renderer.hpp"
29 #include "imgui/backends/imgui_impl_glfw.h"
30 #include "imgui/backends/imgui_impl_opengl3.h"
35 rive::SkiaFactory skiaFactory;
38 std::unique_ptr<rive::File> currentFile;
39 std::unique_ptr<rive::ArtboardInstance> artboardInstance;
40 std::unique_ptr<rive::Scene> currentScene;
42 // ImGui wants raw pointers to names, but our public API returns
43 // names as strings (by value), so we cache these names each time we
45 std::vector<std::string> animationNames;
46 std::vector<std::string> stateMachineNames;
48 constexpr int REQUEST_DEFAULT_SCENE = -1;
51 double GetSecondsToday() {
55 gmtime_r(&m_time, &tstruct);
57 int hours = tstruct.tm_hour - 4;
60 } else if (hours >= 12) {
64 auto secs = (double)hours * 60 * 60 +
65 (double)tstruct.tm_min * 60 +
66 (double)tstruct.tm_sec;
67 // printf("%d %d %d\n", tstruct.tm_sec, tstruct.tm_min, hours);
68 // printf("%g %g %g\n", secs, secs/60, secs/60/60);
72 // We hold onto the file's bytes for the lifetime of the file, in case we want
73 // to change animations or state-machines, we just rebuild the rive::File from
75 std::vector<uint8_t> fileBytes;
77 int animationIndex = 0;
78 int stateMachineIndex = -1;
80 static void loadNames(const rive::Artboard* ab) {
81 animationNames.clear();
82 stateMachineNames.clear();
84 for (size_t i = 0; i < ab->animationCount(); ++i) {
85 animationNames.push_back(ab->animationNameAt(i));
87 for (size_t i = 0; i < ab->stateMachineCount(); ++i) {
88 stateMachineNames.push_back(ab->stateMachineNameAt(i));
93 void initStateMachine(int index) {
94 assert(fileBytes.size() != 0);
95 auto file = rive::File::import(rive::toSpan(fileBytes), &skiaFactory);
98 fprintf(stderr, "failed to import file\n");
102 stateMachineIndex = -1;
104 currentScene = nullptr;
105 artboardInstance = nullptr;
107 currentFile = std::move(file);
108 artboardInstance = currentFile->artboardDefault();
109 artboardInstance->advance(0.0f);
110 loadNames(artboardInstance.get());
113 currentScene = artboardInstance->defaultStateMachine();
114 index = artboardInstance->defaultStateMachineIndex();
117 if (index >= artboardInstance->stateMachineCount()) {
120 currentScene = artboardInstance->stateMachineAt(index);
124 currentScene = artboardInstance->animationAt(0);
127 stateMachineIndex = index;
130 currentScene->inputCount();
134 void initAnimation(int index) {
135 animationIndex = index;
136 stateMachineIndex = -1;
137 assert(fileBytes.size() != 0);
138 auto file = rive::File::import(rive::toSpan(fileBytes), &skiaFactory);
141 fprintf(stderr, "failed to import file\n");
144 currentScene = nullptr;
145 artboardInstance = nullptr;
147 currentFile = std::move(file);
148 artboardInstance = currentFile->artboardDefault();
149 artboardInstance->advance(0.0f);
150 loadNames(artboardInstance.get());
152 if (index >= 0 && index < artboardInstance->animationCount()) {
153 currentScene = artboardInstance->animationAt(index);
154 currentScene->inputCount();
158 rive::Mat2D gInverseViewTransform;
159 rive::Vec2D lastWorldMouse;
160 static void glfwCursorPosCallback(GLFWwindow* window, double x, double y) {
161 float xscale, yscale;
162 glfwGetWindowContentScale(window, &xscale, &yscale);
163 lastWorldMouse = gInverseViewTransform * rive::Vec2D(x * xscale, y * yscale);
165 currentScene->pointerMove(lastWorldMouse);
168 void glfwMouseButtonCallback(GLFWwindow* window, int button, int action, int mods) {
172 currentScene->pointerDown(lastWorldMouse);
175 currentScene->pointerUp(lastWorldMouse);
181 void glfwErrorCallback(int error, const char* description) { puts(description); }
183 void glfwDropCallback(GLFWwindow* window, int count, const char** paths) {
184 // Just get the last dropped file for now...
185 filename = paths[count - 1];
187 FILE* fp = fopen(filename.c_str(), "rb");
188 fseek(fp, 0, SEEK_END);
189 size_t size = ftell(fp);
190 fseek(fp, 0, SEEK_SET);
191 fileBytes.resize(size);
192 if (fread(fileBytes.data(), 1, size, fp) != size) {
194 fprintf(stderr, "failed to read all of %s\n", filename.c_str());
197 initStateMachine(REQUEST_DEFAULT_SCENE);
202 fprintf(stderr, "Failed to initialize glfw.\n");
205 glfwSetErrorCallback(glfwErrorCallback);
207 glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
208 glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
209 GLFWwindow* window = glfwCreateWindow(1280, 720, "Rive Viewer", NULL, NULL);
210 if (window == nullptr) {
211 fprintf(stderr, "Failed to make window or GL.\n");
216 glfwSetDropCallback(window, glfwDropCallback);
217 glfwSetCursorPosCallback(window, glfwCursorPosCallback);
218 glfwSetMouseButtonCallback(window, glfwMouseButtonCallback);
219 glfwMakeContextCurrent(window);
220 if (gl3wInit() != 0) {
221 fprintf(stderr, "Failed to make initialize gl3w.\n");
229 ImGui::CreateContext();
230 ImGuiIO& io = ImGui::GetIO();
233 ImGui::StyleColorsDark();
234 ImGui_ImplGlfw_InitForOpenGL(window, true);
235 ImGui_ImplOpenGL3_Init("#version 150");
236 io.Fonts->AddFontDefault();
239 GrContextOptions options;
240 sk_sp<GrDirectContext> context = GrDirectContext::MakeGL(nullptr, options);
241 GrGLFramebufferInfo framebufferInfo;
242 framebufferInfo.fFBOID = 0;
243 framebufferInfo.fFormat = GL_RGBA8;
245 sk_sp<SkSurface> surface;
246 SkCanvas* canvas = nullptr;
249 int width = 0, height = 0;
250 int lastScreenWidth = 0, lastScreenHeight = 0;
251 double lastTime = glfwGetTime();
252 while (!glfwWindowShouldClose(window)) {
253 glfwGetFramebufferSize(window, &width, &height);
256 if (!surface || width != lastScreenWidth || height != lastScreenHeight) {
257 lastScreenWidth = width;
258 lastScreenHeight = height;
260 SkColorType colorType =
261 kRGBA_8888_SkColorType; // GrColorTypeToSkColorType(GrPixelConfigToColorType(kRGBA_8888_GrPixelConfig));
263 // if (kRGBA_8888_GrPixelConfig == kSkia8888_GrPixelConfig)
265 // colorType = kRGBA_8888_SkColorType;
269 // colorType = kBGRA_8888_SkColorType;
272 GrBackendRenderTarget backendRenderTarget(width,
278 surface = SkSurface::MakeFromBackendRenderTarget(context.get(),
280 kBottomLeft_GrSurfaceOrigin,
285 fprintf(stderr, "Failed to create Skia surface\n");
288 canvas = surface->getCanvas();
291 double time = glfwGetTime();
292 float elapsed = (float)(time - lastTime);
297 paint.setColor(SK_ColorDKGRAY);
298 canvas->drawPaint(paint);
301 // See if we can "set the time" e.g. clock statemachine
302 if (auto num = currentScene->getNumber("isTime")) {
303 num->value(GetSecondsToday()/60/60);
306 currentScene->advanceAndApply(elapsed);
308 rive::SkiaRenderer renderer(canvas);
311 auto viewTransform = rive::computeAlignment(rive::Fit::contain,
312 rive::Alignment::center,
313 rive::AABB(0, 0, width, height),
314 currentScene->bounds());
315 renderer.transform(viewTransform);
316 // Store the inverse view so we can later go from screen to world.
317 gInverseViewTransform = viewTransform.invertOrIdentity();
318 // post_mouse_event(artboard.get(), canvas->getTotalMatrix());
320 currentScene->draw(&renderer);
325 ImGui_ImplOpenGL3_NewFrame();
326 ImGui_ImplGlfw_NewFrame();
329 if (artboardInstance != nullptr) {
330 ImGui::Begin(filename.c_str(), nullptr);
334 [](void* data, int index, const char** name) {
335 *name = animationNames[index].c_str();
338 artboardInstance.get(),
339 animationNames.size(),
342 stateMachineIndex = -1;
343 initAnimation(animationIndex);
348 [](void* data, int index, const char** name) {
349 *name = stateMachineNames[index].c_str();
352 artboardInstance.get(),
353 stateMachineNames.size(),
357 initStateMachine(stateMachineIndex);
359 if (currentScene != nullptr) {
362 ImGui::SetColumnWidth(0, ImGui::GetWindowWidth() * 0.6666);
364 for (int i = 0; i < currentScene->inputCount(); i++) {
365 auto inputInstance = currentScene->input(i);
367 if (inputInstance->input()->is<rive::StateMachineNumber>()) {
368 // ImGui requires names as id's, use ## to hide the
369 // label but still give it an id.
371 snprintf(label, 256, "##%u", i);
373 auto number = static_cast<rive::SMINumber*>(inputInstance);
374 float v = number->value();
375 ImGui::InputFloat(label, &v, 1.0f, 2.0f, "%.3f");
378 } else if (inputInstance->input()->is<rive::StateMachineTrigger>()) {
379 // ImGui requires names as id's, use ## to hide the
380 // label but still give it an id.
382 snprintf(label, 256, "Fire##%u", i);
383 if (ImGui::Button(label)) {
384 auto trigger = static_cast<rive::SMITrigger*>(inputInstance);
388 } else if (inputInstance->input()->is<rive::StateMachineBool>()) {
389 // ImGui requires names as id's, use ## to hide the
390 // label but still give it an id.
392 snprintf(label, 256, "##%u", i);
393 auto boolInput = static_cast<rive::SMIBool*>(inputInstance);
394 bool value = boolInput->value();
396 ImGui::Checkbox(label, &value);
397 boolInput->value(value);
400 ImGui::Text("%s", inputInstance->input()->name().c_str());
409 ImGui::Text("Drop a .riv file to preview.");
413 ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
415 glfwSwapBuffers(window);
423 ImGui_ImplGlfw_Shutdown();
426 glfwDestroyWindow(window);