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.

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