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.

625 lines
15KB

  1. #include "gui.hpp"
  2. #include "app.hpp"
  3. #include "asset.hpp"
  4. #include <map>
  5. #include <queue>
  6. #include <thread>
  7. #include "../ext/osdialog/osdialog.h"
  8. #define NANOVG_GL2 1
  9. // #define NANOVG_GL3 1
  10. // #define NANOVG_GLES2 1
  11. #define NANOVG_GL_IMPLEMENTATION 1
  12. #include "../ext/nanovg/src/nanovg_gl.h"
  13. // Hack to get framebuffer objects working on OpenGL 2 (we blindly assume the extension is supported)
  14. #define NANOVG_FBO_VALID 1
  15. #include "../ext/nanovg/src/nanovg_gl_utils.h"
  16. #define BLENDISH_IMPLEMENTATION
  17. #include "../ext/oui-blendish/blendish.h"
  18. #define NANOSVG_IMPLEMENTATION
  19. #define NANOSVG_ALL_COLOR_KEYWORDS
  20. #include "../ext/nanosvg/src/nanosvg.h"
  21. #ifdef ARCH_MAC
  22. // For CGAssociateMouseAndMouseCursorPosition
  23. #include <ApplicationServices/ApplicationServices.h>
  24. #endif
  25. namespace rack {
  26. GLFWwindow *gWindow = NULL;
  27. NVGcontext *gVg = NULL;
  28. NVGcontext *gFramebufferVg = NULL;
  29. std::shared_ptr<Font> gGuiFont;
  30. float gPixelRatio = 1.0;
  31. float gWindowRatio = 1.0;
  32. bool gAllowCursorLock = true;
  33. int gGuiFrame;
  34. Vec gMousePos;
  35. std::string lastWindowTitle;
  36. void windowSizeCallback(GLFWwindow* window, int width, int height) {
  37. }
  38. void mouseButtonCallback(GLFWwindow *window, int button, int action, int mods) {
  39. #ifdef ARCH_MAC
  40. // Ctrl-left click --> right click
  41. if (button == GLFW_MOUSE_BUTTON_LEFT) {
  42. if (glfwGetKey(gWindow, GLFW_KEY_LEFT_CONTROL) == GLFW_PRESS || glfwGetKey(gWindow, GLFW_KEY_RIGHT_CONTROL) == GLFW_PRESS) {
  43. button = GLFW_MOUSE_BUTTON_RIGHT;
  44. }
  45. }
  46. #endif
  47. if (action == GLFW_PRESS) {
  48. Widget *w = NULL;
  49. // onMouseDown
  50. {
  51. EventMouseDown e;
  52. e.pos = gMousePos;
  53. e.button = button;
  54. gScene->onMouseDown(e);
  55. w = e.target;
  56. }
  57. if (button == GLFW_MOUSE_BUTTON_LEFT) {
  58. if (w) {
  59. // onDragStart
  60. EventDragStart e;
  61. w->onDragStart(e);
  62. }
  63. gDraggedWidget = w;
  64. if (w != gFocusedWidget) {
  65. if (gFocusedWidget) {
  66. // onDefocus
  67. EventDefocus e;
  68. w->onDefocus(e);
  69. }
  70. gFocusedWidget = NULL;
  71. if (w) {
  72. // onFocus
  73. EventFocus e;
  74. w->onFocus(e);
  75. if (e.consumed) {
  76. gFocusedWidget = w;
  77. }
  78. }
  79. }
  80. }
  81. }
  82. else if (action == GLFW_RELEASE) {
  83. // onMouseUp
  84. Widget *w = NULL;
  85. {
  86. EventMouseUp e;
  87. e.pos = gMousePos;
  88. e.button = button;
  89. gScene->onMouseUp(e);
  90. w = e.target;
  91. }
  92. if (button == GLFW_MOUSE_BUTTON_LEFT) {
  93. if (gDraggedWidget) {
  94. // onDragDrop
  95. EventDragDrop e;
  96. e.origin = gDraggedWidget;
  97. w->onDragDrop(e);
  98. }
  99. // gDraggedWidget might have been set to null in the last event, recheck here
  100. if (gDraggedWidget) {
  101. // onDragEnd
  102. EventDragEnd e;
  103. gDraggedWidget->onDragEnd(e);
  104. }
  105. gDraggedWidget = NULL;
  106. gDragHoveredWidget = NULL;
  107. }
  108. }
  109. }
  110. struct MouseButtonArguments {
  111. GLFWwindow *window;
  112. int button;
  113. int action;
  114. int mods;
  115. };
  116. static std::queue<MouseButtonArguments> mouseButtonQueue;
  117. void mouseButtonStickyPop() {
  118. if (!mouseButtonQueue.empty()) {
  119. MouseButtonArguments args = mouseButtonQueue.front();
  120. mouseButtonQueue.pop();
  121. mouseButtonCallback(args.window, args.button, args.action, args.mods);
  122. }
  123. }
  124. void mouseButtonStickyCallback(GLFWwindow *window, int button, int action, int mods) {
  125. // Defer multiple clicks per frame to future frames
  126. MouseButtonArguments args = {window, button, action, mods};
  127. mouseButtonQueue.push(args);
  128. }
  129. void cursorPosCallback(GLFWwindow* window, double xpos, double ypos) {
  130. Vec mousePos = Vec(xpos, ypos).div(gPixelRatio / gWindowRatio).round();
  131. Vec mouseRel = mousePos.minus(gMousePos);
  132. #ifdef ARCH_MAC
  133. // Workaround for Mac. We can't use GLFW_CURSOR_DISABLED because it's buggy, so implement it on our own.
  134. // This is not an ideal implementation. For example, if the user drags off the screen, the new mouse position will be clamped.
  135. int mouseMode = glfwGetInputMode(gWindow, GLFW_CURSOR);
  136. if (mouseMode == GLFW_CURSOR_HIDDEN) {
  137. // CGSetLocalEventsSuppressionInterval(0.0);
  138. glfwSetCursorPos(gWindow, gMousePos.x, gMousePos.y);
  139. CGAssociateMouseAndMouseCursorPosition(true);
  140. mousePos = gMousePos;
  141. }
  142. // Because sometimes the cursor turns into an arrow when its position is on the boundary of the window
  143. glfwSetCursor(gWindow, NULL);
  144. #endif
  145. gMousePos = mousePos;
  146. Widget *hovered = NULL;
  147. // onMouseMove
  148. {
  149. EventMouseMove e;
  150. e.pos = mousePos;
  151. e.mouseRel = mouseRel;
  152. gScene->onMouseMove(e);
  153. hovered = e.target;
  154. }
  155. if (gDraggedWidget) {
  156. // onDragMove
  157. EventDragMove e;
  158. e.mouseRel = mouseRel;
  159. gDraggedWidget->onDragMove(e);
  160. if (hovered != gDragHoveredWidget) {
  161. if (gDragHoveredWidget) {
  162. EventDragEnter e;
  163. e.origin = gDraggedWidget;
  164. gDragHoveredWidget->onDragLeave(e);
  165. }
  166. if (hovered) {
  167. EventDragEnter e;
  168. e.origin = gDraggedWidget;
  169. hovered->onDragEnter(e);
  170. }
  171. gDragHoveredWidget = hovered;
  172. }
  173. }
  174. else {
  175. if (hovered != gHoveredWidget) {
  176. if (gHoveredWidget) {
  177. // onMouseLeave
  178. EventMouseLeave e;
  179. gHoveredWidget->onMouseLeave(e);
  180. }
  181. if (hovered) {
  182. // onMouseEnter
  183. EventMouseEnter e;
  184. hovered->onMouseEnter(e);
  185. }
  186. gHoveredWidget = hovered;
  187. }
  188. }
  189. if (glfwGetMouseButton(gWindow, GLFW_MOUSE_BUTTON_MIDDLE) == GLFW_PRESS) {
  190. // TODO
  191. // Define a new global called gScrollWidget, which remembers the widget where middle-click was first pressed
  192. EventScroll e;
  193. e.pos = mousePos;
  194. e.scrollRel = mouseRel;
  195. gScene->onScroll(e);
  196. }
  197. }
  198. void cursorEnterCallback(GLFWwindow* window, int entered) {
  199. if (!entered) {
  200. if (gHoveredWidget) {
  201. // onMouseLeave
  202. EventMouseLeave e;
  203. gHoveredWidget->onMouseLeave(e);
  204. }
  205. gHoveredWidget = NULL;
  206. }
  207. }
  208. void scrollCallback(GLFWwindow *window, double x, double y) {
  209. Vec scrollRel = Vec(x, y);
  210. #if ARCH_LIN || ARCH_WIN
  211. if (guiIsShiftPressed())
  212. scrollRel = Vec(y, x);
  213. #endif
  214. // onScroll
  215. EventScroll e;
  216. e.pos = gMousePos;
  217. e.scrollRel = scrollRel.mult(50.0);
  218. gScene->onScroll(e);
  219. }
  220. void charCallback(GLFWwindow *window, unsigned int codepoint) {
  221. if (gFocusedWidget) {
  222. // onText
  223. EventText e;
  224. e.codepoint = codepoint;
  225. gFocusedWidget->onText(e);
  226. }
  227. }
  228. void keyCallback(GLFWwindow *window, int key, int scancode, int action, int mods) {
  229. if (action == GLFW_PRESS || action == GLFW_REPEAT) {
  230. if (gFocusedWidget) {
  231. // onKey
  232. EventKey e;
  233. e.key = key;
  234. gFocusedWidget->onKey(e);
  235. if (e.consumed)
  236. return;
  237. }
  238. // onHoverKey
  239. EventHoverKey e;
  240. e.pos = gMousePos;
  241. e.key = key;
  242. gScene->onHoverKey(e);
  243. }
  244. }
  245. void dropCallback(GLFWwindow *window, int count, const char **paths) {
  246. // onPathDrop
  247. EventPathDrop e;
  248. e.pos = gMousePos;
  249. for (int i = 0; i < count; i++) {
  250. e.paths.push_back(paths[i]);
  251. }
  252. gScene->onPathDrop(e);
  253. }
  254. void errorCallback(int error, const char *description) {
  255. warn("GLFW error %d: %s", error, description);
  256. }
  257. void renderGui() {
  258. int width, height;
  259. glfwGetFramebufferSize(gWindow, &width, &height);
  260. // Update and render
  261. nvgBeginFrame(gVg, width, height, gPixelRatio);
  262. nvgReset(gVg);
  263. nvgScale(gVg, gPixelRatio, gPixelRatio);
  264. gScene->draw(gVg);
  265. glViewport(0, 0, width, height);
  266. glClearColor(0.0, 0.0, 0.0, 1.0);
  267. glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
  268. nvgEndFrame(gVg);
  269. glfwSwapBuffers(gWindow);
  270. }
  271. void guiInit() {
  272. int err;
  273. // Set up GLFW
  274. glfwSetErrorCallback(errorCallback);
  275. err = glfwInit();
  276. if (err != GLFW_TRUE) {
  277. osdialog_message(OSDIALOG_ERROR, OSDIALOG_OK, "Could not initialize GLFW.");
  278. exit(1);
  279. }
  280. glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
  281. glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
  282. // glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
  283. // glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
  284. // glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
  285. // glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
  286. glfwWindowHint(GLFW_MAXIMIZED, GLFW_TRUE);
  287. glfwWindowHint(GLFW_DOUBLEBUFFER, GLFW_TRUE);
  288. lastWindowTitle = "";
  289. gWindow = glfwCreateWindow(640, 480, lastWindowTitle.c_str(), NULL, NULL);
  290. if (!gWindow) {
  291. 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.");
  292. exit(1);
  293. }
  294. glfwMakeContextCurrent(gWindow);
  295. glfwSwapInterval(1);
  296. glfwSetWindowSizeCallback(gWindow, windowSizeCallback);
  297. glfwSetMouseButtonCallback(gWindow, mouseButtonStickyCallback);
  298. // glfwSetCursorPosCallback(gWindow, cursorPosCallback);
  299. glfwSetCursorEnterCallback(gWindow, cursorEnterCallback);
  300. glfwSetScrollCallback(gWindow, scrollCallback);
  301. glfwSetCharCallback(gWindow, charCallback);
  302. glfwSetKeyCallback(gWindow, keyCallback);
  303. glfwSetDropCallback(gWindow, dropCallback);
  304. // Set up GLEW
  305. glewExperimental = GL_TRUE;
  306. err = glewInit();
  307. if (err != GLEW_OK) {
  308. 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.");
  309. exit(1);
  310. }
  311. // GLEW generates GL error because it calls glGetString(GL_EXTENSIONS), we'll consume it here.
  312. glGetError();
  313. glfwSetWindowSizeLimits(gWindow, 640, 480, GLFW_DONT_CARE, GLFW_DONT_CARE);
  314. // Set up NanoVG
  315. #if defined NANOVG_GL2
  316. gVg = nvgCreateGL2(NVG_ANTIALIAS);
  317. #elif defined NANOVG_GL3
  318. gVg = nvgCreateGL3(NVG_ANTIALIAS);
  319. #elif defined NANOVG_GLES2
  320. gVg = nvgCreateGLES2(NVG_ANTIALIAS);
  321. #endif
  322. assert(gVg);
  323. #if defined NANOVG_GL2
  324. gFramebufferVg = nvgCreateGL2(NVG_ANTIALIAS);
  325. #elif defined NANOVG_GL3
  326. gFramebufferVg = nvgCreateGL3(NVG_ANTIALIAS);
  327. #elif defined NANOVG_GLES2
  328. gFramebufferVg = nvgCreateGLES2(NVG_ANTIALIAS);
  329. #endif
  330. assert(gFramebufferVg);
  331. // Set up Blendish
  332. gGuiFont = Font::load(assetGlobal("res/DejaVuSans.ttf"));
  333. bndSetFont(gGuiFont->handle);
  334. // bndSetIconImage(loadImage(assetGlobal("res/icons.png")));
  335. // Blendish style
  336. BNDtheme theme;
  337. theme = *bndGetTheme();
  338. theme.nodeTheme.nodeBackdropColor = theme.menuTheme.innerColor;
  339. theme.nodeTheme.nodeBackdropColor.a = 1.0;
  340. bndSetTheme(theme);
  341. }
  342. void guiDestroy() {
  343. gGuiFont.reset();
  344. #if defined NANOVG_GL2
  345. nvgDeleteGL2(gVg);
  346. #elif defined NANOVG_GL3
  347. nvgDeleteGL3(gVg);
  348. #elif defined NANOVG_GLES2
  349. nvgDeleteGLES2(gVg);
  350. #endif
  351. #if defined NANOVG_GL2
  352. nvgDeleteGL2(gFramebufferVg);
  353. #elif defined NANOVG_GL3
  354. nvgDeleteGL3(gFramebufferVg);
  355. #elif defined NANOVG_GLES2
  356. nvgDeleteGLES2(gFramebufferVg);
  357. #endif
  358. glfwDestroyWindow(gWindow);
  359. glfwTerminate();
  360. }
  361. void guiRun() {
  362. assert(gWindow);
  363. gGuiFrame = 0;
  364. while(!glfwWindowShouldClose(gWindow)) {
  365. double startTime = glfwGetTime();
  366. gGuiFrame++;
  367. // Poll events
  368. glfwPollEvents();
  369. {
  370. double xpos, ypos;
  371. glfwGetCursorPos(gWindow, &xpos, &ypos);
  372. cursorPosCallback(gWindow, xpos, ypos);
  373. }
  374. mouseButtonStickyPop();
  375. // Set window title
  376. std::string windowTitle = gApplicationName;
  377. if (!gApplicationVersion.empty()) {
  378. windowTitle += " v" + gApplicationVersion;
  379. }
  380. if (!gRackWidget->lastPath.empty()) {
  381. windowTitle += " - ";
  382. windowTitle += extractFilename(gRackWidget->lastPath);
  383. }
  384. if (windowTitle != lastWindowTitle) {
  385. glfwSetWindowTitle(gWindow, windowTitle.c_str());
  386. lastWindowTitle = windowTitle;
  387. }
  388. // Get desired scaling
  389. float pixelRatio;
  390. glfwGetWindowContentScale(gWindow, &pixelRatio, NULL);
  391. pixelRatio = roundf(pixelRatio);
  392. if (pixelRatio != gPixelRatio) {
  393. EventZoom eZoom;
  394. gScene->onZoom(eZoom);
  395. gPixelRatio = pixelRatio;
  396. }
  397. // Get framebuffer/window ratio
  398. int width, height;
  399. glfwGetFramebufferSize(gWindow, &width, &height);
  400. int windowWidth, windowHeight;
  401. glfwGetWindowSize(gWindow, &windowWidth, &windowHeight);
  402. gWindowRatio = (float)width / windowWidth;
  403. gScene->box.size = Vec(width, height).div(gPixelRatio / gWindowRatio);
  404. // Step scene
  405. gScene->step();
  406. // Render
  407. bool visible = glfwGetWindowAttrib(gWindow, GLFW_VISIBLE) && !glfwGetWindowAttrib(gWindow, GLFW_ICONIFIED);
  408. if (visible) {
  409. renderGui();
  410. }
  411. // Limit framerate manually if vsync isn't working
  412. double endTime = glfwGetTime();
  413. double frameTime = endTime - startTime;
  414. double minTime = 1.0 / 90.0;
  415. if (frameTime < minTime) {
  416. std::this_thread::sleep_for(std::chrono::duration<double>(minTime - frameTime));
  417. }
  418. endTime = glfwGetTime();
  419. // info("%lf fps", 1.0 / (endTime - startTime));
  420. }
  421. }
  422. void guiClose() {
  423. glfwSetWindowShouldClose(gWindow, GLFW_TRUE);
  424. }
  425. void guiCursorLock() {
  426. if (gAllowCursorLock) {
  427. #ifdef ARCH_MAC
  428. glfwSetInputMode(gWindow, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
  429. #else
  430. glfwSetInputMode(gWindow, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
  431. #endif
  432. }
  433. }
  434. void guiCursorUnlock() {
  435. if (gAllowCursorLock) {
  436. glfwSetInputMode(gWindow, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
  437. }
  438. }
  439. bool guiIsModPressed() {
  440. #ifdef ARCH_MAC
  441. return glfwGetKey(gWindow, GLFW_KEY_LEFT_SUPER) == GLFW_PRESS || glfwGetKey(gWindow, GLFW_KEY_RIGHT_SUPER) == GLFW_PRESS;
  442. #else
  443. return glfwGetKey(gWindow, GLFW_KEY_LEFT_CONTROL) == GLFW_PRESS || glfwGetKey(gWindow, GLFW_KEY_RIGHT_CONTROL) == GLFW_PRESS;
  444. #endif
  445. }
  446. bool guiIsShiftPressed() {
  447. return glfwGetKey(gWindow, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS || glfwGetKey(gWindow, GLFW_KEY_RIGHT_SHIFT) == GLFW_PRESS;
  448. }
  449. Vec guiGetWindowSize() {
  450. int width, height;
  451. glfwGetWindowSize(gWindow, &width, &height);
  452. return Vec(width, height);
  453. }
  454. void guiSetWindowSize(Vec size) {
  455. int width = size.x;
  456. int height = size.y;
  457. glfwSetWindowSize(gWindow, width, height);
  458. }
  459. Vec guiGetWindowPos() {
  460. int x, y;
  461. glfwGetWindowPos(gWindow, &x, &y);
  462. return Vec(x, y);
  463. }
  464. void guiSetWindowPos(Vec pos) {
  465. int x = pos.x;
  466. int y = pos.y;
  467. glfwSetWindowPos(gWindow, x, y);
  468. }
  469. bool guiIsMaximized() {
  470. return glfwGetWindowAttrib(gWindow, GLFW_MAXIMIZED);
  471. }
  472. ////////////////////
  473. // resources
  474. ////////////////////
  475. Font::Font(const std::string &filename) {
  476. handle = nvgCreateFont(gVg, filename.c_str(), filename.c_str());
  477. if (handle >= 0) {
  478. info("Loaded font %s", filename.c_str());
  479. }
  480. else {
  481. warn("Failed to load font %s", filename.c_str());
  482. }
  483. }
  484. Font::~Font() {
  485. // There is no NanoVG deleteFont() function yet, so do nothing
  486. }
  487. std::shared_ptr<Font> Font::load(const std::string &filename) {
  488. static std::map<std::string, std::weak_ptr<Font>> cache;
  489. auto sp = cache[filename].lock();
  490. if (!sp)
  491. cache[filename] = sp = std::make_shared<Font>(filename);
  492. return sp;
  493. }
  494. ////////////////////
  495. // Image
  496. ////////////////////
  497. Image::Image(const std::string &filename) {
  498. handle = nvgCreateImage(gVg, filename.c_str(), NVG_IMAGE_REPEATX | NVG_IMAGE_REPEATY);
  499. if (handle > 0) {
  500. info("Loaded image %s", filename.c_str());
  501. }
  502. else {
  503. warn("Failed to load image %s", filename.c_str());
  504. }
  505. }
  506. Image::~Image() {
  507. // TODO What if handle is invalid?
  508. nvgDeleteImage(gVg, handle);
  509. }
  510. std::shared_ptr<Image> Image::load(const std::string &filename) {
  511. static std::map<std::string, std::weak_ptr<Image>> cache;
  512. auto sp = cache[filename].lock();
  513. if (!sp)
  514. cache[filename] = sp = std::make_shared<Image>(filename);
  515. return sp;
  516. }
  517. ////////////////////
  518. // SVG
  519. ////////////////////
  520. SVG::SVG(const std::string &filename) {
  521. handle = nsvgParseFromFile(filename.c_str(), "px", SVG_DPI);
  522. if (handle) {
  523. info("Loaded SVG %s", filename.c_str());
  524. }
  525. else {
  526. warn("Failed to load SVG %s", filename.c_str());
  527. }
  528. }
  529. SVG::~SVG() {
  530. nsvgDelete(handle);
  531. }
  532. std::shared_ptr<SVG> SVG::load(const std::string &filename) {
  533. static std::map<std::string, std::weak_ptr<SVG>> cache;
  534. auto sp = cache[filename].lock();
  535. if (!sp)
  536. cache[filename] = sp = std::make_shared<SVG>(filename);
  537. return sp;
  538. }
  539. } // namespace rack