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.

690 lines
17KB

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