You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

567 lines
15KB

  1. #include <map>
  2. #include <queue>
  3. #include <thread>
  4. #ifdef ARCH_MAC
  5. // For CGAssociateMouseAndMouseCursorPosition
  6. #include <ApplicationServices/ApplicationServices.h>
  7. #endif
  8. #include "osdialog.h"
  9. #include "rack.hpp"
  10. #include "keyboard.hpp"
  11. #include "gamepad.hpp"
  12. #include "event.hpp"
  13. #define NANOVG_GL2_IMPLEMENTATION 1
  14. // #define NANOVG_GL3_IMPLEMENTATION 1
  15. // #define NANOVG_GLES2_IMPLEMENTATION 1
  16. // #define NANOVG_GLES3_IMPLEMENTATION 1
  17. #include "nanovg_gl.h"
  18. // Hack to get framebuffer objects working on OpenGL 2 (we blindly assume the extension is supported)
  19. #define NANOVG_FBO_VALID 1
  20. #include "nanovg_gl_utils.h"
  21. #define BLENDISH_IMPLEMENTATION
  22. #include "blendish.h"
  23. #define NANOSVG_IMPLEMENTATION
  24. #define NANOSVG_ALL_COLOR_KEYWORDS
  25. #include "nanosvg.h"
  26. namespace rack {
  27. GLFWwindow *gWindow = NULL;
  28. NVGcontext *gVg = NULL;
  29. NVGcontext *gFramebufferVg = NULL;
  30. std::shared_ptr<Font> gGuiFont;
  31. float gPixelRatio = 1.0;
  32. float gWindowRatio = 1.0;
  33. bool gAllowCursorLock = true;
  34. int gGuiFrame;
  35. Vec gMousePos;
  36. std::string lastWindowTitle;
  37. static void windowSizeCallback(GLFWwindow* window, int width, int height) {
  38. // Do nothing. Window size is reset each frame anyway.
  39. }
  40. static void mouseButtonCallback(GLFWwindow *window, int button, int action, int mods) {
  41. #ifdef ARCH_MAC
  42. // Remap Ctrl-left click to right click on Mac
  43. if (button == GLFW_MOUSE_BUTTON_LEFT) {
  44. if (mods & GLFW_MOD_CONTROL) {
  45. button = GLFW_MOUSE_BUTTON_RIGHT;
  46. }
  47. }
  48. #endif
  49. event::gContext->handleButton(gMousePos, button, action, mods);
  50. }
  51. struct MouseButtonArguments {
  52. GLFWwindow *window;
  53. int button;
  54. int action;
  55. int mods;
  56. };
  57. static std::queue<MouseButtonArguments> mouseButtonQueue;
  58. void mouseButtonStickyPop() {
  59. if (!mouseButtonQueue.empty()) {
  60. MouseButtonArguments args = mouseButtonQueue.front();
  61. mouseButtonQueue.pop();
  62. mouseButtonCallback(args.window, args.button, args.action, args.mods);
  63. }
  64. }
  65. void mouseButtonStickyCallback(GLFWwindow *window, int button, int action, int mods) {
  66. // Defer multiple clicks per frame to future frames
  67. MouseButtonArguments args = {window, button, action, mods};
  68. mouseButtonQueue.push(args);
  69. }
  70. void cursorPosCallback(GLFWwindow* window, double xpos, double ypos) {
  71. Vec mousePos = Vec(xpos, ypos).div(gPixelRatio / gWindowRatio).round();
  72. Vec mouseDelta = mousePos.minus(gMousePos);
  73. int cursorMode = glfwGetInputMode(gWindow, GLFW_CURSOR);
  74. (void) cursorMode;
  75. #ifdef ARCH_MAC
  76. // Workaround for Mac. We can't use GLFW_CURSOR_DISABLED because it's buggy, so implement it on our own.
  77. // This is not an ideal implementation. For example, if the user drags off the screen, the new mouse position will be clamped.
  78. if (cursorMode == GLFW_CURSOR_HIDDEN) {
  79. // CGSetLocalEventsSuppressionInterval(0.0);
  80. glfwSetCursorPos(gWindow, gMousePos.x, gMousePos.y);
  81. CGAssociateMouseAndMouseCursorPosition(true);
  82. mousePos = gMousePos;
  83. }
  84. // Because sometimes the cursor turns into an arrow when its position is on the boundary of the window
  85. glfwSetCursor(gWindow, NULL);
  86. #endif
  87. gMousePos = mousePos;
  88. event::gContext->handleHover(mousePos, mouseDelta);
  89. }
  90. void cursorEnterCallback(GLFWwindow* window, int entered) {
  91. if (!entered) {
  92. event::gContext->handleLeave();
  93. }
  94. }
  95. void scrollCallback(GLFWwindow *window, double x, double y) {
  96. Vec scrollDelta = Vec(x, y);
  97. #if ARCH_LIN || ARCH_WIN
  98. if (windowIsShiftPressed())
  99. scrollDelta = Vec(y, x);
  100. #endif
  101. scrollDelta = scrollDelta.mult(50.0);
  102. event::gContext->handleScroll(gMousePos, scrollDelta);
  103. }
  104. void charCallback(GLFWwindow *window, unsigned int codepoint) {
  105. event::gContext->handleText(gMousePos, codepoint);
  106. }
  107. void keyCallback(GLFWwindow *window, int key, int scancode, int action, int mods) {
  108. event::gContext->handleKey(gMousePos, key, scancode, action, mods);
  109. // Keyboard MIDI driver
  110. if (!(mods & (GLFW_MOD_SHIFT | GLFW_MOD_CONTROL | GLFW_MOD_ALT | GLFW_MOD_SUPER))) {
  111. if (action == GLFW_PRESS) {
  112. keyboard::press(key);
  113. }
  114. else if (action == GLFW_RELEASE) {
  115. keyboard::release(key);
  116. }
  117. }
  118. }
  119. void dropCallback(GLFWwindow *window, int count, const char **paths) {
  120. std::vector<std::string> pathsVec;
  121. for (int i = 0; i < count; i++) {
  122. pathsVec.push_back(paths[i]);
  123. }
  124. event::gContext->handleDrop(gMousePos, pathsVec);
  125. }
  126. void errorCallback(int error, const char *description) {
  127. WARN("GLFW error %d: %s", error, description);
  128. }
  129. void renderGui() {
  130. int width, height;
  131. glfwGetFramebufferSize(gWindow, &width, &height);
  132. // Update and render
  133. nvgBeginFrame(gVg, width, height, gPixelRatio);
  134. nvgReset(gVg);
  135. nvgScale(gVg, gPixelRatio, gPixelRatio);
  136. event::gContext->rootWidget->draw(gVg);
  137. glViewport(0, 0, width, height);
  138. glClearColor(0.0, 0.0, 0.0, 1.0);
  139. glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
  140. nvgEndFrame(gVg);
  141. glfwSwapBuffers(gWindow);
  142. }
  143. void windowInit() {
  144. int err;
  145. // Set up GLFW
  146. glfwSetErrorCallback(errorCallback);
  147. err = glfwInit();
  148. if (err != GLFW_TRUE) {
  149. osdialog_message(OSDIALOG_ERROR, OSDIALOG_OK, "Could not initialize GLFW.");
  150. exit(1);
  151. }
  152. #if defined NANOVG_GL2
  153. glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
  154. glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
  155. #elif defined NANOVG_GL3
  156. glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
  157. glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
  158. glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
  159. glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
  160. #endif
  161. glfwWindowHint(GLFW_MAXIMIZED, GLFW_TRUE);
  162. glfwWindowHint(GLFW_DOUBLEBUFFER, GLFW_TRUE);
  163. lastWindowTitle = "";
  164. gWindow = glfwCreateWindow(640, 480, lastWindowTitle.c_str(), NULL, NULL);
  165. if (!gWindow) {
  166. osdialog_message(OSDIALOG_ERROR, OSDIALOG_OK, "Cannot open window with OpenGL 2.0 renderer. Does your graphics card support OpenGL 2.0 or greater? If so, make sure you have the latest graphics drivers installed.");
  167. exit(1);
  168. }
  169. glfwMakeContextCurrent(gWindow);
  170. glfwSwapInterval(1);
  171. glfwSetInputMode(gWindow, GLFW_LOCK_KEY_MODS, 1);
  172. glfwSetWindowSizeCallback(gWindow, windowSizeCallback);
  173. glfwSetMouseButtonCallback(gWindow, mouseButtonStickyCallback);
  174. // Call this ourselves, but on every frame instead of only when the mouse moves
  175. // glfwSetCursorPosCallback(gWindow, cursorPosCallback);
  176. glfwSetCursorEnterCallback(gWindow, cursorEnterCallback);
  177. glfwSetScrollCallback(gWindow, scrollCallback);
  178. glfwSetCharCallback(gWindow, charCallback);
  179. glfwSetKeyCallback(gWindow, keyCallback);
  180. glfwSetDropCallback(gWindow, dropCallback);
  181. // Set up GLEW
  182. glewExperimental = GL_TRUE;
  183. err = glewInit();
  184. if (err != GLEW_OK) {
  185. osdialog_message(OSDIALOG_ERROR, OSDIALOG_OK, "Could not initialize GLEW. Does your graphics card support OpenGL 2.0 or greater? If so, make sure you have the latest graphics drivers installed.");
  186. exit(1);
  187. }
  188. // GLEW generates GL error because it calls glGetString(GL_EXTENSIONS), we'll consume it here.
  189. glGetError();
  190. glfwSetWindowSizeLimits(gWindow, 640, 480, GLFW_DONT_CARE, GLFW_DONT_CARE);
  191. // Set up NanoVG
  192. int nvgFlags = NVG_ANTIALIAS;
  193. #if defined NANOVG_GL2
  194. gVg = nvgCreateGL2(nvgFlags);
  195. #elif defined NANOVG_GL3
  196. gVg = nvgCreateGL3(nvgFlags);
  197. #elif defined NANOVG_GLES2
  198. gVg = nvgCreateGLES2(nvgFlags);
  199. #endif
  200. assert(gVg);
  201. #if defined NANOVG_GL2
  202. gFramebufferVg = nvgCreateGL2(nvgFlags);
  203. #elif defined NANOVG_GL3
  204. gFramebufferVg = nvgCreateGL3(nvgFlags);
  205. #elif defined NANOVG_GLES2
  206. gFramebufferVg = nvgCreateGLES2(nvgFlags);
  207. #endif
  208. assert(gFramebufferVg);
  209. // Set up Blendish
  210. gGuiFont = Font::load(asset::global("res/fonts/DejaVuSans.ttf"));
  211. bndSetFont(gGuiFont->handle);
  212. windowSetTheme(nvgRGB(0x33, 0x33, 0x33), nvgRGB(0xf0, 0xf0, 0xf0));
  213. }
  214. void windowDestroy() {
  215. gGuiFont.reset();
  216. #if defined NANOVG_GL2
  217. nvgDeleteGL2(gVg);
  218. #elif defined NANOVG_GL3
  219. nvgDeleteGL3(gVg);
  220. #elif defined NANOVG_GLES2
  221. nvgDeleteGLES2(gVg);
  222. #endif
  223. #if defined NANOVG_GL2
  224. nvgDeleteGL2(gFramebufferVg);
  225. #elif defined NANOVG_GL3
  226. nvgDeleteGL3(gFramebufferVg);
  227. #elif defined NANOVG_GLES2
  228. nvgDeleteGLES2(gFramebufferVg);
  229. #endif
  230. glfwDestroyWindow(gWindow);
  231. glfwTerminate();
  232. }
  233. void windowRun() {
  234. assert(gWindow);
  235. gGuiFrame = 0;
  236. while(!glfwWindowShouldClose(gWindow)) {
  237. double startTime = glfwGetTime();
  238. gGuiFrame++;
  239. // Poll events
  240. glfwPollEvents();
  241. {
  242. double xpos, ypos;
  243. glfwGetCursorPos(gWindow, &xpos, &ypos);
  244. cursorPosCallback(gWindow, xpos, ypos);
  245. }
  246. mouseButtonStickyPop();
  247. gamepad::step();
  248. // Set window title
  249. std::string windowTitle;
  250. windowTitle = gApplicationName;
  251. windowTitle += " ";
  252. windowTitle += gApplicationVersion;
  253. if (!gRackWidget->lastPath.empty()) {
  254. windowTitle += " - ";
  255. windowTitle += string::filename(gRackWidget->lastPath);
  256. }
  257. if (windowTitle != lastWindowTitle) {
  258. glfwSetWindowTitle(gWindow, windowTitle.c_str());
  259. lastWindowTitle = windowTitle;
  260. }
  261. // Get desired scaling
  262. float pixelRatio;
  263. glfwGetWindowContentScale(gWindow, &pixelRatio, NULL);
  264. pixelRatio = roundf(pixelRatio);
  265. if (pixelRatio != gPixelRatio) {
  266. event::gContext->handleZoom();
  267. gPixelRatio = pixelRatio;
  268. }
  269. // Get framebuffer/window ratio
  270. int width, height;
  271. glfwGetFramebufferSize(gWindow, &width, &height);
  272. int windowWidth, windowHeight;
  273. glfwGetWindowSize(gWindow, &windowWidth, &windowHeight);
  274. gWindowRatio = (float)width / windowWidth;
  275. event::gContext->rootWidget->box.size = Vec(width, height).div(gPixelRatio);
  276. // Step scene
  277. event::gContext->rootWidget->step();
  278. // Render
  279. bool visible = glfwGetWindowAttrib(gWindow, GLFW_VISIBLE) && !glfwGetWindowAttrib(gWindow, GLFW_ICONIFIED);
  280. if (visible) {
  281. renderGui();
  282. }
  283. // Limit framerate manually if vsync isn't working
  284. double endTime = glfwGetTime();
  285. double frameTime = endTime - startTime;
  286. double minTime = 1.0 / 90.0;
  287. if (frameTime < minTime) {
  288. std::this_thread::sleep_for(std::chrono::duration<double>(minTime - frameTime));
  289. }
  290. endTime = glfwGetTime();
  291. // INFO("%lf fps", 1.0 / (endTime - startTime));
  292. }
  293. }
  294. void windowClose() {
  295. glfwSetWindowShouldClose(gWindow, GLFW_TRUE);
  296. }
  297. void windowCursorLock() {
  298. if (gAllowCursorLock) {
  299. #ifdef ARCH_MAC
  300. glfwSetInputMode(gWindow, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
  301. #else
  302. glfwSetInputMode(gWindow, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
  303. #endif
  304. }
  305. }
  306. void windowCursorUnlock() {
  307. if (gAllowCursorLock) {
  308. glfwSetInputMode(gWindow, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
  309. }
  310. }
  311. bool windowIsModPressed() {
  312. #ifdef ARCH_MAC
  313. return glfwGetKey(gWindow, GLFW_KEY_LEFT_SUPER) == GLFW_PRESS || glfwGetKey(gWindow, GLFW_KEY_RIGHT_SUPER) == GLFW_PRESS;
  314. #else
  315. return glfwGetKey(gWindow, GLFW_KEY_LEFT_CONTROL) == GLFW_PRESS || glfwGetKey(gWindow, GLFW_KEY_RIGHT_CONTROL) == GLFW_PRESS;
  316. #endif
  317. }
  318. bool windowIsShiftPressed() {
  319. return glfwGetKey(gWindow, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS || glfwGetKey(gWindow, GLFW_KEY_RIGHT_SHIFT) == GLFW_PRESS;
  320. }
  321. Vec windowGetWindowSize() {
  322. int width, height;
  323. glfwGetWindowSize(gWindow, &width, &height);
  324. return Vec(width, height);
  325. }
  326. void windowSetWindowSize(Vec size) {
  327. int width = size.x;
  328. int height = size.y;
  329. glfwSetWindowSize(gWindow, width, height);
  330. }
  331. Vec windowGetWindowPos() {
  332. int x, y;
  333. glfwGetWindowPos(gWindow, &x, &y);
  334. return Vec(x, y);
  335. }
  336. void windowSetWindowPos(Vec pos) {
  337. int x = pos.x;
  338. int y = pos.y;
  339. glfwSetWindowPos(gWindow, x, y);
  340. }
  341. bool windowIsMaximized() {
  342. return glfwGetWindowAttrib(gWindow, GLFW_MAXIMIZED);
  343. }
  344. void windowSetTheme(NVGcolor bg, NVGcolor fg) {
  345. // Assume dark background and light foreground
  346. BNDwidgetTheme w;
  347. w.outlineColor = bg;
  348. w.itemColor = fg;
  349. w.innerColor = bg;
  350. w.innerSelectedColor = color::plus(bg, nvgRGB(0x30, 0x30, 0x30));
  351. w.textColor = fg;
  352. w.textSelectedColor = fg;
  353. w.shadeTop = 0;
  354. w.shadeDown = 0;
  355. BNDtheme t;
  356. t.backgroundColor = color::plus(bg, nvgRGB(0x30, 0x30, 0x30));
  357. t.regularTheme = w;
  358. t.toolTheme = w;
  359. t.radioTheme = w;
  360. t.textFieldTheme = w;
  361. t.optionTheme = w;
  362. t.choiceTheme = w;
  363. t.numberFieldTheme = w;
  364. t.sliderTheme = w;
  365. t.scrollBarTheme = w;
  366. t.tooltipTheme = w;
  367. t.menuTheme = w;
  368. t.menuItemTheme = w;
  369. t.sliderTheme.itemColor = bg;
  370. t.sliderTheme.innerColor = color::plus(bg, nvgRGB(0x50, 0x50, 0x50));
  371. t.sliderTheme.innerSelectedColor = color::plus(bg, nvgRGB(0x60, 0x60, 0x60));
  372. t.textFieldTheme = t.sliderTheme;
  373. t.textFieldTheme.textColor = color::minus(bg, nvgRGB(0x20, 0x20, 0x20));
  374. t.textFieldTheme.textSelectedColor = t.textFieldTheme.textColor;
  375. t.scrollBarTheme.itemColor = color::plus(bg, nvgRGB(0x50, 0x50, 0x50));
  376. t.scrollBarTheme.innerColor = bg;
  377. t.menuTheme.innerColor = color::minus(bg, nvgRGB(0x10, 0x10, 0x10));
  378. t.menuTheme.textColor = color::minus(fg, nvgRGB(0x50, 0x50, 0x50));
  379. t.menuTheme.textSelectedColor = t.menuTheme.textColor;
  380. bndSetTheme(t);
  381. }
  382. static int windowX = 0;
  383. static int windowY = 0;
  384. static int windowWidth = 0;
  385. static int windowHeight = 0;
  386. void windowSetFullScreen(bool fullScreen) {
  387. if (windowGetFullScreen()) {
  388. glfwSetWindowMonitor(gWindow, NULL, windowX, windowY, windowWidth, windowHeight, GLFW_DONT_CARE);
  389. }
  390. else {
  391. glfwGetWindowPos(gWindow, &windowX, &windowY);
  392. glfwGetWindowSize(gWindow, &windowWidth, &windowHeight);
  393. GLFWmonitor *monitor = glfwGetPrimaryMonitor();
  394. const GLFWvidmode* mode = glfwGetVideoMode(monitor);
  395. glfwSetWindowMonitor(gWindow, monitor, 0, 0, mode->width, mode->height, mode->refreshRate);
  396. }
  397. }
  398. bool windowGetFullScreen() {
  399. GLFWmonitor *monitor = glfwGetWindowMonitor(gWindow);
  400. return monitor != NULL;
  401. }
  402. ////////////////////
  403. // resources
  404. ////////////////////
  405. Font::Font(const std::string &filename) {
  406. handle = nvgCreateFont(gVg, filename.c_str(), filename.c_str());
  407. if (handle >= 0) {
  408. INFO("Loaded font %s", filename.c_str());
  409. }
  410. else {
  411. WARN("Failed to load font %s", filename.c_str());
  412. }
  413. }
  414. Font::~Font() {
  415. // There is no NanoVG deleteFont() function yet, so do nothing
  416. }
  417. std::shared_ptr<Font> Font::load(const std::string &filename) {
  418. static std::map<std::string, std::weak_ptr<Font>> cache;
  419. auto sp = cache[filename].lock();
  420. if (!sp)
  421. cache[filename] = sp = std::make_shared<Font>(filename);
  422. return sp;
  423. }
  424. ////////////////////
  425. // Image
  426. ////////////////////
  427. Image::Image(const std::string &filename) {
  428. handle = nvgCreateImage(gVg, filename.c_str(), NVG_IMAGE_REPEATX | NVG_IMAGE_REPEATY);
  429. if (handle > 0) {
  430. INFO("Loaded image %s", filename.c_str());
  431. }
  432. else {
  433. WARN("Failed to load image %s", filename.c_str());
  434. }
  435. }
  436. Image::~Image() {
  437. // TODO What if handle is invalid?
  438. nvgDeleteImage(gVg, handle);
  439. }
  440. std::shared_ptr<Image> Image::load(const std::string &filename) {
  441. static std::map<std::string, std::weak_ptr<Image>> cache;
  442. auto sp = cache[filename].lock();
  443. if (!sp)
  444. cache[filename] = sp = std::make_shared<Image>(filename);
  445. return sp;
  446. }
  447. ////////////////////
  448. // SVG
  449. ////////////////////
  450. SVG::SVG(const std::string &filename) {
  451. handle = nsvgParseFromFile(filename.c_str(), "px", SVG_DPI);
  452. if (handle) {
  453. INFO("Loaded SVG %s", filename.c_str());
  454. }
  455. else {
  456. WARN("Failed to load SVG %s", filename.c_str());
  457. }
  458. }
  459. SVG::~SVG() {
  460. nsvgDelete(handle);
  461. }
  462. std::shared_ptr<SVG> SVG::load(const std::string &filename) {
  463. static std::map<std::string, std::weak_ptr<SVG>> cache;
  464. auto sp = cache[filename].lock();
  465. if (!sp)
  466. cache[filename] = sp = std::make_shared<SVG>(filename);
  467. return sp;
  468. }
  469. } // namespace rack