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.

469 lines
12KB

  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. std::shared_ptr<Font> gGuiFont;
  27. float gPixelRatio = 0.0;
  28. bool gAllowCursorLock = true;
  29. void windowSizeCallback(GLFWwindow* window, int width, int height) {
  30. gScene->box.size = Vec(width, height);
  31. }
  32. void mouseButtonCallback(GLFWwindow *window, int button, int action, int mods) {
  33. #ifdef ARCH_MAC
  34. // Ctrl-left click --> right click
  35. if (button == GLFW_MOUSE_BUTTON_LEFT) {
  36. if (glfwGetKey(gWindow, GLFW_KEY_LEFT_CONTROL) == GLFW_PRESS || glfwGetKey(gWindow, GLFW_KEY_RIGHT_CONTROL) == GLFW_PRESS) {
  37. button = GLFW_MOUSE_BUTTON_RIGHT;
  38. }
  39. }
  40. #endif
  41. if (action == GLFW_PRESS) {
  42. // onMouseDown
  43. Widget *w = gScene->onMouseDown(gMousePos, button);
  44. if (button == GLFW_MOUSE_BUTTON_LEFT) {
  45. if (w) {
  46. // onDragStart
  47. w->onDragStart();
  48. }
  49. gDraggedWidget = w;
  50. if (w != gFocusedWidget) {
  51. if (gFocusedWidget) {
  52. // onDefocus
  53. w->onDefocus();
  54. }
  55. gFocusedWidget = NULL;
  56. if (w) {
  57. // onFocus
  58. if (w->onFocus()) {
  59. gFocusedWidget = w;
  60. }
  61. }
  62. }
  63. }
  64. }
  65. else if (action == GLFW_RELEASE) {
  66. // onMouseUp
  67. Widget *w = gScene->onMouseUp(gMousePos, button);
  68. if (button == GLFW_MOUSE_BUTTON_LEFT) {
  69. if (gDraggedWidget) {
  70. // onDragDrop
  71. w->onDragDrop(gDraggedWidget);
  72. }
  73. // gDraggedWidget might have been set to null in the last event, recheck here
  74. if (gDraggedWidget) {
  75. // onDragEnd
  76. gDraggedWidget->onDragEnd();
  77. }
  78. gDraggedWidget = NULL;
  79. gDragHoveredWidget = NULL;
  80. }
  81. }
  82. }
  83. struct MouseButtonArguments {
  84. GLFWwindow *window;
  85. int button;
  86. int action;
  87. int mods;
  88. };
  89. static std::queue<MouseButtonArguments> mouseButtonQueue;
  90. void mouseButtonStickyPop() {
  91. if (!mouseButtonQueue.empty()) {
  92. MouseButtonArguments args = mouseButtonQueue.front();
  93. mouseButtonQueue.pop();
  94. mouseButtonCallback(args.window, args.button, args.action, args.mods);
  95. }
  96. }
  97. void mouseButtonStickyCallback(GLFWwindow *window, int button, int action, int mods) {
  98. // Defer multiple clicks per frame to future frames
  99. MouseButtonArguments args = {window, button, action, mods};
  100. mouseButtonQueue.push(args);
  101. }
  102. void cursorPosCallback(GLFWwindow* window, double xpos, double ypos) {
  103. Vec mousePos = Vec(xpos, ypos).round();
  104. Vec mouseRel = mousePos.minus(gMousePos);
  105. #ifdef ARCH_MAC
  106. // Workaround for Mac. We can't use GLFW_CURSOR_DISABLED because it's buggy, so implement it on our own.
  107. // This is not an ideal implementation. For example, if the user drags off the screen, the new mouse position will be clamped.
  108. int mouseMode = glfwGetInputMode(gWindow, GLFW_CURSOR);
  109. if (mouseMode == GLFW_CURSOR_HIDDEN) {
  110. // CGSetLocalEventsSuppressionInterval(0.0);
  111. glfwSetCursorPos(gWindow, gMousePos.x, gMousePos.y);
  112. CGAssociateMouseAndMouseCursorPosition(true);
  113. mousePos = gMousePos;
  114. }
  115. // Because sometimes the cursor turns into an arrow when its position is on the boundary of the window
  116. glfwSetCursor(gWindow, NULL);
  117. #endif
  118. gMousePos = mousePos;
  119. // onMouseMove
  120. Widget *hovered = gScene->onMouseMove(gMousePos, mouseRel);
  121. if (gDraggedWidget) {
  122. // onDragMove
  123. gDraggedWidget->onDragMove(mouseRel);
  124. if (hovered != gDragHoveredWidget) {
  125. if (gDragHoveredWidget) {
  126. gDragHoveredWidget->onDragLeave(gDraggedWidget);
  127. }
  128. if (hovered) {
  129. hovered->onDragEnter(gDraggedWidget);
  130. }
  131. gDragHoveredWidget = hovered;
  132. }
  133. }
  134. else {
  135. if (hovered != gHoveredWidget) {
  136. if (gHoveredWidget) {
  137. // onMouseLeave
  138. gHoveredWidget->onMouseLeave();
  139. }
  140. if (hovered) {
  141. // onMouseEnter
  142. hovered->onMouseEnter();
  143. }
  144. gHoveredWidget = hovered;
  145. }
  146. }
  147. if (glfwGetMouseButton(gWindow, GLFW_MOUSE_BUTTON_MIDDLE) == GLFW_PRESS) {
  148. // TODO
  149. // Define a new global called gScrollWidget, which remembers the widget where middle-click was first pressed
  150. gScene->onScroll(mousePos, mouseRel);
  151. }
  152. }
  153. void cursorEnterCallback(GLFWwindow* window, int entered) {
  154. if (!entered) {
  155. if (gHoveredWidget) {
  156. gHoveredWidget->onMouseLeave();
  157. }
  158. gHoveredWidget = NULL;
  159. }
  160. }
  161. void scrollCallback(GLFWwindow *window, double x, double y) {
  162. Vec scrollRel = Vec(x, y);
  163. #if ARCH_LIN || ARCH_WIN
  164. if (guiIsShiftPressed())
  165. scrollRel = Vec(y, x);
  166. #endif
  167. // onScroll
  168. gScene->onScroll(gMousePos, scrollRel.mult(50.0));
  169. }
  170. void charCallback(GLFWwindow *window, unsigned int codepoint) {
  171. if (gFocusedWidget) {
  172. gFocusedWidget->onFocusText(codepoint);
  173. }
  174. }
  175. void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods) {
  176. if (action == GLFW_PRESS || action == GLFW_REPEAT) {
  177. // onFocusKey
  178. if (gFocusedWidget && gFocusedWidget->onFocusKey(key))
  179. return;
  180. // onHoverKey
  181. gScene->onHoverKey(gMousePos, key);
  182. }
  183. }
  184. void errorCallback(int error, const char *description) {
  185. fprintf(stderr, "GLFW error %d: %s\n", error, description);
  186. }
  187. void renderGui() {
  188. int width, height;
  189. glfwGetFramebufferSize(gWindow, &width, &height);
  190. int windowWidth, windowHeight;
  191. glfwGetWindowSize(gWindow, &windowWidth, &windowHeight);
  192. gPixelRatio = (float)width / windowWidth;
  193. // Update and render
  194. glViewport(0, 0, width, height);
  195. glClearColor(0.0, 0.0, 0.0, 1.0);
  196. glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
  197. nvgBeginFrame(gVg, width, height, gPixelRatio);
  198. nvgReset(gVg);
  199. nvgScale(gVg, gPixelRatio, gPixelRatio);
  200. gScene->draw(gVg);
  201. nvgEndFrame(gVg);
  202. glfwSwapBuffers(gWindow);
  203. }
  204. void guiInit() {
  205. int err;
  206. // Set up GLFW
  207. glfwSetErrorCallback(errorCallback);
  208. err = glfwInit();
  209. if (err != GLFW_TRUE) {
  210. osdialog_message(OSDIALOG_ERROR, OSDIALOG_OK, "Could not initialize GLFW.");
  211. exit(1);
  212. }
  213. glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
  214. glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
  215. // glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
  216. // glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
  217. // glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
  218. // glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
  219. glfwWindowHint(GLFW_MAXIMIZED, GLFW_TRUE);
  220. glfwWindowHint(GLFW_DOUBLEBUFFER, GLFW_TRUE);
  221. gWindow = glfwCreateWindow(640, 480, "", NULL, NULL);
  222. if (!gWindow) {
  223. 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, are the latest drivers installed?");
  224. exit(1);
  225. }
  226. glfwMakeContextCurrent(gWindow);
  227. glfwSwapInterval(1);
  228. glfwSetWindowSizeCallback(gWindow, windowSizeCallback);
  229. glfwSetMouseButtonCallback(gWindow, mouseButtonStickyCallback);
  230. // glfwSetCursorPosCallback(gWindow, cursorPosCallback);
  231. glfwSetCursorEnterCallback(gWindow, cursorEnterCallback);
  232. glfwSetScrollCallback(gWindow, scrollCallback);
  233. glfwSetCharCallback(gWindow, charCallback);
  234. glfwSetKeyCallback(gWindow, keyCallback);
  235. // Set up GLEW
  236. glewExperimental = GL_TRUE;
  237. err = glewInit();
  238. if (err != GLEW_OK) {
  239. osdialog_message(OSDIALOG_ERROR, OSDIALOG_OK, "Could not initialize GLEW. Does your graphics card support OpenGL 2.0 or greater? If so, are the latest drivers installed?");
  240. exit(1);
  241. }
  242. // GLEW generates GL error because it calls glGetString(GL_EXTENSIONS), we'll consume it here.
  243. glGetError();
  244. glfwSetWindowSizeLimits(gWindow, 640, 480, GLFW_DONT_CARE, GLFW_DONT_CARE);
  245. // Set up NanoVG
  246. gVg = nvgCreateGL2(NVG_ANTIALIAS);
  247. // gVg = nvgCreateGL3(NVG_ANTIALIAS);
  248. assert(gVg);
  249. // Set up Blendish
  250. gGuiFont = Font::load(assetGlobal("res/DejaVuSans.ttf"));
  251. bndSetFont(gGuiFont->handle);
  252. // bndSetIconImage(loadImage(assetGlobal("res/icons.png")));
  253. }
  254. void guiDestroy() {
  255. gGuiFont.reset();
  256. nvgDeleteGL2(gVg);
  257. // nvgDeleteGL3(gVg);
  258. glfwDestroyWindow(gWindow);
  259. glfwTerminate();
  260. }
  261. void guiRun() {
  262. assert(gWindow);
  263. {
  264. int width, height;
  265. glfwGetWindowSize(gWindow, &width, &height);
  266. windowSizeCallback(gWindow, width, height);
  267. }
  268. gGuiFrame = 0;
  269. while(!glfwWindowShouldClose(gWindow)) {
  270. double startTime = glfwGetTime();
  271. gGuiFrame++;
  272. // Poll events
  273. glfwPollEvents();
  274. {
  275. double xpos, ypos;
  276. glfwGetCursorPos(gWindow, &xpos, &ypos);
  277. cursorPosCallback(gWindow, xpos, ypos);
  278. }
  279. mouseButtonStickyPop();
  280. // Set window title
  281. std::string title = gApplicationName + " " + gApplicationVersion;
  282. if (!gRackWidget->lastPath.empty()) {
  283. title += " - ";
  284. title += extractFilename(gRackWidget->lastPath);
  285. }
  286. glfwSetWindowTitle(gWindow, title.c_str());
  287. // Step scene
  288. gScene->step();
  289. // Render
  290. bool visible = glfwGetWindowAttrib(gWindow, GLFW_VISIBLE) && !glfwGetWindowAttrib(gWindow, GLFW_ICONIFIED);
  291. if (visible) {
  292. renderGui();
  293. }
  294. // Limit framerate manually if vsync isn't working
  295. double endTime = glfwGetTime();
  296. double frameTime = endTime - startTime;
  297. double minTime = 1.0 / 90.0;
  298. if (frameTime < minTime) {
  299. std::this_thread::sleep_for(std::chrono::duration<double>(minTime - frameTime));
  300. }
  301. endTime = glfwGetTime();
  302. // printf("%lf fps\n", 1.0 / (endTime - startTime));
  303. }
  304. }
  305. void guiClose() {
  306. glfwSetWindowShouldClose(gWindow, GLFW_TRUE);
  307. }
  308. void guiCursorLock() {
  309. if (gAllowCursorLock) {
  310. #ifdef ARCH_MAC
  311. glfwSetInputMode(gWindow, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
  312. #else
  313. glfwSetInputMode(gWindow, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
  314. #endif
  315. }
  316. }
  317. void guiCursorUnlock() {
  318. if (gAllowCursorLock) {
  319. glfwSetInputMode(gWindow, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
  320. }
  321. }
  322. bool guiIsModPressed() {
  323. #ifdef ARCH_MAC
  324. return glfwGetKey(gWindow, GLFW_KEY_LEFT_SUPER) == GLFW_PRESS || glfwGetKey(gWindow, GLFW_KEY_RIGHT_SUPER) == GLFW_PRESS;
  325. #else
  326. return glfwGetKey(gWindow, GLFW_KEY_LEFT_CONTROL) == GLFW_PRESS || glfwGetKey(gWindow, GLFW_KEY_RIGHT_CONTROL) == GLFW_PRESS;
  327. #endif
  328. }
  329. bool guiIsShiftPressed() {
  330. return glfwGetKey(gWindow, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS || glfwGetKey(gWindow, GLFW_KEY_RIGHT_SHIFT) == GLFW_PRESS;
  331. }
  332. ////////////////////
  333. // resources
  334. ////////////////////
  335. Font::Font(const std::string &filename) {
  336. handle = nvgCreateFont(gVg, filename.c_str(), filename.c_str());
  337. if (handle >= 0) {
  338. fprintf(stderr, "Loaded font %s\n", filename.c_str());
  339. }
  340. else {
  341. fprintf(stderr, "Failed to load font %s\n", filename.c_str());
  342. }
  343. }
  344. Font::~Font() {
  345. // There is no NanoVG deleteFont() function yet, so do nothing
  346. }
  347. std::shared_ptr<Font> Font::load(const std::string &filename) {
  348. static std::map<std::string, std::weak_ptr<Font>> cache;
  349. auto sp = cache[filename].lock();
  350. if (!sp)
  351. cache[filename] = sp = std::make_shared<Font>(filename);
  352. return sp;
  353. }
  354. ////////////////////
  355. // Image
  356. ////////////////////
  357. Image::Image(const std::string &filename) {
  358. handle = nvgCreateImage(gVg, filename.c_str(), NVG_IMAGE_REPEATX | NVG_IMAGE_REPEATY);
  359. if (handle > 0) {
  360. fprintf(stderr, "Loaded image %s\n", filename.c_str());
  361. }
  362. else {
  363. fprintf(stderr, "Failed to load image %s\n", filename.c_str());
  364. }
  365. }
  366. Image::~Image() {
  367. // TODO What if handle is invalid?
  368. nvgDeleteImage(gVg, handle);
  369. }
  370. std::shared_ptr<Image> Image::load(const std::string &filename) {
  371. static std::map<std::string, std::weak_ptr<Image>> cache;
  372. auto sp = cache[filename].lock();
  373. if (!sp)
  374. cache[filename] = sp = std::make_shared<Image>(filename);
  375. return sp;
  376. }
  377. ////////////////////
  378. // SVG
  379. ////////////////////
  380. SVG::SVG(const std::string &filename) {
  381. handle = nsvgParseFromFile(filename.c_str(), "px", 96.0);
  382. if (handle) {
  383. fprintf(stderr, "Loaded SVG %s\n", filename.c_str());
  384. }
  385. else {
  386. fprintf(stderr, "Failed to load SVG %s\n", filename.c_str());
  387. }
  388. }
  389. SVG::~SVG() {
  390. nsvgDelete(handle);
  391. }
  392. std::shared_ptr<SVG> SVG::load(const std::string &filename) {
  393. static std::map<std::string, std::weak_ptr<SVG>> cache;
  394. auto sp = cache[filename].lock();
  395. if (!sp)
  396. cache[filename] = sp = std::make_shared<SVG>(filename);
  397. return sp;
  398. }
  399. } // namespace rack