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.

483 lines
13KB

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