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.

512 lines
14KB

  1. #include "window.hpp"
  2. #include "asset.hpp"
  3. #include "app/Scene.hpp"
  4. #include "keyboard.hpp"
  5. #include "gamepad.hpp"
  6. #include "event.hpp"
  7. #include "app.hpp"
  8. #include "patch.hpp"
  9. #include "settings.hpp"
  10. #include <map>
  11. #include <queue>
  12. #include <thread>
  13. #if defined ARCH_MAC
  14. // For CGAssociateMouseAndMouseCursorPosition
  15. #include <ApplicationServices/ApplicationServices.h>
  16. #endif
  17. #include <osdialog.h>
  18. namespace rack {
  19. void Font::loadFile(const std::string &filename, NVGcontext *vg) {
  20. handle = nvgCreateFont(vg, filename.c_str(), filename.c_str());
  21. if (handle >= 0) {
  22. INFO("Loaded font %s", filename.c_str());
  23. }
  24. else {
  25. WARN("Failed to load font %s", filename.c_str());
  26. }
  27. }
  28. Font::~Font() {
  29. // There is no NanoVG deleteFont() function yet, so do nothing
  30. }
  31. std::shared_ptr<Font> Font::load(const std::string &filename) {
  32. return APP->window->loadFont(filename);
  33. }
  34. void Image::loadFile(const std::string &filename, NVGcontext *vg) {
  35. handle = nvgCreateImage(vg, filename.c_str(), NVG_IMAGE_REPEATX | NVG_IMAGE_REPEATY);
  36. if (handle > 0) {
  37. INFO("Loaded image %s", filename.c_str());
  38. }
  39. else {
  40. WARN("Failed to load image %s", filename.c_str());
  41. }
  42. }
  43. Image::~Image() {
  44. // TODO What if handle is invalid?
  45. if (handle >= 0)
  46. nvgDeleteImage(vg, handle);
  47. }
  48. std::shared_ptr<Image> Image::load(const std::string &filename) {
  49. return APP->window->loadImage(filename);
  50. }
  51. void Svg::loadFile(const std::string &filename) {
  52. handle = nsvgParseFromFile(filename.c_str(), "px", app::SVG_DPI);
  53. if (handle) {
  54. INFO("Loaded SVG %s", filename.c_str());
  55. }
  56. else {
  57. WARN("Failed to load SVG %s", filename.c_str());
  58. }
  59. }
  60. Svg::~Svg() {
  61. if (handle)
  62. nsvgDelete(handle);
  63. }
  64. std::shared_ptr<Svg> Svg::load(const std::string &filename) {
  65. return APP->window->loadSvg(filename);
  66. }
  67. struct Window::Internal {
  68. std::string lastWindowTitle;
  69. int lastWindowX = 0;
  70. int lastWindowY = 0;
  71. int lastWindowWidth = 0;
  72. int lastWindowHeight = 0;
  73. };
  74. static void windowSizeCallback(GLFWwindow *win, int width, int height) {
  75. // Do nothing. Window size is reset each frame anyway.
  76. }
  77. static void mouseButtonCallback(GLFWwindow *win, int button, int action, int mods) {
  78. Window *window = (Window*) glfwGetWindowUserPointer(win);
  79. #if defined ARCH_MAC
  80. // Remap Ctrl-left click to right click on Mac
  81. if (button == GLFW_MOUSE_BUTTON_LEFT && (mods & GLFW_MOD_CONTROL)) {
  82. button = GLFW_MOUSE_BUTTON_RIGHT;
  83. mods &= ~GLFW_MOD_CONTROL;
  84. }
  85. #endif
  86. APP->event->handleButton(window->mousePos, button, action, mods);
  87. }
  88. static void cursorPosCallback(GLFWwindow *win, double xpos, double ypos) {
  89. Window *window = (Window*) glfwGetWindowUserPointer(win);
  90. math::Vec mousePos = math::Vec(xpos, ypos).div(window->pixelRatio / window->windowRatio).round();
  91. math::Vec mouseDelta = mousePos.minus(window->mousePos);
  92. int cursorMode = glfwGetInputMode(win, GLFW_CURSOR);
  93. (void) cursorMode;
  94. #if defined ARCH_MAC
  95. // Workaround for Mac. We can't use GLFW_CURSOR_DISABLED because it's buggy, so implement it on our own.
  96. // This is not an ideal implementation. For example, if the user drags off the screen, the new mouse position will be clamped.
  97. if (cursorMode == GLFW_CURSOR_HIDDEN) {
  98. // CGSetLocalEventsSuppressionInterval(0.0);
  99. glfwSetCursorPos(win, window->mousePos.x, window->mousePos.y);
  100. CGAssociateMouseAndMouseCursorPosition(true);
  101. mousePos = window->mousePos;
  102. }
  103. // Because sometimes the cursor turns into an arrow when its position is on the boundary of the window
  104. glfwSetCursor(win, NULL);
  105. #endif
  106. window->mousePos = mousePos;
  107. APP->event->handleHover(mousePos, mouseDelta);
  108. }
  109. static void cursorEnterCallback(GLFWwindow *win, int entered) {
  110. if (!entered) {
  111. APP->event->handleLeave();
  112. }
  113. }
  114. static void scrollCallback(GLFWwindow *win, double x, double y) {
  115. Window *window = (Window*) glfwGetWindowUserPointer(win);
  116. math::Vec scrollDelta = math::Vec(x, y);
  117. scrollDelta = scrollDelta.mult(50.0);
  118. APP->event->handleScroll(window->mousePos, scrollDelta);
  119. }
  120. static void charCallback(GLFWwindow *win, unsigned int codepoint) {
  121. Window *window = (Window*) glfwGetWindowUserPointer(win);
  122. APP->event->handleText(window->mousePos, codepoint);
  123. }
  124. static void keyCallback(GLFWwindow *win, int key, int scancode, int action, int mods) {
  125. Window *window = (Window*) glfwGetWindowUserPointer(win);
  126. if (APP->event->handleKey(window->mousePos, key, scancode, action, mods))
  127. return;
  128. // Keyboard MIDI driver
  129. if ((mods & RACK_MOD_MASK) == 0) {
  130. if (action == GLFW_PRESS) {
  131. keyboard::press(key);
  132. }
  133. else if (action == GLFW_RELEASE) {
  134. keyboard::release(key);
  135. }
  136. }
  137. }
  138. static void dropCallback(GLFWwindow *win, int count, const char **paths) {
  139. Window *window = (Window*) glfwGetWindowUserPointer(win);
  140. std::vector<std::string> pathsVec;
  141. for (int i = 0; i < count; i++) {
  142. pathsVec.push_back(paths[i]);
  143. }
  144. APP->event->handleDrop(window->mousePos, pathsVec);
  145. }
  146. static void errorCallback(int error, const char *description) {
  147. WARN("GLFW error %d: %s", error, description);
  148. }
  149. Window::Window() {
  150. internal = new Internal;
  151. int err;
  152. // Set window hints
  153. #if defined NANOVG_GL2
  154. glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
  155. glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
  156. #elif defined NANOVG_GL3
  157. glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
  158. glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
  159. glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
  160. glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
  161. #endif
  162. glfwWindowHint(GLFW_DOUBLEBUFFER, GLFW_TRUE);
  163. glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
  164. #if defined ARCH_MAC
  165. glfwWindowHint(GLFW_COCOA_RETINA_FRAMEBUFFER, GLFW_TRUE);
  166. #endif
  167. // Create window
  168. win = glfwCreateWindow(800, 600, "", NULL, NULL);
  169. if (!win) {
  170. osdialog_message(OSDIALOG_ERROR, OSDIALOG_OK, "Could not open GLFW window. Does your graphics card support OpenGL 2.0 or greater? If so, make sure you have the latest graphics drivers installed.");
  171. exit(1);
  172. }
  173. float contentScale;
  174. glfwGetWindowContentScale(win, &contentScale, NULL);
  175. INFO("Window content scale: %f", contentScale);
  176. glfwSetWindowSizeLimits(win, 800, 600, GLFW_DONT_CARE, GLFW_DONT_CARE);
  177. if (settings::windowSize.isZero()) {
  178. glfwMaximizeWindow(win);
  179. }
  180. else {
  181. glfwSetWindowPos(win, settings::windowPos.x, settings::windowPos.y);
  182. glfwSetWindowSize(win, settings::windowSize.x, settings::windowSize.y);
  183. }
  184. glfwShowWindow(win);
  185. glfwSetWindowUserPointer(win, this);
  186. glfwSetInputMode(win, GLFW_LOCK_KEY_MODS, 1);
  187. glfwMakeContextCurrent(win);
  188. // Enable v-sync
  189. glfwSwapInterval(settings::frameRateSync ? 1 : 0);
  190. // Set window callbacks
  191. glfwSetWindowSizeCallback(win, windowSizeCallback);
  192. glfwSetMouseButtonCallback(win, mouseButtonCallback);
  193. // Call this ourselves, but on every frame instead of only when the mouse moves
  194. // glfwSetCursorPosCallback(win, cursorPosCallback);
  195. glfwSetCursorEnterCallback(win, cursorEnterCallback);
  196. glfwSetScrollCallback(win, scrollCallback);
  197. glfwSetCharCallback(win, charCallback);
  198. glfwSetKeyCallback(win, keyCallback);
  199. glfwSetDropCallback(win, dropCallback);
  200. // Set up GLEW
  201. glewExperimental = GL_TRUE;
  202. err = glewInit();
  203. if (err != GLEW_OK) {
  204. 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.");
  205. exit(1);
  206. }
  207. const GLubyte *renderer = glGetString(GL_RENDERER);
  208. const GLubyte *version = glGetString(GL_VERSION);
  209. INFO("Renderer: %s", renderer);
  210. INFO("OpenGL: %s", version);
  211. // GLEW generates GL error because it calls glGetString(GL_EXTENSIONS), we'll consume it here.
  212. glGetError();
  213. // Set up NanoVG
  214. int nvgFlags = NVG_ANTIALIAS;
  215. #if defined NANOVG_GL2
  216. vg = nvgCreateGL2(nvgFlags);
  217. #elif defined NANOVG_GL3
  218. vg = nvgCreateGL3(nvgFlags);
  219. #elif defined NANOVG_GLES2
  220. vg = nvgCreateGLES2(nvgFlags);
  221. #endif
  222. if (!vg) {
  223. osdialog_message(OSDIALOG_ERROR, OSDIALOG_OK, "Could not initialize NanoVG. Does your graphics card support OpenGL 2.0 or greater? If so, make sure you have the latest graphics drivers installed.");
  224. exit(1);
  225. }
  226. }
  227. Window::~Window() {
  228. if (glfwGetWindowAttrib(win, GLFW_MAXIMIZED)) {
  229. settings::windowSize = math::Vec();
  230. settings::windowPos = math::Vec();
  231. }
  232. else {
  233. int winWidth, winHeight;
  234. glfwGetWindowSize(win, &winWidth, &winHeight);
  235. int winX, winY;
  236. glfwGetWindowPos(win, &winX, &winY);
  237. settings::windowSize = math::Vec(winWidth, winHeight);
  238. settings::windowPos = math::Vec(winX, winY);
  239. }
  240. #if defined NANOVG_GL2
  241. nvgDeleteGL2(vg);
  242. #elif defined NANOVG_GL3
  243. nvgDeleteGL3(vg);
  244. #elif defined NANOVG_GLES2
  245. nvgDeleteGLES2(vg);
  246. #endif
  247. glfwDestroyWindow(win);
  248. delete internal;
  249. }
  250. void Window::run() {
  251. uiFont = APP->window->loadFont(asset::system("res/fonts/DejaVuSans.ttf"));
  252. frame = 0;
  253. while(!glfwWindowShouldClose(win)) {
  254. frameTimeStart = glfwGetTime();
  255. // Make event handlers and step() have a clean nanovg context
  256. nvgReset(vg);
  257. bndSetFont(uiFont->handle);
  258. // Poll events
  259. glfwPollEvents();
  260. // In case glfwPollEvents() set another OpenGL context
  261. glfwMakeContextCurrent(win);
  262. // Call cursorPosCallback every frame, not just when the mouse moves
  263. {
  264. double xpos, ypos;
  265. glfwGetCursorPos(win, &xpos, &ypos);
  266. cursorPosCallback(win, xpos, ypos);
  267. }
  268. gamepad::step();
  269. // Set window title
  270. std::string windowTitle;
  271. windowTitle = app::APP_NAME;
  272. windowTitle += " v";
  273. windowTitle += app::APP_VERSION;
  274. if (!APP->patch->path.empty()) {
  275. windowTitle += " - ";
  276. if (!APP->history->isSaved())
  277. windowTitle += "*";
  278. windowTitle += string::filename(APP->patch->path);
  279. }
  280. if (windowTitle != internal->lastWindowTitle) {
  281. glfwSetWindowTitle(win, windowTitle.c_str());
  282. internal->lastWindowTitle = windowTitle;
  283. }
  284. // Get desired scaling
  285. float pixelRatio;
  286. glfwGetWindowContentScale(win, &pixelRatio, NULL);
  287. pixelRatio = std::floor(pixelRatio + 0.25);
  288. if (pixelRatio != this->pixelRatio) {
  289. APP->event->handleZoom();
  290. this->pixelRatio = pixelRatio;
  291. }
  292. // Get framebuffer/window ratio
  293. int fbWidth, fbHeight;
  294. glfwGetFramebufferSize(win, &fbWidth, &fbHeight);
  295. int winWidth, winHeight;
  296. glfwGetWindowSize(win, &winWidth, &winHeight);
  297. windowRatio = (float)fbWidth / winWidth;
  298. // DEBUG("%f %f %d %d", pixelRatio, windowRatio, fbWidth, winWidth);
  299. // Resize scene
  300. APP->scene->box.size = math::Vec(fbWidth, fbHeight).div(pixelRatio);
  301. // Step scene
  302. APP->scene->step();
  303. // Render scene
  304. bool visible = glfwGetWindowAttrib(win, GLFW_VISIBLE) && !glfwGetWindowAttrib(win, GLFW_ICONIFIED);
  305. if (visible) {
  306. // Update and render
  307. nvgBeginFrame(vg, fbWidth, fbHeight, pixelRatio);
  308. nvgScale(vg, pixelRatio, pixelRatio);
  309. // Draw scene
  310. widget::Widget::DrawArgs args;
  311. args.vg = vg;
  312. args.clipBox = APP->scene->box.zeroPos();
  313. APP->scene->draw(args);
  314. glViewport(0, 0, fbWidth, fbHeight);
  315. glClearColor(0.0, 0.0, 0.0, 1.0);
  316. glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
  317. nvgEndFrame(vg);
  318. glfwSwapBuffers(win);
  319. }
  320. // Limit frame rate
  321. double frameTimeEnd = glfwGetTime();
  322. if (settings::frameRateLimit > 0.0) {
  323. double frameDuration = frameTimeEnd - frameTimeStart;
  324. double waitDuration = 1.0 / settings::frameRateLimit - frameDuration;
  325. if (waitDuration > 0.0) {
  326. std::this_thread::sleep_for(std::chrono::duration<double>(waitDuration));
  327. }
  328. }
  329. // Compute actual frame rate
  330. frameTimeEnd = glfwGetTime();
  331. // DEBUG("%g fps", 1 / (endTime - startTime));
  332. frame++;
  333. }
  334. }
  335. void Window::close() {
  336. glfwSetWindowShouldClose(win, GLFW_TRUE);
  337. }
  338. void Window::cursorLock() {
  339. if (settings::allowCursorLock) {
  340. #if defined ARCH_MAC
  341. glfwSetInputMode(win, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
  342. #else
  343. glfwSetInputMode(win, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
  344. #endif
  345. }
  346. }
  347. void Window::cursorUnlock() {
  348. if (settings::allowCursorLock) {
  349. glfwSetInputMode(win, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
  350. }
  351. }
  352. int Window::getMods() {
  353. int mods = 0;
  354. if (glfwGetKey(win, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS || glfwGetKey(win, GLFW_KEY_RIGHT_SHIFT) == GLFW_PRESS)
  355. mods |= GLFW_MOD_SHIFT;
  356. if (glfwGetKey(win, GLFW_KEY_LEFT_CONTROL) == GLFW_PRESS || glfwGetKey(win, GLFW_KEY_RIGHT_CONTROL) == GLFW_PRESS)
  357. mods |= GLFW_MOD_CONTROL;
  358. if (glfwGetKey(win, GLFW_KEY_LEFT_ALT) == GLFW_PRESS || glfwGetKey(win, GLFW_KEY_RIGHT_ALT) == GLFW_PRESS)
  359. mods |= GLFW_MOD_ALT;
  360. if (glfwGetKey(win, GLFW_KEY_LEFT_SUPER) == GLFW_PRESS || glfwGetKey(win, GLFW_KEY_RIGHT_SUPER) == GLFW_PRESS)
  361. mods |= GLFW_MOD_SUPER;
  362. return mods;
  363. }
  364. void Window::setFullScreen(bool fullScreen) {
  365. if (!fullScreen) {
  366. glfwSetWindowMonitor(win, NULL, internal->lastWindowX, internal->lastWindowY, internal->lastWindowWidth, internal->lastWindowHeight, GLFW_DONT_CARE);
  367. }
  368. else {
  369. glfwGetWindowPos(win, &internal->lastWindowX, &internal->lastWindowY);
  370. glfwGetWindowSize(win, &internal->lastWindowWidth, &internal->lastWindowHeight);
  371. GLFWmonitor *monitor = glfwGetPrimaryMonitor();
  372. const GLFWvidmode* mode = glfwGetVideoMode(monitor);
  373. glfwSetWindowMonitor(win, monitor, 0, 0, mode->width, mode->height, mode->refreshRate);
  374. }
  375. }
  376. bool Window::isFullScreen() {
  377. GLFWmonitor *monitor = glfwGetWindowMonitor(win);
  378. return monitor != NULL;
  379. }
  380. bool Window::isFrameOverdue() {
  381. if (settings::frameRateLimit == 0.0)
  382. return false;
  383. double frameDuration = glfwGetTime() - frameTimeStart;
  384. return frameDuration > 1.0 / settings::frameRateLimit;
  385. }
  386. std::shared_ptr<Font> Window::loadFont(const std::string &filename) {
  387. auto sp = fontCache[filename].lock();
  388. if (!sp) {
  389. fontCache[filename] = sp = std::make_shared<Font>();
  390. sp->loadFile(filename, vg);
  391. }
  392. return sp;
  393. }
  394. std::shared_ptr<Image> Window::loadImage(const std::string &filename) {
  395. auto sp = imageCache[filename].lock();
  396. if (!sp) {
  397. imageCache[filename] = sp = std::make_shared<Image>();
  398. sp->loadFile(filename, vg);
  399. }
  400. return sp;
  401. }
  402. std::shared_ptr<Svg> Window::loadSvg(const std::string &filename) {
  403. auto sp = svgCache[filename].lock();
  404. if (!sp) {
  405. svgCache[filename] = sp = std::make_shared<Svg>();
  406. sp->loadFile(filename);
  407. }
  408. return sp;
  409. }
  410. void windowInit() {
  411. int err;
  412. // Set up GLFW
  413. #if defined ARCH_MAC
  414. glfwInitHint(GLFW_COCOA_CHDIR_RESOURCES, GLFW_TRUE);
  415. glfwInitHint(GLFW_COCOA_MENUBAR, GLFW_TRUE);
  416. #endif
  417. glfwSetErrorCallback(errorCallback);
  418. err = glfwInit();
  419. if (err != GLFW_TRUE) {
  420. osdialog_message(OSDIALOG_ERROR, OSDIALOG_OK, "Could not initialize GLFW.");
  421. exit(1);
  422. }
  423. }
  424. void windowDestroy() {
  425. glfwTerminate();
  426. }
  427. } // namespace rack