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.

790 lines
25KB

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