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.

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