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.

566 lines
16KB

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