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.

781 lines
22KB

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