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.

991 lines
26KB

  1. /*
  2. * DISTRHO Cardinal Plugin
  3. * Copyright (C) 2021-2024 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. /**
  18. * This file is an edited version of VCVRack's app/MenuBar.cpp
  19. * Copyright (C) 2016-2023 VCV.
  20. *
  21. * This program is free software: you can redistribute it and/or
  22. * modify it under the terms of the GNU General Public License as
  23. * published by the Free Software Foundation; either version 3 of
  24. * the License, or (at your option) any later version.
  25. */
  26. #include <thread>
  27. #include <utility>
  28. #include <app/MenuBar.hpp>
  29. #include <app/TipWindow.hpp>
  30. #include <widget/OpaqueWidget.hpp>
  31. #include <ui/Button.hpp>
  32. #include <ui/MenuItem.hpp>
  33. #include <ui/MenuSeparator.hpp>
  34. #include <ui/SequentialLayout.hpp>
  35. #include <ui/Slider.hpp>
  36. #include <ui/TextField.hpp>
  37. #include <ui/ProgressBar.hpp>
  38. #include <ui/Label.hpp>
  39. #include <engine/Engine.hpp>
  40. #include <widget/FramebufferWidget.hpp>
  41. #include <window/Window.hpp>
  42. #include <asset.hpp>
  43. #include <context.hpp>
  44. #include <settings.hpp>
  45. #include <helpers.hpp>
  46. #include <system.hpp>
  47. #include <plugin.hpp>
  48. #include <patch.hpp>
  49. #include <library.hpp>
  50. #include "../CardinalCommon.hpp"
  51. #include "../CardinalRemote.hpp"
  52. #include "../CardinalPluginContext.hpp"
  53. #include "DistrhoPlugin.hpp"
  54. #include "DistrhoStandaloneUtils.hpp"
  55. #ifdef DISTRHO_OS_WASM
  56. # include <emscripten/emscripten.h>
  57. # undef HAVE_LIBLO
  58. #endif
  59. #ifdef HAVE_LIBLO
  60. # include <lo/lo.h>
  61. #endif
  62. namespace rack {
  63. namespace asset {
  64. std::string patchesPath();
  65. }
  66. namespace engine {
  67. void Engine_setRemoteDetails(Engine*, remoteUtils::RemoteDetails*);
  68. }
  69. namespace app {
  70. namespace menuBar {
  71. struct MenuButton : ui::Button {
  72. void step() override {
  73. box.size.x = bndLabelWidth(APP->window->vg, -1, text.c_str()) + 1.0;
  74. Widget::step();
  75. }
  76. void draw(const DrawArgs& args) override {
  77. BNDwidgetState state = BND_DEFAULT;
  78. if (APP->event->hoveredWidget == this)
  79. state = BND_HOVER;
  80. if (APP->event->draggedWidget == this)
  81. state = BND_ACTIVE;
  82. bndMenuItem(args.vg, 0.0, 0.0, box.size.x, box.size.y, state, -1, text.c_str());
  83. Widget::draw(args);
  84. }
  85. };
  86. ////////////////////
  87. // File
  88. ////////////////////
  89. struct FileButton : MenuButton {
  90. const bool isStandalone;
  91. std::vector<std::string> demoPatches;
  92. #ifdef DISTRHO_OS_WASM
  93. static void WebBrowserDataSaved(const int err)
  94. {
  95. err ? async_dialog_message("Error, could not save web browser data!")
  96. : async_dialog_message("Web browser data saved!");
  97. }
  98. static void wasmSaveAs()
  99. {
  100. async_dialog_text_input("Filename", nullptr, [](char* const filename) {
  101. if (filename == nullptr)
  102. return;
  103. APP->patch->path = asset::user("patches");
  104. system::createDirectories(APP->patch->path);
  105. APP->patch->path += filename;
  106. if (rack::system::getExtension(filename) != ".vcv")
  107. APP->patch->path += ".vcv";
  108. patchUtils::saveDialog(APP->patch->path);
  109. std::free(filename);
  110. });
  111. }
  112. #endif
  113. FileButton(const bool standalone)
  114. : MenuButton(), isStandalone(standalone)
  115. {
  116. #if CARDINAL_VARIANT_MINI
  117. const std::string patchesDir = asset::patchesPath() + DISTRHO_OS_SEP_STR "mini";
  118. #else
  119. const std::string patchesDir = asset::patchesPath() + DISTRHO_OS_SEP_STR "examples";
  120. #endif
  121. if (system::isDirectory(patchesDir))
  122. {
  123. demoPatches = system::getEntries(patchesDir);
  124. std::sort(demoPatches.begin(), demoPatches.end(), [](const std::string& a, const std::string& b){
  125. return string::lowercase(a) < string::lowercase(b);
  126. });
  127. }
  128. }
  129. void onAction(const ActionEvent& e) override {
  130. ui::Menu* menu = createMenu();
  131. menu->cornerFlags = BND_CORNER_TOP;
  132. menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y));
  133. #ifndef DISTRHO_OS_WASM
  134. constexpr const char* const NewShortcut = RACK_MOD_CTRL_NAME "+N";
  135. #else
  136. constexpr const char* const NewShortcut = "";
  137. #endif
  138. menu->addChild(createMenuItem("New", NewShortcut, []() {
  139. patchUtils::loadTemplateDialog(false);
  140. }));
  141. #if DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
  142. menu->addChild(createMenuItem("New (factory template)", "", []() {
  143. patchUtils::loadTemplateDialog(true);
  144. }));
  145. #ifndef DISTRHO_OS_WASM
  146. constexpr const char* const OpenName = "Open...";
  147. #else
  148. constexpr const char* const OpenName = "Import patch...";
  149. #endif
  150. menu->addChild(createMenuItem(OpenName, RACK_MOD_CTRL_NAME "+O", []() {
  151. patchUtils::loadDialog();
  152. }));
  153. const std::string patchesDir = asset::user("patches");
  154. const std::vector<std::string> patches = system::isDirectory(patchesDir) ? system::getEntries(patchesDir) : std::vector<std::string>();
  155. menu->addChild(createSubmenuItem("Open local patch", "", [patches](ui::Menu* menu) {
  156. for (const std::string& path : patches) {
  157. std::string name = system::getStem(path);
  158. menu->addChild(createMenuItem(name, "", [=]() {
  159. patchUtils::loadPathDialog(path, false);
  160. }));
  161. }
  162. }, patches.empty()));
  163. menu->addChild(createSubmenuItem("Open recent", "", [](ui::Menu* menu) {
  164. for (const std::string& path : settings::recentPatchPaths) {
  165. std::string name = system::getStem(path);
  166. menu->addChild(createMenuItem(name, "", [=]() {
  167. patchUtils::loadPathDialog(path, false);
  168. }));
  169. }
  170. }, settings::recentPatchPaths.empty()));
  171. #endif
  172. if (!demoPatches.empty())
  173. {
  174. menu->addChild(createSubmenuItem("Open demo / example project", "", [=](ui::Menu* const menu) {
  175. for (std::string path : demoPatches) {
  176. std::string label = system::getStem(path);
  177. for (size_t i=0, len=label.size(); i<len; ++i) {
  178. if (label[i] == '_')
  179. label[i] = ' ';
  180. }
  181. menu->addChild(createMenuItem(label, "", [path]() {
  182. patchUtils::loadPathDialog(path, true);
  183. }));
  184. }
  185. menu->addChild(new ui::MenuSeparator);
  186. menu->addChild(createMenuItem("Open patchstorage.com for more patches", "", []() {
  187. patchUtils::openBrowser("https://patchstorage.com/platform/cardinal/");
  188. }));
  189. }));
  190. }
  191. #if DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
  192. menu->addChild(createMenuItem("Import selection...", "", [=]() {
  193. patchUtils::loadSelectionDialog();
  194. }, false, true));
  195. menu->addChild(new ui::MenuSeparator);
  196. #ifndef DISTRHO_OS_WASM
  197. menu->addChild(createMenuItem("Save", RACK_MOD_CTRL_NAME "+S", []() {
  198. // NOTE: for plugin versions it will do nothing if path is empty, intentionally
  199. patchUtils::saveDialog(APP->patch->path);
  200. }, APP->patch->path.empty() && !isStandalone));
  201. menu->addChild(createMenuItem("Save as / Export...", RACK_MOD_CTRL_NAME "+Shift+S", []() {
  202. patchUtils::saveAsDialog();
  203. }));
  204. #else
  205. menu->addChild(createMenuItem("Save", "", []() {
  206. if (APP->patch->path.empty())
  207. wasmSaveAs();
  208. else
  209. patchUtils::saveDialog(APP->patch->path);
  210. }));
  211. menu->addChild(createMenuItem("Save as...", "", []() {
  212. wasmSaveAs();
  213. }));
  214. menu->addChild(createMenuItem("Save and download compressed", "", []() {
  215. patchUtils::saveAsDialog();
  216. }));
  217. menu->addChild(createMenuItem("Save and download uncompressed", "", []() {
  218. patchUtils::saveAsDialogUncompressed();
  219. }));
  220. #endif
  221. #endif
  222. menu->addChild(createMenuItem("Revert", RACK_MOD_CTRL_NAME "+" RACK_MOD_SHIFT_NAME "+O", []() {
  223. patchUtils::revertDialog();
  224. }, APP->patch->path.empty()));
  225. #if DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
  226. menu->addChild(createMenuItem("Overwrite template", "", []() {
  227. patchUtils::saveTemplateDialog();
  228. }));
  229. #ifdef DISTRHO_OS_WASM
  230. menu->addChild(new ui::MenuSeparator);
  231. menu->addChild(createMenuItem("Save persistent browser data", "", []() {
  232. settings::save();
  233. EM_ASM({
  234. Module.FS.syncfs(false, function(err){ dynCall('vi', $0, [!!err]) });
  235. }, WebBrowserDataSaved);
  236. }));
  237. #endif
  238. #endif
  239. #if defined(HAVE_LIBLO) || ! DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
  240. #ifdef __MOD_DEVICES__
  241. #define REMOTE_NAME "MOD"
  242. #else
  243. #define REMOTE_NAME "Remote"
  244. #endif
  245. menu->addChild(new ui::MenuSeparator);
  246. remoteUtils::RemoteDetails* const remoteDetails = remoteUtils::getRemote();
  247. if (remoteDetails != nullptr && remoteDetails->connected) {
  248. menu->addChild(createMenuItem("Deploy to " REMOTE_NAME, "F7", [remoteDetails]() {
  249. remoteUtils::sendFullPatchToRemote(remoteDetails);
  250. }));
  251. menu->addChild(createCheckMenuItem("Auto deploy to " REMOTE_NAME, "",
  252. [remoteDetails]() {return remoteDetails->autoDeploy;},
  253. [remoteDetails]() {
  254. remoteDetails->autoDeploy = !remoteDetails->autoDeploy;
  255. Engine_setRemoteDetails(APP->engine, remoteDetails->autoDeploy ? remoteDetails : nullptr);
  256. }
  257. ));
  258. #ifndef __MOD_DEVICES__
  259. } else {
  260. menu->addChild(createMenuItem("Connect to " REMOTE_NAME "...", "", [remoteDetails]() {
  261. const std::string url = remoteDetails != nullptr ? remoteDetails->url : CARDINAL_DEFAULT_REMOTE_URL;
  262. async_dialog_text_input("Remote:", url.c_str(), [](char* const url) {
  263. if (url == nullptr)
  264. return;
  265. DISTRHO_SAFE_ASSERT(remoteUtils::connectToRemote(url));
  266. std::free(url);
  267. });
  268. }));
  269. #endif
  270. }
  271. #endif
  272. #ifndef DISTRHO_OS_WASM
  273. if (isStandalone) {
  274. menu->addChild(new ui::MenuSeparator);
  275. menu->addChild(createMenuItem("Quit", RACK_MOD_CTRL_NAME "+Q", []() {
  276. APP->window->close();
  277. }));
  278. }
  279. #endif
  280. }
  281. };
  282. ////////////////////
  283. // Edit
  284. ////////////////////
  285. struct EditButton : MenuButton {
  286. void onAction(const ActionEvent& e) override {
  287. ui::Menu* menu = createMenu();
  288. menu->cornerFlags = BND_CORNER_TOP;
  289. menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y));
  290. struct UndoItem : ui::MenuItem {
  291. void step() override {
  292. text = "Undo " + APP->history->getUndoName();
  293. disabled = !APP->history->canUndo();
  294. MenuItem::step();
  295. }
  296. void onAction(const ActionEvent& e) override {
  297. APP->history->undo();
  298. }
  299. };
  300. menu->addChild(createMenuItem<UndoItem>("", RACK_MOD_CTRL_NAME "+Z"));
  301. struct RedoItem : ui::MenuItem {
  302. void step() override {
  303. text = "Redo " + APP->history->getRedoName();
  304. disabled = !APP->history->canRedo();
  305. MenuItem::step();
  306. }
  307. void onAction(const ActionEvent& e) override {
  308. APP->history->redo();
  309. }
  310. };
  311. menu->addChild(createMenuItem<RedoItem>("", RACK_MOD_CTRL_NAME "+" RACK_MOD_SHIFT_NAME "+Z"));
  312. menu->addChild(createMenuItem("Clear cables", "", [=]() {
  313. APP->patch->disconnectDialog();
  314. }));
  315. menu->addChild(new ui::MenuSeparator);
  316. patchUtils::appendSelectionContextMenu(menu);
  317. }
  318. };
  319. ////////////////////
  320. // View
  321. ////////////////////
  322. struct ZoomQuantity : Quantity {
  323. void setValue(float value) override {
  324. APP->scene->rackScroll->setZoom(std::pow(2.f, value));
  325. }
  326. float getValue() override {
  327. return std::log2(APP->scene->rackScroll->getZoom());
  328. }
  329. float getMinValue() override {
  330. return -2.f;
  331. }
  332. float getMaxValue() override {
  333. return 2.f;
  334. }
  335. float getDefaultValue() override {
  336. return 0.0;
  337. }
  338. float getDisplayValue() override {
  339. return std::round(std::pow(2.f, getValue()) * 100);
  340. }
  341. void setDisplayValue(float displayValue) override {
  342. setValue(std::log2(displayValue / 100));
  343. }
  344. std::string getLabel() override {
  345. return "Zoom";
  346. }
  347. std::string getUnit() override {
  348. return "%";
  349. }
  350. };
  351. struct ZoomSlider : ui::Slider {
  352. ZoomSlider() {
  353. quantity = new ZoomQuantity;
  354. }
  355. ~ZoomSlider() {
  356. delete quantity;
  357. }
  358. };
  359. struct CableOpacityQuantity : Quantity {
  360. void setValue(float value) override {
  361. settings::cableOpacity = math::clamp(value, getMinValue(), getMaxValue());
  362. }
  363. float getValue() override {
  364. return settings::cableOpacity;
  365. }
  366. float getDefaultValue() override {
  367. return 0.5;
  368. }
  369. float getDisplayValue() override {
  370. return getValue() * 100;
  371. }
  372. void setDisplayValue(float displayValue) override {
  373. setValue(displayValue / 100);
  374. }
  375. std::string getLabel() override {
  376. return "Cable opacity";
  377. }
  378. std::string getUnit() override {
  379. return "%";
  380. }
  381. };
  382. struct CableOpacitySlider : ui::Slider {
  383. CableOpacitySlider() {
  384. quantity = new CableOpacityQuantity;
  385. }
  386. ~CableOpacitySlider() {
  387. delete quantity;
  388. }
  389. };
  390. struct CableTensionQuantity : Quantity {
  391. void setValue(float value) override {
  392. settings::cableTension = math::clamp(value, getMinValue(), getMaxValue());
  393. }
  394. float getValue() override {
  395. return settings::cableTension;
  396. }
  397. float getDefaultValue() override {
  398. return 0.75;
  399. }
  400. float getDisplayValue() override {
  401. return getValue() * 100;
  402. }
  403. void setDisplayValue(float displayValue) override {
  404. setValue(displayValue / 100);
  405. }
  406. std::string getLabel() override {
  407. return "Cable tension";
  408. }
  409. std::string getUnit() override {
  410. return "%";
  411. }
  412. };
  413. struct CableTensionSlider : ui::Slider {
  414. CableTensionSlider() {
  415. quantity = new CableTensionQuantity;
  416. }
  417. ~CableTensionSlider() {
  418. delete quantity;
  419. }
  420. };
  421. struct RackBrightnessQuantity : Quantity {
  422. void setValue(float value) override {
  423. settings::rackBrightness = math::clamp(value, getMinValue(), getMaxValue());
  424. }
  425. float getValue() override {
  426. return settings::rackBrightness;
  427. }
  428. float getDefaultValue() override {
  429. return 1.0;
  430. }
  431. float getDisplayValue() override {
  432. return getValue() * 100;
  433. }
  434. void setDisplayValue(float displayValue) override {
  435. setValue(displayValue / 100);
  436. }
  437. std::string getUnit() override {
  438. return "%";
  439. }
  440. std::string getLabel() override {
  441. return "Room brightness";
  442. }
  443. int getDisplayPrecision() override {
  444. return 3;
  445. }
  446. };
  447. struct RackBrightnessSlider : ui::Slider {
  448. RackBrightnessSlider() {
  449. quantity = new RackBrightnessQuantity;
  450. }
  451. ~RackBrightnessSlider() {
  452. delete quantity;
  453. }
  454. };
  455. struct HaloBrightnessQuantity : Quantity {
  456. void setValue(float value) override {
  457. settings::haloBrightness = math::clamp(value, getMinValue(), getMaxValue());
  458. }
  459. float getValue() override {
  460. return settings::haloBrightness;
  461. }
  462. float getDefaultValue() override {
  463. return 0.25;
  464. }
  465. float getDisplayValue() override {
  466. return getValue() * 100;
  467. }
  468. void setDisplayValue(float displayValue) override {
  469. setValue(displayValue / 100);
  470. }
  471. std::string getUnit() override {
  472. return "%";
  473. }
  474. std::string getLabel() override {
  475. return "Light bloom";
  476. }
  477. int getDisplayPrecision() override {
  478. return 3;
  479. }
  480. };
  481. struct HaloBrightnessSlider : ui::Slider {
  482. HaloBrightnessSlider() {
  483. quantity = new HaloBrightnessQuantity;
  484. }
  485. ~HaloBrightnessSlider() {
  486. delete quantity;
  487. }
  488. };
  489. struct KnobScrollSensitivityQuantity : Quantity {
  490. void setValue(float value) override {
  491. value = math::clamp(value, getMinValue(), getMaxValue());
  492. settings::knobScrollSensitivity = std::pow(2.f, value);
  493. }
  494. float getValue() override {
  495. return std::log2(settings::knobScrollSensitivity);
  496. }
  497. float getMinValue() override {
  498. return std::log2(1e-4f);
  499. }
  500. float getMaxValue() override {
  501. return std::log2(1e-2f);
  502. }
  503. float getDefaultValue() override {
  504. return std::log2(1e-3f);
  505. }
  506. float getDisplayValue() override {
  507. return std::pow(2.f, getValue() - getDefaultValue());
  508. }
  509. void setDisplayValue(float displayValue) override {
  510. setValue(std::log2(displayValue) + getDefaultValue());
  511. }
  512. std::string getLabel() override {
  513. return "Scroll wheel knob sensitivity";
  514. }
  515. int getDisplayPrecision() override {
  516. return 2;
  517. }
  518. };
  519. struct KnobScrollSensitivitySlider : ui::Slider {
  520. KnobScrollSensitivitySlider() {
  521. quantity = new KnobScrollSensitivityQuantity;
  522. }
  523. ~KnobScrollSensitivitySlider() {
  524. delete quantity;
  525. }
  526. };
  527. #if DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
  528. static void setAllFramebufferWidgetsDirty(widget::Widget* const widget)
  529. {
  530. for (widget::Widget* child : widget->children)
  531. {
  532. if (widget::FramebufferWidget* const fbw = dynamic_cast<widget::FramebufferWidget*>(child))
  533. fbw->setDirty();
  534. setAllFramebufferWidgetsDirty(child);
  535. }
  536. }
  537. #endif
  538. struct ViewButton : MenuButton {
  539. void onAction(const ActionEvent& e) override {
  540. ui::Menu* menu = createMenu();
  541. menu->cornerFlags = BND_CORNER_TOP;
  542. menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y));
  543. menu->addChild(createMenuLabel("Appearance"));
  544. #if DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
  545. std::string darkModeText;
  546. if (settings::preferDarkPanels)
  547. darkModeText = CHECKMARK_STRING;
  548. menu->addChild(createMenuItem("Dark Mode", darkModeText, []() {
  549. switchDarkMode(!settings::preferDarkPanels);
  550. setAllFramebufferWidgetsDirty(APP->scene);
  551. }));
  552. #endif
  553. menu->addChild(createBoolPtrMenuItem("Show tooltips", "", &settings::tooltips));
  554. ZoomSlider* zoomSlider = new ZoomSlider;
  555. zoomSlider->box.size.x = 250.0;
  556. menu->addChild(zoomSlider);
  557. CableOpacitySlider* cableOpacitySlider = new CableOpacitySlider;
  558. cableOpacitySlider->box.size.x = 250.0;
  559. menu->addChild(cableOpacitySlider);
  560. CableTensionSlider* cableTensionSlider = new CableTensionSlider;
  561. cableTensionSlider->box.size.x = 250.0;
  562. menu->addChild(cableTensionSlider);
  563. RackBrightnessSlider* rackBrightnessSlider = new RackBrightnessSlider;
  564. rackBrightnessSlider->box.size.x = 250.0;
  565. menu->addChild(rackBrightnessSlider);
  566. HaloBrightnessSlider* haloBrightnessSlider = new HaloBrightnessSlider;
  567. haloBrightnessSlider->box.size.x = 250.0;
  568. menu->addChild(haloBrightnessSlider);
  569. menu->addChild(new ui::MenuSeparator);
  570. menu->addChild(createMenuLabel("Module dragging"));
  571. menu->addChild(createBoolPtrMenuItem("Lock module positions", "", &settings::lockModules));
  572. menu->addChild(createBoolPtrMenuItem("Auto-squeeze modules when dragging", "", &settings::squeezeModules));
  573. menu->addChild(new ui::MenuSeparator);
  574. menu->addChild(createMenuLabel("Parameters"));
  575. #ifdef DISTRHO_OS_WASM
  576. menu->addChild(createBoolPtrMenuItem("Lock cursor while dragging", "", &settings::allowCursorLock));
  577. #endif
  578. static const std::vector<std::string> knobModeLabels = {
  579. "Linear",
  580. "Scaled linear",
  581. "Absolute rotary",
  582. "Relative rotary",
  583. };
  584. static const std::vector<int> knobModes = {0, 2, 3};
  585. menu->addChild(createSubmenuItem("Knob mode", knobModeLabels[settings::knobMode], [=](ui::Menu* menu) {
  586. for (int knobMode : knobModes) {
  587. menu->addChild(createCheckMenuItem(knobModeLabels[knobMode], "",
  588. [=]() {return settings::knobMode == knobMode;},
  589. [=]() {settings::knobMode = (settings::KnobMode) knobMode;}
  590. ));
  591. }
  592. }));
  593. menu->addChild(createBoolPtrMenuItem("Scroll wheel knob control", "", &settings::knobScroll));
  594. KnobScrollSensitivitySlider* knobScrollSensitivitySlider = new KnobScrollSensitivitySlider;
  595. knobScrollSensitivitySlider->box.size.x = 250.0;
  596. menu->addChild(knobScrollSensitivitySlider);
  597. menu->addChild(new ui::MenuSeparator);
  598. menu->addChild(createMenuLabel("Window"));
  599. #ifdef DISTRHO_OS_WASM
  600. const bool fullscreen = APP->window->isFullScreen();
  601. std::string rightText = "F11";
  602. if (fullscreen)
  603. rightText += " " CHECKMARK_STRING;
  604. menu->addChild(createMenuItem("Fullscreen", rightText, [=]() {
  605. APP->window->setFullScreen(!fullscreen);
  606. }));
  607. #endif
  608. menu->addChild(createBoolPtrMenuItem("Invert zoom", "", &settings::invertZoom));
  609. static const std::vector<std::string> rateLimitLabels = {
  610. "None",
  611. "2x",
  612. "4x",
  613. };
  614. static const std::vector<int> rateLimits = {0, 1, 2};
  615. menu->addChild(createSubmenuItem("Update rate limit", rateLimitLabels[settings::rateLimit], [=](ui::Menu* menu) {
  616. for (int rateLimit : rateLimits) {
  617. menu->addChild(createCheckMenuItem(rateLimitLabels[rateLimit], "",
  618. [=]() {return settings::rateLimit == rateLimit;},
  619. [=]() {settings::rateLimit = rateLimit;}
  620. ));
  621. }
  622. }));
  623. }
  624. };
  625. ////////////////////
  626. // Engine
  627. ////////////////////
  628. struct EngineButton : MenuButton {
  629. #ifdef HAVE_LIBLO
  630. bool remoteServerStarted = false;
  631. #endif
  632. void onAction(const ActionEvent& e) override {
  633. ui::Menu* menu = createMenu();
  634. menu->cornerFlags = BND_CORNER_TOP;
  635. menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y));
  636. std::string cpuMeterText = "F3";
  637. if (settings::cpuMeter)
  638. cpuMeterText += " " CHECKMARK_STRING;
  639. menu->addChild(createMenuItem("Performance meters", cpuMeterText, [=]() {
  640. settings::cpuMeter ^= true;
  641. }));
  642. #ifdef HAVE_LIBLO
  643. if (isStandalone()) {
  644. CardinalPluginContext* const context = static_cast<CardinalPluginContext*>(APP);
  645. CardinalBasePlugin* const plugin = static_cast<CardinalBasePlugin*>(context->plugin);
  646. // const bool remoteServerStarted = this->remoteServerStarted;
  647. const std::string remoteControlText = remoteServerStarted ? " " CHECKMARK_STRING : "";
  648. menu->addChild(createMenuItem("Enable OSC remote control", remoteControlText, [=]() {
  649. if (remoteServerStarted) {
  650. remoteServerStarted = false;
  651. plugin->stopRemoteServer();
  652. return;
  653. }
  654. async_dialog_text_input("OSC network port", CARDINAL_DEFAULT_REMOTE_PORT, [=](char* const port) {
  655. if (port == nullptr)
  656. return;
  657. if (plugin->startRemoteServer(port))
  658. remoteServerStarted = true;
  659. std::free(port);
  660. });
  661. }));
  662. }
  663. #endif
  664. if (isUsingNativeAudio()) {
  665. if (supportsAudioInput()) {
  666. const bool enabled = isAudioInputEnabled();
  667. std::string rightText;
  668. if (enabled)
  669. rightText = CHECKMARK_STRING;
  670. menu->addChild(createMenuItem("Enable Audio Input", rightText, [enabled]() {
  671. if (!enabled)
  672. requestAudioInput();
  673. }));
  674. }
  675. if (supportsMIDI()) {
  676. std::string rightText;
  677. if (isMIDIEnabled())
  678. rightText = CHECKMARK_STRING;
  679. menu->addChild(createMenuItem("Enable/Reconnect MIDI", rightText, []() {
  680. requestMIDI();
  681. }));
  682. }
  683. if (supportsBufferSizeChanges()) {
  684. static const std::vector<uint32_t> bufferSizes = {
  685. #ifdef DISTRHO_OS_WASM
  686. 256, 512, 1024, 2048, 4096, 8192, 16384
  687. #else
  688. 128, 256, 512, 1024, 2048, 4096, 8192
  689. #endif
  690. };
  691. const uint32_t currentBufferSize = getBufferSize();
  692. menu->addChild(createSubmenuItem("Buffer Size", std::to_string(currentBufferSize), [=](ui::Menu* menu) {
  693. for (uint32_t bufferSize : bufferSizes) {
  694. menu->addChild(createCheckMenuItem(std::to_string(bufferSize), "",
  695. [=]() {return currentBufferSize == bufferSize;},
  696. [=]() {requestBufferSizeChange(bufferSize);}
  697. ));
  698. }
  699. }));
  700. }
  701. }
  702. }
  703. #ifdef HAVE_LIBLO
  704. void step() override {
  705. MenuButton::step();
  706. if (remoteServerStarted) {
  707. CardinalPluginContext* const context = static_cast<CardinalPluginContext*>(APP);
  708. CardinalBasePlugin* const plugin = static_cast<CardinalBasePlugin*>(context->plugin);
  709. plugin->stepRemoteServer();
  710. }
  711. }
  712. #endif
  713. };
  714. ////////////////////
  715. // Help
  716. ////////////////////
  717. struct HelpButton : MenuButton {
  718. void onAction(const ActionEvent& e) override {
  719. ui::Menu* menu = createMenu();
  720. menu->cornerFlags = BND_CORNER_TOP;
  721. menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y));
  722. menu->addChild(createMenuItem("Rack User manual", "F1", [=]() {
  723. patchUtils::openBrowser("https://vcvrack.com/manual");
  724. }));
  725. menu->addChild(createMenuItem("Cardinal project page", "", [=]() {
  726. patchUtils::openBrowser("https://github.com/DISTRHO/Cardinal/");
  727. }));
  728. menu->addChild(new ui::MenuSeparator);
  729. #ifndef DISTRHO_OS_WASM
  730. menu->addChild(createMenuItem("Open user folder", "", [=]() {
  731. system::openDirectory(asset::user(""));
  732. }));
  733. menu->addChild(new ui::MenuSeparator);
  734. #endif
  735. menu->addChild(createMenuLabel("Rack " + APP_VERSION + " Compatible"));
  736. }
  737. };
  738. ////////////////////
  739. // MenuBar
  740. ////////////////////
  741. struct InfoLabel : ui::Label {
  742. int frameCount = 0;
  743. double frameDurationTotal = 0.0;
  744. double frameDurationAvg = NAN;
  745. // double uiLastTime = 0.0;
  746. // double uiLastThreadTime = 0.0;
  747. // double uiFrac = 0.0;
  748. void step() override {
  749. // Compute frame rate
  750. double frameDuration = APP->window->getLastFrameDuration();
  751. if (std::isfinite(frameDuration)) {
  752. frameDurationTotal += frameDuration;
  753. frameCount++;
  754. }
  755. if (frameDurationTotal >= 1.0) {
  756. frameDurationAvg = frameDurationTotal / frameCount;
  757. frameDurationTotal = 0.0;
  758. frameCount = 0;
  759. }
  760. // Compute UI thread CPU
  761. // double time = system::getTime();
  762. // double uiDuration = time - uiLastTime;
  763. // if (uiDuration >= 1.0) {
  764. // double threadTime = system::getThreadTime();
  765. // uiFrac = (threadTime - uiLastThreadTime) / uiDuration;
  766. // uiLastThreadTime = threadTime;
  767. // uiLastTime = time;
  768. // }
  769. text = "";
  770. if (box.size.x >= 400) {
  771. double fps = std::isfinite(frameDurationAvg) ? 1.0 / frameDurationAvg : 0.0;
  772. #if DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
  773. double meterAverage = APP->engine->getMeterAverage();
  774. double meterMax = APP->engine->getMeterMax();
  775. text = string::f("%.1f fps %.1f%% avg %.1f%% max", fps, meterAverage * 100, meterMax * 100);
  776. #else
  777. text = string::f("%.1f fps", fps);
  778. #endif
  779. text += " ";
  780. }
  781. text += "Cardinal " + APP_EDITION + " " + CARDINAL_VERSION;
  782. Label::step();
  783. }
  784. };
  785. struct MenuBar : widget::OpaqueWidget {
  786. InfoLabel* infoLabel;
  787. MenuBar(const bool isStandalone)
  788. : widget::OpaqueWidget()
  789. {
  790. const float margin = 5;
  791. box.size.y = BND_WIDGET_HEIGHT + 2 * margin;
  792. ui::SequentialLayout* layout = new ui::SequentialLayout;
  793. layout->margin = math::Vec(margin, margin);
  794. layout->spacing = math::Vec(0, 0);
  795. addChild(layout);
  796. FileButton* fileButton = new FileButton(isStandalone);
  797. fileButton->text = "File";
  798. layout->addChild(fileButton);
  799. EditButton* editButton = new EditButton;
  800. editButton->text = "Edit";
  801. layout->addChild(editButton);
  802. ViewButton* viewButton = new ViewButton;
  803. viewButton->text = "View";
  804. layout->addChild(viewButton);
  805. #if DISTRHO_PLUGIN_WANT_DIRECT_ACCESS
  806. EngineButton* engineButton = new EngineButton;
  807. engineButton->text = "Engine";
  808. layout->addChild(engineButton);
  809. #endif
  810. HelpButton* helpButton = new HelpButton;
  811. helpButton->text = "Help";
  812. layout->addChild(helpButton);
  813. infoLabel = new InfoLabel;
  814. infoLabel->box.size.x = 600;
  815. infoLabel->alignment = ui::Label::RIGHT_ALIGNMENT;
  816. layout->addChild(infoLabel);
  817. }
  818. void draw(const DrawArgs& args) override {
  819. bndMenuBackground(args.vg, 0.0, 0.0, box.size.x, box.size.y, BND_CORNER_ALL);
  820. bndBevel(args.vg, 0.0, 0.0, box.size.x, box.size.y);
  821. Widget::draw(args);
  822. }
  823. void step() override {
  824. Widget::step();
  825. infoLabel->box.size.x = box.size.x - infoLabel->box.pos.x - 5;
  826. // Setting 50% alpha prevents Label from using the default UI theme color, so set the color manually here.
  827. infoLabel->color = color::alpha(bndGetTheme()->regularTheme.textColor, 0.5);
  828. }
  829. };
  830. } // namespace menuBar
  831. widget::Widget* createMenuBar() {
  832. menuBar::MenuBar* menuBar = new menuBar::MenuBar(isStandalone());
  833. return menuBar;
  834. }
  835. } // namespace app
  836. } // namespace rack