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.

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