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.

843 lines
27KB

  1. /*
  2. * DISTRHO Cardinal Plugin
  3. * Copyright (C) 2021-2022 Filipe Coelho <falktx@falktx.com>
  4. *
  5. * This program is free software; you can redistribute it and/or
  6. * modify it under the terms of the GNU General Public License as
  7. * published by the Free Software Foundation; either version 3 of
  8. * the License, or any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * For a full copy of the GNU General Public License see the LICENSE file.
  16. */
  17. #include <app/MenuBar.hpp>
  18. #include <app/Scene.hpp>
  19. #include <asset.hpp>
  20. #include <context.hpp>
  21. #include <engine/Engine.hpp>
  22. #include <helpers.hpp>
  23. #include <patch.hpp>
  24. #include <settings.hpp>
  25. #include <string.hpp>
  26. #include <system.hpp>
  27. #include <ui/Button.hpp>
  28. #include <ui/MenuItem.hpp>
  29. #include <ui/MenuSeparator.hpp>
  30. #include <window/Window.hpp>
  31. #ifdef NDEBUG
  32. # undef DEBUG
  33. #endif
  34. #include <Application.hpp>
  35. #include "AsyncDialog.hpp"
  36. #include "PluginContext.hpp"
  37. #include "WindowParameters.hpp"
  38. GLFWAPI int glfwGetKeyScancode(int) { return 0; }
  39. GLFWAPI const char* glfwGetClipboardString(GLFWwindow*)
  40. {
  41. CardinalPluginContext* const context = static_cast<CardinalPluginContext*>(APP);
  42. DISTRHO_SAFE_ASSERT_RETURN(context != nullptr, nullptr);
  43. DISTRHO_SAFE_ASSERT_RETURN(context->ui != nullptr, nullptr);
  44. const char* mimeType = nullptr;
  45. size_t dataSize = 0;
  46. if (const void* const clipboard = context->ui->getClipboard(mimeType, dataSize))
  47. {
  48. if (mimeType == nullptr || std::strcmp(mimeType, "text/plain") != 0)
  49. return nullptr;
  50. return static_cast<const char*>(clipboard);
  51. }
  52. return nullptr;
  53. }
  54. GLFWAPI void glfwSetClipboardString(GLFWwindow*, const char* const text)
  55. {
  56. DISTRHO_SAFE_ASSERT_RETURN(text != nullptr,);
  57. CardinalPluginContext* const context = static_cast<CardinalPluginContext*>(APP);
  58. DISTRHO_SAFE_ASSERT_RETURN(context != nullptr,);
  59. DISTRHO_SAFE_ASSERT_RETURN(context->ui != nullptr,);
  60. context->ui->setClipboard(nullptr, text, std::strlen(text)+1);
  61. }
  62. GLFWAPI void glfwSetCursor(GLFWwindow*, GLFWcursor* const cursor)
  63. {
  64. CardinalPluginContext* const context = static_cast<CardinalPluginContext*>(APP);
  65. DISTRHO_SAFE_ASSERT_RETURN(context != nullptr,);
  66. DISTRHO_SAFE_ASSERT_RETURN(context->ui != nullptr,);
  67. context->ui->setCursor(cursor != nullptr ? kMouseCursorDiagonal : kMouseCursorArrow);
  68. }
  69. GLFWAPI double glfwGetTime(void)
  70. {
  71. CardinalPluginContext* const context = static_cast<CardinalPluginContext*>(APP);
  72. DISTRHO_SAFE_ASSERT_RETURN(context != nullptr, 0.0);
  73. DISTRHO_SAFE_ASSERT_RETURN(context->ui != nullptr, 0.0);
  74. return context->ui->getApp().getTime();
  75. }
  76. GLFWAPI const char* glfwGetKeyName(const int key, int)
  77. {
  78. switch (key)
  79. {
  80. case '\"': return "\"";
  81. case '\'': return "\'";
  82. case '\\': return "\\";
  83. case ' ': return " ";
  84. case '!': return "!";
  85. case '#': return "#";
  86. case '$': return "$";
  87. case '%': return "%";
  88. case '&': return "&";
  89. case '(': return "(";
  90. case ')': return ")";
  91. case '*': return "*";
  92. case '+': return "+";
  93. case ',': return ",";
  94. case '-': return "-";
  95. case '.': return ".";
  96. case '/': return "/";
  97. case '0': return "0";
  98. case '1': return "1";
  99. case '2': return "2";
  100. case '3': return "3";
  101. case '4': return "4";
  102. case '5': return "5";
  103. case '6': return "6";
  104. case '7': return "7";
  105. case '8': return "8";
  106. case '9': return "9";
  107. case ':': return ":";
  108. case ';': return ";";
  109. case '<': return "<";
  110. case '=': return "=";
  111. case '>': return ">";
  112. case '?': return "?";
  113. case '@': return "@";
  114. /* Rack expects lowercase, forced below
  115. case 'A': return "A";
  116. case 'B': return "B";
  117. case 'C': return "C";
  118. case 'D': return "D";
  119. case 'E': return "E";
  120. case 'F': return "F";
  121. case 'G': return "G";
  122. case 'H': return "H";
  123. case 'I': return "I";
  124. case 'J': return "J";
  125. case 'K': return "K";
  126. case 'L': return "L";
  127. case 'M': return "M";
  128. case 'N': return "N";
  129. case 'O': return "O";
  130. case 'P': return "P";
  131. case 'Q': return "Q";
  132. case 'R': return "R";
  133. case 'S': return "S";
  134. case 'T': return "T";
  135. case 'U': return "U";
  136. case 'V': return "V";
  137. case 'W': return "W";
  138. case 'X': return "X";
  139. case 'Y': return "Y";
  140. case 'Z': return "Z";
  141. */
  142. case '[': return "[";
  143. case ']': return "]";
  144. case '^': return "^";
  145. case '_': return "_";
  146. case '`': return "`";
  147. case 'a': case 'A': return "a";
  148. case 'b': case 'B': return "b";
  149. case 'c': case 'C': return "c";
  150. case 'd': case 'D': return "d";
  151. case 'e': case 'E': return "e";
  152. case 'f': case 'F': return "f";
  153. case 'g': case 'G': return "g";
  154. case 'h': case 'H': return "h";
  155. case 'i': case 'I': return "i";
  156. case 'j': case 'J': return "j";
  157. case 'k': case 'K': return "k";
  158. case 'l': case 'L': return "l";
  159. case 'm': case 'M': return "m";
  160. case 'n': case 'N': return "n";
  161. case 'o': case 'O': return "o";
  162. case 'p': case 'P': return "p";
  163. case 'q': case 'Q': return "q";
  164. case 'r': case 'R': return "r";
  165. case 's': case 'S': return "s";
  166. case 't': case 'T': return "t";
  167. case 'u': case 'U': return "u";
  168. case 'v': case 'V': return "v";
  169. case 'w': case 'W': return "w";
  170. case 'x': case 'X': return "x";
  171. case 'y': case 'Y': return "y";
  172. case 'z': case 'Z': return "z";
  173. default: return nullptr;
  174. }
  175. }
  176. namespace rack {
  177. namespace app {
  178. widget::Widget* createMenuBar(bool isStandalone);
  179. }
  180. namespace window {
  181. void WindowSetPluginUI(Window* window, DISTRHO_NAMESPACE::UI* ui);
  182. void WindowSetMods(Window* window, int mods);
  183. }
  184. }
  185. START_NAMESPACE_DISTRHO
  186. // -----------------------------------------------------------------------------------------------------------
  187. bool CardinalPluginContext::addIdleCallback(IdleCallback* const cb) const
  188. {
  189. if (ui == nullptr)
  190. return false;
  191. ui->addIdleCallback(cb);
  192. return true;
  193. }
  194. void CardinalPluginContext::removeIdleCallback(IdleCallback* const cb) const
  195. {
  196. if (ui == nullptr)
  197. return;
  198. ui->removeIdleCallback(cb);
  199. }
  200. void handleHostParameterDrag(const CardinalPluginContext* pcontext, uint index, bool started)
  201. {
  202. DISTRHO_SAFE_ASSERT_RETURN(pcontext->ui != nullptr,);
  203. if (started)
  204. {
  205. pcontext->ui->editParameter(index, true);
  206. pcontext->ui->setParameterValue(index, pcontext->parameters[index]);
  207. }
  208. else
  209. {
  210. pcontext->ui->editParameter(index, false);
  211. }
  212. }
  213. // -----------------------------------------------------------------------------------------------------------
  214. class CardinalUI : public CardinalBaseUI,
  215. public WindowParametersCallback
  216. {
  217. rack::math::Vec lastMousePos;
  218. WindowParameters windowParameters;
  219. int rateLimitStep = 0;
  220. bool firstIdle = true;
  221. struct ScopedContext {
  222. CardinalPluginContext* const context;
  223. ScopedContext(CardinalUI* const ui)
  224. : context(ui->context)
  225. {
  226. rack::contextSet(context);
  227. WindowParametersRestore(context->window);
  228. }
  229. ScopedContext(CardinalUI* const ui, const int mods)
  230. : context(ui->context)
  231. {
  232. rack::contextSet(context);
  233. rack::window::WindowSetMods(context->window, mods);
  234. WindowParametersRestore(context->window);
  235. }
  236. ~ScopedContext()
  237. {
  238. if (context->window != nullptr)
  239. WindowParametersSave(context->window);
  240. rack::contextSet(nullptr);
  241. }
  242. };
  243. public:
  244. CardinalUI()
  245. : CardinalBaseUI(1228, 666)
  246. {
  247. Window& window(getWindow());
  248. window.setIgnoringKeyRepeat(true);
  249. context->nativeWindowId = window.getNativeWindowHandle();
  250. const double scaleFactor = getScaleFactor();
  251. setGeometryConstraints(648 * scaleFactor, 538 * scaleFactor);
  252. if (scaleFactor != 1.0)
  253. setSize(1228 * scaleFactor, 666 * scaleFactor);
  254. rack::contextSet(context);
  255. rack::window::WindowSetPluginUI(context->window, this);
  256. if (rack::widget::Widget* const menuBar = context->scene->menuBar)
  257. {
  258. context->scene->removeChild(menuBar);
  259. delete menuBar;
  260. }
  261. context->scene->menuBar = rack::app::createMenuBar(getApp().isStandalone());
  262. context->scene->addChildBelow(context->scene->menuBar, context->scene->rackScroll);
  263. // hide "Browse VCV Library" button
  264. rack::widget::Widget* const browser = context->scene->browser->children.back();
  265. rack::widget::Widget* const headerLayout = browser->children.front();
  266. rack::widget::Widget* const favoriteButton = *std::next(headerLayout->children.begin(), 3);
  267. rack::widget::Widget* const libraryButton = headerLayout->children.back();
  268. favoriteButton->hide();
  269. libraryButton->hide();
  270. // Report to user if something is wrong with the installation
  271. std::string errorMessage;
  272. if (rack::asset::systemDir.empty())
  273. {
  274. errorMessage = "Failed to locate Cardinal plugin bundle.\n"
  275. "Install Cardinal with its plugin bundle folder intact and try again.";
  276. }
  277. else if (! rack::system::exists(rack::asset::systemDir))
  278. {
  279. errorMessage = rack::string::f("System directory \"%s\" does not exist. "
  280. "Make sure Cardinal was downloaded and installed correctly.",
  281. rack::asset::systemDir.c_str());
  282. }
  283. if (! errorMessage.empty())
  284. asyncDialog::create(errorMessage.c_str());
  285. context->window->step();
  286. rack::contextSet(nullptr);
  287. WindowParametersSetCallback(context->window, this);
  288. }
  289. ~CardinalUI() override
  290. {
  291. rack::contextSet(context);
  292. context->nativeWindowId = 0;
  293. if (rack::widget::Widget* const menuBar = context->scene->menuBar)
  294. {
  295. context->scene->removeChild(menuBar);
  296. delete menuBar;
  297. }
  298. context->scene->menuBar = rack::app::createMenuBar();
  299. context->scene->addChildBelow(context->scene->menuBar, context->scene->rackScroll);
  300. rack::window::WindowSetPluginUI(context->window, nullptr);
  301. rack::contextSet(nullptr);
  302. }
  303. void onNanoDisplay() override
  304. {
  305. const ScopedContext sc(this);
  306. context->window->step();
  307. }
  308. void uiIdle() override
  309. {
  310. if (firstIdle)
  311. {
  312. firstIdle = false;
  313. getWindow().focus();
  314. }
  315. if (filebrowserhandle != nullptr && fileBrowserIdle(filebrowserhandle))
  316. {
  317. {
  318. const char* const path = fileBrowserGetPath(filebrowserhandle);
  319. const ScopedContext sc(this);
  320. filebrowseraction(path != nullptr ? strdup(path) : nullptr);
  321. }
  322. fileBrowserClose(filebrowserhandle);
  323. filebrowseraction = nullptr;
  324. filebrowserhandle = nullptr;
  325. }
  326. #ifndef DISTRHO_OS_MAC
  327. if (windowParameters.rateLimit != 0 && ++rateLimitStep % (windowParameters.rateLimit * 2))
  328. return;
  329. rateLimitStep = 0;
  330. repaint();
  331. #endif
  332. }
  333. void WindowParametersChanged(const WindowParameterList param, float value) override
  334. {
  335. float mult = 1.0f;
  336. switch (param)
  337. {
  338. case kWindowParameterShowTooltips:
  339. windowParameters.tooltips = value > 0.5f;
  340. break;
  341. case kWindowParameterCableOpacity:
  342. mult = 100.0f;
  343. windowParameters.cableOpacity = value;
  344. break;
  345. case kWindowParameterCableTension:
  346. mult = 100.0f;
  347. windowParameters.cableTension = value;
  348. break;
  349. case kWindowParameterRackBrightness:
  350. mult = 100.0f;
  351. windowParameters.rackBrightness = value;
  352. break;
  353. case kWindowParameterHaloBrightness:
  354. mult = 100.0f;
  355. windowParameters.haloBrightness = value;
  356. break;
  357. case kWindowParameterKnobMode:
  358. switch (static_cast<int>(value + 0.5f))
  359. {
  360. case rack::settings::KNOB_MODE_LINEAR:
  361. value = 0;
  362. windowParameters.knobMode = rack::settings::KNOB_MODE_LINEAR;
  363. break;
  364. case rack::settings::KNOB_MODE_ROTARY_ABSOLUTE:
  365. value = 1;
  366. windowParameters.knobMode = rack::settings::KNOB_MODE_ROTARY_ABSOLUTE;
  367. break;
  368. case rack::settings::KNOB_MODE_ROTARY_RELATIVE:
  369. value = 2;
  370. windowParameters.knobMode = rack::settings::KNOB_MODE_ROTARY_RELATIVE;
  371. break;
  372. }
  373. break;
  374. case kWindowParameterWheelKnobControl:
  375. windowParameters.knobScroll = value > 0.5f;
  376. break;
  377. case kWindowParameterWheelSensitivity:
  378. mult = 1000.0f;
  379. windowParameters.knobScrollSensitivity = value;
  380. break;
  381. case kWindowParameterLockModulePositions:
  382. windowParameters.lockModules = value > 0.5f;
  383. break;
  384. case kWindowParameterUpdateRateLimit:
  385. windowParameters.rateLimit = static_cast<int>(value + 0.5f);
  386. rateLimitStep = 0;
  387. break;
  388. default:
  389. return;
  390. }
  391. setParameterValue(kModuleParameters + param + 1, value * mult);
  392. }
  393. protected:
  394. /* --------------------------------------------------------------------------------------------------------
  395. * DSP/Plugin Callbacks */
  396. /**
  397. A parameter has changed on the plugin side.
  398. This is called by the host to inform the UI about parameter changes.
  399. */
  400. void parameterChanged(const uint32_t index, const float value) override
  401. {
  402. // host mapped parameters + bypass
  403. if (index <= kModuleParameters)
  404. return;
  405. switch (index - kModuleParameters - 1)
  406. {
  407. case kWindowParameterShowTooltips:
  408. windowParameters.tooltips = value > 0.5f;
  409. break;
  410. case kWindowParameterCableOpacity:
  411. windowParameters.cableOpacity = value / 100.0f;
  412. break;
  413. case kWindowParameterCableTension:
  414. windowParameters.cableTension = value / 100.0f;
  415. break;
  416. case kWindowParameterRackBrightness:
  417. windowParameters.rackBrightness = value / 100.0f;
  418. break;
  419. case kWindowParameterHaloBrightness:
  420. windowParameters.haloBrightness = value / 100.0f;
  421. break;
  422. case kWindowParameterKnobMode:
  423. switch (static_cast<int>(value + 0.5f))
  424. {
  425. case 0:
  426. windowParameters.knobMode = rack::settings::KNOB_MODE_LINEAR;
  427. break;
  428. case 1:
  429. windowParameters.knobMode = rack::settings::KNOB_MODE_ROTARY_ABSOLUTE;
  430. break;
  431. case 2:
  432. windowParameters.knobMode = rack::settings::KNOB_MODE_ROTARY_RELATIVE;
  433. break;
  434. }
  435. break;
  436. case kWindowParameterWheelKnobControl:
  437. windowParameters.knobScroll = value > 0.5f;
  438. break;
  439. case kWindowParameterWheelSensitivity:
  440. windowParameters.knobScrollSensitivity = value / 1000.0f;
  441. break;
  442. case kWindowParameterLockModulePositions:
  443. windowParameters.lockModules = value > 0.5f;
  444. break;
  445. case kWindowParameterUpdateRateLimit:
  446. windowParameters.rateLimit = static_cast<int>(value + 0.5f);
  447. rateLimitStep = 0;
  448. break;
  449. default:
  450. return;
  451. }
  452. WindowParametersSetValues(context->window, windowParameters);
  453. }
  454. void stateChanged(const char* key, const char* value) override
  455. {
  456. if (std::strcmp(key, "windowSize") != 0)
  457. return;
  458. int width = 0;
  459. int height = 0;
  460. std::sscanf(value, "%i:%i", &width, &height);
  461. if (width > 0 && height > 0)
  462. {
  463. const double scaleFactor = getScaleFactor();
  464. setSize(width * scaleFactor, height * scaleFactor);
  465. }
  466. }
  467. // -------------------------------------------------------------------------------------------------------
  468. static int glfwMods(const uint mod) noexcept
  469. {
  470. int mods = 0;
  471. if (mod & kModifierControl)
  472. mods |= GLFW_MOD_CONTROL;
  473. if (mod & kModifierShift)
  474. mods |= GLFW_MOD_SHIFT;
  475. if (mod & kModifierAlt)
  476. mods |= GLFW_MOD_ALT;
  477. if (mod & kModifierSuper)
  478. mods |= GLFW_MOD_SUPER;
  479. /*
  480. if (glfwGetKey(win, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS || glfwGetKey(win, GLFW_KEY_RIGHT_SHIFT) == GLFW_PRESS)
  481. mods |= GLFW_MOD_SHIFT;
  482. if (glfwGetKey(win, GLFW_KEY_LEFT_CONTROL) == GLFW_PRESS || glfwGetKey(win, GLFW_KEY_RIGHT_CONTROL) == GLFW_PRESS)
  483. mods |= GLFW_MOD_CONTROL;
  484. if (glfwGetKey(win, GLFW_KEY_LEFT_ALT) == GLFW_PRESS || glfwGetKey(win, GLFW_KEY_RIGHT_ALT) == GLFW_PRESS)
  485. mods |= GLFW_MOD_ALT;
  486. if (glfwGetKey(win, GLFW_KEY_LEFT_SUPER) == GLFW_PRESS || glfwGetKey(win, GLFW_KEY_RIGHT_SUPER) == GLFW_PRESS)
  487. mods |= GLFW_MOD_SUPER;
  488. */
  489. return mods;
  490. }
  491. bool onMouse(const MouseEvent& ev) override
  492. {
  493. const int action = ev.press ? GLFW_PRESS : GLFW_RELEASE;
  494. const int mods = glfwMods(ev.mod);
  495. int button;
  496. switch (ev.button)
  497. {
  498. case 1: button = GLFW_MOUSE_BUTTON_LEFT; break;
  499. #ifdef DISTRHO_OS_MAC
  500. case 2: button = GLFW_MOUSE_BUTTON_RIGHT; break;
  501. case 3: button = GLFW_MOUSE_BUTTON_MIDDLE; break;
  502. #else
  503. case 2: button = GLFW_MOUSE_BUTTON_MIDDLE; break;
  504. case 3: button = GLFW_MOUSE_BUTTON_RIGHT; break;
  505. #endif
  506. default:
  507. button = ev.button;
  508. break;
  509. }
  510. /*
  511. #if defined ARCH_MAC
  512. // Remap Ctrl-left click to right click on Mac
  513. if (button == GLFW_MOUSE_BUTTON_LEFT && (mods & RACK_MOD_MASK) == GLFW_MOD_CONTROL) {
  514. button = GLFW_MOUSE_BUTTON_RIGHT;
  515. mods &= ~GLFW_MOD_CONTROL;
  516. }
  517. // Remap Ctrl-shift-left click to middle click on Mac
  518. if (button == GLFW_MOUSE_BUTTON_LEFT && (mods & RACK_MOD_MASK) == (GLFW_MOD_CONTROL | GLFW_MOD_SHIFT)) {
  519. button = GLFW_MOUSE_BUTTON_MIDDLE;
  520. mods &= ~(GLFW_MOD_CONTROL | GLFW_MOD_SHIFT);
  521. }
  522. #endif
  523. */
  524. const ScopedContext sc(this, mods);
  525. return context->event->handleButton(lastMousePos, button, action, mods);
  526. }
  527. bool onMotion(const MotionEvent& ev) override
  528. {
  529. const rack::math::Vec mousePos = rack::math::Vec(ev.pos.getX(), ev.pos.getY()).div(getScaleFactor()).round();
  530. const rack::math::Vec mouseDelta = mousePos.minus(lastMousePos);
  531. lastMousePos = mousePos;
  532. const ScopedContext sc(this, glfwMods(ev.mod));
  533. return context->event->handleHover(mousePos, mouseDelta);
  534. }
  535. bool onScroll(const ScrollEvent& ev) override
  536. {
  537. rack::math::Vec scrollDelta = rack::math::Vec(ev.delta.getX(), ev.delta.getY());
  538. #ifdef DISTRHO_OS_MAC
  539. scrollDelta = scrollDelta.mult(10.0);
  540. #else
  541. scrollDelta = scrollDelta.mult(50.0);
  542. #endif
  543. const int mods = glfwMods(ev.mod);
  544. const ScopedContext sc(this, mods);
  545. return context->event->handleScroll(lastMousePos, scrollDelta);
  546. }
  547. bool onCharacterInput(const CharacterInputEvent& ev) override
  548. {
  549. if (ev.character < ' ' || ev.character >= kKeyDelete)
  550. return false;
  551. const int mods = glfwMods(ev.mod);
  552. const ScopedContext sc(this, mods);
  553. return context->event->handleText(lastMousePos, ev.character);
  554. }
  555. bool onKeyboard(const KeyboardEvent& ev) override
  556. {
  557. const int action = ev.press ? GLFW_PRESS : GLFW_RELEASE;
  558. const int mods = glfwMods(ev.mod);
  559. /* These are unsupported in pugl right now
  560. #define GLFW_KEY_KP_0 320
  561. #define GLFW_KEY_KP_1 321
  562. #define GLFW_KEY_KP_2 322
  563. #define GLFW_KEY_KP_3 323
  564. #define GLFW_KEY_KP_4 324
  565. #define GLFW_KEY_KP_5 325
  566. #define GLFW_KEY_KP_6 326
  567. #define GLFW_KEY_KP_7 327
  568. #define GLFW_KEY_KP_8 328
  569. #define GLFW_KEY_KP_9 329
  570. #define GLFW_KEY_KP_DECIMAL 330
  571. #define GLFW_KEY_KP_DIVIDE 331
  572. #define GLFW_KEY_KP_MULTIPLY 332
  573. #define GLFW_KEY_KP_SUBTRACT 333
  574. #define GLFW_KEY_KP_ADD 334
  575. #define GLFW_KEY_KP_ENTER 335
  576. #define GLFW_KEY_KP_EQUAL 336
  577. */
  578. int key;
  579. switch (ev.key)
  580. {
  581. case '\r': key = GLFW_KEY_ENTER; break;
  582. case '\t': key = GLFW_KEY_TAB; break;
  583. case kKeyBackspace: key = GLFW_KEY_BACKSPACE; break;
  584. case kKeyEscape: key = GLFW_KEY_ESCAPE; break;
  585. case kKeyDelete: key = GLFW_KEY_DELETE; break;
  586. case kKeyF1: key = GLFW_KEY_F1; break;
  587. case kKeyF2: key = GLFW_KEY_F2; break;
  588. case kKeyF3: key = GLFW_KEY_F3; break;
  589. case kKeyF4: key = GLFW_KEY_F4; break;
  590. case kKeyF5: key = GLFW_KEY_F5; break;
  591. case kKeyF6: key = GLFW_KEY_F6; break;
  592. case kKeyF7: key = GLFW_KEY_F7; break;
  593. case kKeyF8: key = GLFW_KEY_F8; break;
  594. case kKeyF9: key = GLFW_KEY_F9; break;
  595. case kKeyF10: key = GLFW_KEY_F10; break;
  596. case kKeyF11: key = GLFW_KEY_F11; break;
  597. case kKeyF12: key = GLFW_KEY_F12; break;
  598. case kKeyLeft: key = GLFW_KEY_LEFT; break;
  599. case kKeyUp: key = GLFW_KEY_UP; break;
  600. case kKeyRight: key = GLFW_KEY_RIGHT; break;
  601. case kKeyDown: key = GLFW_KEY_DOWN; break;
  602. case kKeyPageUp: key = GLFW_KEY_PAGE_UP; break;
  603. case kKeyPageDown: key = GLFW_KEY_PAGE_DOWN; break;
  604. case kKeyHome: key = GLFW_KEY_HOME; break;
  605. case kKeyEnd: key = GLFW_KEY_END; break;
  606. case kKeyInsert: key = GLFW_KEY_INSERT; break;
  607. case kKeyShiftL: key = GLFW_KEY_LEFT_SHIFT; break;
  608. case kKeyShiftR: key = GLFW_KEY_RIGHT_SHIFT; break;
  609. case kKeyControlL: key = GLFW_KEY_LEFT_CONTROL; break;
  610. case kKeyControlR: key = GLFW_KEY_RIGHT_CONTROL; break;
  611. case kKeyAltL: key = GLFW_KEY_LEFT_ALT; break;
  612. case kKeyAltR: key = GLFW_KEY_RIGHT_ALT; break;
  613. case kKeySuperL: key = GLFW_KEY_LEFT_SUPER; break;
  614. case kKeySuperR: key = GLFW_KEY_RIGHT_SUPER; break;
  615. case kKeyMenu: key = GLFW_KEY_MENU; break;
  616. case kKeyCapsLock: key = GLFW_KEY_CAPS_LOCK; break;
  617. case kKeyScrollLock: key = GLFW_KEY_SCROLL_LOCK; break;
  618. case kKeyNumLock: key = GLFW_KEY_NUM_LOCK; break;
  619. case kKeyPrintScreen: key = GLFW_KEY_PRINT_SCREEN; break;
  620. case kKeyPause: key = GLFW_KEY_PAUSE; break;
  621. default:
  622. // glfw expects uppercase
  623. if (ev.key >= 'a' && ev.key <= 'z')
  624. key = ev.key - ('a' - 'A');
  625. else
  626. key = ev.key;
  627. break;
  628. }
  629. const ScopedContext sc(this, mods);
  630. return context->event->handleKey(lastMousePos, key, ev.keycode, action, mods);
  631. }
  632. void onResize(const ResizeEvent& ev) override
  633. {
  634. UI::onResize(ev);
  635. if (context->window != nullptr)
  636. context->window->setSize(rack::math::Vec(ev.size.getWidth(), ev.size.getHeight()));
  637. const double scaleFactor = getScaleFactor();
  638. char sizeString[64];
  639. std::snprintf(sizeString, sizeof(sizeString), "%d:%d",
  640. (int)(ev.size.getWidth() / scaleFactor), (int)(ev.size.getHeight() / scaleFactor));
  641. setState("windowSize", sizeString);
  642. }
  643. void uiFocus(const bool focus, const CrossingMode mode) override
  644. {
  645. if (focus)
  646. {
  647. if (mode == kCrossingNormal)
  648. getWindow().focus();
  649. }
  650. else
  651. {
  652. const ScopedContext sc(this, 0);
  653. context->event->handleLeave();
  654. }
  655. }
  656. void uiFileBrowserSelected(const char* const filename) override
  657. {
  658. if (filename == nullptr)
  659. return;
  660. rack::contextSet(context);
  661. WindowParametersRestore(context->window);
  662. std::string sfilename = filename;
  663. if (saving)
  664. {
  665. const bool uncompressed = savingUncompressed;
  666. savingUncompressed = false;
  667. if (rack::system::getExtension(sfilename) != ".vcv")
  668. sfilename += ".vcv";
  669. try {
  670. if (uncompressed)
  671. {
  672. context->engine->prepareSave();
  673. if (json_t* const rootJ = context->patch->toJson())
  674. {
  675. if (FILE* const file = std::fopen(sfilename.c_str(), "w"))
  676. {
  677. json_dumpf(rootJ, file, JSON_INDENT(2));
  678. std::fclose(file);
  679. }
  680. json_decref(rootJ);
  681. }
  682. }
  683. else
  684. {
  685. context->patch->save(sfilename);
  686. }
  687. }
  688. catch (rack::Exception& e) {
  689. std::string message = rack::string::f("Could not save patch: %s", e.what());
  690. asyncDialog::create(message.c_str());
  691. return;
  692. }
  693. }
  694. else
  695. {
  696. try {
  697. context->patch->load(sfilename);
  698. } catch (rack::Exception& e) {
  699. std::string message = rack::string::f("Could not load patch: %s", e.what());
  700. asyncDialog::create(message.c_str());
  701. return;
  702. }
  703. }
  704. context->patch->path = sfilename;
  705. context->history->setSaved();
  706. }
  707. #if 0
  708. void uiReshape(const uint width, const uint height) override
  709. {
  710. glEnable(GL_BLEND);
  711. glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  712. glMatrixMode(GL_PROJECTION);
  713. glLoadIdentity();
  714. glOrtho(0.0, width, 0.0, height, -1.0, 1.0);
  715. glViewport(0, 0, width, height);
  716. glMatrixMode(GL_MODELVIEW);
  717. glLoadIdentity();
  718. }
  719. #endif
  720. private:
  721. /**
  722. Set our UI class as non-copyable and add a leak detector just in case.
  723. */
  724. DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CardinalUI)
  725. };
  726. /* ------------------------------------------------------------------------------------------------------------
  727. * UI entry point, called by DPF to create a new UI instance. */
  728. UI* createUI()
  729. {
  730. return new CardinalUI();
  731. }
  732. // -----------------------------------------------------------------------------------------------------------
  733. END_NAMESPACE_DISTRHO