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.

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