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.

857 lines
22KB

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