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.

672 lines
16KB

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