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.

621 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 = 0.0;
  31. bool gAllowCursorLock = true;
  32. int gGuiFrame;
  33. Vec gMousePos;
  34. std::string lastWindowTitle;
  35. void windowSizeCallback(GLFWwindow* window, int width, int height) {
  36. gScene->box.size = Vec(width, 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).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. {
  364. int width, height;
  365. glfwGetWindowSize(gWindow, &width, &height);
  366. windowSizeCallback(gWindow, width, height);
  367. }
  368. gGuiFrame = 0;
  369. while(!glfwWindowShouldClose(gWindow)) {
  370. double startTime = glfwGetTime();
  371. gGuiFrame++;
  372. // Poll events
  373. glfwPollEvents();
  374. {
  375. double xpos, ypos;
  376. glfwGetCursorPos(gWindow, &xpos, &ypos);
  377. cursorPosCallback(gWindow, xpos, ypos);
  378. }
  379. mouseButtonStickyPop();
  380. // Set window title
  381. std::string windowTitle = gApplicationName;
  382. if (!gApplicationVersion.empty()) {
  383. windowTitle += " v" + gApplicationVersion;
  384. }
  385. if (!gRackWidget->lastPath.empty()) {
  386. windowTitle += " - ";
  387. windowTitle += extractFilename(gRackWidget->lastPath);
  388. }
  389. if (windowTitle != lastWindowTitle) {
  390. glfwSetWindowTitle(gWindow, windowTitle.c_str());
  391. lastWindowTitle = windowTitle;
  392. }
  393. // Get framebuffer size
  394. float pixelRatio;
  395. glfwGetWindowContentScale(gWindow, &pixelRatio, NULL);
  396. pixelRatio = roundf(pixelRatio);
  397. if (pixelRatio != gPixelRatio) {
  398. EventZoom eZoom;
  399. gScene->onZoom(eZoom);
  400. gPixelRatio = pixelRatio;
  401. }
  402. // Step scene
  403. gScene->step();
  404. // Render
  405. bool visible = glfwGetWindowAttrib(gWindow, GLFW_VISIBLE) && !glfwGetWindowAttrib(gWindow, GLFW_ICONIFIED);
  406. if (visible) {
  407. renderGui();
  408. }
  409. // Limit framerate manually if vsync isn't working
  410. double endTime = glfwGetTime();
  411. double frameTime = endTime - startTime;
  412. double minTime = 1.0 / 90.0;
  413. if (frameTime < minTime) {
  414. std::this_thread::sleep_for(std::chrono::duration<double>(minTime - frameTime));
  415. }
  416. endTime = glfwGetTime();
  417. // info("%lf fps", 1.0 / (endTime - startTime));
  418. }
  419. }
  420. void guiClose() {
  421. glfwSetWindowShouldClose(gWindow, GLFW_TRUE);
  422. }
  423. void guiCursorLock() {
  424. if (gAllowCursorLock) {
  425. #ifdef ARCH_MAC
  426. glfwSetInputMode(gWindow, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
  427. #else
  428. glfwSetInputMode(gWindow, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
  429. #endif
  430. }
  431. }
  432. void guiCursorUnlock() {
  433. if (gAllowCursorLock) {
  434. glfwSetInputMode(gWindow, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
  435. }
  436. }
  437. bool guiIsModPressed() {
  438. #ifdef ARCH_MAC
  439. return glfwGetKey(gWindow, GLFW_KEY_LEFT_SUPER) == GLFW_PRESS || glfwGetKey(gWindow, GLFW_KEY_RIGHT_SUPER) == GLFW_PRESS;
  440. #else
  441. return glfwGetKey(gWindow, GLFW_KEY_LEFT_CONTROL) == GLFW_PRESS || glfwGetKey(gWindow, GLFW_KEY_RIGHT_CONTROL) == GLFW_PRESS;
  442. #endif
  443. }
  444. bool guiIsShiftPressed() {
  445. return glfwGetKey(gWindow, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS || glfwGetKey(gWindow, GLFW_KEY_RIGHT_SHIFT) == GLFW_PRESS;
  446. }
  447. Vec guiGetWindowSize() {
  448. int width, height;
  449. glfwGetWindowSize(gWindow, &width, &height);
  450. return Vec(width, height);
  451. }
  452. void guiSetWindowSize(Vec size) {
  453. int width = size.x;
  454. int height = size.y;
  455. glfwSetWindowSize(gWindow, width, height);
  456. }
  457. Vec guiGetWindowPos() {
  458. int x, y;
  459. glfwGetWindowPos(gWindow, &x, &y);
  460. return Vec(x, y);
  461. }
  462. void guiSetWindowPos(Vec pos) {
  463. int x = pos.x;
  464. int y = pos.y;
  465. glfwSetWindowPos(gWindow, x, y);
  466. }
  467. bool guiIsMaximized() {
  468. return glfwGetWindowAttrib(gWindow, GLFW_MAXIMIZED);
  469. }
  470. ////////////////////
  471. // resources
  472. ////////////////////
  473. Font::Font(const std::string &filename) {
  474. handle = nvgCreateFont(gVg, filename.c_str(), filename.c_str());
  475. if (handle >= 0) {
  476. info("Loaded font %s", filename.c_str());
  477. }
  478. else {
  479. warn("Failed to load font %s", filename.c_str());
  480. }
  481. }
  482. Font::~Font() {
  483. // There is no NanoVG deleteFont() function yet, so do nothing
  484. }
  485. std::shared_ptr<Font> Font::load(const std::string &filename) {
  486. static std::map<std::string, std::weak_ptr<Font>> cache;
  487. auto sp = cache[filename].lock();
  488. if (!sp)
  489. cache[filename] = sp = std::make_shared<Font>(filename);
  490. return sp;
  491. }
  492. ////////////////////
  493. // Image
  494. ////////////////////
  495. Image::Image(const std::string &filename) {
  496. handle = nvgCreateImage(gVg, filename.c_str(), NVG_IMAGE_REPEATX | NVG_IMAGE_REPEATY);
  497. if (handle > 0) {
  498. info("Loaded image %s", filename.c_str());
  499. }
  500. else {
  501. warn("Failed to load image %s", filename.c_str());
  502. }
  503. }
  504. Image::~Image() {
  505. // TODO What if handle is invalid?
  506. nvgDeleteImage(gVg, handle);
  507. }
  508. std::shared_ptr<Image> Image::load(const std::string &filename) {
  509. static std::map<std::string, std::weak_ptr<Image>> cache;
  510. auto sp = cache[filename].lock();
  511. if (!sp)
  512. cache[filename] = sp = std::make_shared<Image>(filename);
  513. return sp;
  514. }
  515. ////////////////////
  516. // SVG
  517. ////////////////////
  518. SVG::SVG(const std::string &filename) {
  519. handle = nsvgParseFromFile(filename.c_str(), "px", SVG_DPI);
  520. if (handle) {
  521. info("Loaded SVG %s", filename.c_str());
  522. }
  523. else {
  524. warn("Failed to load SVG %s", filename.c_str());
  525. }
  526. }
  527. SVG::~SVG() {
  528. nsvgDelete(handle);
  529. }
  530. std::shared_ptr<SVG> SVG::load(const std::string &filename) {
  531. static std::map<std::string, std::weak_ptr<SVG>> cache;
  532. auto sp = cache[filename].lock();
  533. if (!sp)
  534. cache[filename] = sp = std::make_shared<SVG>(filename);
  535. return sp;
  536. }
  537. } // namespace rack