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.

1042 lines
35KB

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