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.

1301 lines
44KB

  1. /*
  2. * DISTRHO Cardinal Plugin
  3. * Copyright (C) 2021-2023 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 <emscripten/emscripten.h>
  40. #endif
  41. #ifdef NDEBUG
  42. # undef DEBUG
  43. #endif
  44. #include "Application.hpp"
  45. #include "AsyncDialog.hpp"
  46. #include "CardinalCommon.hpp"
  47. #include "PluginContext.hpp"
  48. #include "WindowParameters.hpp"
  49. #include "extra/Base64.hpp"
  50. #ifndef DISTRHO_OS_WASM
  51. # include "extra/SharedResourcePointer.hpp"
  52. #endif
  53. #ifndef HEADLESS
  54. # include "extra/ScopedValueSetter.hpp"
  55. #endif
  56. namespace rack {
  57. #ifdef DISTRHO_OS_WASM
  58. namespace asset {
  59. std::string patchesPath();
  60. }
  61. #endif
  62. namespace engine {
  63. void Engine_setAboutToClose(Engine*);
  64. void Engine_setRemoteDetails(Engine*, remoteUtils::RemoteDetails*);
  65. }
  66. namespace window {
  67. void WindowSetPluginUI(Window* window, CardinalBaseUI* ui);
  68. void WindowSetMods(Window* window, int mods);
  69. void WindowSetInternalSize(rack::window::Window* window, math::Vec size);
  70. }
  71. }
  72. START_NAMESPACE_DISTRHO
  73. // --------------------------------------------------------------------------------------------------------------------
  74. #if ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
  75. uint32_t Plugin::getBufferSize() const noexcept { return 0; }
  76. double Plugin::getSampleRate() const noexcept { return 0.0; }
  77. const char* Plugin::getBundlePath() const noexcept { return nullptr; }
  78. bool Plugin::isSelfTestInstance() const noexcept { return false; }
  79. bool Plugin::writeMidiEvent(const MidiEvent&) noexcept { return false; }
  80. #endif
  81. // --------------------------------------------------------------------------------------------------------------------
  82. #if defined(DISTRHO_OS_WASM) && ! CARDINAL_VARIANT_MINI
  83. struct WasmWelcomeDialog : rack::widget::OpaqueWidget
  84. {
  85. static const constexpr float margin = 10;
  86. static const constexpr float buttonWidth = 110;
  87. WasmWelcomeDialog()
  88. {
  89. using rack::ui::Button;
  90. using rack::ui::Label;
  91. using rack::ui::MenuOverlay;
  92. using rack::ui::SequentialLayout;
  93. box.size = rack::math::Vec(550, 310);
  94. SequentialLayout* const layout = new SequentialLayout;
  95. layout->box.pos = rack::math::Vec(0, 0);
  96. layout->box.size = box.size;
  97. layout->orientation = SequentialLayout::VERTICAL_ORIENTATION;
  98. layout->margin = rack::math::Vec(margin, margin);
  99. layout->spacing = rack::math::Vec(margin, margin);
  100. layout->wrap = false;
  101. addChild(layout);
  102. SequentialLayout* const contentLayout = new SequentialLayout;
  103. contentLayout->spacing = rack::math::Vec(margin, margin);
  104. layout->addChild(contentLayout);
  105. SequentialLayout* const buttonLayout = new SequentialLayout;
  106. buttonLayout->alignment = SequentialLayout::CENTER_ALIGNMENT;
  107. buttonLayout->box.size = box.size;
  108. buttonLayout->spacing = rack::math::Vec(margin, margin);
  109. layout->addChild(buttonLayout);
  110. Label* const label = new Label;
  111. label->box.size.x = box.size.x - 2*margin;
  112. label->box.size.y = box.size.y - 2*margin - 40;
  113. label->fontSize = 20;
  114. label->text = ""
  115. "Welcome to Cardinal on the Web!\n"
  116. "\n"
  117. "If using mobile/touch devices, please note:\n"
  118. " - Single quick press does simple mouse click\n"
  119. " - Press & move does click & drag action\n"
  120. " - Press & hold does right-click (and opens module browser)\n"
  121. "\n"
  122. "Still a bit experimental, so proceed with caution.\n"
  123. "Have fun!";
  124. contentLayout->addChild(label);
  125. struct JoinDiscussionButton : Button {
  126. WasmWelcomeDialog* dialog;
  127. void onAction(const ActionEvent& e) override {
  128. patchUtils::openBrowser("https://github.com/DISTRHO/Cardinal/issues/287");
  129. dialog->getParent()->requestDelete();
  130. }
  131. };
  132. JoinDiscussionButton* const discussionButton = new JoinDiscussionButton;
  133. discussionButton->box.size.x = buttonWidth;
  134. discussionButton->text = "Join discussion";
  135. discussionButton->dialog = this;
  136. buttonLayout->addChild(discussionButton);
  137. struct DismissButton : Button {
  138. WasmWelcomeDialog* dialog;
  139. void onAction(const ActionEvent& e) override {
  140. dialog->getParent()->requestDelete();
  141. }
  142. };
  143. DismissButton* const dismissButton = new DismissButton;
  144. dismissButton->box.size.x = buttonWidth;
  145. dismissButton->text = "Dismiss";
  146. dismissButton->dialog = this;
  147. buttonLayout->addChild(dismissButton);
  148. MenuOverlay* const overlay = new MenuOverlay;
  149. overlay->bgColor = nvgRGBAf(0, 0, 0, 0.33);
  150. overlay->addChild(this);
  151. APP->scene->addChild(overlay);
  152. }
  153. void step() override
  154. {
  155. OpaqueWidget::step();
  156. box.pos = parent->box.size.minus(box.size).div(2).round();
  157. }
  158. void draw(const DrawArgs& args) override
  159. {
  160. bndMenuBackground(args.vg, 0.0, 0.0, box.size.x, box.size.y, 0);
  161. Widget::draw(args);
  162. }
  163. };
  164. struct WasmRemotePatchLoadingDialog : rack::widget::OpaqueWidget
  165. {
  166. static const constexpr float margin = 10;
  167. rack::ui::MenuOverlay* overlay;
  168. WasmRemotePatchLoadingDialog(const bool isFromPatchStorage)
  169. {
  170. using rack::ui::Label;
  171. using rack::ui::MenuOverlay;
  172. using rack::ui::SequentialLayout;
  173. box.size = rack::math::Vec(300, 40);
  174. SequentialLayout* const layout = new SequentialLayout;
  175. layout->box.pos = rack::math::Vec(0, 0);
  176. layout->box.size = box.size;
  177. layout->alignment = SequentialLayout::CENTER_ALIGNMENT;
  178. layout->margin = rack::math::Vec(margin, margin);
  179. layout->spacing = rack::math::Vec(margin, margin);
  180. layout->wrap = false;
  181. addChild(layout);
  182. Label* const label = new Label;
  183. label->box.size.x = box.size.x - 2*margin;
  184. label->box.size.y = box.size.y - 2*margin;
  185. label->fontSize = 16;
  186. label->text = isFromPatchStorage
  187. ? "Loading patch from PatchStorage...\n"
  188. : "Loading remote patch...\n";
  189. layout->addChild(label);
  190. overlay = new MenuOverlay;
  191. overlay->bgColor = nvgRGBAf(0, 0, 0, 0.33);
  192. overlay->addChild(this);
  193. APP->scene->addChild(overlay);
  194. }
  195. void step() override
  196. {
  197. OpaqueWidget::step();
  198. box.pos = parent->box.size.minus(box.size).div(2).round();
  199. }
  200. void draw(const DrawArgs& args) override
  201. {
  202. bndMenuBackground(args.vg, 0.0, 0.0, box.size.x, box.size.y, 0);
  203. Widget::draw(args);
  204. }
  205. };
  206. static void downloadRemotePatchFailed(const char* const filename)
  207. {
  208. d_stdout("downloadRemotePatchFailed %s", filename);
  209. CardinalPluginContext* const context = static_cast<CardinalPluginContext*>(APP);
  210. CardinalBaseUI* const ui = static_cast<CardinalBaseUI*>(context->ui);
  211. if (ui->psDialog != nullptr)
  212. {
  213. ui->psDialog->overlay->requestDelete();
  214. ui->psDialog = nullptr;
  215. asyncDialog::create("Failed to fetch remote patch");
  216. }
  217. using namespace rack;
  218. context->patch->templatePath = rack::system::join(asset::patchesPath(), "templates/main.vcv");
  219. context->patch->loadTemplate();
  220. context->scene->rackScroll->reset();
  221. }
  222. static void downloadRemotePatchSucceeded(const char* const filename)
  223. {
  224. d_stdout("downloadRemotePatchSucceeded %s | %s", filename, APP->patch->templatePath.c_str());
  225. CardinalPluginContext* const context = static_cast<CardinalPluginContext*>(APP);
  226. CardinalBaseUI* const ui = static_cast<CardinalBaseUI*>(context->ui);
  227. ui->psDialog->overlay->requestDelete();
  228. ui->psDialog = nullptr;
  229. if (FILE* f = fopen(filename, "r"))
  230. {
  231. uint8_t buf[8] = {};
  232. fread(buf, 8, 1, f);
  233. d_stdout("read patch %x %x %x %x %x %x %x %x",
  234. buf[0],buf[1],buf[2],buf[3],buf[4],buf[5],buf[6],buf[7]);
  235. fclose(f);
  236. }
  237. try {
  238. context->patch->load(filename);
  239. } catch (rack::Exception& e) {
  240. const std::string message = rack::string::f("Could not load patch: %s", e.what());
  241. asyncDialog::create(message.c_str());
  242. return;
  243. }
  244. context->patch->path.clear();
  245. context->scene->rackScroll->reset();
  246. context->history->setSaved();
  247. }
  248. #endif
  249. // -----------------------------------------------------------------------------------------------------------
  250. class CardinalUI : public CardinalBaseUI,
  251. public WindowParametersCallback
  252. {
  253. #if ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
  254. #ifdef DISTRHO_OS_WASM
  255. ScopedPointer<Initializer> fInitializer;
  256. #else
  257. SharedResourcePointer<Initializer> fInitializer;
  258. #endif
  259. std::string fAutosavePath;
  260. #endif
  261. rack::math::Vec lastMousePos;
  262. WindowParameters windowParameters;
  263. int rateLimitStep = 0;
  264. #if defined(DISTRHO_OS_WASM) && ! CARDINAL_VARIANT_MINI
  265. int8_t counterForFirstIdlePoint = 0;
  266. #endif
  267. #ifdef DPF_RUNTIME_TESTING
  268. bool inSelfTest = false;
  269. #endif
  270. struct ScopedContext {
  271. CardinalPluginContext* const context;
  272. ScopedContext(CardinalUI* const ui)
  273. : context(ui->context)
  274. {
  275. rack::contextSet(context);
  276. WindowParametersRestore(context->window);
  277. }
  278. ScopedContext(CardinalUI* const ui, const int mods)
  279. : context(ui->context)
  280. {
  281. rack::contextSet(context);
  282. rack::window::WindowSetMods(context->window, mods);
  283. WindowParametersRestore(context->window);
  284. }
  285. ~ScopedContext()
  286. {
  287. if (context->window != nullptr)
  288. WindowParametersSave(context->window);
  289. }
  290. };
  291. public:
  292. CardinalUI()
  293. : CardinalBaseUI(DISTRHO_UI_DEFAULT_WIDTH, DISTRHO_UI_DEFAULT_HEIGHT),
  294. #if ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
  295. #ifdef DISTRHO_OS_WASM
  296. fInitializer(new Initializer(static_cast<const CardinalBasePlugin*>(nullptr), this)),
  297. #else
  298. fInitializer(static_cast<const CardinalBasePlugin*>(nullptr), this),
  299. #endif
  300. #endif
  301. lastMousePos()
  302. {
  303. rack::contextSet(context);
  304. #if CARDINAL_VARIANT_MINI && ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
  305. // create unique temporary path for this instance
  306. try {
  307. char uidBuf[24];
  308. const std::string tmp = rack::system::getTempDirectory();
  309. for (int i=1;; ++i)
  310. {
  311. std::snprintf(uidBuf, sizeof(uidBuf), "Cardinal.%04d", i);
  312. const std::string trypath = rack::system::join(tmp, uidBuf);
  313. if (! rack::system::exists(trypath))
  314. {
  315. if (rack::system::createDirectories(trypath))
  316. fAutosavePath = trypath;
  317. break;
  318. }
  319. }
  320. } DISTRHO_SAFE_EXCEPTION("create unique temporary path");
  321. const float sampleRate = 60; // fake audio running at 60 fps
  322. rack::settings::sampleRate = sampleRate;
  323. context->dataIns = new const float*[DISTRHO_PLUGIN_NUM_INPUTS];
  324. context->dataOuts = new float*[DISTRHO_PLUGIN_NUM_OUTPUTS];
  325. for (uint32_t i=0; i<DISTRHO_PLUGIN_NUM_INPUTS;++i)
  326. {
  327. float** const bufferptr = const_cast<float**>(&context->dataIns[i]);
  328. *bufferptr = new float[1];
  329. (*bufferptr)[0] = 0.f;
  330. }
  331. for (uint32_t i=0; i<DISTRHO_PLUGIN_NUM_OUTPUTS;++i)
  332. context->dataOuts[i] = new float[1];
  333. context->bufferSize = 1;
  334. context->sampleRate = sampleRate;
  335. context->engine = new rack::engine::Engine;
  336. context->engine->setSampleRate(sampleRate);
  337. context->history = new rack::history::State;
  338. context->patch = new rack::patch::Manager;
  339. context->patch->autosavePath = fAutosavePath;
  340. context->patch->templatePath = context->patch->factoryTemplatePath = fInitializer->factoryTemplatePath;
  341. context->event = new rack::widget::EventState;
  342. context->scene = new rack::app::Scene;
  343. context->event->rootWidget = context->scene;
  344. context->window = new rack::window::Window;
  345. context->patch->loadTemplate();
  346. context->scene->rackScroll->reset();
  347. DISTRHO_SAFE_ASSERT(remoteUtils::connectToRemote(CARDINAL_DEFAULT_REMOTE_URL));
  348. Engine_setRemoteDetails(context->engine, remoteDetails);
  349. #endif
  350. Window& window(getWindow());
  351. window.setIgnoringKeyRepeat(true);
  352. context->nativeWindowId = window.getNativeWindowHandle();
  353. const double scaleFactor = getScaleFactor();
  354. setGeometryConstraints(648 * scaleFactor, 538 * scaleFactor);
  355. if (rack::isStandalone() && rack::system::exists(rack::settings::settingsPath))
  356. {
  357. const double width = std::max(648.f, rack::settings::windowSize.x) * scaleFactor;
  358. const double height = std::max(538.f, rack::settings::windowSize.y) * scaleFactor;
  359. setSize(width, height);
  360. }
  361. else if (scaleFactor != 1.0)
  362. {
  363. setSize(DISTRHO_UI_DEFAULT_WIDTH * scaleFactor, DISTRHO_UI_DEFAULT_HEIGHT * scaleFactor);
  364. }
  365. #if DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
  366. const DGL_NAMESPACE::Window::ScopedGraphicsContext sgc(window);
  367. #endif
  368. rack::window::WindowSetPluginUI(context->window, this);
  369. // hide "Browse VCV Library" button
  370. rack::widget::Widget* const browser = context->scene->browser->children.back();
  371. rack::widget::Widget* const headerLayout = browser->children.front();
  372. rack::widget::Widget* const libraryButton = headerLayout->children.back();
  373. libraryButton->hide();
  374. // Report to user if something is wrong with the installation
  375. std::string errorMessage;
  376. if (rack::asset::systemDir.empty())
  377. {
  378. errorMessage = "Failed to locate Cardinal plugin bundle.\n"
  379. "Install Cardinal with its plugin bundle folder intact and try again.";
  380. }
  381. else if (! rack::system::exists(rack::asset::systemDir))
  382. {
  383. errorMessage = rack::string::f("System directory \"%s\" does not exist. "
  384. "Make sure Cardinal was downloaded and installed correctly.",
  385. rack::asset::systemDir.c_str());
  386. }
  387. if (! errorMessage.empty())
  388. {
  389. static bool shown = false;
  390. if (! shown)
  391. {
  392. shown = true;
  393. asyncDialog::create(errorMessage.c_str());
  394. }
  395. }
  396. #if defined(DISTRHO_OS_WASM) && ! CARDINAL_VARIANT_MINI
  397. if (rack::patchStorageSlug != nullptr)
  398. {
  399. psDialog = new WasmRemotePatchLoadingDialog(true);
  400. }
  401. else if (rack::patchRemoteURL != nullptr)
  402. {
  403. psDialog = new WasmRemotePatchLoadingDialog(false);
  404. }
  405. else if (rack::patchFromURL != nullptr)
  406. {
  407. static_cast<CardinalBasePlugin*>(context->plugin)->setState("patch", rack::patchFromURL);
  408. rack::contextSet(context);
  409. }
  410. else
  411. {
  412. new WasmWelcomeDialog();
  413. }
  414. #endif
  415. context->window->step();
  416. rack::contextSet(nullptr);
  417. WindowParametersSetCallback(context->window, this);
  418. }
  419. ~CardinalUI() override
  420. {
  421. rack::contextSet(context);
  422. context->nativeWindowId = 0;
  423. rack::window::WindowSetPluginUI(context->window, nullptr);
  424. context->tlw = nullptr;
  425. context->ui = nullptr;
  426. #if CARDINAL_VARIANT_MINI && ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
  427. {
  428. const ScopedContext sc(this);
  429. context->patch->clear();
  430. // do a little dance to prevent context scene deletion from saving to temp dir
  431. const ScopedValueSetter<bool> svs(rack::settings::headless, true);
  432. Engine_setAboutToClose(context->engine);
  433. delete context;
  434. }
  435. if (! fAutosavePath.empty())
  436. rack::system::removeRecursively(fAutosavePath);
  437. #endif
  438. #if ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
  439. if (fInitializer->shouldSaveSettings)
  440. {
  441. INFO("Save settings");
  442. rack::settings::save();
  443. }
  444. #endif
  445. rack::contextSet(nullptr);
  446. }
  447. void onNanoDisplay() override
  448. {
  449. const ScopedContext sc(this);
  450. context->window->step();
  451. }
  452. void uiIdle() override
  453. {
  454. #ifdef DPF_RUNTIME_TESTING
  455. if (inSelfTest)
  456. {
  457. context->window->step();
  458. return;
  459. }
  460. if (context->plugin->isSelfTestInstance())
  461. {
  462. inSelfTest = true;
  463. Application& app(getApp());
  464. const ScopedContext sc(this);
  465. context->patch->clear();
  466. app.idle();
  467. const rack::math::Vec mousePos(getWidth()/2,getHeight()/2);
  468. context->event->handleButton(mousePos, GLFW_MOUSE_BUTTON_LEFT, GLFW_RELEASE, 0x0);
  469. context->event->handleHover(mousePos, rack::math::Vec(0,0));
  470. app.idle();
  471. for (rack::plugin::Plugin* p : rack::plugin::plugins)
  472. {
  473. for (rack::plugin::Model* m : p->models)
  474. {
  475. d_stdout(">>>>>>>>>>>>>>>>> LOADING module %s : %s", p->slug.c_str(), m->slug.c_str());
  476. rack::engine::Module* const module = m->createModule();
  477. DISTRHO_SAFE_ASSERT_CONTINUE(module != nullptr);
  478. rack::CardinalPluginModelHelper* const helper = dynamic_cast<rack::CardinalPluginModelHelper*>(m);
  479. DISTRHO_SAFE_ASSERT_CONTINUE(helper != nullptr);
  480. d_stdout(">>>>>>>>>>>>>>>>> LOADING moduleWidget %s : %s", p->slug.c_str(), m->slug.c_str());
  481. rack::app::ModuleWidget* const moduleWidget = helper->createModuleWidget(module);
  482. DISTRHO_SAFE_ASSERT_CONTINUE(moduleWidget != nullptr);
  483. d_stdout(">>>>>>>>>>>>>>>>> ADDING TO ENGINE %s : %s", p->slug.c_str(), m->slug.c_str());
  484. context->engine->addModule(module);
  485. d_stdout(">>>>>>>>>>>>>>>>> ADDING TO RACK VIEW %s : %s", p->slug.c_str(), m->slug.c_str());
  486. context->scene->rack->addModuleAtMouse(moduleWidget);
  487. for (int i=5; --i>=0;)
  488. app.idle();
  489. d_stdout(">>>>>>>>>>>>>>>>> REMOVING FROM RACK VIEW %s : %s", p->slug.c_str(), m->slug.c_str());
  490. context->scene->rack->removeModule(moduleWidget);
  491. app.idle();
  492. d_stdout(">>>>>>>>>>>>>>>>> DELETING module + moduleWidget %s : %s", p->slug.c_str(), m->slug.c_str());
  493. delete moduleWidget;
  494. app.idle();
  495. }
  496. }
  497. inSelfTest = false;
  498. }
  499. #endif
  500. #if defined(DISTRHO_OS_WASM) && ! CARDINAL_VARIANT_MINI
  501. if (counterForFirstIdlePoint >= 0 && ++counterForFirstIdlePoint == 30)
  502. {
  503. counterForFirstIdlePoint = -1;
  504. if (rack::patchStorageSlug != nullptr)
  505. {
  506. std::string url("/patchstorage.php?slug=");
  507. url += rack::patchStorageSlug;
  508. std::free(rack::patchStorageSlug);
  509. rack::patchStorageSlug = nullptr;
  510. emscripten_async_wget(url.c_str(), context->patch->templatePath.c_str(),
  511. downloadRemotePatchSucceeded, downloadRemotePatchFailed);
  512. }
  513. else if (rack::patchRemoteURL != nullptr)
  514. {
  515. std::string url("/patchurl.php?url=");
  516. url += rack::patchRemoteURL;
  517. std::free(rack::patchRemoteURL);
  518. rack::patchRemoteURL = nullptr;
  519. emscripten_async_wget(url.c_str(), context->patch->templatePath.c_str(),
  520. downloadRemotePatchSucceeded, downloadRemotePatchFailed);
  521. }
  522. }
  523. #endif
  524. if (filebrowserhandle != nullptr && fileBrowserIdle(filebrowserhandle))
  525. {
  526. {
  527. const char* const path = fileBrowserGetPath(filebrowserhandle);
  528. const ScopedContext sc(this);
  529. filebrowseraction(path != nullptr ? strdup(path) : nullptr);
  530. }
  531. fileBrowserClose(filebrowserhandle);
  532. filebrowseraction = nullptr;
  533. filebrowserhandle = nullptr;
  534. }
  535. #if CARDINAL_VARIANT_MINI && ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
  536. {
  537. const ScopedContext sc(this);
  538. for (uint32_t i=0; i<DISTRHO_PLUGIN_NUM_OUTPUTS;++i)
  539. context->dataOuts[i][0] = 0.f;
  540. ++context->processCounter;
  541. context->engine->stepBlock(1);
  542. }
  543. #endif
  544. if (windowParameters.rateLimit != 0 && ++rateLimitStep % (windowParameters.rateLimit * 2))
  545. return;
  546. rateLimitStep = 0;
  547. repaint();
  548. }
  549. void WindowParametersChanged(const WindowParameterList param, float value) override
  550. {
  551. float mult = 1.0f;
  552. switch (param)
  553. {
  554. case kWindowParameterShowTooltips:
  555. windowParameters.tooltips = value > 0.5f;
  556. break;
  557. case kWindowParameterCableOpacity:
  558. mult = 100.0f;
  559. windowParameters.cableOpacity = value;
  560. break;
  561. case kWindowParameterCableTension:
  562. mult = 100.0f;
  563. windowParameters.cableTension = value;
  564. break;
  565. case kWindowParameterRackBrightness:
  566. mult = 100.0f;
  567. windowParameters.rackBrightness = value;
  568. break;
  569. case kWindowParameterHaloBrightness:
  570. mult = 100.0f;
  571. windowParameters.haloBrightness = value;
  572. break;
  573. case kWindowParameterKnobMode:
  574. switch (static_cast<int>(value + 0.5f))
  575. {
  576. case rack::settings::KNOB_MODE_LINEAR:
  577. value = 0;
  578. windowParameters.knobMode = rack::settings::KNOB_MODE_LINEAR;
  579. break;
  580. case rack::settings::KNOB_MODE_ROTARY_ABSOLUTE:
  581. value = 1;
  582. windowParameters.knobMode = rack::settings::KNOB_MODE_ROTARY_ABSOLUTE;
  583. break;
  584. case rack::settings::KNOB_MODE_ROTARY_RELATIVE:
  585. value = 2;
  586. windowParameters.knobMode = rack::settings::KNOB_MODE_ROTARY_RELATIVE;
  587. break;
  588. }
  589. break;
  590. case kWindowParameterWheelKnobControl:
  591. windowParameters.knobScroll = value > 0.5f;
  592. break;
  593. case kWindowParameterWheelSensitivity:
  594. mult = 1000.0f;
  595. windowParameters.knobScrollSensitivity = value;
  596. break;
  597. case kWindowParameterLockModulePositions:
  598. windowParameters.lockModules = value > 0.5f;
  599. break;
  600. case kWindowParameterUpdateRateLimit:
  601. windowParameters.rateLimit = static_cast<int>(value + 0.5f);
  602. rateLimitStep = 0;
  603. break;
  604. case kWindowParameterBrowserSort:
  605. windowParameters.browserSort = static_cast<int>(value + 0.5f);
  606. break;
  607. case kWindowParameterBrowserZoom:
  608. windowParameters.browserZoom = value;
  609. value = std::pow(2.f, value) * 100.0f;
  610. break;
  611. case kWindowParameterInvertZoom:
  612. windowParameters.invertZoom = value > 0.5f;
  613. break;
  614. case kWindowParameterSqueezeModulePositions:
  615. windowParameters.squeezeModules = value > 0.5f;
  616. break;
  617. default:
  618. return;
  619. }
  620. setParameterValue(kCardinalParameterStartWindow + param, value * mult);
  621. }
  622. protected:
  623. /* --------------------------------------------------------------------------------------------------------
  624. * DSP/Plugin Callbacks */
  625. /**
  626. A parameter has changed on the plugin side.
  627. This is called by the host to inform the UI about parameter changes.
  628. */
  629. void parameterChanged(const uint32_t index, const float value) override
  630. {
  631. // host mapped parameters
  632. if (index < kCardinalParameterCountAtModules)
  633. {
  634. #if CARDINAL_VARIANT_MINI && ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
  635. context->parameters[index] = value;
  636. #endif
  637. return;
  638. }
  639. // bypass
  640. if (index == kCardinalParameterBypass)
  641. {
  642. #if CARDINAL_VARIANT_MINI && ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
  643. context->bypassed = value > 0.5f;
  644. #endif
  645. return;
  646. }
  647. if (index < kCardinalParameterCountAtWindow)
  648. {
  649. switch (index - kCardinalParameterStartWindow)
  650. {
  651. case kWindowParameterShowTooltips:
  652. windowParameters.tooltips = value > 0.5f;
  653. break;
  654. case kWindowParameterCableOpacity:
  655. windowParameters.cableOpacity = value / 100.0f;
  656. break;
  657. case kWindowParameterCableTension:
  658. windowParameters.cableTension = value / 100.0f;
  659. break;
  660. case kWindowParameterRackBrightness:
  661. windowParameters.rackBrightness = value / 100.0f;
  662. break;
  663. case kWindowParameterHaloBrightness:
  664. windowParameters.haloBrightness = value / 100.0f;
  665. break;
  666. case kWindowParameterKnobMode:
  667. switch (static_cast<int>(value + 0.5f))
  668. {
  669. case 0:
  670. windowParameters.knobMode = rack::settings::KNOB_MODE_LINEAR;
  671. break;
  672. case 1:
  673. windowParameters.knobMode = rack::settings::KNOB_MODE_ROTARY_ABSOLUTE;
  674. break;
  675. case 2:
  676. windowParameters.knobMode = rack::settings::KNOB_MODE_ROTARY_RELATIVE;
  677. break;
  678. }
  679. break;
  680. case kWindowParameterWheelKnobControl:
  681. windowParameters.knobScroll = value > 0.5f;
  682. break;
  683. case kWindowParameterWheelSensitivity:
  684. windowParameters.knobScrollSensitivity = value / 1000.0f;
  685. break;
  686. case kWindowParameterLockModulePositions:
  687. windowParameters.lockModules = value > 0.5f;
  688. break;
  689. case kWindowParameterUpdateRateLimit:
  690. windowParameters.rateLimit = static_cast<int>(value + 0.5f);
  691. rateLimitStep = 0;
  692. break;
  693. case kWindowParameterBrowserSort:
  694. windowParameters.browserSort = static_cast<int>(value + 0.5f);
  695. break;
  696. case kWindowParameterBrowserZoom:
  697. // round up to nearest valid value
  698. {
  699. float rvalue = value - 1.0f;
  700. if (rvalue <= 25.0f)
  701. rvalue = -2.0f;
  702. else if (rvalue <= 35.0f)
  703. rvalue = -1.5f;
  704. else if (rvalue <= 50.0f)
  705. rvalue = -1.0f;
  706. else if (rvalue <= 71.0f)
  707. rvalue = -0.5f;
  708. else if (rvalue <= 100.0f)
  709. rvalue = 0.0f;
  710. else if (rvalue <= 141.0f)
  711. rvalue = 0.5f;
  712. else if (rvalue <= 200.0f)
  713. rvalue = 1.0f;
  714. else
  715. rvalue = 0.0f;
  716. windowParameters.browserZoom = rvalue;
  717. }
  718. break;
  719. case kWindowParameterInvertZoom:
  720. windowParameters.invertZoom = value > 0.5f;
  721. break;
  722. case kWindowParameterSqueezeModulePositions:
  723. windowParameters.squeezeModules = value > 0.5f;
  724. break;
  725. default:
  726. return;
  727. }
  728. WindowParametersSetValues(context->window, windowParameters);
  729. return;
  730. }
  731. #if CARDINAL_VARIANT_MINI && ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
  732. if (index < kCardinalParameterCountAtMiniBuffers)
  733. {
  734. float* const buffer = *const_cast<float**>(&context->dataIns[index - kCardinalParameterStartMiniBuffers]);
  735. buffer[0] = value;
  736. return;
  737. }
  738. switch (index)
  739. {
  740. case kCardinalParameterMiniTimeFlags: {
  741. const int32_t flags = static_cast<int32_t>(value + 0.5f);
  742. context->playing = flags & 0x1;
  743. context->bbtValid = flags & 0x2;
  744. context->reset = flags & 0x4;
  745. return;
  746. }
  747. case kCardinalParameterMiniTimeBar:
  748. context->bar = static_cast<int32_t>(value + 0.5f);
  749. return;
  750. case kCardinalParameterMiniTimeBeat:
  751. context->beat = static_cast<int32_t>(value + 0.5f);
  752. return;
  753. case kCardinalParameterMiniTimeBeatsPerBar:
  754. context->beatsPerBar = static_cast<int32_t>(value + 0.5f);
  755. return;
  756. case kCardinalParameterMiniTimeBeatType:
  757. context->beatType = static_cast<int32_t>(value + 0.5f);
  758. context->ticksPerClock = context->ticksPerBeat / context->beatType;
  759. context->tickClock = std::fmod(context->tick, context->ticksPerClock);
  760. return;
  761. case kCardinalParameterMiniTimeFrame:
  762. context->frame = static_cast<uint64_t>(value * context->sampleRate + 0.5f);
  763. return;
  764. case kCardinalParameterMiniTimeBarStartTick:
  765. context->barStartTick = value;
  766. return;
  767. case kCardinalParameterMiniTimeBeatsPerMinute:
  768. context->beatsPerMinute = value;
  769. context->ticksPerFrame = 1.0 / (60.0 * context->sampleRate / context->beatsPerMinute / context->ticksPerBeat);
  770. return;
  771. case kCardinalParameterMiniTimeTick:
  772. context->tick = value;
  773. context->tickClock = std::fmod(context->tick, context->ticksPerClock);
  774. return;
  775. case kCardinalParameterMiniTimeTicksPerBeat:
  776. context->ticksPerBeat = value;
  777. context->ticksPerClock = context->ticksPerBeat / context->beatType;
  778. context->ticksPerFrame = 1.0 / (60.0 * context->sampleRate / context->beatsPerMinute / context->ticksPerBeat);
  779. context->tickClock = std::fmod(context->tick, context->ticksPerClock);
  780. return;
  781. }
  782. #endif
  783. }
  784. void stateChanged(const char* const key, const char* const value) override
  785. {
  786. #if CARDINAL_VARIANT_MINI && ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
  787. if (std::strcmp(key, "patch") == 0)
  788. {
  789. if (fAutosavePath.empty())
  790. return;
  791. rack::system::removeRecursively(fAutosavePath);
  792. rack::system::createDirectories(fAutosavePath);
  793. FILE* const f = std::fopen(rack::system::join(fAutosavePath, "patch.json").c_str(), "w");
  794. DISTRHO_SAFE_ASSERT_RETURN(f != nullptr,);
  795. std::fwrite(value, std::strlen(value), 1, f);
  796. std::fclose(f);
  797. const ScopedContext sc(this);
  798. try {
  799. context->patch->loadAutosave();
  800. } catch(const rack::Exception& e) {
  801. d_stderr(e.what());
  802. } DISTRHO_SAFE_EXCEPTION_RETURN("setState loadAutosave",);
  803. return;
  804. }
  805. #endif
  806. if (std::strcmp(key, "windowSize") == 0)
  807. {
  808. int width = 0;
  809. int height = 0;
  810. std::sscanf(value, "%d:%d", &width, &height);
  811. if (width > 0 && height > 0)
  812. {
  813. const double scaleFactor = getScaleFactor();
  814. setSize(width * scaleFactor, height * scaleFactor);
  815. }
  816. return;
  817. }
  818. }
  819. // -------------------------------------------------------------------------------------------------------
  820. static int glfwMods(const uint mod) noexcept
  821. {
  822. int mods = 0;
  823. if (mod & kModifierControl)
  824. mods |= GLFW_MOD_CONTROL;
  825. if (mod & kModifierShift)
  826. mods |= GLFW_MOD_SHIFT;
  827. if (mod & kModifierAlt)
  828. mods |= GLFW_MOD_ALT;
  829. if (mod & kModifierSuper)
  830. mods |= GLFW_MOD_SUPER;
  831. /*
  832. if (glfwGetKey(win, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS || glfwGetKey(win, GLFW_KEY_RIGHT_SHIFT) == GLFW_PRESS)
  833. mods |= GLFW_MOD_SHIFT;
  834. if (glfwGetKey(win, GLFW_KEY_LEFT_CONTROL) == GLFW_PRESS || glfwGetKey(win, GLFW_KEY_RIGHT_CONTROL) == GLFW_PRESS)
  835. mods |= GLFW_MOD_CONTROL;
  836. if (glfwGetKey(win, GLFW_KEY_LEFT_ALT) == GLFW_PRESS || glfwGetKey(win, GLFW_KEY_RIGHT_ALT) == GLFW_PRESS)
  837. mods |= GLFW_MOD_ALT;
  838. if (glfwGetKey(win, GLFW_KEY_LEFT_SUPER) == GLFW_PRESS || glfwGetKey(win, GLFW_KEY_RIGHT_SUPER) == GLFW_PRESS)
  839. mods |= GLFW_MOD_SUPER;
  840. */
  841. return mods;
  842. }
  843. bool onMouse(const MouseEvent& ev) override
  844. {
  845. #ifdef DPF_RUNTIME_TESTING
  846. if (inSelfTest) return false;
  847. #endif
  848. if (ev.press)
  849. getWindow().focus();
  850. const int action = ev.press ? GLFW_PRESS : GLFW_RELEASE;
  851. int mods = glfwMods(ev.mod);
  852. int button;
  853. switch (ev.button)
  854. {
  855. case kMouseButtonLeft: button = GLFW_MOUSE_BUTTON_LEFT; break;
  856. case kMouseButtonRight: button = GLFW_MOUSE_BUTTON_RIGHT; break;
  857. case kMouseButtonMiddle: button = GLFW_MOUSE_BUTTON_MIDDLE; break;
  858. default:
  859. button = ev.button;
  860. break;
  861. }
  862. #ifdef DISTRHO_OS_MAC
  863. // Remap Ctrl-left click to right click on macOS
  864. if (button == GLFW_MOUSE_BUTTON_LEFT && (mods & RACK_MOD_MASK) == GLFW_MOD_CONTROL) {
  865. button = GLFW_MOUSE_BUTTON_RIGHT;
  866. mods &= ~GLFW_MOD_CONTROL;
  867. }
  868. // Remap Ctrl-shift-left click to middle click on macOS
  869. if (button == GLFW_MOUSE_BUTTON_LEFT && (mods & RACK_MOD_MASK) == (GLFW_MOD_CONTROL | GLFW_MOD_SHIFT)) {
  870. button = GLFW_MOUSE_BUTTON_MIDDLE;
  871. mods &= ~(GLFW_MOD_CONTROL | GLFW_MOD_SHIFT);
  872. }
  873. #endif
  874. const ScopedContext sc(this, mods);
  875. return context->event->handleButton(lastMousePos, button, action, mods);
  876. }
  877. bool onMotion(const MotionEvent& ev) override
  878. {
  879. #ifdef DPF_RUNTIME_TESTING
  880. if (inSelfTest) return false;
  881. #endif
  882. const rack::math::Vec mousePos = rack::math::Vec(ev.pos.getX(), ev.pos.getY()).div(getScaleFactor()).round();
  883. const rack::math::Vec mouseDelta = mousePos.minus(lastMousePos);
  884. lastMousePos = mousePos;
  885. const ScopedContext sc(this, glfwMods(ev.mod));
  886. return context->event->handleHover(mousePos, mouseDelta);
  887. }
  888. bool onScroll(const ScrollEvent& ev) override
  889. {
  890. #ifdef DPF_RUNTIME_TESTING
  891. if (inSelfTest) return false;
  892. #endif
  893. rack::math::Vec scrollDelta = rack::math::Vec(-ev.delta.getX(), ev.delta.getY());
  894. #ifndef DISTRHO_OS_MAC
  895. scrollDelta = scrollDelta.mult(50.0);
  896. #endif
  897. const int mods = glfwMods(ev.mod);
  898. const ScopedContext sc(this, mods);
  899. return context->event->handleScroll(lastMousePos, scrollDelta);
  900. }
  901. bool onCharacterInput(const CharacterInputEvent& ev) override
  902. {
  903. #ifdef DPF_RUNTIME_TESTING
  904. if (inSelfTest) return false;
  905. #endif
  906. if (ev.character < ' ' || ev.character >= kKeyDelete)
  907. return false;
  908. const int mods = glfwMods(ev.mod);
  909. const ScopedContext sc(this, mods);
  910. return context->event->handleText(lastMousePos, ev.character);
  911. }
  912. bool onKeyboard(const KeyboardEvent& ev) override
  913. {
  914. #ifdef DPF_RUNTIME_TESTING
  915. if (inSelfTest) return false;
  916. #endif
  917. const int action = ev.press ? GLFW_PRESS : GLFW_RELEASE;
  918. const int mods = glfwMods(ev.mod);
  919. int key;
  920. switch (ev.key)
  921. {
  922. case kKeyTab: key = GLFW_KEY_TAB; break;
  923. case kKeyBackspace: key = GLFW_KEY_BACKSPACE; break;
  924. case kKeyEnter: key = GLFW_KEY_ENTER; break;
  925. case kKeyEscape: key = GLFW_KEY_ESCAPE; break;
  926. case kKeyDelete: key = GLFW_KEY_DELETE; break;
  927. case kKeySpace: key = GLFW_KEY_SPACE; break;
  928. case kKeyF1: key = GLFW_KEY_F1; break;
  929. case kKeyF2: key = GLFW_KEY_F2; break;
  930. case kKeyF3: key = GLFW_KEY_F3; break;
  931. case kKeyF4: key = GLFW_KEY_F4; break;
  932. case kKeyF5: key = GLFW_KEY_F5; break;
  933. case kKeyF6: key = GLFW_KEY_F6; break;
  934. case kKeyF7: key = GLFW_KEY_F7; break;
  935. case kKeyF8: key = GLFW_KEY_F8; break;
  936. case kKeyF9: key = GLFW_KEY_F9; break;
  937. case kKeyF10: key = GLFW_KEY_F10; break;
  938. case kKeyF11: key = GLFW_KEY_F11; break;
  939. case kKeyF12: key = GLFW_KEY_F12; break;
  940. case kKeyPageUp: key = GLFW_KEY_PAGE_UP; break;
  941. case kKeyPageDown: key = GLFW_KEY_PAGE_DOWN; break;
  942. case kKeyEnd: key = GLFW_KEY_END; break;
  943. case kKeyHome: key = GLFW_KEY_HOME; break;
  944. case kKeyLeft: key = GLFW_KEY_LEFT; break;
  945. case kKeyUp: key = GLFW_KEY_UP; break;
  946. case kKeyRight: key = GLFW_KEY_RIGHT; break;
  947. case kKeyDown: key = GLFW_KEY_DOWN; break;
  948. case kKeyPrintScreen: key = GLFW_KEY_PRINT_SCREEN; break;
  949. case kKeyInsert: key = GLFW_KEY_INSERT; break;
  950. case kKeyPause: key = GLFW_KEY_PAUSE; break;
  951. case kKeyMenu: key = GLFW_KEY_MENU; break;
  952. case kKeyNumLock: key = GLFW_KEY_NUM_LOCK; break;
  953. case kKeyScrollLock: key = GLFW_KEY_SCROLL_LOCK; break;
  954. case kKeyCapsLock: key = GLFW_KEY_CAPS_LOCK; break;
  955. case kKeyShiftL: key = GLFW_KEY_LEFT_SHIFT; break;
  956. case kKeyShiftR: key = GLFW_KEY_RIGHT_SHIFT; break;
  957. case kKeyControlL: key = GLFW_KEY_LEFT_CONTROL; break;
  958. case kKeyControlR: key = GLFW_KEY_RIGHT_CONTROL; break;
  959. case kKeyAltL: key = GLFW_KEY_LEFT_ALT; break;
  960. case kKeyAltR: key = GLFW_KEY_RIGHT_ALT; break;
  961. case kKeySuperL: key = GLFW_KEY_LEFT_SUPER; break;
  962. case kKeySuperR: key = GLFW_KEY_RIGHT_SUPER; break;
  963. case kKeyPad0: key = GLFW_KEY_KP_0; break;
  964. case kKeyPad1: key = GLFW_KEY_KP_1; break;
  965. case kKeyPad2: key = GLFW_KEY_KP_2; break;
  966. case kKeyPad3: key = GLFW_KEY_KP_3; break;
  967. case kKeyPad4: key = GLFW_KEY_KP_4; break;
  968. case kKeyPad5: key = GLFW_KEY_KP_5; break;
  969. case kKeyPad6: key = GLFW_KEY_KP_6; break;
  970. case kKeyPad7: key = GLFW_KEY_KP_7; break;
  971. case kKeyPad8: key = GLFW_KEY_KP_8; break;
  972. case kKeyPad9: key = GLFW_KEY_KP_9; break;
  973. case kKeyPadEnter: key = GLFW_KEY_KP_ENTER; break;
  974. /* undefined in glfw
  975. case kKeyPadPageUp:
  976. case kKeyPadPageDown:
  977. case kKeyPadEnd:
  978. case kKeyPadHome:
  979. case kKeyPadLeft:
  980. case kKeyPadUp:
  981. case kKeyPadRight:
  982. case kKeyPadDown:
  983. case kKeyPadClear:
  984. case kKeyPadInsert:
  985. case kKeyPadDelete:
  986. */
  987. case kKeyPadEqual: key = GLFW_KEY_KP_EQUAL; break;
  988. case kKeyPadMultiply: key = GLFW_KEY_KP_MULTIPLY; break;
  989. case kKeyPadAdd: key = GLFW_KEY_KP_ADD; break;
  990. /* undefined in glfw
  991. case kKeyPadSeparator:
  992. */
  993. case kKeyPadSubtract: key = GLFW_KEY_KP_SUBTRACT; break;
  994. case kKeyPadDecimal: key = GLFW_KEY_KP_DECIMAL; break;
  995. case kKeyPadDivide: key = GLFW_KEY_KP_DIVIDE; break;
  996. default:
  997. // glfw expects uppercase
  998. if (ev.key >= 'a' && ev.key <= 'z')
  999. key = ev.key - ('a' - 'A');
  1000. else
  1001. key = ev.key;
  1002. break;
  1003. }
  1004. const ScopedContext sc(this, mods);
  1005. return context->event->handleKey(lastMousePos, key, ev.keycode, action, mods);
  1006. }
  1007. void onResize(const ResizeEvent& ev) override
  1008. {
  1009. UI::onResize(ev);
  1010. if (context->window != nullptr)
  1011. WindowSetInternalSize(context->window, rack::math::Vec(ev.size.getWidth(), ev.size.getHeight()));
  1012. const double scaleFactor = getScaleFactor();
  1013. const int width = static_cast<int>(ev.size.getWidth() / scaleFactor + 0.5);
  1014. const int height = static_cast<int>(ev.size.getHeight() / scaleFactor + 0.5);
  1015. char sizeString[64] = {};
  1016. std::snprintf(sizeString, sizeof(sizeString), "%d:%d", width, height);
  1017. setState("windowSize", sizeString);
  1018. if (rack::isStandalone())
  1019. rack::settings::windowSize = rack::math::Vec(width, height);
  1020. }
  1021. void uiFocus(const bool focus, CrossingMode) override
  1022. {
  1023. #ifdef DPF_RUNTIME_TESTING
  1024. if (inSelfTest) return;
  1025. #endif
  1026. if (!focus)
  1027. {
  1028. const ScopedContext sc(this, 0);
  1029. context->event->handleLeave();
  1030. }
  1031. }
  1032. void uiFileBrowserSelected(const char* const filename) override
  1033. {
  1034. if (filename == nullptr)
  1035. return;
  1036. rack::contextSet(context);
  1037. WindowParametersRestore(context->window);
  1038. std::string sfilename = filename;
  1039. if (saving)
  1040. {
  1041. const bool uncompressed = savingUncompressed;
  1042. savingUncompressed = false;
  1043. if (rack::system::getExtension(sfilename) != ".vcv")
  1044. sfilename += ".vcv";
  1045. try {
  1046. if (uncompressed)
  1047. {
  1048. context->engine->prepareSave();
  1049. if (json_t* const rootJ = context->patch->toJson())
  1050. {
  1051. if (FILE* const file = std::fopen(sfilename.c_str(), "w"))
  1052. {
  1053. json_dumpf(rootJ, file, JSON_INDENT(2));
  1054. std::fclose(file);
  1055. }
  1056. json_decref(rootJ);
  1057. }
  1058. }
  1059. else
  1060. {
  1061. context->patch->save(sfilename);
  1062. }
  1063. }
  1064. catch (rack::Exception& e) {
  1065. std::string message = rack::string::f("Could not save patch: %s", e.what());
  1066. asyncDialog::create(message.c_str());
  1067. return;
  1068. }
  1069. }
  1070. else
  1071. {
  1072. try {
  1073. context->patch->load(sfilename);
  1074. } catch (rack::Exception& e) {
  1075. std::string message = rack::string::f("Could not load patch: %s", e.what());
  1076. asyncDialog::create(message.c_str());
  1077. return;
  1078. }
  1079. }
  1080. context->patch->path = sfilename;
  1081. context->patch->pushRecentPath(sfilename);
  1082. context->history->setSaved();
  1083. #ifdef DISTRHO_OS_WASM
  1084. rack::syncfs();
  1085. #else
  1086. rack::settings::save();
  1087. #endif
  1088. }
  1089. #if 0
  1090. void uiReshape(const uint width, const uint height) override
  1091. {
  1092. glEnable(GL_BLEND);
  1093. glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  1094. glMatrixMode(GL_PROJECTION);
  1095. glLoadIdentity();
  1096. glOrtho(0.0, width, 0.0, height, -1.0, 1.0);
  1097. glViewport(0, 0, width, height);
  1098. glMatrixMode(GL_MODELVIEW);
  1099. glLoadIdentity();
  1100. }
  1101. #endif
  1102. private:
  1103. /**
  1104. Set our UI class as non-copyable and add a leak detector just in case.
  1105. */
  1106. DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CardinalUI)
  1107. };
  1108. /* ------------------------------------------------------------------------------------------------------------
  1109. * UI entry point, called by DPF to create a new UI instance. */
  1110. UI* createUI()
  1111. {
  1112. return new CardinalUI();
  1113. }
  1114. // -----------------------------------------------------------------------------------------------------------
  1115. END_NAMESPACE_DISTRHO