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.

677 lines
17KB

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