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.

869 lines
28KB

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