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.

596 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. warn("GLFW error %d: %s", 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. theme.nodeTheme.nodeBackdropColor.a = 1.0;
  327. bndSetTheme(theme);
  328. }
  329. void guiDestroy() {
  330. gGuiFont.reset();
  331. nvgDeleteGL2(gVg);
  332. // nvgDeleteGL3(gVg);
  333. nvgDeleteGL2(gFramebufferVg);
  334. glfwDestroyWindow(gWindow);
  335. glfwTerminate();
  336. }
  337. void guiRun() {
  338. assert(gWindow);
  339. {
  340. int width, height;
  341. glfwGetWindowSize(gWindow, &width, &height);
  342. windowSizeCallback(gWindow, width, height);
  343. }
  344. gGuiFrame = 0;
  345. while(!glfwWindowShouldClose(gWindow)) {
  346. double startTime = glfwGetTime();
  347. gGuiFrame++;
  348. // Poll events
  349. glfwPollEvents();
  350. {
  351. double xpos, ypos;
  352. glfwGetCursorPos(gWindow, &xpos, &ypos);
  353. cursorPosCallback(gWindow, xpos, ypos);
  354. }
  355. mouseButtonStickyPop();
  356. // Set window title
  357. std::string windowTitle = gApplicationName;
  358. if (!gApplicationVersion.empty()) {
  359. windowTitle += " v" + gApplicationVersion;
  360. }
  361. if (!gRackWidget->lastPath.empty()) {
  362. windowTitle += " - ";
  363. windowTitle += extractFilename(gRackWidget->lastPath);
  364. }
  365. if (windowTitle != lastWindowTitle) {
  366. glfwSetWindowTitle(gWindow, windowTitle.c_str());
  367. lastWindowTitle = windowTitle;
  368. }
  369. // Get framebuffer size
  370. int width, height;
  371. glfwGetFramebufferSize(gWindow, &width, &height);
  372. int windowWidth, windowHeight;
  373. glfwGetWindowSize(gWindow, &windowWidth, &windowHeight);
  374. float pixelRatio = (float)width / windowWidth;
  375. if (pixelRatio != gPixelRatio) {
  376. EventZoom eZoom;
  377. gScene->onZoom(eZoom);
  378. gPixelRatio = pixelRatio;
  379. }
  380. // Step scene
  381. gScene->step();
  382. // Render
  383. bool visible = glfwGetWindowAttrib(gWindow, GLFW_VISIBLE) && !glfwGetWindowAttrib(gWindow, GLFW_ICONIFIED);
  384. if (visible) {
  385. renderGui();
  386. }
  387. // Limit framerate manually if vsync isn't working
  388. double endTime = glfwGetTime();
  389. double frameTime = endTime - startTime;
  390. double minTime = 1.0 / 90.0;
  391. if (frameTime < minTime) {
  392. std::this_thread::sleep_for(std::chrono::duration<double>(minTime - frameTime));
  393. }
  394. endTime = glfwGetTime();
  395. // info("%lf fps", 1.0 / (endTime - startTime));
  396. }
  397. }
  398. void guiClose() {
  399. glfwSetWindowShouldClose(gWindow, GLFW_TRUE);
  400. }
  401. void guiCursorLock() {
  402. if (gAllowCursorLock) {
  403. #ifdef ARCH_MAC
  404. glfwSetInputMode(gWindow, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
  405. #else
  406. glfwSetInputMode(gWindow, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
  407. #endif
  408. }
  409. }
  410. void guiCursorUnlock() {
  411. if (gAllowCursorLock) {
  412. glfwSetInputMode(gWindow, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
  413. }
  414. }
  415. bool guiIsModPressed() {
  416. #ifdef ARCH_MAC
  417. return glfwGetKey(gWindow, GLFW_KEY_LEFT_SUPER) == GLFW_PRESS || glfwGetKey(gWindow, GLFW_KEY_RIGHT_SUPER) == GLFW_PRESS;
  418. #else
  419. return glfwGetKey(gWindow, GLFW_KEY_LEFT_CONTROL) == GLFW_PRESS || glfwGetKey(gWindow, GLFW_KEY_RIGHT_CONTROL) == GLFW_PRESS;
  420. #endif
  421. }
  422. bool guiIsShiftPressed() {
  423. return glfwGetKey(gWindow, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS || glfwGetKey(gWindow, GLFW_KEY_RIGHT_SHIFT) == GLFW_PRESS;
  424. }
  425. Vec guiGetWindowSize() {
  426. int width, height;
  427. glfwGetWindowSize(gWindow, &width, &height);
  428. return Vec(width, height);
  429. }
  430. void guiSetWindowSize(Vec size) {
  431. int width = size.x;
  432. int height = size.y;
  433. glfwSetWindowSize(gWindow, width, height);
  434. }
  435. Vec guiGetWindowPos() {
  436. int x, y;
  437. glfwGetWindowPos(gWindow, &x, &y);
  438. return Vec(x, y);
  439. }
  440. void guiSetWindowPos(Vec pos) {
  441. int x = pos.x;
  442. int y = pos.y;
  443. glfwSetWindowPos(gWindow, x, y);
  444. }
  445. bool guiIsMaximized() {
  446. return glfwGetWindowAttrib(gWindow, GLFW_MAXIMIZED);
  447. }
  448. ////////////////////
  449. // resources
  450. ////////////////////
  451. Font::Font(const std::string &filename) {
  452. handle = nvgCreateFont(gVg, filename.c_str(), filename.c_str());
  453. if (handle >= 0) {
  454. info("Loaded font %s", filename.c_str());
  455. }
  456. else {
  457. warn("Failed to load font %s", filename.c_str());
  458. }
  459. }
  460. Font::~Font() {
  461. // There is no NanoVG deleteFont() function yet, so do nothing
  462. }
  463. std::shared_ptr<Font> Font::load(const std::string &filename) {
  464. static std::map<std::string, std::weak_ptr<Font>> cache;
  465. auto sp = cache[filename].lock();
  466. if (!sp)
  467. cache[filename] = sp = std::make_shared<Font>(filename);
  468. return sp;
  469. }
  470. ////////////////////
  471. // Image
  472. ////////////////////
  473. Image::Image(const std::string &filename) {
  474. handle = nvgCreateImage(gVg, filename.c_str(), NVG_IMAGE_REPEATX | NVG_IMAGE_REPEATY);
  475. if (handle > 0) {
  476. info("Loaded image %s", filename.c_str());
  477. }
  478. else {
  479. warn("Failed to load image %s", filename.c_str());
  480. }
  481. }
  482. Image::~Image() {
  483. // TODO What if handle is invalid?
  484. nvgDeleteImage(gVg, handle);
  485. }
  486. std::shared_ptr<Image> Image::load(const std::string &filename) {
  487. static std::map<std::string, std::weak_ptr<Image>> cache;
  488. auto sp = cache[filename].lock();
  489. if (!sp)
  490. cache[filename] = sp = std::make_shared<Image>(filename);
  491. return sp;
  492. }
  493. ////////////////////
  494. // SVG
  495. ////////////////////
  496. SVG::SVG(const std::string &filename) {
  497. handle = nsvgParseFromFile(filename.c_str(), "px", SVG_DPI);
  498. if (handle) {
  499. info("Loaded SVG %s", filename.c_str());
  500. }
  501. else {
  502. warn("Failed to load SVG %s", filename.c_str());
  503. }
  504. }
  505. SVG::~SVG() {
  506. nsvgDelete(handle);
  507. }
  508. std::shared_ptr<SVG> SVG::load(const std::string &filename) {
  509. static std::map<std::string, std::weak_ptr<SVG>> cache;
  510. auto sp = cache[filename].lock();
  511. if (!sp)
  512. cache[filename] = sp = std::make_shared<SVG>(filename);
  513. return sp;
  514. }
  515. } // namespace rack