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.

679 lines
17KB

  1. #include "window.hpp"
  2. #include "app.hpp"
  3. #include "asset.hpp"
  4. #include "gamepad.hpp"
  5. #include "util/color.hpp"
  6. #include <map>
  7. #include <queue>
  8. #include <thread>
  9. #include "osdialog.h"
  10. #define NANOVG_GL2 1
  11. // #define NANOVG_GL3 1
  12. // #define NANOVG_GLES2 1
  13. #define NANOVG_GL_IMPLEMENTATION 1
  14. #include "nanovg_gl.h"
  15. // Hack to get framebuffer objects working on OpenGL 2 (we blindly assume the extension is supported)
  16. #define NANOVG_FBO_VALID 1
  17. #include "nanovg_gl_utils.h"
  18. #define BLENDISH_IMPLEMENTATION
  19. #include "blendish.h"
  20. #define NANOSVG_IMPLEMENTATION
  21. #define NANOSVG_ALL_COLOR_KEYWORDS
  22. #include "nanosvg.h"
  23. #ifdef ARCH_MAC
  24. // For CGAssociateMouseAndMouseCursorPosition
  25. #include <ApplicationServices/ApplicationServices.h>
  26. #endif
  27. namespace rack {
  28. GLFWwindow *gWindow = NULL;
  29. NVGcontext *gVg = NULL;
  30. NVGcontext *gFramebufferVg = NULL;
  31. std::shared_ptr<Font> gGuiFont;
  32. float gPixelRatio = 1.0;
  33. float gWindowRatio = 1.0;
  34. bool gAllowCursorLock = true;
  35. int gGuiFrame;
  36. Vec gMousePos;
  37. std::string lastWindowTitle;
  38. void windowSizeCallback(GLFWwindow* window, int width, int height) {
  39. }
  40. void mouseButtonCallback(GLFWwindow *window, int button, int action, int mods) {
  41. #ifdef ARCH_MAC
  42. // Ctrl-left click --> right click
  43. if (button == GLFW_MOUSE_BUTTON_LEFT) {
  44. if (glfwGetKey(gWindow, GLFW_KEY_LEFT_CONTROL) == GLFW_PRESS || glfwGetKey(gWindow, GLFW_KEY_RIGHT_CONTROL) == GLFW_PRESS) {
  45. button = GLFW_MOUSE_BUTTON_RIGHT;
  46. }
  47. }
  48. #endif
  49. if (action == GLFW_PRESS) {
  50. gTempWidget = NULL;
  51. // onMouseDown
  52. {
  53. EventMouseDown e;
  54. e.pos = gMousePos;
  55. e.button = button;
  56. gScene->onMouseDown(e);
  57. gTempWidget = e.target;
  58. }
  59. if (button == GLFW_MOUSE_BUTTON_LEFT) {
  60. if (gTempWidget) {
  61. // onDragStart
  62. EventDragStart e;
  63. gTempWidget->onDragStart(e);
  64. }
  65. gDraggedWidget = gTempWidget;
  66. if (gTempWidget != gFocusedWidget) {
  67. if (gFocusedWidget) {
  68. // onDefocus
  69. EventDefocus e;
  70. gFocusedWidget->onDefocus(e);
  71. }
  72. gFocusedWidget = NULL;
  73. if (gTempWidget) {
  74. // onFocus
  75. EventFocus e;
  76. gTempWidget->onFocus(e);
  77. if (e.consumed) {
  78. gFocusedWidget = gTempWidget;
  79. }
  80. }
  81. }
  82. }
  83. gTempWidget = NULL;
  84. }
  85. else if (action == GLFW_RELEASE) {
  86. // onMouseUp
  87. gTempWidget = NULL;
  88. {
  89. EventMouseUp e;
  90. e.pos = gMousePos;
  91. e.button = button;
  92. gScene->onMouseUp(e);
  93. gTempWidget = e.target;
  94. }
  95. if (button == GLFW_MOUSE_BUTTON_LEFT) {
  96. if (gDraggedWidget) {
  97. // onDragDrop
  98. EventDragDrop e;
  99. e.origin = gDraggedWidget;
  100. gTempWidget->onDragDrop(e);
  101. }
  102. // gDraggedWidget might have been set to null in the last event, recheck here
  103. if (gDraggedWidget) {
  104. // onDragEnd
  105. EventDragEnd e;
  106. gDraggedWidget->onDragEnd(e);
  107. }
  108. gDraggedWidget = NULL;
  109. gDragHoveredWidget = NULL;
  110. }
  111. gTempWidget = NULL;
  112. }
  113. }
  114. struct MouseButtonArguments {
  115. GLFWwindow *window;
  116. int button;
  117. int action;
  118. int mods;
  119. };
  120. static std::queue<MouseButtonArguments> mouseButtonQueue;
  121. void mouseButtonStickyPop() {
  122. if (!mouseButtonQueue.empty()) {
  123. MouseButtonArguments args = mouseButtonQueue.front();
  124. mouseButtonQueue.pop();
  125. mouseButtonCallback(args.window, args.button, args.action, args.mods);
  126. }
  127. }
  128. void mouseButtonStickyCallback(GLFWwindow *window, int button, int action, int mods) {
  129. // Defer multiple clicks per frame to future frames
  130. MouseButtonArguments args = {window, button, action, mods};
  131. mouseButtonQueue.push(args);
  132. }
  133. void cursorPosCallback(GLFWwindow* window, double xpos, double ypos) {
  134. Vec mousePos = Vec(xpos, ypos).div(gPixelRatio / gWindowRatio).round();
  135. Vec mouseRel = mousePos.minus(gMousePos);
  136. int cursorMode = glfwGetInputMode(gWindow, GLFW_CURSOR);
  137. (void) cursorMode;
  138. #ifdef ARCH_MAC
  139. // Workaround for Mac. We can't use GLFW_CURSOR_DISABLED because it's buggy, so implement it on our own.
  140. // This is not an ideal implementation. For example, if the user drags off the screen, the new mouse position will be clamped.
  141. if (cursorMode == GLFW_CURSOR_HIDDEN) {
  142. // CGSetLocalEventsSuppressionInterval(0.0);
  143. glfwSetCursorPos(gWindow, gMousePos.x, gMousePos.y);
  144. CGAssociateMouseAndMouseCursorPosition(true);
  145. mousePos = gMousePos;
  146. }
  147. // Because sometimes the cursor turns into an arrow when its position is on the boundary of the window
  148. glfwSetCursor(gWindow, NULL);
  149. #endif
  150. gMousePos = mousePos;
  151. gTempWidget = NULL;
  152. // onMouseMove
  153. {
  154. EventMouseMove e;
  155. e.pos = mousePos;
  156. e.mouseRel = mouseRel;
  157. gScene->onMouseMove(e);
  158. gTempWidget = e.target;
  159. }
  160. if (gDraggedWidget) {
  161. // onDragMove
  162. EventDragMove e;
  163. e.mouseRel = mouseRel;
  164. gDraggedWidget->onDragMove(e);
  165. if (gTempWidget != gDragHoveredWidget) {
  166. if (gDragHoveredWidget) {
  167. EventDragEnter e;
  168. e.origin = gDraggedWidget;
  169. gDragHoveredWidget->onDragLeave(e);
  170. }
  171. gDragHoveredWidget = gTempWidget;
  172. if (gDragHoveredWidget) {
  173. EventDragEnter e;
  174. e.origin = gDraggedWidget;
  175. gDragHoveredWidget->onDragEnter(e);
  176. }
  177. }
  178. }
  179. else {
  180. if (gTempWidget != gHoveredWidget) {
  181. if (gHoveredWidget) {
  182. // onMouseLeave
  183. EventMouseLeave e;
  184. gHoveredWidget->onMouseLeave(e);
  185. }
  186. gHoveredWidget = gTempWidget;
  187. if (gHoveredWidget) {
  188. // onMouseEnter
  189. EventMouseEnter e;
  190. gHoveredWidget->onMouseEnter(e);
  191. }
  192. }
  193. }
  194. gTempWidget = NULL;
  195. if (glfwGetMouseButton(gWindow, GLFW_MOUSE_BUTTON_MIDDLE) == GLFW_PRESS) {
  196. // TODO
  197. // Define a new global called gScrollWidget, which remembers the widget where middle-click was first pressed
  198. EventScroll e;
  199. e.pos = mousePos;
  200. e.scrollRel = mouseRel;
  201. gScene->onScroll(e);
  202. }
  203. }
  204. void cursorEnterCallback(GLFWwindow* window, int entered) {
  205. if (!entered) {
  206. if (gHoveredWidget) {
  207. // onMouseLeave
  208. EventMouseLeave e;
  209. gHoveredWidget->onMouseLeave(e);
  210. }
  211. gHoveredWidget = NULL;
  212. }
  213. }
  214. void scrollCallback(GLFWwindow *window, double x, double y) {
  215. Vec scrollRel = Vec(x, y);
  216. #if ARCH_LIN || ARCH_WIN
  217. if (windowIsShiftPressed())
  218. scrollRel = Vec(y, x);
  219. #endif
  220. // onScroll
  221. EventScroll e;
  222. e.pos = gMousePos;
  223. e.scrollRel = scrollRel.mult(50.0);
  224. gScene->onScroll(e);
  225. }
  226. void charCallback(GLFWwindow *window, unsigned int codepoint) {
  227. if (gFocusedWidget) {
  228. // onText
  229. EventText e;
  230. e.codepoint = codepoint;
  231. gFocusedWidget->onText(e);
  232. }
  233. }
  234. void keyCallback(GLFWwindow *window, int key, int scancode, int action, int mods) {
  235. if (action == GLFW_PRESS || action == GLFW_REPEAT) {
  236. if (gFocusedWidget) {
  237. // onKey
  238. EventKey e;
  239. e.key = key;
  240. gFocusedWidget->onKey(e);
  241. if (e.consumed)
  242. return;
  243. }
  244. // onHoverKey
  245. EventHoverKey e;
  246. e.pos = gMousePos;
  247. e.key = key;
  248. gScene->onHoverKey(e);
  249. }
  250. }
  251. void dropCallback(GLFWwindow *window, int count, const char **paths) {
  252. // onPathDrop
  253. EventPathDrop e;
  254. e.pos = gMousePos;
  255. for (int i = 0; i < count; i++) {
  256. e.paths.push_back(paths[i]);
  257. }
  258. gScene->onPathDrop(e);
  259. }
  260. void errorCallback(int error, const char *description) {
  261. warn("GLFW error %d: %s", error, description);
  262. }
  263. void renderGui() {
  264. int width, height;
  265. glfwGetFramebufferSize(gWindow, &width, &height);
  266. // Update and render
  267. nvgBeginFrame(gVg, width, height, gPixelRatio);
  268. nvgReset(gVg);
  269. nvgScale(gVg, gPixelRatio, gPixelRatio);
  270. gScene->draw(gVg);
  271. glViewport(0, 0, width, height);
  272. glClearColor(0.0, 0.0, 0.0, 1.0);
  273. glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
  274. nvgEndFrame(gVg);
  275. glfwSwapBuffers(gWindow);
  276. }
  277. void windowInit() {
  278. int err;
  279. // Set up GLFW
  280. glfwSetErrorCallback(errorCallback);
  281. err = glfwInit();
  282. if (err != GLFW_TRUE) {
  283. osdialog_message(OSDIALOG_ERROR, OSDIALOG_OK, "Could not initialize GLFW.");
  284. exit(1);
  285. }
  286. #if defined NANOVG_GL2
  287. glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
  288. glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
  289. #elif defined NANOVG_GL3
  290. glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
  291. glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
  292. glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
  293. glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
  294. #endif
  295. glfwWindowHint(GLFW_MAXIMIZED, GLFW_TRUE);
  296. glfwWindowHint(GLFW_DOUBLEBUFFER, GLFW_TRUE);
  297. lastWindowTitle = "";
  298. gWindow = glfwCreateWindow(640, 480, lastWindowTitle.c_str(), NULL, NULL);
  299. if (!gWindow) {
  300. 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.");
  301. exit(1);
  302. }
  303. glfwMakeContextCurrent(gWindow);
  304. glfwSwapInterval(1);
  305. glfwSetWindowSizeCallback(gWindow, windowSizeCallback);
  306. glfwSetMouseButtonCallback(gWindow, mouseButtonStickyCallback);
  307. // Call this ourselves, but on every frame instead of only when the mouse moves
  308. // glfwSetCursorPosCallback(gWindow, cursorPosCallback);
  309. glfwSetCursorEnterCallback(gWindow, cursorEnterCallback);
  310. glfwSetScrollCallback(gWindow, scrollCallback);
  311. glfwSetCharCallback(gWindow, charCallback);
  312. glfwSetKeyCallback(gWindow, keyCallback);
  313. glfwSetDropCallback(gWindow, dropCallback);
  314. // Set up GLEW
  315. glewExperimental = GL_TRUE;
  316. err = glewInit();
  317. if (err != GLEW_OK) {
  318. 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.");
  319. exit(1);
  320. }
  321. // GLEW generates GL error because it calls glGetString(GL_EXTENSIONS), we'll consume it here.
  322. glGetError();
  323. glfwSetWindowSizeLimits(gWindow, 640, 480, GLFW_DONT_CARE, GLFW_DONT_CARE);
  324. // Set up NanoVG
  325. #if defined NANOVG_GL2
  326. gVg = nvgCreateGL2(NVG_ANTIALIAS);
  327. #elif defined NANOVG_GL3
  328. gVg = nvgCreateGL3(NVG_ANTIALIAS);
  329. #elif defined NANOVG_GLES2
  330. gVg = nvgCreateGLES2(NVG_ANTIALIAS);
  331. #endif
  332. assert(gVg);
  333. #if defined NANOVG_GL2
  334. gFramebufferVg = nvgCreateGL2(NVG_ANTIALIAS);
  335. #elif defined NANOVG_GL3
  336. gFramebufferVg = nvgCreateGL3(NVG_ANTIALIAS);
  337. #elif defined NANOVG_GLES2
  338. gFramebufferVg = nvgCreateGLES2(NVG_ANTIALIAS);
  339. #endif
  340. assert(gFramebufferVg);
  341. // Set up Blendish
  342. gGuiFont = Font::load(assetGlobal("res/fonts/DejaVuSans.ttf"));
  343. bndSetFont(gGuiFont->handle);
  344. // bndSetIconImage(loadImage(assetGlobal("res/icons.png")));
  345. windowSetTheme(nvgRGB(0x33, 0x33, 0x33), nvgRGB(0xf0, 0xf0, 0xf0));
  346. }
  347. void windowDestroy() {
  348. gGuiFont.reset();
  349. #if defined NANOVG_GL2
  350. nvgDeleteGL2(gVg);
  351. #elif defined NANOVG_GL3
  352. nvgDeleteGL3(gVg);
  353. #elif defined NANOVG_GLES2
  354. nvgDeleteGLES2(gVg);
  355. #endif
  356. #if defined NANOVG_GL2
  357. nvgDeleteGL2(gFramebufferVg);
  358. #elif defined NANOVG_GL3
  359. nvgDeleteGL3(gFramebufferVg);
  360. #elif defined NANOVG_GLES2
  361. nvgDeleteGLES2(gFramebufferVg);
  362. #endif
  363. glfwDestroyWindow(gWindow);
  364. glfwTerminate();
  365. }
  366. void windowRun() {
  367. assert(gWindow);
  368. gGuiFrame = 0;
  369. while(!glfwWindowShouldClose(gWindow)) {
  370. double startTime = glfwGetTime();
  371. gGuiFrame++;
  372. // Poll events
  373. glfwPollEvents();
  374. {
  375. double xpos, ypos;
  376. glfwGetCursorPos(gWindow, &xpos, &ypos);
  377. cursorPosCallback(gWindow, xpos, ypos);
  378. }
  379. mouseButtonStickyPop();
  380. gamepadStep();
  381. // Set window title
  382. std::string windowTitle;
  383. windowTitle = gApplicationName;
  384. windowTitle += " ";
  385. windowTitle += gApplicationVersion;
  386. if (!gRackWidget->lastPath.empty()) {
  387. windowTitle += " - ";
  388. windowTitle += stringFilename(gRackWidget->lastPath);
  389. }
  390. if (windowTitle != lastWindowTitle) {
  391. glfwSetWindowTitle(gWindow, windowTitle.c_str());
  392. lastWindowTitle = windowTitle;
  393. }
  394. // Get desired scaling
  395. float pixelRatio;
  396. glfwGetWindowContentScale(gWindow, &pixelRatio, NULL);
  397. pixelRatio = roundf(pixelRatio);
  398. if (pixelRatio != gPixelRatio) {
  399. EventZoom eZoom;
  400. gScene->onZoom(eZoom);
  401. gPixelRatio = pixelRatio;
  402. }
  403. // Get framebuffer/window ratio
  404. int width, height;
  405. glfwGetFramebufferSize(gWindow, &width, &height);
  406. int windowWidth, windowHeight;
  407. glfwGetWindowSize(gWindow, &windowWidth, &windowHeight);
  408. gWindowRatio = (float)width / windowWidth;
  409. gScene->box.size = Vec(width, height).div(gPixelRatio);
  410. // Step scene
  411. gScene->step();
  412. // Render
  413. bool visible = glfwGetWindowAttrib(gWindow, GLFW_VISIBLE) && !glfwGetWindowAttrib(gWindow, GLFW_ICONIFIED);
  414. if (visible) {
  415. renderGui();
  416. }
  417. // Limit framerate manually if vsync isn't working
  418. double endTime = glfwGetTime();
  419. double frameTime = endTime - startTime;
  420. double minTime = 1.0 / 90.0;
  421. if (frameTime < minTime) {
  422. std::this_thread::sleep_for(std::chrono::duration<double>(minTime - frameTime));
  423. }
  424. endTime = glfwGetTime();
  425. // info("%lf fps", 1.0 / (endTime - startTime));
  426. }
  427. }
  428. void windowClose() {
  429. glfwSetWindowShouldClose(gWindow, GLFW_TRUE);
  430. }
  431. void windowCursorLock() {
  432. if (gAllowCursorLock) {
  433. #ifdef ARCH_MAC
  434. glfwSetInputMode(gWindow, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
  435. #else
  436. glfwSetInputMode(gWindow, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
  437. #endif
  438. }
  439. }
  440. void windowCursorUnlock() {
  441. if (gAllowCursorLock) {
  442. glfwSetInputMode(gWindow, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
  443. }
  444. }
  445. bool windowIsModPressed() {
  446. #ifdef ARCH_MAC
  447. return glfwGetKey(gWindow, GLFW_KEY_LEFT_SUPER) == GLFW_PRESS || glfwGetKey(gWindow, GLFW_KEY_RIGHT_SUPER) == GLFW_PRESS;
  448. #else
  449. return glfwGetKey(gWindow, GLFW_KEY_LEFT_CONTROL) == GLFW_PRESS || glfwGetKey(gWindow, GLFW_KEY_RIGHT_CONTROL) == GLFW_PRESS;
  450. #endif
  451. }
  452. bool windowIsShiftPressed() {
  453. return glfwGetKey(gWindow, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS || glfwGetKey(gWindow, GLFW_KEY_RIGHT_SHIFT) == GLFW_PRESS;
  454. }
  455. Vec windowGetWindowSize() {
  456. int width, height;
  457. glfwGetWindowSize(gWindow, &width, &height);
  458. return Vec(width, height);
  459. }
  460. void windowSetWindowSize(Vec size) {
  461. int width = size.x;
  462. int height = size.y;
  463. glfwSetWindowSize(gWindow, width, height);
  464. }
  465. Vec windowGetWindowPos() {
  466. int x, y;
  467. glfwGetWindowPos(gWindow, &x, &y);
  468. return Vec(x, y);
  469. }
  470. void windowSetWindowPos(Vec pos) {
  471. int x = pos.x;
  472. int y = pos.y;
  473. glfwSetWindowPos(gWindow, x, y);
  474. }
  475. bool windowIsMaximized() {
  476. return glfwGetWindowAttrib(gWindow, GLFW_MAXIMIZED);
  477. }
  478. void windowSetTheme(NVGcolor bg, NVGcolor fg) {
  479. // Assume dark background and light foreground
  480. BNDwidgetTheme w;
  481. w.outlineColor = bg;
  482. w.itemColor = fg;
  483. w.innerColor = bg;
  484. w.innerSelectedColor = colorPlus(bg, nvgRGB(0x30, 0x30, 0x30));
  485. w.textColor = fg;
  486. w.textSelectedColor = fg;
  487. w.shadeTop = 0;
  488. w.shadeDown = 0;
  489. BNDtheme t;
  490. t.backgroundColor = colorPlus(bg, nvgRGB(0x30, 0x30, 0x30));
  491. t.regularTheme = w;
  492. t.toolTheme = w;
  493. t.radioTheme = w;
  494. t.textFieldTheme = w;
  495. t.optionTheme = w;
  496. t.choiceTheme = w;
  497. t.numberFieldTheme = w;
  498. t.sliderTheme = w;
  499. t.scrollBarTheme = w;
  500. t.tooltipTheme = w;
  501. t.menuTheme = w;
  502. t.menuItemTheme = w;
  503. t.sliderTheme.itemColor = bg;
  504. t.sliderTheme.innerColor = colorPlus(bg, nvgRGB(0x50, 0x50, 0x50));
  505. t.sliderTheme.innerSelectedColor = colorPlus(bg, nvgRGB(0x60, 0x60, 0x60));
  506. t.textFieldTheme = t.sliderTheme;
  507. t.textFieldTheme.textColor = colorMinus(bg, nvgRGB(0x20, 0x20, 0x20));
  508. t.textFieldTheme.textSelectedColor = t.textFieldTheme.textColor;
  509. t.scrollBarTheme.itemColor = colorPlus(bg, nvgRGB(0x50, 0x50, 0x50));
  510. t.scrollBarTheme.innerColor = bg;
  511. t.menuTheme.innerColor = colorMinus(bg, nvgRGB(0x10, 0x10, 0x10));
  512. t.menuTheme.textColor = colorMinus(fg, nvgRGB(0x50, 0x50, 0x50));
  513. t.menuTheme.textSelectedColor = t.menuTheme.textColor;
  514. bndSetTheme(t);
  515. }
  516. ////////////////////
  517. // resources
  518. ////////////////////
  519. Font::Font(const std::string &filename) {
  520. handle = nvgCreateFont(gVg, filename.c_str(), filename.c_str());
  521. if (handle >= 0) {
  522. info("Loaded font %s", filename.c_str());
  523. }
  524. else {
  525. warn("Failed to load font %s", filename.c_str());
  526. }
  527. }
  528. Font::~Font() {
  529. // There is no NanoVG deleteFont() function yet, so do nothing
  530. }
  531. std::shared_ptr<Font> Font::load(const std::string &filename) {
  532. static std::map<std::string, std::weak_ptr<Font>> cache;
  533. auto sp = cache[filename].lock();
  534. if (!sp)
  535. cache[filename] = sp = std::make_shared<Font>(filename);
  536. return sp;
  537. }
  538. ////////////////////
  539. // Image
  540. ////////////////////
  541. Image::Image(const std::string &filename) {
  542. handle = nvgCreateImage(gVg, filename.c_str(), NVG_IMAGE_REPEATX | NVG_IMAGE_REPEATY);
  543. if (handle > 0) {
  544. info("Loaded image %s", filename.c_str());
  545. }
  546. else {
  547. warn("Failed to load image %s", filename.c_str());
  548. }
  549. }
  550. Image::~Image() {
  551. // TODO What if handle is invalid?
  552. nvgDeleteImage(gVg, handle);
  553. }
  554. std::shared_ptr<Image> Image::load(const std::string &filename) {
  555. static std::map<std::string, std::weak_ptr<Image>> cache;
  556. auto sp = cache[filename].lock();
  557. if (!sp)
  558. cache[filename] = sp = std::make_shared<Image>(filename);
  559. return sp;
  560. }
  561. ////////////////////
  562. // SVG
  563. ////////////////////
  564. SVG::SVG(const std::string &filename) {
  565. handle = nsvgParseFromFile(filename.c_str(), "px", SVG_DPI);
  566. if (handle) {
  567. info("Loaded SVG %s", filename.c_str());
  568. }
  569. else {
  570. warn("Failed to load SVG %s", filename.c_str());
  571. }
  572. }
  573. SVG::~SVG() {
  574. nsvgDelete(handle);
  575. }
  576. std::shared_ptr<SVG> SVG::load(const std::string &filename) {
  577. static std::map<std::string, std::weak_ptr<SVG>> cache;
  578. auto sp = cache[filename].lock();
  579. if (!sp)
  580. cache[filename] = sp = std::make_shared<SVG>(filename);
  581. return sp;
  582. }
  583. } // namespace rack