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.

965 lines
32KB

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