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.

849 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. {
  285. static bool shown = false;
  286. if (! shown)
  287. {
  288. shown = true;
  289. asyncDialog::create(errorMessage.c_str());
  290. }
  291. }
  292. context->window->step();
  293. rack::contextSet(nullptr);
  294. WindowParametersSetCallback(context->window, this);
  295. }
  296. ~CardinalUI() override
  297. {
  298. rack::contextSet(context);
  299. context->nativeWindowId = 0;
  300. if (rack::widget::Widget* const menuBar = context->scene->menuBar)
  301. {
  302. context->scene->removeChild(menuBar);
  303. delete menuBar;
  304. }
  305. context->scene->menuBar = rack::app::createMenuBar();
  306. context->scene->addChildBelow(context->scene->menuBar, context->scene->rackScroll);
  307. rack::window::WindowSetPluginUI(context->window, nullptr);
  308. rack::contextSet(nullptr);
  309. }
  310. void onNanoDisplay() override
  311. {
  312. const ScopedContext sc(this);
  313. context->window->step();
  314. }
  315. void uiIdle() override
  316. {
  317. if (firstIdle)
  318. {
  319. firstIdle = false;
  320. getWindow().focus();
  321. }
  322. if (filebrowserhandle != nullptr && fileBrowserIdle(filebrowserhandle))
  323. {
  324. {
  325. const char* const path = fileBrowserGetPath(filebrowserhandle);
  326. const ScopedContext sc(this);
  327. filebrowseraction(path != nullptr ? strdup(path) : nullptr);
  328. }
  329. fileBrowserClose(filebrowserhandle);
  330. filebrowseraction = nullptr;
  331. filebrowserhandle = nullptr;
  332. }
  333. if (windowParameters.rateLimit != 0 && ++rateLimitStep % (windowParameters.rateLimit * 2))
  334. return;
  335. rateLimitStep = 0;
  336. repaint();
  337. }
  338. void WindowParametersChanged(const WindowParameterList param, float value) override
  339. {
  340. float mult = 1.0f;
  341. switch (param)
  342. {
  343. case kWindowParameterShowTooltips:
  344. windowParameters.tooltips = value > 0.5f;
  345. break;
  346. case kWindowParameterCableOpacity:
  347. mult = 100.0f;
  348. windowParameters.cableOpacity = value;
  349. break;
  350. case kWindowParameterCableTension:
  351. mult = 100.0f;
  352. windowParameters.cableTension = value;
  353. break;
  354. case kWindowParameterRackBrightness:
  355. mult = 100.0f;
  356. windowParameters.rackBrightness = value;
  357. break;
  358. case kWindowParameterHaloBrightness:
  359. mult = 100.0f;
  360. windowParameters.haloBrightness = value;
  361. break;
  362. case kWindowParameterKnobMode:
  363. switch (static_cast<int>(value + 0.5f))
  364. {
  365. case rack::settings::KNOB_MODE_LINEAR:
  366. value = 0;
  367. windowParameters.knobMode = rack::settings::KNOB_MODE_LINEAR;
  368. break;
  369. case rack::settings::KNOB_MODE_ROTARY_ABSOLUTE:
  370. value = 1;
  371. windowParameters.knobMode = rack::settings::KNOB_MODE_ROTARY_ABSOLUTE;
  372. break;
  373. case rack::settings::KNOB_MODE_ROTARY_RELATIVE:
  374. value = 2;
  375. windowParameters.knobMode = rack::settings::KNOB_MODE_ROTARY_RELATIVE;
  376. break;
  377. }
  378. break;
  379. case kWindowParameterWheelKnobControl:
  380. windowParameters.knobScroll = value > 0.5f;
  381. break;
  382. case kWindowParameterWheelSensitivity:
  383. mult = 1000.0f;
  384. windowParameters.knobScrollSensitivity = value;
  385. break;
  386. case kWindowParameterLockModulePositions:
  387. windowParameters.lockModules = value > 0.5f;
  388. break;
  389. case kWindowParameterUpdateRateLimit:
  390. windowParameters.rateLimit = static_cast<int>(value + 0.5f);
  391. rateLimitStep = 0;
  392. break;
  393. default:
  394. return;
  395. }
  396. setParameterValue(kModuleParameters + param + 1, value * mult);
  397. }
  398. protected:
  399. /* --------------------------------------------------------------------------------------------------------
  400. * DSP/Plugin Callbacks */
  401. /**
  402. A parameter has changed on the plugin side.
  403. This is called by the host to inform the UI about parameter changes.
  404. */
  405. void parameterChanged(const uint32_t index, const float value) override
  406. {
  407. // host mapped parameters + bypass
  408. if (index <= kModuleParameters)
  409. return;
  410. switch (index - kModuleParameters - 1)
  411. {
  412. case kWindowParameterShowTooltips:
  413. windowParameters.tooltips = value > 0.5f;
  414. break;
  415. case kWindowParameterCableOpacity:
  416. windowParameters.cableOpacity = value / 100.0f;
  417. break;
  418. case kWindowParameterCableTension:
  419. windowParameters.cableTension = value / 100.0f;
  420. break;
  421. case kWindowParameterRackBrightness:
  422. windowParameters.rackBrightness = value / 100.0f;
  423. break;
  424. case kWindowParameterHaloBrightness:
  425. windowParameters.haloBrightness = value / 100.0f;
  426. break;
  427. case kWindowParameterKnobMode:
  428. switch (static_cast<int>(value + 0.5f))
  429. {
  430. case 0:
  431. windowParameters.knobMode = rack::settings::KNOB_MODE_LINEAR;
  432. break;
  433. case 1:
  434. windowParameters.knobMode = rack::settings::KNOB_MODE_ROTARY_ABSOLUTE;
  435. break;
  436. case 2:
  437. windowParameters.knobMode = rack::settings::KNOB_MODE_ROTARY_RELATIVE;
  438. break;
  439. }
  440. break;
  441. case kWindowParameterWheelKnobControl:
  442. windowParameters.knobScroll = value > 0.5f;
  443. break;
  444. case kWindowParameterWheelSensitivity:
  445. windowParameters.knobScrollSensitivity = value / 1000.0f;
  446. break;
  447. case kWindowParameterLockModulePositions:
  448. windowParameters.lockModules = value > 0.5f;
  449. break;
  450. case kWindowParameterUpdateRateLimit:
  451. windowParameters.rateLimit = static_cast<int>(value + 0.5f);
  452. rateLimitStep = 0;
  453. break;
  454. default:
  455. return;
  456. }
  457. WindowParametersSetValues(context->window, windowParameters);
  458. }
  459. void stateChanged(const char* key, const char* value) override
  460. {
  461. if (std::strcmp(key, "windowSize") != 0)
  462. return;
  463. int width = 0;
  464. int height = 0;
  465. std::sscanf(value, "%i:%i", &width, &height);
  466. if (width > 0 && height > 0)
  467. {
  468. const double scaleFactor = getScaleFactor();
  469. setSize(width * scaleFactor, height * scaleFactor);
  470. }
  471. }
  472. // -------------------------------------------------------------------------------------------------------
  473. static int glfwMods(const uint mod) noexcept
  474. {
  475. int mods = 0;
  476. if (mod & kModifierControl)
  477. mods |= GLFW_MOD_CONTROL;
  478. if (mod & kModifierShift)
  479. mods |= GLFW_MOD_SHIFT;
  480. if (mod & kModifierAlt)
  481. mods |= GLFW_MOD_ALT;
  482. if (mod & kModifierSuper)
  483. mods |= GLFW_MOD_SUPER;
  484. /*
  485. if (glfwGetKey(win, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS || glfwGetKey(win, GLFW_KEY_RIGHT_SHIFT) == GLFW_PRESS)
  486. mods |= GLFW_MOD_SHIFT;
  487. if (glfwGetKey(win, GLFW_KEY_LEFT_CONTROL) == GLFW_PRESS || glfwGetKey(win, GLFW_KEY_RIGHT_CONTROL) == GLFW_PRESS)
  488. mods |= GLFW_MOD_CONTROL;
  489. if (glfwGetKey(win, GLFW_KEY_LEFT_ALT) == GLFW_PRESS || glfwGetKey(win, GLFW_KEY_RIGHT_ALT) == GLFW_PRESS)
  490. mods |= GLFW_MOD_ALT;
  491. if (glfwGetKey(win, GLFW_KEY_LEFT_SUPER) == GLFW_PRESS || glfwGetKey(win, GLFW_KEY_RIGHT_SUPER) == GLFW_PRESS)
  492. mods |= GLFW_MOD_SUPER;
  493. */
  494. return mods;
  495. }
  496. bool onMouse(const MouseEvent& ev) override
  497. {
  498. const int action = ev.press ? GLFW_PRESS : GLFW_RELEASE;
  499. const int mods = glfwMods(ev.mod);
  500. int button;
  501. switch (ev.button)
  502. {
  503. case 1: button = GLFW_MOUSE_BUTTON_LEFT; break;
  504. #ifdef DISTRHO_OS_MAC
  505. case 2: button = GLFW_MOUSE_BUTTON_RIGHT; break;
  506. case 3: button = GLFW_MOUSE_BUTTON_MIDDLE; break;
  507. #else
  508. case 2: button = GLFW_MOUSE_BUTTON_MIDDLE; break;
  509. case 3: button = GLFW_MOUSE_BUTTON_RIGHT; break;
  510. #endif
  511. default:
  512. button = ev.button;
  513. break;
  514. }
  515. /*
  516. #if defined ARCH_MAC
  517. // Remap Ctrl-left click to right click on Mac
  518. if (button == GLFW_MOUSE_BUTTON_LEFT && (mods & RACK_MOD_MASK) == GLFW_MOD_CONTROL) {
  519. button = GLFW_MOUSE_BUTTON_RIGHT;
  520. mods &= ~GLFW_MOD_CONTROL;
  521. }
  522. // Remap Ctrl-shift-left click to middle click on Mac
  523. if (button == GLFW_MOUSE_BUTTON_LEFT && (mods & RACK_MOD_MASK) == (GLFW_MOD_CONTROL | GLFW_MOD_SHIFT)) {
  524. button = GLFW_MOUSE_BUTTON_MIDDLE;
  525. mods &= ~(GLFW_MOD_CONTROL | GLFW_MOD_SHIFT);
  526. }
  527. #endif
  528. */
  529. const ScopedContext sc(this, mods);
  530. return context->event->handleButton(lastMousePos, button, action, mods);
  531. }
  532. bool onMotion(const MotionEvent& ev) override
  533. {
  534. const rack::math::Vec mousePos = rack::math::Vec(ev.pos.getX(), ev.pos.getY()).div(getScaleFactor()).round();
  535. const rack::math::Vec mouseDelta = mousePos.minus(lastMousePos);
  536. lastMousePos = mousePos;
  537. const ScopedContext sc(this, glfwMods(ev.mod));
  538. return context->event->handleHover(mousePos, mouseDelta);
  539. }
  540. bool onScroll(const ScrollEvent& ev) override
  541. {
  542. rack::math::Vec scrollDelta = rack::math::Vec(ev.delta.getX(), ev.delta.getY());
  543. #ifdef DISTRHO_OS_MAC
  544. scrollDelta = scrollDelta.mult(10.0);
  545. #else
  546. scrollDelta = scrollDelta.mult(50.0);
  547. #endif
  548. const int mods = glfwMods(ev.mod);
  549. const ScopedContext sc(this, mods);
  550. return context->event->handleScroll(lastMousePos, scrollDelta);
  551. }
  552. bool onCharacterInput(const CharacterInputEvent& ev) override
  553. {
  554. if (ev.character < ' ' || ev.character >= kKeyDelete)
  555. return false;
  556. const int mods = glfwMods(ev.mod);
  557. const ScopedContext sc(this, mods);
  558. return context->event->handleText(lastMousePos, ev.character);
  559. }
  560. bool onKeyboard(const KeyboardEvent& ev) override
  561. {
  562. const int action = ev.press ? GLFW_PRESS : GLFW_RELEASE;
  563. const int mods = glfwMods(ev.mod);
  564. /* These are unsupported in pugl right now
  565. #define GLFW_KEY_KP_0 320
  566. #define GLFW_KEY_KP_1 321
  567. #define GLFW_KEY_KP_2 322
  568. #define GLFW_KEY_KP_3 323
  569. #define GLFW_KEY_KP_4 324
  570. #define GLFW_KEY_KP_5 325
  571. #define GLFW_KEY_KP_6 326
  572. #define GLFW_KEY_KP_7 327
  573. #define GLFW_KEY_KP_8 328
  574. #define GLFW_KEY_KP_9 329
  575. #define GLFW_KEY_KP_DECIMAL 330
  576. #define GLFW_KEY_KP_DIVIDE 331
  577. #define GLFW_KEY_KP_MULTIPLY 332
  578. #define GLFW_KEY_KP_SUBTRACT 333
  579. #define GLFW_KEY_KP_ADD 334
  580. #define GLFW_KEY_KP_ENTER 335
  581. #define GLFW_KEY_KP_EQUAL 336
  582. */
  583. int key;
  584. switch (ev.key)
  585. {
  586. case '\r': key = GLFW_KEY_ENTER; break;
  587. case '\t': key = GLFW_KEY_TAB; break;
  588. case kKeyBackspace: key = GLFW_KEY_BACKSPACE; break;
  589. case kKeyEscape: key = GLFW_KEY_ESCAPE; break;
  590. case kKeyDelete: key = GLFW_KEY_DELETE; break;
  591. case kKeyF1: key = GLFW_KEY_F1; break;
  592. case kKeyF2: key = GLFW_KEY_F2; break;
  593. case kKeyF3: key = GLFW_KEY_F3; break;
  594. case kKeyF4: key = GLFW_KEY_F4; break;
  595. case kKeyF5: key = GLFW_KEY_F5; break;
  596. case kKeyF6: key = GLFW_KEY_F6; break;
  597. case kKeyF7: key = GLFW_KEY_F7; break;
  598. case kKeyF8: key = GLFW_KEY_F8; break;
  599. case kKeyF9: key = GLFW_KEY_F9; break;
  600. case kKeyF10: key = GLFW_KEY_F10; break;
  601. case kKeyF11: key = GLFW_KEY_F11; break;
  602. case kKeyF12: key = GLFW_KEY_F12; break;
  603. case kKeyLeft: key = GLFW_KEY_LEFT; break;
  604. case kKeyUp: key = GLFW_KEY_UP; break;
  605. case kKeyRight: key = GLFW_KEY_RIGHT; break;
  606. case kKeyDown: key = GLFW_KEY_DOWN; break;
  607. case kKeyPageUp: key = GLFW_KEY_PAGE_UP; break;
  608. case kKeyPageDown: key = GLFW_KEY_PAGE_DOWN; break;
  609. case kKeyHome: key = GLFW_KEY_HOME; break;
  610. case kKeyEnd: key = GLFW_KEY_END; break;
  611. case kKeyInsert: key = GLFW_KEY_INSERT; break;
  612. case kKeyShiftL: key = GLFW_KEY_LEFT_SHIFT; break;
  613. case kKeyShiftR: key = GLFW_KEY_RIGHT_SHIFT; break;
  614. case kKeyControlL: key = GLFW_KEY_LEFT_CONTROL; break;
  615. case kKeyControlR: key = GLFW_KEY_RIGHT_CONTROL; break;
  616. case kKeyAltL: key = GLFW_KEY_LEFT_ALT; break;
  617. case kKeyAltR: key = GLFW_KEY_RIGHT_ALT; break;
  618. case kKeySuperL: key = GLFW_KEY_LEFT_SUPER; break;
  619. case kKeySuperR: key = GLFW_KEY_RIGHT_SUPER; break;
  620. case kKeyMenu: key = GLFW_KEY_MENU; break;
  621. case kKeyCapsLock: key = GLFW_KEY_CAPS_LOCK; break;
  622. case kKeyScrollLock: key = GLFW_KEY_SCROLL_LOCK; break;
  623. case kKeyNumLock: key = GLFW_KEY_NUM_LOCK; break;
  624. case kKeyPrintScreen: key = GLFW_KEY_PRINT_SCREEN; break;
  625. case kKeyPause: key = GLFW_KEY_PAUSE; break;
  626. default:
  627. // glfw expects uppercase
  628. if (ev.key >= 'a' && ev.key <= 'z')
  629. key = ev.key - ('a' - 'A');
  630. else
  631. key = ev.key;
  632. break;
  633. }
  634. const ScopedContext sc(this, mods);
  635. return context->event->handleKey(lastMousePos, key, ev.keycode, action, mods);
  636. }
  637. void onResize(const ResizeEvent& ev) override
  638. {
  639. UI::onResize(ev);
  640. if (context->window != nullptr)
  641. context->window->setSize(rack::math::Vec(ev.size.getWidth(), ev.size.getHeight()));
  642. const double scaleFactor = getScaleFactor();
  643. char sizeString[64];
  644. std::snprintf(sizeString, sizeof(sizeString), "%d:%d",
  645. (int)(ev.size.getWidth() / scaleFactor), (int)(ev.size.getHeight() / scaleFactor));
  646. setState("windowSize", sizeString);
  647. }
  648. void uiFocus(const bool focus, const CrossingMode mode) override
  649. {
  650. if (focus)
  651. {
  652. if (mode == kCrossingNormal)
  653. getWindow().focus();
  654. }
  655. else
  656. {
  657. const ScopedContext sc(this, 0);
  658. context->event->handleLeave();
  659. }
  660. }
  661. void uiFileBrowserSelected(const char* const filename) override
  662. {
  663. if (filename == nullptr)
  664. return;
  665. rack::contextSet(context);
  666. WindowParametersRestore(context->window);
  667. std::string sfilename = filename;
  668. if (saving)
  669. {
  670. const bool uncompressed = savingUncompressed;
  671. savingUncompressed = false;
  672. if (rack::system::getExtension(sfilename) != ".vcv")
  673. sfilename += ".vcv";
  674. try {
  675. if (uncompressed)
  676. {
  677. context->engine->prepareSave();
  678. if (json_t* const rootJ = context->patch->toJson())
  679. {
  680. if (FILE* const file = std::fopen(sfilename.c_str(), "w"))
  681. {
  682. json_dumpf(rootJ, file, JSON_INDENT(2));
  683. std::fclose(file);
  684. }
  685. json_decref(rootJ);
  686. }
  687. }
  688. else
  689. {
  690. context->patch->save(sfilename);
  691. }
  692. }
  693. catch (rack::Exception& e) {
  694. std::string message = rack::string::f("Could not save patch: %s", e.what());
  695. asyncDialog::create(message.c_str());
  696. return;
  697. }
  698. }
  699. else
  700. {
  701. try {
  702. context->patch->load(sfilename);
  703. } catch (rack::Exception& e) {
  704. std::string message = rack::string::f("Could not load patch: %s", e.what());
  705. asyncDialog::create(message.c_str());
  706. return;
  707. }
  708. }
  709. context->patch->path = sfilename;
  710. context->history->setSaved();
  711. }
  712. #if 0
  713. void uiReshape(const uint width, const uint height) override
  714. {
  715. glEnable(GL_BLEND);
  716. glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  717. glMatrixMode(GL_PROJECTION);
  718. glLoadIdentity();
  719. glOrtho(0.0, width, 0.0, height, -1.0, 1.0);
  720. glViewport(0, 0, width, height);
  721. glMatrixMode(GL_MODELVIEW);
  722. glLoadIdentity();
  723. }
  724. #endif
  725. private:
  726. /**
  727. Set our UI class as non-copyable and add a leak detector just in case.
  728. */
  729. DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CardinalUI)
  730. };
  731. /* ------------------------------------------------------------------------------------------------------------
  732. * UI entry point, called by DPF to create a new UI instance. */
  733. UI* createUI()
  734. {
  735. return new CardinalUI();
  736. }
  737. // -----------------------------------------------------------------------------------------------------------
  738. END_NAMESPACE_DISTRHO