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.

772 lines
21KB

  1. #include <map>
  2. #include <queue>
  3. #include <thread>
  4. #if defined ARCH_MAC
  5. // For CGAssociateMouseAndMouseCursorPosition
  6. #include <ApplicationServices/ApplicationServices.h>
  7. #endif
  8. #include <stb_image_write.h>
  9. #include <osdialog.h>
  10. #include <window/Window.hpp>
  11. #include <asset.hpp>
  12. #include <widget/Widget.hpp>
  13. #include <app/Scene.hpp>
  14. #include <keyboard.hpp>
  15. #include <gamepad.hpp>
  16. #include <context.hpp>
  17. #include <patch.hpp>
  18. #include <settings.hpp>
  19. #include <plugin.hpp> // used in Window::screenshot
  20. #include <system.hpp> // used in Window::screenshot
  21. namespace rack {
  22. namespace window {
  23. static const math::Vec WINDOW_SIZE_MIN = math::Vec(480, 320);
  24. Font::~Font() {
  25. // There is no NanoVG deleteFont() function yet, so do nothing
  26. }
  27. void Font::loadFile(const std::string& filename, NVGcontext* vg) {
  28. this->vg = vg;
  29. handle = nvgCreateFont(vg, filename.c_str(), filename.c_str());
  30. if (handle < 0)
  31. throw Exception("Failed to load font %s", filename.c_str());
  32. INFO("Loaded font %s", filename.c_str());
  33. }
  34. std::shared_ptr<Font> Font::load(const std::string& filename) {
  35. return APP->window->loadFont(filename);
  36. }
  37. Image::~Image() {
  38. // TODO What if handle is invalid?
  39. if (handle >= 0)
  40. nvgDeleteImage(vg, handle);
  41. }
  42. void Image::loadFile(const std::string& filename, NVGcontext* vg) {
  43. this->vg = vg;
  44. handle = nvgCreateImage(vg, filename.c_str(), NVG_IMAGE_REPEATX | NVG_IMAGE_REPEATY);
  45. if (handle <= 0)
  46. throw Exception("Failed to load image %s", filename.c_str());
  47. INFO("Loaded image %s", filename.c_str());
  48. }
  49. std::shared_ptr<Image> Image::load(const std::string& filename) {
  50. return APP->window->loadImage(filename);
  51. }
  52. struct Window::Internal {
  53. std::string lastWindowTitle;
  54. int lastWindowX = 0;
  55. int lastWindowY = 0;
  56. int lastWindowWidth = 0;
  57. int lastWindowHeight = 0;
  58. int frame = 0;
  59. bool ignoreNextMouseDelta = false;
  60. int frameSwapInterval = -1;
  61. double monitorRefreshRate = 0.0;
  62. double frameTime = 0.0;
  63. double lastFrameDuration = 0.0;
  64. math::Vec lastMousePos;
  65. std::map<std::string, std::shared_ptr<Font>> fontCache;
  66. std::map<std::string, std::shared_ptr<Image>> imageCache;
  67. bool fbDirtyOnSubpixelChange = true;
  68. };
  69. static void windowPosCallback(GLFWwindow* win, int x, int y) {
  70. if (glfwGetWindowAttrib(win, GLFW_MAXIMIZED))
  71. return;
  72. if (glfwGetWindowMonitor(win))
  73. return;
  74. settings::windowPos = math::Vec(x, y);
  75. // DEBUG("windowPosCallback %d %d", x, y);
  76. }
  77. static void windowSizeCallback(GLFWwindow* win, int width, int height) {
  78. if (glfwGetWindowAttrib(win, GLFW_MAXIMIZED))
  79. return;
  80. if (glfwGetWindowMonitor(win))
  81. return;
  82. settings::windowSize = math::Vec(width, height);
  83. // DEBUG("windowSizeCallback %d %d", width, height);
  84. }
  85. static void windowMaximizeCallback(GLFWwindow* win, int maximized) {
  86. settings::windowMaximized = maximized;
  87. // DEBUG("windowMaximizeCallback %d", maximized);
  88. }
  89. static void mouseButtonCallback(GLFWwindow* win, int button, int action, int mods) {
  90. contextSet((Context*) glfwGetWindowUserPointer(win));
  91. #if defined ARCH_MAC
  92. // Remap Ctrl-left click to right click on Mac
  93. if (button == GLFW_MOUSE_BUTTON_LEFT && (mods & RACK_MOD_MASK) == GLFW_MOD_CONTROL) {
  94. button = GLFW_MOUSE_BUTTON_RIGHT;
  95. mods &= ~GLFW_MOD_CONTROL;
  96. }
  97. // Remap Ctrl-shift-left click to middle click on Mac
  98. if (button == GLFW_MOUSE_BUTTON_LEFT && (mods & RACK_MOD_MASK) == (GLFW_MOD_CONTROL | GLFW_MOD_SHIFT)) {
  99. button = GLFW_MOUSE_BUTTON_MIDDLE;
  100. mods &= ~(GLFW_MOD_CONTROL | GLFW_MOD_SHIFT);
  101. }
  102. #endif
  103. APP->event->handleButton(APP->window->internal->lastMousePos, button, action, mods);
  104. }
  105. static void cursorPosCallback(GLFWwindow* win, double xpos, double ypos) {
  106. contextSet((Context*) glfwGetWindowUserPointer(win));
  107. math::Vec mousePos = math::Vec(xpos, ypos).div(APP->window->pixelRatio / APP->window->windowRatio).round();
  108. math::Vec mouseDelta = mousePos.minus(APP->window->internal->lastMousePos);
  109. // Workaround for GLFW warping mouse to a different position when the cursor is locked or unlocked.
  110. if (APP->window->internal->ignoreNextMouseDelta) {
  111. APP->window->internal->ignoreNextMouseDelta = false;
  112. mouseDelta = math::Vec();
  113. }
  114. int cursorMode = glfwGetInputMode(win, GLFW_CURSOR);
  115. (void) cursorMode;
  116. #if defined ARCH_MAC
  117. // Workaround for Mac. We can't use GLFW_CURSOR_DISABLED because it's buggy, so implement it on our own.
  118. // This is not an ideal implementation. For example, if the user drags off the screen, the new mouse position will be clamped.
  119. if (cursorMode == GLFW_CURSOR_HIDDEN) {
  120. // CGSetLocalEventsSuppressionInterval(0.0);
  121. glfwSetCursorPos(win, APP->window->internal->lastMousePos.x, APP->window->internal->lastMousePos.y);
  122. CGAssociateMouseAndMouseCursorPosition(true);
  123. mousePos = APP->window->internal->lastMousePos;
  124. }
  125. // Because sometimes the cursor turns into an arrow when its position is on the boundary of the window
  126. glfwSetCursor(win, NULL);
  127. #endif
  128. APP->window->internal->lastMousePos = mousePos;
  129. APP->event->handleHover(mousePos, mouseDelta);
  130. // Keyboard/mouse MIDI driver
  131. int width, height;
  132. glfwGetWindowSize(win, &width, &height);
  133. math::Vec scaledPos(xpos / width, ypos / height);
  134. keyboard::mouseMove(scaledPos);
  135. }
  136. static void cursorEnterCallback(GLFWwindow* win, int entered) {
  137. contextSet((Context*) glfwGetWindowUserPointer(win));
  138. if (!entered) {
  139. APP->event->handleLeave();
  140. }
  141. }
  142. static void scrollCallback(GLFWwindow* win, double x, double y) {
  143. contextSet((Context*) glfwGetWindowUserPointer(win));
  144. math::Vec scrollDelta = math::Vec(x, y);
  145. #if defined ARCH_MAC
  146. scrollDelta = scrollDelta.mult(10.0);
  147. #else
  148. scrollDelta = scrollDelta.mult(50.0);
  149. #endif
  150. APP->event->handleScroll(APP->window->internal->lastMousePos, scrollDelta);
  151. }
  152. static void charCallback(GLFWwindow* win, unsigned int codepoint) {
  153. contextSet((Context*) glfwGetWindowUserPointer(win));
  154. if (APP->event->handleText(APP->window->internal->lastMousePos, codepoint))
  155. return;
  156. }
  157. static void keyCallback(GLFWwindow* win, int key, int scancode, int action, int mods) {
  158. contextSet((Context*) glfwGetWindowUserPointer(win));
  159. if (APP->event->handleKey(APP->window->internal->lastMousePos, key, scancode, action, mods))
  160. return;
  161. // Keyboard/mouse MIDI driver
  162. if (action == GLFW_PRESS && (mods & RACK_MOD_MASK) == 0) {
  163. keyboard::press(key);
  164. }
  165. if (action == GLFW_RELEASE) {
  166. keyboard::release(key);
  167. }
  168. }
  169. static void dropCallback(GLFWwindow* win, int count, const char** paths) {
  170. contextSet((Context*) glfwGetWindowUserPointer(win));
  171. std::vector<std::string> pathsVec;
  172. for (int i = 0; i < count; i++) {
  173. pathsVec.push_back(paths[i]);
  174. }
  175. APP->event->handleDrop(APP->window->internal->lastMousePos, pathsVec);
  176. }
  177. static void errorCallback(int error, const char* description) {
  178. WARN("GLFW error %d: %s", error, description);
  179. }
  180. Window::Window() {
  181. internal = new Internal;
  182. int err;
  183. // Set window hints
  184. #if defined NANOVG_GL2
  185. glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
  186. glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
  187. #elif defined NANOVG_GL3
  188. glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
  189. glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
  190. glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
  191. glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
  192. #endif
  193. glfwWindowHint(GLFW_DOUBLEBUFFER, GLFW_TRUE);
  194. glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
  195. #if defined ARCH_MAC
  196. glfwWindowHint(GLFW_COCOA_RETINA_FRAMEBUFFER, GLFW_TRUE);
  197. #endif
  198. // Create window
  199. win = glfwCreateWindow(1024, 768, "", NULL, NULL);
  200. if (!win) {
  201. 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.");
  202. exit(1);
  203. }
  204. float contentScale;
  205. glfwGetWindowContentScale(win, &contentScale, NULL);
  206. INFO("Window content scale: %f", contentScale);
  207. glfwSetWindowSizeLimits(win, WINDOW_SIZE_MIN.x, WINDOW_SIZE_MIN.y, GLFW_DONT_CARE, GLFW_DONT_CARE);
  208. glfwSetWindowSize(win, settings::windowSize.x, settings::windowSize.y);
  209. if (settings::windowPos.isFinite()) {
  210. glfwSetWindowPos(win, settings::windowPos.x, settings::windowPos.y);
  211. }
  212. if (settings::windowMaximized) {
  213. glfwMaximizeWindow(win);
  214. }
  215. glfwShowWindow(win);
  216. glfwSetWindowUserPointer(win, contextGet());
  217. glfwSetInputMode(win, GLFW_LOCK_KEY_MODS, 1);
  218. glfwMakeContextCurrent(win);
  219. glfwSwapInterval(1);
  220. const GLFWvidmode* monitorMode = glfwGetVideoMode(glfwGetPrimaryMonitor());
  221. if (monitorMode->refreshRate > 0) {
  222. internal->monitorRefreshRate = monitorMode->refreshRate;
  223. }
  224. else {
  225. // Some monitors report 0Hz refresh rate for some reason, so as a workaround, assume 60Hz.
  226. internal->monitorRefreshRate = 60;
  227. }
  228. // Set window callbacks
  229. glfwSetWindowPosCallback(win, windowPosCallback);
  230. glfwSetWindowSizeCallback(win, windowSizeCallback);
  231. glfwSetWindowMaximizeCallback(win, windowMaximizeCallback);
  232. glfwSetMouseButtonCallback(win, mouseButtonCallback);
  233. // Call this ourselves, but on every frame instead of only when the mouse moves
  234. // glfwSetCursorPosCallback(win, cursorPosCallback);
  235. glfwSetCursorEnterCallback(win, cursorEnterCallback);
  236. glfwSetScrollCallback(win, scrollCallback);
  237. glfwSetCharCallback(win, charCallback);
  238. glfwSetKeyCallback(win, keyCallback);
  239. glfwSetDropCallback(win, dropCallback);
  240. // Set up GLEW
  241. glewExperimental = GL_TRUE;
  242. err = glewInit();
  243. if (err != GLEW_OK) {
  244. 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.");
  245. exit(1);
  246. }
  247. const GLubyte* vendor = glGetString(GL_VENDOR);
  248. const GLubyte* renderer = glGetString(GL_RENDERER);
  249. const GLubyte* version = glGetString(GL_VERSION);
  250. INFO("Renderer: %s %s", vendor, renderer);
  251. INFO("OpenGL: %s", version);
  252. // GLEW generates GL error because it calls glGetString(GL_EXTENSIONS), we'll consume it here.
  253. glGetError();
  254. // Set up NanoVG
  255. int nvgFlags = NVG_ANTIALIAS;
  256. #if defined NANOVG_GL2
  257. vg = nvgCreateGL2(nvgFlags);
  258. fbVg = nvgCreateSharedGL2(vg, nvgFlags);
  259. #elif defined NANOVG_GL3
  260. vg = nvgCreateGL3(nvgFlags);
  261. #elif defined NANOVG_GLES2
  262. vg = nvgCreateGLES2(nvgFlags);
  263. #endif
  264. if (!vg) {
  265. 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.");
  266. exit(1);
  267. }
  268. // Load default Blendish font
  269. uiFont = loadFont(asset::system("res/fonts/DejaVuSans.ttf"));
  270. bndSetFont(uiFont->handle);
  271. if (APP->scene) {
  272. widget::Widget::ContextCreateEvent e;
  273. APP->scene->onContextCreate(e);
  274. }
  275. }
  276. Window::~Window() {
  277. if (APP->scene) {
  278. widget::Widget::ContextDestroyEvent e;
  279. APP->scene->onContextDestroy(e);
  280. }
  281. // Fonts and Images in the cache must be deleted before the NanoVG context is deleted
  282. internal->fontCache.clear();
  283. internal->imageCache.clear();
  284. // nvgDeleteClone(fbVg);
  285. #if defined NANOVG_GL2
  286. nvgDeleteGL2(vg);
  287. nvgDeleteGL2(fbVg);
  288. #elif defined NANOVG_GL3
  289. nvgDeleteGL3(vg);
  290. #elif defined NANOVG_GLES2
  291. nvgDeleteGLES2(vg);
  292. #endif
  293. glfwDestroyWindow(win);
  294. delete internal;
  295. }
  296. math::Vec Window::getSize() {
  297. int width, height;
  298. glfwGetWindowSize(win, &width, &height);
  299. return math::Vec(width, height);
  300. }
  301. void Window::setSize(math::Vec size) {
  302. size = size.max(WINDOW_SIZE_MIN);
  303. glfwSetWindowSize(win, size.x, size.y);
  304. }
  305. void Window::run() {
  306. internal->frame = 0;
  307. while (!glfwWindowShouldClose(win)) {
  308. step();
  309. }
  310. }
  311. void Window::step() {
  312. double frameTime = system::getTime();
  313. double lastFrameTime = internal->frameTime;
  314. internal->frameTime = frameTime;
  315. internal->lastFrameDuration = frameTime - lastFrameTime;
  316. // DEBUG("%.2lf Hz", 1.0 / internal->lastFrameDuration);
  317. double t1 = 0.0, t2 = 0.0, t3 = 0.0, t4 = 0.0, t5 = 0.0;
  318. // Make event handlers and step() have a clean NanoVG context
  319. nvgReset(vg);
  320. bndSetFont(uiFont->handle);
  321. // Poll events
  322. // Save and restore context because event handler set their own context based on which window they originate from.
  323. Context* context = contextGet();
  324. glfwPollEvents();
  325. contextSet(context);
  326. // In case glfwPollEvents() sets another OpenGL context
  327. glfwMakeContextCurrent(win);
  328. if (settings::frameSwapInterval != internal->frameSwapInterval) {
  329. glfwSwapInterval(settings::frameSwapInterval);
  330. internal->frameSwapInterval = settings::frameSwapInterval;
  331. }
  332. // Call cursorPosCallback every frame, not just when the mouse moves
  333. {
  334. double xpos, ypos;
  335. glfwGetCursorPos(win, &xpos, &ypos);
  336. cursorPosCallback(win, xpos, ypos);
  337. }
  338. gamepad::step();
  339. // Set window title
  340. std::string windowTitle = APP_NAME + " " + APP_EDITION_NAME + " " + APP_VERSION;
  341. if (APP->patch->path != "") {
  342. windowTitle += " - ";
  343. if (!APP->history->isSaved())
  344. windowTitle += "*";
  345. windowTitle += system::getFilename(APP->patch->path);
  346. }
  347. if (windowTitle != internal->lastWindowTitle) {
  348. glfwSetWindowTitle(win, windowTitle.c_str());
  349. internal->lastWindowTitle = windowTitle;
  350. }
  351. // Get desired pixel ratio
  352. float newPixelRatio;
  353. if (settings::pixelRatio > 0.0) {
  354. newPixelRatio = settings::pixelRatio;
  355. }
  356. else {
  357. glfwGetWindowContentScale(win, &newPixelRatio, NULL);
  358. newPixelRatio = std::floor(newPixelRatio + 0.5);
  359. }
  360. if (newPixelRatio != pixelRatio) {
  361. pixelRatio = newPixelRatio;
  362. APP->event->handleDirty();
  363. }
  364. // Get framebuffer/window ratio
  365. int fbWidth, fbHeight;
  366. glfwGetFramebufferSize(win, &fbWidth, &fbHeight);
  367. int winWidth, winHeight;
  368. glfwGetWindowSize(win, &winWidth, &winHeight);
  369. windowRatio = (float)fbWidth / winWidth;
  370. t1 = system::getTime();
  371. if (APP->scene) {
  372. // DEBUG("%f %f %d %d", pixelRatio, windowRatio, fbWidth, winWidth);
  373. // Resize scene
  374. APP->scene->box.size = math::Vec(fbWidth, fbHeight).div(pixelRatio);
  375. // Step scene
  376. APP->scene->step();
  377. t2 = system::getTime();
  378. // Render scene
  379. bool visible = glfwGetWindowAttrib(win, GLFW_VISIBLE) && !glfwGetWindowAttrib(win, GLFW_ICONIFIED);
  380. if (visible) {
  381. // Update and render
  382. nvgBeginFrame(vg, fbWidth, fbHeight, pixelRatio);
  383. nvgScale(vg, pixelRatio, pixelRatio);
  384. // Draw scene
  385. widget::Widget::DrawArgs args;
  386. args.vg = vg;
  387. args.clipBox = APP->scene->box.zeroPos();
  388. APP->scene->draw(args);
  389. t3 = system::getTime();
  390. glViewport(0, 0, fbWidth, fbHeight);
  391. glClearColor(0.0, 0.0, 0.0, 1.0);
  392. glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
  393. nvgEndFrame(vg);
  394. t4 = system::getTime();
  395. }
  396. }
  397. glfwSwapBuffers(win);
  398. // On some platforms, glfwSwapBuffers() doesn't wait on monitor refresh, so we have to sleep as a fallback.
  399. double frameDurationRemaining = getFrameDurationRemaining();
  400. if (frameDurationRemaining > 0.0) {
  401. std::this_thread::sleep_for(std::chrono::duration<double>(frameDurationRemaining));
  402. }
  403. t5 = system::getTime();
  404. // DEBUG("pre-step %6.1f step %6.1f draw %6.1f nvgEndFrame %6.1f glfwSwapBuffers %6.1f total %6.1f",
  405. // (t1 - frameTime) * 1e3f,
  406. // (t2 - t1) * 1e3f,
  407. // (t3 - t2) * 1e3f,
  408. // (t4 - t2) * 1e3f,
  409. // (t5 - t4) * 1e3f,
  410. // (t5 - frameTime) * 1e3f
  411. // );
  412. internal->frame++;
  413. }
  414. void Window::activateContext() {
  415. glfwMakeContextCurrent(win);
  416. }
  417. static void flipBitmap(uint8_t* pixels, int width, int height, int depth) {
  418. for (int y = 0; y < height / 2; y++) {
  419. int flipY = height - y - 1;
  420. uint8_t tmp[width * depth];
  421. std::memcpy(tmp, &pixels[y * width * depth], width * depth);
  422. std::memcpy(&pixels[y * width * depth], &pixels[flipY * width * depth], width * depth);
  423. std::memcpy(&pixels[flipY * width * depth], tmp, width * depth);
  424. }
  425. }
  426. void Window::screenshot(const std::string& screenshotPath) {
  427. // Get window framebuffer size
  428. int width, height;
  429. glfwGetFramebufferSize(APP->window->win, &width, &height);
  430. // Allocate pixel color buffer
  431. uint8_t* pixels = new uint8_t[height * width * 4];
  432. // glReadPixels defaults to GL_BACK, but the back-buffer is unstable, so use the front buffer (what the user sees)
  433. glReadBuffer(GL_FRONT);
  434. glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
  435. // Write pixels to PNG
  436. flipBitmap(pixels, width, height, 4);
  437. stbi_write_png(screenshotPath.c_str(), width, height, 4, pixels, width * 4);
  438. delete[] pixels;
  439. }
  440. void Window::screenshotModules(const std::string& screenshotsDir, float zoom) {
  441. // Iterate plugins and create directories
  442. system::createDirectories(screenshotsDir);
  443. for (plugin::Plugin* p : plugin::plugins) {
  444. std::string dir = system::join(screenshotsDir, p->slug);
  445. system::createDirectory(dir);
  446. for (plugin::Model* model : p->models) {
  447. std::string filename = system::join(dir, model->slug + ".png");
  448. // Skip model if screenshot already exists
  449. if (system::isFile(filename))
  450. continue;
  451. INFO("Screenshotting %s %s to %s", p->slug.c_str(), model->slug.c_str(), filename.c_str());
  452. // Create widgets
  453. widget::FramebufferWidget* fbw = new widget::FramebufferWidget;
  454. fbw->oversample = 2;
  455. struct ModuleWidgetContainer : widget::Widget {
  456. void draw(const DrawArgs& args) override {
  457. Widget::draw(args);
  458. Widget::drawLayer(args, 1);
  459. }
  460. };
  461. ModuleWidgetContainer* mwc = new ModuleWidgetContainer;
  462. fbw->addChild(mwc);
  463. app::ModuleWidget* mw = model->createModuleWidget(NULL);
  464. mwc->box.size = mw->box.size;
  465. fbw->box.size = mw->box.size;
  466. mwc->addChild(mw);
  467. // Step to allow the ModuleWidget state to set its default appearance.
  468. fbw->step();
  469. // Draw to framebuffer
  470. fbw->render(math::Vec(zoom, zoom));
  471. // Read pixels
  472. nvgluBindFramebuffer(fbw->getFramebuffer());
  473. int width, height;
  474. nvgImageSize(vg, fbw->getImageHandle(), &width, &height);
  475. uint8_t* pixels = new uint8_t[height * width * 4];
  476. glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
  477. // Write pixels to PNG
  478. flipBitmap(pixels, width, height, 4);
  479. stbi_write_png(filename.c_str(), width, height, 4, pixels, width * 4);
  480. // Cleanup
  481. delete[] pixels;
  482. nvgluBindFramebuffer(NULL);
  483. delete fbw;
  484. }
  485. }
  486. }
  487. void Window::close() {
  488. glfwSetWindowShouldClose(win, GLFW_TRUE);
  489. }
  490. void Window::cursorLock() {
  491. if (!settings::allowCursorLock)
  492. return;
  493. #if defined ARCH_MAC
  494. glfwSetInputMode(win, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
  495. #else
  496. glfwSetInputMode(win, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
  497. #endif
  498. internal->ignoreNextMouseDelta = true;
  499. }
  500. void Window::cursorUnlock() {
  501. if (!settings::allowCursorLock)
  502. return;
  503. glfwSetInputMode(win, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
  504. internal->ignoreNextMouseDelta = true;
  505. }
  506. bool Window::isCursorLocked() {
  507. return glfwGetInputMode(win, GLFW_CURSOR) != GLFW_CURSOR_NORMAL;
  508. }
  509. int Window::getMods() {
  510. int mods = 0;
  511. if (glfwGetKey(win, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS || glfwGetKey(win, GLFW_KEY_RIGHT_SHIFT) == GLFW_PRESS)
  512. mods |= GLFW_MOD_SHIFT;
  513. if (glfwGetKey(win, GLFW_KEY_LEFT_CONTROL) == GLFW_PRESS || glfwGetKey(win, GLFW_KEY_RIGHT_CONTROL) == GLFW_PRESS)
  514. mods |= GLFW_MOD_CONTROL;
  515. if (glfwGetKey(win, GLFW_KEY_LEFT_ALT) == GLFW_PRESS || glfwGetKey(win, GLFW_KEY_RIGHT_ALT) == GLFW_PRESS)
  516. mods |= GLFW_MOD_ALT;
  517. if (glfwGetKey(win, GLFW_KEY_LEFT_SUPER) == GLFW_PRESS || glfwGetKey(win, GLFW_KEY_RIGHT_SUPER) == GLFW_PRESS)
  518. mods |= GLFW_MOD_SUPER;
  519. return mods;
  520. }
  521. void Window::setFullScreen(bool fullScreen) {
  522. if (!fullScreen) {
  523. glfwSetWindowMonitor(win, NULL, internal->lastWindowX, internal->lastWindowY, internal->lastWindowWidth, internal->lastWindowHeight, GLFW_DONT_CARE);
  524. }
  525. else {
  526. glfwGetWindowPos(win, &internal->lastWindowX, &internal->lastWindowY);
  527. glfwGetWindowSize(win, &internal->lastWindowWidth, &internal->lastWindowHeight);
  528. GLFWmonitor* monitor = glfwGetPrimaryMonitor();
  529. const GLFWvidmode* mode = glfwGetVideoMode(monitor);
  530. glfwSetWindowMonitor(win, monitor, 0, 0, mode->width, mode->height, mode->refreshRate);
  531. }
  532. }
  533. bool Window::isFullScreen() {
  534. GLFWmonitor* monitor = glfwGetWindowMonitor(win);
  535. return monitor != NULL;
  536. }
  537. double Window::getMonitorRefreshRate() {
  538. return internal->monitorRefreshRate;
  539. }
  540. double Window::getFrameTime() {
  541. return internal->frameTime;
  542. }
  543. double Window::getLastFrameDuration() {
  544. return internal->lastFrameDuration;
  545. }
  546. double Window::getFrameDurationRemaining() {
  547. double frameDurationDesired = internal->frameSwapInterval / internal->monitorRefreshRate;
  548. return frameDurationDesired - (system::getTime() - internal->frameTime);
  549. }
  550. std::shared_ptr<Font> Window::loadFont(const std::string& filename) {
  551. const auto& pair = internal->fontCache.find(filename);
  552. if (pair != internal->fontCache.end())
  553. return pair->second;
  554. // Load font
  555. std::shared_ptr<Font> font;
  556. try {
  557. font = std::make_shared<Font>();
  558. font->loadFile(filename, vg);
  559. }
  560. catch (Exception& e) {
  561. WARN("%s", e.what());
  562. font = NULL;
  563. }
  564. internal->fontCache[filename] = font;
  565. return font;
  566. }
  567. std::shared_ptr<Image> Window::loadImage(const std::string& filename) {
  568. const auto& pair = internal->imageCache.find(filename);
  569. if (pair != internal->imageCache.end())
  570. return pair->second;
  571. // Load image
  572. std::shared_ptr<Image> image;
  573. try {
  574. image = std::make_shared<Image>();
  575. image->loadFile(filename, vg);
  576. }
  577. catch (Exception& e) {
  578. WARN("%s", e.what());
  579. image = NULL;
  580. }
  581. internal->imageCache[filename] = image;
  582. return image;
  583. }
  584. bool& Window::fbDirtyOnSubpixelChange() {
  585. return internal->fbDirtyOnSubpixelChange;
  586. }
  587. void init() {
  588. int err;
  589. // Set up GLFW
  590. #if defined ARCH_MAC
  591. glfwInitHint(GLFW_COCOA_CHDIR_RESOURCES, GLFW_TRUE);
  592. glfwInitHint(GLFW_COCOA_MENUBAR, GLFW_FALSE);
  593. #endif
  594. glfwSetErrorCallback(errorCallback);
  595. err = glfwInit();
  596. if (err != GLFW_TRUE) {
  597. osdialog_message(OSDIALOG_ERROR, OSDIALOG_OK, "Could not initialize GLFW.");
  598. exit(1);
  599. }
  600. }
  601. void destroy() {
  602. glfwTerminate();
  603. }
  604. } // namespace window
  605. } // namespace rack