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.

594 lines
17KB

  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 <plugin.hpp> // used in Window::screenshot
  11. #include <system.hpp> // used in Window::screenshot
  12. #include <map>
  13. #include <queue>
  14. #include <thread>
  15. #if defined ARCH_MAC
  16. // For CGAssociateMouseAndMouseCursorPosition
  17. #include <ApplicationServices/ApplicationServices.h>
  18. #endif
  19. #include <osdialog.h>
  20. #include <stb_image_write.h>
  21. namespace rack {
  22. void Font::loadFile(const std::string& filename, NVGcontext* vg) {
  23. this->vg = vg;
  24. handle = nvgCreateFont(vg, filename.c_str(), filename.c_str());
  25. if (handle >= 0) {
  26. INFO("Loaded font %s", filename.c_str());
  27. }
  28. else {
  29. WARN("Failed to load font %s", filename.c_str());
  30. }
  31. }
  32. Font::~Font() {
  33. // There is no NanoVG deleteFont() function yet, so do nothing
  34. }
  35. std::shared_ptr<Font> Font::load(const std::string& filename) {
  36. return APP->window->loadFont(filename);
  37. }
  38. void Image::loadFile(const std::string& filename, NVGcontext* vg) {
  39. this->vg = vg;
  40. handle = nvgCreateImage(vg, filename.c_str(), NVG_IMAGE_REPEATX | NVG_IMAGE_REPEATY);
  41. if (handle > 0) {
  42. INFO("Loaded image %s", filename.c_str());
  43. }
  44. else {
  45. WARN("Failed to load image %s", filename.c_str());
  46. }
  47. }
  48. Image::~Image() {
  49. // TODO What if handle is invalid?
  50. if (handle >= 0)
  51. nvgDeleteImage(vg, handle);
  52. }
  53. std::shared_ptr<Image> Image::load(const std::string& filename) {
  54. return APP->window->loadImage(filename);
  55. }
  56. void Svg::loadFile(const std::string& filename) {
  57. handle = nsvgParseFromFile(filename.c_str(), "px", app::SVG_DPI);
  58. if (handle) {
  59. INFO("Loaded SVG %s", filename.c_str());
  60. }
  61. else {
  62. WARN("Failed to load SVG %s", filename.c_str());
  63. }
  64. }
  65. Svg::~Svg() {
  66. if (handle)
  67. nsvgDelete(handle);
  68. }
  69. std::shared_ptr<Svg> Svg::load(const std::string& filename) {
  70. return APP->window->loadSvg(filename);
  71. }
  72. struct Window::Internal {
  73. std::string lastWindowTitle;
  74. int lastWindowX = 0;
  75. int lastWindowY = 0;
  76. int lastWindowWidth = 0;
  77. int lastWindowHeight = 0;
  78. bool ignoreNextMouseDelta = false;
  79. int frameSwapInterval = -1;
  80. float monitorRefreshRate = 0.f;
  81. float lastFrameRate = 0.f;
  82. };
  83. static void windowSizeCallback(GLFWwindow* win, int width, int height) {
  84. // Do nothing. Window size is reset each frame anyway.
  85. }
  86. static void mouseButtonCallback(GLFWwindow* win, int button, int action, int mods) {
  87. Window* window = (Window*) glfwGetWindowUserPointer(win);
  88. #if defined ARCH_MAC
  89. // Remap Ctrl-left click to right click on Mac
  90. if (button == GLFW_MOUSE_BUTTON_LEFT && (mods & RACK_MOD_MASK) == GLFW_MOD_CONTROL) {
  91. button = GLFW_MOUSE_BUTTON_RIGHT;
  92. mods &= ~GLFW_MOD_CONTROL;
  93. }
  94. // Remap Ctrl-shift-left click to middle click on Mac
  95. if (button == GLFW_MOUSE_BUTTON_LEFT && (mods & RACK_MOD_MASK) == (GLFW_MOD_CONTROL | GLFW_MOD_SHIFT)) {
  96. button = GLFW_MOUSE_BUTTON_MIDDLE;
  97. mods &= ~(GLFW_MOD_CONTROL | GLFW_MOD_SHIFT);
  98. }
  99. #endif
  100. APP->event->handleButton(window->mousePos, button, action, mods);
  101. }
  102. static void cursorPosCallback(GLFWwindow* win, double xpos, double ypos) {
  103. Window* window = (Window*) glfwGetWindowUserPointer(win);
  104. math::Vec mousePos = math::Vec(xpos, ypos).div(window->pixelRatio / window->windowRatio).round();
  105. math::Vec mouseDelta = mousePos.minus(window->mousePos);
  106. // Workaround for GLFW warping mouse to a different position when the cursor is locked or unlocked.
  107. if (window->internal->ignoreNextMouseDelta) {
  108. window->internal->ignoreNextMouseDelta = false;
  109. mouseDelta = math::Vec();
  110. }
  111. int cursorMode = glfwGetInputMode(win, GLFW_CURSOR);
  112. (void) cursorMode;
  113. #if defined ARCH_MAC
  114. // Workaround for Mac. We can't use GLFW_CURSOR_DISABLED because it's buggy, so implement it on our own.
  115. // This is not an ideal implementation. For example, if the user drags off the screen, the new mouse position will be clamped.
  116. if (cursorMode == GLFW_CURSOR_HIDDEN) {
  117. // CGSetLocalEventsSuppressionInterval(0.0);
  118. glfwSetCursorPos(win, window->mousePos.x, window->mousePos.y);
  119. CGAssociateMouseAndMouseCursorPosition(true);
  120. mousePos = window->mousePos;
  121. }
  122. // Because sometimes the cursor turns into an arrow when its position is on the boundary of the window
  123. glfwSetCursor(win, NULL);
  124. #endif
  125. window->mousePos = mousePos;
  126. APP->event->handleHover(mousePos, mouseDelta);
  127. }
  128. static void cursorEnterCallback(GLFWwindow* win, int entered) {
  129. if (!entered) {
  130. APP->event->handleLeave();
  131. }
  132. }
  133. static void scrollCallback(GLFWwindow* win, double x, double y) {
  134. Window* window = (Window*) glfwGetWindowUserPointer(win);
  135. math::Vec scrollDelta = math::Vec(x, y);
  136. #if defined ARCH_MAC
  137. scrollDelta = scrollDelta.mult(10.0);
  138. #else
  139. scrollDelta = scrollDelta.mult(50.0);
  140. #endif
  141. APP->event->handleScroll(window->mousePos, scrollDelta);
  142. }
  143. static void charCallback(GLFWwindow* win, unsigned int codepoint) {
  144. Window* window = (Window*) glfwGetWindowUserPointer(win);
  145. APP->event->handleText(window->mousePos, codepoint);
  146. }
  147. static void keyCallback(GLFWwindow* win, int key, int scancode, int action, int mods) {
  148. Window* window = (Window*) glfwGetWindowUserPointer(win);
  149. if (APP->event->handleKey(window->mousePos, key, scancode, action, mods))
  150. return;
  151. // Keyboard MIDI driver
  152. if (action == GLFW_PRESS && (mods & RACK_MOD_MASK) == 0) {
  153. keyboard::press(key);
  154. }
  155. if (action == GLFW_RELEASE) {
  156. keyboard::release(key);
  157. }
  158. }
  159. static void dropCallback(GLFWwindow* win, int count, const char** paths) {
  160. Window* window = (Window*) glfwGetWindowUserPointer(win);
  161. std::vector<std::string> pathsVec;
  162. for (int i = 0; i < count; i++) {
  163. pathsVec.push_back(paths[i]);
  164. }
  165. APP->event->handleDrop(window->mousePos, pathsVec);
  166. }
  167. static void errorCallback(int error, const char* description) {
  168. WARN("GLFW error %d: %s", error, description);
  169. }
  170. Window::Window() {
  171. internal = new Internal;
  172. int err;
  173. // Set window hints
  174. #if defined NANOVG_GL2
  175. glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
  176. glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
  177. #elif defined NANOVG_GL3
  178. glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
  179. glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
  180. glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
  181. glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
  182. #endif
  183. glfwWindowHint(GLFW_DOUBLEBUFFER, GLFW_TRUE);
  184. glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
  185. #if defined ARCH_MAC
  186. glfwWindowHint(GLFW_COCOA_RETINA_FRAMEBUFFER, GLFW_TRUE);
  187. #endif
  188. // Create window
  189. win = glfwCreateWindow(800, 600, "", NULL, NULL);
  190. if (!win) {
  191. 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.");
  192. exit(1);
  193. }
  194. float contentScale;
  195. glfwGetWindowContentScale(win, &contentScale, NULL);
  196. INFO("Window content scale: %f", contentScale);
  197. glfwSetWindowSizeLimits(win, 640, 480, GLFW_DONT_CARE, GLFW_DONT_CARE);
  198. if (settings::windowSize.isZero()) {
  199. glfwMaximizeWindow(win);
  200. }
  201. else {
  202. glfwSetWindowPos(win, settings::windowPos.x, settings::windowPos.y);
  203. glfwSetWindowSize(win, settings::windowSize.x, settings::windowSize.y);
  204. }
  205. glfwShowWindow(win);
  206. glfwSetWindowUserPointer(win, this);
  207. glfwSetInputMode(win, GLFW_LOCK_KEY_MODS, 1);
  208. glfwMakeContextCurrent(win);
  209. glfwSwapInterval(1);
  210. const GLFWvidmode* monitorMode = glfwGetVideoMode(glfwGetPrimaryMonitor());
  211. internal->monitorRefreshRate = monitorMode->refreshRate;
  212. // Set window callbacks
  213. glfwSetWindowSizeCallback(win, windowSizeCallback);
  214. glfwSetMouseButtonCallback(win, mouseButtonCallback);
  215. // Call this ourselves, but on every frame instead of only when the mouse moves
  216. // glfwSetCursorPosCallback(win, cursorPosCallback);
  217. glfwSetCursorEnterCallback(win, cursorEnterCallback);
  218. glfwSetScrollCallback(win, scrollCallback);
  219. glfwSetCharCallback(win, charCallback);
  220. glfwSetKeyCallback(win, keyCallback);
  221. glfwSetDropCallback(win, dropCallback);
  222. // Set up GLEW
  223. glewExperimental = GL_TRUE;
  224. err = glewInit();
  225. if (err != GLEW_OK) {
  226. 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.");
  227. exit(1);
  228. }
  229. const GLubyte* renderer = glGetString(GL_RENDERER);
  230. const GLubyte* version = glGetString(GL_VERSION);
  231. INFO("Renderer: %s", renderer);
  232. INFO("OpenGL: %s", version);
  233. // GLEW generates GL error because it calls glGetString(GL_EXTENSIONS), we'll consume it here.
  234. glGetError();
  235. // Set up NanoVG
  236. int nvgFlags = NVG_ANTIALIAS;
  237. #if defined NANOVG_GL2
  238. vg = nvgCreateGL2(nvgFlags);
  239. #elif defined NANOVG_GL3
  240. vg = nvgCreateGL3(nvgFlags);
  241. #elif defined NANOVG_GLES2
  242. vg = nvgCreateGLES2(nvgFlags);
  243. #endif
  244. if (!vg) {
  245. 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.");
  246. exit(1);
  247. }
  248. // Load default Blendish font
  249. uiFont = loadFont(asset::system("res/fonts/DejaVuSans.ttf"));
  250. bndSetFont(uiFont->handle);
  251. }
  252. Window::~Window() {
  253. if (glfwGetWindowAttrib(win, GLFW_MAXIMIZED)) {
  254. settings::windowSize = math::Vec();
  255. settings::windowPos = math::Vec();
  256. }
  257. else {
  258. int winWidth, winHeight;
  259. glfwGetWindowSize(win, &winWidth, &winHeight);
  260. int winX, winY;
  261. glfwGetWindowPos(win, &winX, &winY);
  262. settings::windowSize = math::Vec(winWidth, winHeight);
  263. settings::windowPos = math::Vec(winX, winY);
  264. }
  265. #if defined NANOVG_GL2
  266. nvgDeleteGL2(vg);
  267. #elif defined NANOVG_GL3
  268. nvgDeleteGL3(vg);
  269. #elif defined NANOVG_GLES2
  270. nvgDeleteGLES2(vg);
  271. #endif
  272. glfwDestroyWindow(win);
  273. delete internal;
  274. }
  275. void Window::run() {
  276. frame = 0;
  277. while (!glfwWindowShouldClose(win)) {
  278. double frameTime = glfwGetTime();
  279. internal->lastFrameRate = 1.f / float(frameTime - frameTimeStart);
  280. // DEBUG("%.2f Hz", internal->lastFrameRate);
  281. frameTimeStart = frameTime;
  282. // Make event handlers and step() have a clean nanovg context
  283. nvgReset(vg);
  284. bndSetFont(uiFont->handle);
  285. // Poll events
  286. glfwPollEvents();
  287. // In case glfwPollEvents() sets another OpenGL context
  288. glfwMakeContextCurrent(win);
  289. if (settings::frameSwapInterval != internal->frameSwapInterval) {
  290. internal->frameSwapInterval = settings::frameSwapInterval;
  291. glfwSwapInterval(settings::frameSwapInterval);
  292. }
  293. // Call cursorPosCallback every frame, not just when the mouse moves
  294. {
  295. double xpos, ypos;
  296. glfwGetCursorPos(win, &xpos, &ypos);
  297. cursorPosCallback(win, xpos, ypos);
  298. }
  299. gamepad::step();
  300. // Set window title
  301. std::string windowTitle = app::APP_NAME + " v" + app::APP_VERSION;
  302. if (!APP->patch->path.empty()) {
  303. windowTitle += " - ";
  304. if (!APP->history->isSaved())
  305. windowTitle += "*";
  306. windowTitle += string::filename(APP->patch->path);
  307. }
  308. if (windowTitle != internal->lastWindowTitle) {
  309. glfwSetWindowTitle(win, windowTitle.c_str());
  310. internal->lastWindowTitle = windowTitle;
  311. }
  312. // Get desired scaling
  313. float newPixelRatio;
  314. glfwGetWindowContentScale(win, &newPixelRatio, NULL);
  315. newPixelRatio = std::floor(newPixelRatio + 0.5);
  316. if (newPixelRatio != pixelRatio) {
  317. APP->event->handleZoom();
  318. pixelRatio = newPixelRatio;
  319. }
  320. // Get framebuffer/window ratio
  321. int fbWidth, fbHeight;
  322. glfwGetFramebufferSize(win, &fbWidth, &fbHeight);
  323. int winWidth, winHeight;
  324. glfwGetWindowSize(win, &winWidth, &winHeight);
  325. windowRatio = (float)fbWidth / winWidth;
  326. // DEBUG("%f %f %d %d", pixelRatio, windowRatio, fbWidth, winWidth);
  327. // Resize scene
  328. APP->scene->box.size = math::Vec(fbWidth, fbHeight).div(pixelRatio);
  329. // Step scene
  330. APP->scene->step();
  331. // Render scene
  332. bool visible = glfwGetWindowAttrib(win, GLFW_VISIBLE) && !glfwGetWindowAttrib(win, GLFW_ICONIFIED);
  333. if (visible) {
  334. // Update and render
  335. nvgBeginFrame(vg, fbWidth, fbHeight, pixelRatio);
  336. nvgScale(vg, pixelRatio, pixelRatio);
  337. // Draw scene
  338. widget::Widget::DrawArgs args;
  339. args.vg = vg;
  340. args.clipBox = APP->scene->box.zeroPos();
  341. APP->scene->draw(args);
  342. glViewport(0, 0, fbWidth, fbHeight);
  343. glClearColor(0.0, 0.0, 0.0, 1.0);
  344. glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
  345. nvgEndFrame(vg);
  346. }
  347. glfwSwapBuffers(win);
  348. frame++;
  349. }
  350. }
  351. void Window::screenshot(float zoom) {
  352. // Iterate plugins and create directories
  353. std::string screenshotsDir = asset::user("screenshots");
  354. system::createDirectory(screenshotsDir);
  355. for (plugin::Plugin* p : plugin::plugins) {
  356. std::string dir = screenshotsDir + "/" + p->slug;
  357. system::createDirectory(dir);
  358. for (plugin::Model* model : p->models) {
  359. std::string filename = dir + "/" + model->slug + ".png";
  360. // Skip model if screenshot already exists
  361. if (system::isFile(filename))
  362. continue;
  363. INFO("Screenshotting %s %s to %s", p->slug.c_str(), model->slug.c_str(), filename.c_str());
  364. // Create widgets
  365. app::ModuleWidget* mw = model->createModuleWidgetNull();
  366. widget::FramebufferWidget* fb = new widget::FramebufferWidget;
  367. fb->oversample = 2;
  368. fb->addChild(mw);
  369. fb->scale = math::Vec(zoom, zoom);
  370. // Draw to framebuffer
  371. frameTimeStart = glfwGetTime();
  372. fb->step();
  373. nvgluBindFramebuffer(fb->fb);
  374. // Read pixels
  375. int width, height;
  376. nvgImageSize(vg, fb->getImageHandle(), &width, &height);
  377. uint8_t* data = new uint8_t[height * width * 4];
  378. glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, data);
  379. // Flip image vertically
  380. for (int y = 0; y < height / 2; y++) {
  381. int flipY = height - y - 1;
  382. uint8_t tmp[width * 4];
  383. memcpy(tmp, &data[y * width * 4], width * 4);
  384. memcpy(&data[y * width * 4], &data[flipY * width * 4], width * 4);
  385. memcpy(&data[flipY * width * 4], tmp, width * 4);
  386. }
  387. // Write pixels to PNG
  388. stbi_write_png(filename.c_str(), width, height, 4, data, width * 4);
  389. // Cleanup
  390. delete[] data;
  391. nvgluBindFramebuffer(NULL);
  392. delete fb;
  393. }
  394. }
  395. }
  396. void Window::close() {
  397. glfwSetWindowShouldClose(win, GLFW_TRUE);
  398. }
  399. void Window::cursorLock() {
  400. if (settings::allowCursorLock) {
  401. #if defined ARCH_MAC
  402. glfwSetInputMode(win, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
  403. #else
  404. glfwSetInputMode(win, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
  405. #endif
  406. internal->ignoreNextMouseDelta = true;
  407. }
  408. }
  409. void Window::cursorUnlock() {
  410. if (settings::allowCursorLock) {
  411. glfwSetInputMode(win, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
  412. internal->ignoreNextMouseDelta = true;
  413. }
  414. }
  415. int Window::getMods() {
  416. int mods = 0;
  417. if (glfwGetKey(win, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS || glfwGetKey(win, GLFW_KEY_RIGHT_SHIFT) == GLFW_PRESS)
  418. mods |= GLFW_MOD_SHIFT;
  419. if (glfwGetKey(win, GLFW_KEY_LEFT_CONTROL) == GLFW_PRESS || glfwGetKey(win, GLFW_KEY_RIGHT_CONTROL) == GLFW_PRESS)
  420. mods |= GLFW_MOD_CONTROL;
  421. if (glfwGetKey(win, GLFW_KEY_LEFT_ALT) == GLFW_PRESS || glfwGetKey(win, GLFW_KEY_RIGHT_ALT) == GLFW_PRESS)
  422. mods |= GLFW_MOD_ALT;
  423. if (glfwGetKey(win, GLFW_KEY_LEFT_SUPER) == GLFW_PRESS || glfwGetKey(win, GLFW_KEY_RIGHT_SUPER) == GLFW_PRESS)
  424. mods |= GLFW_MOD_SUPER;
  425. return mods;
  426. }
  427. void Window::setFullScreen(bool fullScreen) {
  428. if (!fullScreen) {
  429. glfwSetWindowMonitor(win, NULL, internal->lastWindowX, internal->lastWindowY, internal->lastWindowWidth, internal->lastWindowHeight, GLFW_DONT_CARE);
  430. }
  431. else {
  432. glfwGetWindowPos(win, &internal->lastWindowX, &internal->lastWindowY);
  433. glfwGetWindowSize(win, &internal->lastWindowWidth, &internal->lastWindowHeight);
  434. GLFWmonitor* monitor = glfwGetPrimaryMonitor();
  435. const GLFWvidmode* mode = glfwGetVideoMode(monitor);
  436. glfwSetWindowMonitor(win, monitor, 0, 0, mode->width, mode->height, mode->refreshRate);
  437. }
  438. }
  439. bool Window::isFullScreen() {
  440. GLFWmonitor* monitor = glfwGetWindowMonitor(win);
  441. return monitor != NULL;
  442. }
  443. bool Window::isFrameOverdue() {
  444. if (settings::frameSwapInterval == 0)
  445. return false;
  446. double frameDuration = glfwGetTime() - frameTimeStart;
  447. double frameDeadline = settings::frameSwapInterval / internal->monitorRefreshRate;
  448. return frameDuration > frameDeadline;
  449. }
  450. float Window::getMonitorRefreshRate() {
  451. return internal->monitorRefreshRate;
  452. }
  453. float Window::getLastFrameRate() {
  454. return internal->lastFrameRate;
  455. }
  456. std::shared_ptr<Font> Window::loadFont(const std::string& filename) {
  457. auto sp = fontCache[filename].lock();
  458. if (!sp) {
  459. fontCache[filename] = sp = std::make_shared<Font>();
  460. sp->loadFile(filename, vg);
  461. }
  462. return sp;
  463. }
  464. std::shared_ptr<Image> Window::loadImage(const std::string& filename) {
  465. auto sp = imageCache[filename].lock();
  466. if (!sp) {
  467. imageCache[filename] = sp = std::make_shared<Image>();
  468. sp->loadFile(filename, vg);
  469. }
  470. return sp;
  471. }
  472. std::shared_ptr<Svg> Window::loadSvg(const std::string& filename) {
  473. auto sp = svgCache[filename].lock();
  474. if (!sp) {
  475. svgCache[filename] = sp = std::make_shared<Svg>();
  476. sp->loadFile(filename);
  477. }
  478. return sp;
  479. }
  480. void windowInit() {
  481. int err;
  482. // Set up GLFW
  483. #if defined ARCH_MAC
  484. glfwInitHint(GLFW_COCOA_CHDIR_RESOURCES, GLFW_TRUE);
  485. glfwInitHint(GLFW_COCOA_MENUBAR, GLFW_TRUE);
  486. #endif
  487. glfwSetErrorCallback(errorCallback);
  488. err = glfwInit();
  489. if (err != GLFW_TRUE) {
  490. osdialog_message(OSDIALOG_ERROR, OSDIALOG_OK, "Could not initialize GLFW.");
  491. exit(1);
  492. }
  493. }
  494. void windowDestroy() {
  495. glfwTerminate();
  496. }
  497. } // namespace rack