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.

590 lines
14KB

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