#include #include #include #include "gui.hpp" #include "app.hpp" #define NANOVG_GL2_IMPLEMENTATION // #define NANOVG_GL3_IMPLEMENTATION #include "../ext/nanovg/src/nanovg_gl.h" // Hack to get framebuffer objects working on OpenGL 2 (we blindly assume the extension is supported) #define NANOVG_FBO_VALID 1 #include "../ext/nanovg/src/nanovg_gl_utils.h" #define BLENDISH_IMPLEMENTATION #include "../ext/oui-blendish/blendish.h" #define NANOSVG_IMPLEMENTATION #include "../ext/nanosvg/src/nanosvg.h" #ifdef ARCH_MAC // For CGAssociateMouseAndMouseCursorPosition #include #endif namespace rack { static GLFWwindow *window = NULL; std::shared_ptr gGuiFont; NVGcontext *gVg = NULL; float gPixelRatio = 0.0; void windowSizeCallback(GLFWwindow* window, int width, int height) { gScene->box.size = Vec(width, height); } void mouseButtonCallback(GLFWwindow *window, int button, int action, int mods) { #ifdef ARCH_MAC // Ctrl-left click --> right click if (button == GLFW_MOUSE_BUTTON_LEFT) { if (guiIsKeyPressed(GLFW_KEY_LEFT_CONTROL) || guiIsKeyPressed(GLFW_KEY_RIGHT_CONTROL)) button = GLFW_MOUSE_BUTTON_RIGHT; } #endif if (action == GLFW_PRESS) { // onMouseDown Widget *w = gScene->onMouseDown(gMousePos, button); if (button == GLFW_MOUSE_BUTTON_LEFT) { if (w) { // onDragStart w->onDragStart(); } gDraggedWidget = w; if (w != gSelectedWidget) { if (gSelectedWidget) { w->onDeselect(); } if (w) { w->onSelect(); } gSelectedWidget = w; } } } else if (action == GLFW_RELEASE) { // onMouseUp Widget *w = gScene->onMouseUp(gMousePos, button); if (button == GLFW_MOUSE_BUTTON_LEFT) { if (gDraggedWidget) { // onDragDrop w->onDragDrop(gDraggedWidget); } // gDraggedWidget might have been set to null in the last event, recheck here if (gDraggedWidget) { // onDragEnd gDraggedWidget->onDragEnd(); } gDraggedWidget = NULL; gDragHoveredWidget = NULL; } } } void cursorPosCallback(GLFWwindow* window, double xpos, double ypos) { Vec mousePos = Vec(xpos, ypos).round(); Vec mouseRel = mousePos.minus(gMousePos); #ifdef ARCH_MAC // Workaround for Mac. We can't use GLFW_CURSOR_DISABLED because it's buggy, so implement it on our own. // This is not an ideal implementation. For example, if the user drags off the screen, the new mouse position will be clamped. int mouseMode = glfwGetInputMode(window, GLFW_CURSOR); if (mouseMode == GLFW_CURSOR_HIDDEN) { // CGSetLocalEventsSuppressionInterval(0.0); glfwSetCursorPos(window, gMousePos.x, gMousePos.y); CGAssociateMouseAndMouseCursorPosition(true); mousePos = gMousePos; } // Because sometimes the cursor turns into an arrow when its position is on the boundary of the window glfwSetCursor(window, NULL); #endif gMousePos = mousePos; // onMouseMove Widget *hovered = gScene->onMouseMove(gMousePos, mouseRel); if (gDraggedWidget) { // onDragMove gDraggedWidget->onDragMove(mouseRel); if (hovered != gDragHoveredWidget) { if (gDragHoveredWidget) { gDragHoveredWidget->onDragLeave(gDraggedWidget); } if (hovered) { hovered->onDragEnter(gDraggedWidget); } gDragHoveredWidget = hovered; } } else { if (hovered != gHoveredWidget) { if (gHoveredWidget) { // onMouseLeave gHoveredWidget->onMouseLeave(); } if (hovered) { // onMouseEnter hovered->onMouseEnter(); } gHoveredWidget = hovered; } } } void cursorEnterCallback(GLFWwindow* window, int entered) { if (!entered) { if (gHoveredWidget) { gHoveredWidget->onMouseLeave(); } gHoveredWidget = NULL; } } void scrollCallback(GLFWwindow *window, double x, double y) { Vec scrollRel = Vec(x, y); // onScroll gScene->onScroll(gMousePos, scrollRel.mult(-95)); } void charCallback(GLFWwindow *window, unsigned int codepoint) { if (gSelectedWidget) { gSelectedWidget->onText(codepoint); } } static int lastWindowX, lastWindowY, lastWindowWidth, lastWindowHeight; void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods) { if (action == GLFW_PRESS || action == GLFW_REPEAT) { if (key == GLFW_KEY_F11 || key == GLFW_KEY_ESCAPE) { /* // Toggle fullscreen GLFWmonitor *monitor = glfwGetWindowMonitor(window); if (monitor) { // Window mode glfwSetWindowMonitor(window, NULL, lastWindowX, lastWindowY, lastWindowWidth, lastWindowHeight, 0); } else { // Fullscreen glfwGetWindowPos(window, &lastWindowX, &lastWindowY); glfwGetWindowSize(window, &lastWindowWidth, &lastWindowHeight); monitor = glfwGetPrimaryMonitor(); assert(monitor); const GLFWvidmode *mode = glfwGetVideoMode(monitor); glfwSetWindowMonitor(window, monitor, 0, 0, mode->width, mode->height, mode->refreshRate); } */ } else { if (gSelectedWidget) { gSelectedWidget->onKey(key); } } } } void errorCallback(int error, const char *description) { fprintf(stderr, "GLFW error %d: %s\n", error, description); } void renderGui() { int width, height; glfwGetFramebufferSize(window, &width, &height); int windowWidth, windowHeight; glfwGetWindowSize(window, &windowWidth, &windowHeight); gPixelRatio = (float)width / windowWidth; bool visible = glfwGetWindowAttrib(window, GLFW_VISIBLE) && !glfwGetWindowAttrib(window, GLFW_ICONIFIED); if (visible) { // Update and render glViewport(0, 0, width, height); glClearColor(0.0, 0.0, 0.0, 1.0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); nvgBeginFrame(gVg, width, height, gPixelRatio); nvgSave(gVg); nvgReset(gVg); nvgScale(gVg, gPixelRatio, gPixelRatio); gScene->draw(gVg); nvgRestore(gVg); nvgEndFrame(gVg); } glfwSwapBuffers(window); } void guiInit() { int err; // Set up GLFW glfwSetErrorCallback(errorCallback); err = glfwInit(); assert(err); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); // glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); // glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2); // glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); glfwWindowHint(GLFW_MAXIMIZED, GLFW_TRUE); std::string title = gApplicationName + " " + gApplicationVersion; window = glfwCreateWindow(1000, 750, title.c_str(), NULL, NULL); assert(window); glfwMakeContextCurrent(window); glfwSwapInterval(1); glfwSetWindowSizeCallback(window, windowSizeCallback); glfwSetMouseButtonCallback(window, mouseButtonCallback); // glfwSetCursorPosCallback(window, cursorPosCallback); glfwSetCursorEnterCallback(window, cursorEnterCallback); glfwSetScrollCallback(window, scrollCallback); glfwSetCharCallback(window, charCallback); glfwSetKeyCallback(window, keyCallback); // Set up GLEW glewExperimental = GL_TRUE; err = glewInit(); assert(err == GLEW_OK); // Check framebuffer support assert(GLEW_EXT_framebuffer_object); // GLEW generates GL error because it calls glGetString(GL_EXTENSIONS), we'll consume it here. glGetError(); glfwSetWindowSizeLimits(window, 640, 480, GLFW_DONT_CARE, GLFW_DONT_CARE); // Set up NanoVG gVg = nvgCreateGL2(NVG_ANTIALIAS); // gVg = nvgCreateGL3(NVG_ANTIALIAS); assert(gVg); // Set up Blendish gGuiFont = Font::load("res/DejaVuSans.ttf"); bndSetFont(gGuiFont->handle); // bndSetIconImage(loadImage("res/icons.png")); } void guiDestroy() { gGuiFont.reset(); nvgDeleteGL2(gVg); // nvgDeleteGL3(gVg); glfwDestroyWindow(window); glfwTerminate(); } void guiRun() { assert(window); { int width, height; glfwGetWindowSize(window, &width, &height); windowSizeCallback(window, width, height); } gGuiFrame = 0; double lastTime = 0.0; while(!glfwWindowShouldClose(window)) { gGuiFrame++; glfwPollEvents(); { double xpos, ypos; glfwGetCursorPos(window, &xpos, &ypos); cursorPosCallback(window, xpos, ypos); } gScene->step(); renderGui(); double currTime = glfwGetTime(); // printf("%lf fps\n", 1.0/(currTime - lastTime)); lastTime = currTime; (void) lastTime; } } bool guiIsKeyPressed(int key) { return glfwGetKey(window, key) == GLFW_PRESS; } void guiCursorLock() { #ifdef ARCH_MAC glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_HIDDEN); #else glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); #endif } void guiCursorUnlock() { glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL); } //////////////////// // resources //////////////////// Font::Font(const std::string &filename) { handle = nvgCreateFont(gVg, filename.c_str(), filename.c_str()); if (handle >= 0) { fprintf(stderr, "Loaded font %s\n", filename.c_str()); } else { fprintf(stderr, "Failed to load font %s\n", filename.c_str()); } } Font::~Font() { // There is no NanoVG deleteFont() function yet, so do nothing } std::shared_ptr Font::load(const std::string &filename) { static std::map> cache; auto sp = cache[filename].lock(); if (!sp) cache[filename] = sp = std::make_shared(filename); return sp; } //////////////////// // Image //////////////////// Image::Image(const std::string &filename) { handle = nvgCreateImage(gVg, filename.c_str(), NVG_IMAGE_REPEATX | NVG_IMAGE_REPEATY); if (handle > 0) { fprintf(stderr, "Loaded image %s\n", filename.c_str()); } else { fprintf(stderr, "Failed to load image %s\n", filename.c_str()); } } Image::~Image() { // TODO What if handle is invalid? nvgDeleteImage(gVg, handle); } std::shared_ptr Image::load(const std::string &filename) { static std::map> cache; auto sp = cache[filename].lock(); if (!sp) cache[filename] = sp = std::make_shared(filename); return sp; } //////////////////// // SVG //////////////////// SVG::SVG(const std::string &filename) { handle = nsvgParseFromFile(filename.c_str(), "px", 96.0); if (handle) { fprintf(stderr, "Loaded SVG %s\n", filename.c_str()); } else { fprintf(stderr, "Failed to load SVG %s\n", filename.c_str()); } } SVG::~SVG() { nsvgDelete(handle); } std::shared_ptr SVG::load(const std::string &filename) { static std::map> cache; auto sp = cache[filename].lock(); if (!sp) cache[filename] = sp = std::make_shared(filename); return sp; } } // namespace rack