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.

880 lines
23KB

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