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.

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