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.

gui.cpp 15KB

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