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.

667 lines
17KB

  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 <window/Window.hpp>
  41. #include <asset.hpp>
  42. #include <context.hpp>
  43. #include <settings.hpp>
  44. #include <helpers.hpp>
  45. #include <system.hpp>
  46. #include <plugin.hpp>
  47. #include <patch.hpp>
  48. #include <library.hpp>
  49. #ifdef HAVE_LIBLO
  50. # include <lo/lo.h>
  51. #endif
  52. #include "../CardinalCommon.hpp"
  53. namespace rack {
  54. namespace app {
  55. namespace menuBar {
  56. struct MenuButton : ui::Button {
  57. void step() override {
  58. box.size.x = bndLabelWidth(APP->window->vg, -1, text.c_str()) + 1.0;
  59. Widget::step();
  60. }
  61. void draw(const DrawArgs& args) override {
  62. BNDwidgetState state = BND_DEFAULT;
  63. if (APP->event->hoveredWidget == this)
  64. state = BND_HOVER;
  65. if (APP->event->draggedWidget == this)
  66. state = BND_ACTIVE;
  67. bndMenuItem(args.vg, 0.0, 0.0, box.size.x, box.size.y, state, -1, text.c_str());
  68. Widget::draw(args);
  69. }
  70. };
  71. ////////////////////
  72. // File
  73. ////////////////////
  74. struct FileButton : MenuButton {
  75. const bool isStandalone;
  76. FileButton(const bool standalone)
  77. : MenuButton(), isStandalone(standalone) {}
  78. void onAction(const ActionEvent& e) override {
  79. ui::Menu* menu = createMenu();
  80. menu->cornerFlags = BND_CORNER_TOP;
  81. menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y));
  82. menu->addChild(createMenuItem("New", RACK_MOD_CTRL_NAME "+N", []() {
  83. patchUtils::loadTemplateDialog();
  84. }));
  85. menu->addChild(createMenuItem("Open / Import...", RACK_MOD_CTRL_NAME "+O", []() {
  86. patchUtils::loadDialog();
  87. }));
  88. menu->addChild(createMenuItem("Save", RACK_MOD_CTRL_NAME "+S", []() {
  89. // NOTE: will do nothing if path is empty, intentionally
  90. patchUtils::saveDialog(APP->patch->path);
  91. }, APP->patch->path.empty()));
  92. menu->addChild(createMenuItem("Save as / Export...", RACK_MOD_CTRL_NAME "+Shift+S", []() {
  93. patchUtils::saveAsDialog();
  94. }));
  95. menu->addChild(createMenuItem("Export uncompressed json...", "", []() {
  96. patchUtils::saveAsDialogUncompressed();
  97. }));
  98. #ifdef HAVE_LIBLO
  99. if (patchUtils::isRemoteConnected()) {
  100. menu->addChild(createMenuItem("Deploy to MOD", "F7", []() {
  101. patchUtils::deployToRemote();
  102. }));
  103. const bool autoDeploy = patchUtils::isRemoteAutoDeployed();
  104. menu->addChild(createCheckMenuItem("Auto deploy to MOD", "",
  105. [=]() {return autoDeploy;},
  106. [=]() {patchUtils::setRemoteAutoDeploy(!autoDeploy);}
  107. ));
  108. } else {
  109. menu->addChild(createMenuItem("Connect to MOD", "", []() {
  110. patchUtils::connectToRemote();
  111. }));
  112. }
  113. #endif
  114. menu->addChild(createMenuItem("Revert", RACK_MOD_CTRL_NAME "+" RACK_MOD_SHIFT_NAME "+O", []() {
  115. patchUtils::revertDialog();
  116. }, APP->patch->path.empty()));
  117. menu->addChild(new ui::MenuSeparator);
  118. // Load selection
  119. menu->addChild(createMenuItem("Import selection", "", [=]() {
  120. patchUtils::loadSelectionDialog();
  121. }, false, true));
  122. if (isStandalone) {
  123. menu->addChild(new ui::MenuSeparator);
  124. menu->addChild(createMenuItem("Quit", RACK_MOD_CTRL_NAME "+Q", []() {
  125. APP->window->close();
  126. }));
  127. };
  128. }
  129. };
  130. ////////////////////
  131. // Edit
  132. ////////////////////
  133. struct EditButton : MenuButton {
  134. void onAction(const ActionEvent& e) override {
  135. ui::Menu* menu = createMenu();
  136. menu->cornerFlags = BND_CORNER_TOP;
  137. menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y));
  138. struct UndoItem : ui::MenuItem {
  139. void step() override {
  140. text = "Undo " + APP->history->getUndoName();
  141. disabled = !APP->history->canUndo();
  142. MenuItem::step();
  143. }
  144. void onAction(const ActionEvent& e) override {
  145. APP->history->undo();
  146. }
  147. };
  148. menu->addChild(createMenuItem<UndoItem>("", RACK_MOD_CTRL_NAME "+Z"));
  149. struct RedoItem : ui::MenuItem {
  150. void step() override {
  151. text = "Redo " + APP->history->getRedoName();
  152. disabled = !APP->history->canRedo();
  153. MenuItem::step();
  154. }
  155. void onAction(const ActionEvent& e) override {
  156. APP->history->redo();
  157. }
  158. };
  159. menu->addChild(createMenuItem<RedoItem>("", RACK_MOD_CTRL_NAME "+" RACK_MOD_SHIFT_NAME "+Z"));
  160. menu->addChild(createMenuItem("Clear cables", "", [=]() {
  161. APP->patch->disconnectDialog();
  162. }));
  163. menu->addChild(new ui::MenuSeparator);
  164. patchUtils::appendSelectionContextMenu(menu);
  165. }
  166. };
  167. ////////////////////
  168. // View
  169. ////////////////////
  170. struct ZoomQuantity : Quantity {
  171. void setValue(float value) override {
  172. APP->scene->rackScroll->setZoom(std::pow(2.f, value));
  173. }
  174. float getValue() override {
  175. return std::log2(APP->scene->rackScroll->getZoom());
  176. }
  177. float getMinValue() override {
  178. return -2.f;
  179. }
  180. float getMaxValue() override {
  181. return 2.f;
  182. }
  183. float getDefaultValue() override {
  184. return 0.0;
  185. }
  186. float getDisplayValue() override {
  187. return std::round(std::pow(2.f, getValue()) * 100);
  188. }
  189. void setDisplayValue(float displayValue) override {
  190. setValue(std::log2(displayValue / 100));
  191. }
  192. std::string getLabel() override {
  193. return "Zoom";
  194. }
  195. std::string getUnit() override {
  196. return "%";
  197. }
  198. };
  199. struct ZoomSlider : ui::Slider {
  200. ZoomSlider() {
  201. quantity = new ZoomQuantity;
  202. }
  203. ~ZoomSlider() {
  204. delete quantity;
  205. }
  206. };
  207. struct CableOpacityQuantity : Quantity {
  208. void setValue(float value) override {
  209. settings::cableOpacity = math::clamp(value, getMinValue(), getMaxValue());
  210. }
  211. float getValue() override {
  212. return settings::cableOpacity;
  213. }
  214. float getDefaultValue() override {
  215. return 0.5;
  216. }
  217. float getDisplayValue() override {
  218. return getValue() * 100;
  219. }
  220. void setDisplayValue(float displayValue) override {
  221. setValue(displayValue / 100);
  222. }
  223. std::string getLabel() override {
  224. return "Cable opacity";
  225. }
  226. std::string getUnit() override {
  227. return "%";
  228. }
  229. };
  230. struct CableOpacitySlider : ui::Slider {
  231. CableOpacitySlider() {
  232. quantity = new CableOpacityQuantity;
  233. }
  234. ~CableOpacitySlider() {
  235. delete quantity;
  236. }
  237. };
  238. struct CableTensionQuantity : Quantity {
  239. void setValue(float value) override {
  240. settings::cableTension = math::clamp(value, getMinValue(), getMaxValue());
  241. }
  242. float getValue() override {
  243. return settings::cableTension;
  244. }
  245. float getDefaultValue() override {
  246. return 0.75;
  247. }
  248. float getDisplayValue() override {
  249. return getValue() * 100;
  250. }
  251. void setDisplayValue(float displayValue) override {
  252. setValue(displayValue / 100);
  253. }
  254. std::string getLabel() override {
  255. return "Cable tension";
  256. }
  257. std::string getUnit() override {
  258. return "%";
  259. }
  260. };
  261. struct CableTensionSlider : ui::Slider {
  262. CableTensionSlider() {
  263. quantity = new CableTensionQuantity;
  264. }
  265. ~CableTensionSlider() {
  266. delete quantity;
  267. }
  268. };
  269. struct RackBrightnessQuantity : Quantity {
  270. void setValue(float value) override {
  271. settings::rackBrightness = math::clamp(value, getMinValue(), getMaxValue());
  272. }
  273. float getValue() override {
  274. return settings::rackBrightness;
  275. }
  276. float getDefaultValue() override {
  277. return 1.0;
  278. }
  279. float getDisplayValue() override {
  280. return getValue() * 100;
  281. }
  282. void setDisplayValue(float displayValue) override {
  283. setValue(displayValue / 100);
  284. }
  285. std::string getUnit() override {
  286. return "%";
  287. }
  288. std::string getLabel() override {
  289. return "Room brightness";
  290. }
  291. int getDisplayPrecision() override {
  292. return 3;
  293. }
  294. };
  295. struct RackBrightnessSlider : ui::Slider {
  296. RackBrightnessSlider() {
  297. quantity = new RackBrightnessQuantity;
  298. }
  299. ~RackBrightnessSlider() {
  300. delete quantity;
  301. }
  302. };
  303. struct HaloBrightnessQuantity : Quantity {
  304. void setValue(float value) override {
  305. settings::haloBrightness = math::clamp(value, getMinValue(), getMaxValue());
  306. }
  307. float getValue() override {
  308. return settings::haloBrightness;
  309. }
  310. float getDefaultValue() override {
  311. return 0.25;
  312. }
  313. float getDisplayValue() override {
  314. return getValue() * 100;
  315. }
  316. void setDisplayValue(float displayValue) override {
  317. setValue(displayValue / 100);
  318. }
  319. std::string getUnit() override {
  320. return "%";
  321. }
  322. std::string getLabel() override {
  323. return "Light bloom";
  324. }
  325. int getDisplayPrecision() override {
  326. return 3;
  327. }
  328. };
  329. struct HaloBrightnessSlider : ui::Slider {
  330. HaloBrightnessSlider() {
  331. quantity = new HaloBrightnessQuantity;
  332. }
  333. ~HaloBrightnessSlider() {
  334. delete quantity;
  335. }
  336. };
  337. struct KnobScrollSensitivityQuantity : Quantity {
  338. void setValue(float value) override {
  339. value = math::clamp(value, getMinValue(), getMaxValue());
  340. settings::knobScrollSensitivity = std::pow(2.f, value);
  341. }
  342. float getValue() override {
  343. return std::log2(settings::knobScrollSensitivity);
  344. }
  345. float getMinValue() override {
  346. return std::log2(1e-4f);
  347. }
  348. float getMaxValue() override {
  349. return std::log2(1e-2f);
  350. }
  351. float getDefaultValue() override {
  352. return std::log2(1e-3f);
  353. }
  354. float getDisplayValue() override {
  355. return std::pow(2.f, getValue() - getDefaultValue());
  356. }
  357. void setDisplayValue(float displayValue) override {
  358. setValue(std::log2(displayValue) + getDefaultValue());
  359. }
  360. std::string getLabel() override {
  361. return "Scroll wheel knob sensitivity";
  362. }
  363. int getDisplayPrecision() override {
  364. return 2;
  365. }
  366. };
  367. struct KnobScrollSensitivitySlider : ui::Slider {
  368. KnobScrollSensitivitySlider() {
  369. quantity = new KnobScrollSensitivityQuantity;
  370. }
  371. ~KnobScrollSensitivitySlider() {
  372. delete quantity;
  373. }
  374. };
  375. struct ViewButton : MenuButton {
  376. void onAction(const ActionEvent& e) override {
  377. ui::Menu* menu = createMenu();
  378. menu->cornerFlags = BND_CORNER_TOP;
  379. menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y));
  380. menu->addChild(createBoolPtrMenuItem("Show tooltips", "", &settings::tooltips));
  381. ZoomSlider* zoomSlider = new ZoomSlider;
  382. zoomSlider->box.size.x = 250.0;
  383. menu->addChild(zoomSlider);
  384. CableOpacitySlider* cableOpacitySlider = new CableOpacitySlider;
  385. cableOpacitySlider->box.size.x = 250.0;
  386. menu->addChild(cableOpacitySlider);
  387. CableTensionSlider* cableTensionSlider = new CableTensionSlider;
  388. cableTensionSlider->box.size.x = 250.0;
  389. menu->addChild(cableTensionSlider);
  390. RackBrightnessSlider* rackBrightnessSlider = new RackBrightnessSlider;
  391. rackBrightnessSlider->box.size.x = 250.0;
  392. menu->addChild(rackBrightnessSlider);
  393. HaloBrightnessSlider* haloBrightnessSlider = new HaloBrightnessSlider;
  394. haloBrightnessSlider->box.size.x = 250.0;
  395. menu->addChild(haloBrightnessSlider);
  396. menu->addChild(new ui::MenuSeparator);
  397. // menu->addChild(createBoolPtrMenuItem("Hide cursor while dragging", "", &settings::allowCursorLock));
  398. static const std::vector<std::string> knobModeLabels = {
  399. "Linear",
  400. "Scaled linear",
  401. "Absolute rotary",
  402. "Relative rotary",
  403. };
  404. static const std::vector<int> knobModes = {0, 2, 3};
  405. menu->addChild(createSubmenuItem("Knob mode", knobModeLabels[settings::knobMode], [=](ui::Menu* menu) {
  406. for (int knobMode : knobModes) {
  407. menu->addChild(createCheckMenuItem(knobModeLabels[knobMode], "",
  408. [=]() {return settings::knobMode == knobMode;},
  409. [=]() {settings::knobMode = (settings::KnobMode) knobMode;}
  410. ));
  411. }
  412. }));
  413. menu->addChild(createBoolPtrMenuItem("Scroll wheel knob control", "", &settings::knobScroll));
  414. KnobScrollSensitivitySlider* knobScrollSensitivitySlider = new KnobScrollSensitivitySlider;
  415. knobScrollSensitivitySlider->box.size.x = 250.0;
  416. menu->addChild(knobScrollSensitivitySlider);
  417. menu->addChild(createBoolPtrMenuItem("Lock module positions", "", &settings::lockModules));
  418. menu->addChild(new ui::MenuSeparator);
  419. static const std::vector<std::string> rateLimitLabels = {
  420. "None",
  421. "2x",
  422. "4x",
  423. };
  424. static const std::vector<int> rateLimits = {0, 1, 2};
  425. menu->addChild(createSubmenuItem("Update rate limit", rateLimitLabels[settings::rateLimit], [=](ui::Menu* menu) {
  426. for (int rateLimit : rateLimits) {
  427. menu->addChild(createCheckMenuItem(rateLimitLabels[rateLimit], "",
  428. [=]() {return settings::rateLimit == rateLimit;},
  429. [=]() {settings::rateLimit = rateLimit;}
  430. ));
  431. }
  432. }));
  433. }
  434. };
  435. ////////////////////
  436. // Engine
  437. ////////////////////
  438. struct EngineButton : MenuButton {
  439. void onAction(const ActionEvent& e) override {
  440. ui::Menu* menu = createMenu();
  441. menu->cornerFlags = BND_CORNER_TOP;
  442. menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y));
  443. std::string cpuMeterText = "F3";
  444. if (settings::cpuMeter)
  445. cpuMeterText += " " CHECKMARK_STRING;
  446. menu->addChild(createMenuItem("Performance meters", cpuMeterText, [=]() {
  447. settings::cpuMeter ^= true;
  448. }));
  449. }
  450. };
  451. ////////////////////
  452. // Help
  453. ////////////////////
  454. struct HelpButton : MenuButton {
  455. void onAction(const ActionEvent& e) override {
  456. ui::Menu* menu = createMenu();
  457. menu->cornerFlags = BND_CORNER_TOP;
  458. menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y));
  459. menu->addChild(createMenuItem("Rack User manual", "F1", [=]() {
  460. system::openBrowser("https://vcvrack.com/manual/");
  461. }));
  462. menu->addChild(createMenuItem("Cardinal Project page", "", [=]() {
  463. system::openBrowser("https://github.com/DISTRHO/Cardinal/");
  464. }));
  465. menu->addChild(new ui::MenuSeparator);
  466. menu->addChild(createMenuLabel("Cardinal " + APP_EDITION + " " + CARDINAL_VERSION));
  467. menu->addChild(createMenuLabel("Rack " + APP_VERSION + " Compatible"));
  468. }
  469. };
  470. ////////////////////
  471. // MenuBar
  472. ////////////////////
  473. struct MeterLabel : ui::Label {
  474. int frameIndex = 0;
  475. double frameDurationTotal = 0.0;
  476. double frameDurationAvg = 0.0;
  477. double uiLastTime = 0.0;
  478. double uiLastThreadTime = 0.0;
  479. double uiFrac = 0.0;
  480. void step() override {
  481. // Compute frame rate
  482. double frameDuration = APP->window->getLastFrameDuration();
  483. frameDurationTotal += frameDuration;
  484. frameIndex++;
  485. if (frameDurationTotal >= 1.0) {
  486. frameDurationAvg = frameDurationTotal / frameIndex;
  487. frameDurationTotal = 0.0;
  488. frameIndex = 0;
  489. }
  490. // Compute UI thread CPU
  491. // double time = system::getTime();
  492. // double uiDuration = time - uiLastTime;
  493. // if (uiDuration >= 1.0) {
  494. // double threadTime = system::getThreadTime();
  495. // uiFrac = (threadTime - uiLastThreadTime) / uiDuration;
  496. // uiLastThreadTime = threadTime;
  497. // uiLastTime = time;
  498. // }
  499. double meterAverage = APP->engine->getMeterAverage();
  500. double meterMax = APP->engine->getMeterMax();
  501. text = string::f("%.1f fps %.1f%% avg %.1f%% max", 1.0 / frameDurationAvg, meterAverage * 100, meterMax * 100);
  502. Label::step();
  503. }
  504. };
  505. struct MenuBar : widget::OpaqueWidget {
  506. MeterLabel* meterLabel;
  507. MenuBar(const bool isStandalone)
  508. : widget::OpaqueWidget()
  509. {
  510. const float margin = 5;
  511. box.size.y = BND_WIDGET_HEIGHT + 2 * margin;
  512. ui::SequentialLayout* layout = new ui::SequentialLayout;
  513. layout->margin = math::Vec(margin, margin);
  514. layout->spacing = math::Vec(0, 0);
  515. addChild(layout);
  516. FileButton* fileButton = new FileButton(isStandalone);
  517. fileButton->text = "File";
  518. layout->addChild(fileButton);
  519. EditButton* editButton = new EditButton;
  520. editButton->text = "Edit";
  521. layout->addChild(editButton);
  522. ViewButton* viewButton = new ViewButton;
  523. viewButton->text = "View";
  524. layout->addChild(viewButton);
  525. EngineButton* engineButton = new EngineButton;
  526. engineButton->text = "Engine";
  527. layout->addChild(engineButton);
  528. HelpButton* helpButton = new HelpButton;
  529. helpButton->text = "Help";
  530. layout->addChild(helpButton);
  531. // ui::Label* titleLabel = new ui::Label;
  532. // titleLabel->color.a = 0.5;
  533. // layout->addChild(titleLabel);
  534. meterLabel = new MeterLabel;
  535. meterLabel->box.pos.y = margin;
  536. meterLabel->box.size.x = 300;
  537. meterLabel->alignment = ui::Label::RIGHT_ALIGNMENT;
  538. meterLabel->color.a = 0.5;
  539. addChild(meterLabel);
  540. }
  541. void draw(const DrawArgs& args) override {
  542. bndMenuBackground(args.vg, 0.0, 0.0, box.size.x, box.size.y, BND_CORNER_ALL);
  543. bndBevel(args.vg, 0.0, 0.0, box.size.x, box.size.y);
  544. Widget::draw(args);
  545. }
  546. void step() override {
  547. meterLabel->box.pos.x = box.size.x - meterLabel->box.size.x - 5;
  548. Widget::step();
  549. }
  550. };
  551. } // namespace menuBar
  552. widget::Widget* createMenuBar() {
  553. return new widget::Widget;
  554. }
  555. widget::Widget* createMenuBar(const bool isStandalone) {
  556. menuBar::MenuBar* menuBar = new menuBar::MenuBar(isStandalone);
  557. return menuBar;
  558. }
  559. } // namespace app
  560. } // namespace rack