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.

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