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.

1112 lines
29KB

  1. #include <thread>
  2. #include <utility>
  3. #include <algorithm>
  4. #include <osdialog.h>
  5. #include <app/MenuBar.hpp>
  6. #include <app/TipWindow.hpp>
  7. #include <widget/OpaqueWidget.hpp>
  8. #include <ui/Button.hpp>
  9. #include <ui/MenuItem.hpp>
  10. #include <ui/MenuSeparator.hpp>
  11. #include <ui/SequentialLayout.hpp>
  12. #include <ui/Slider.hpp>
  13. #include <ui/TextField.hpp>
  14. #include <ui/ProgressBar.hpp>
  15. #include <ui/Label.hpp>
  16. #include <engine/Engine.hpp>
  17. #include <window/Window.hpp>
  18. #include <asset.hpp>
  19. #include <context.hpp>
  20. #include <settings.hpp>
  21. #include <helpers.hpp>
  22. #include <system.hpp>
  23. #include <plugin.hpp>
  24. #include <patch.hpp>
  25. #include <library.hpp>
  26. namespace rack {
  27. namespace app {
  28. namespace menuBar {
  29. struct MenuButton : ui::Button {
  30. void step() override {
  31. box.size.x = bndLabelWidth(APP->window->vg, -1, text.c_str()) + 1.0;
  32. Widget::step();
  33. }
  34. void draw(const DrawArgs& args) override {
  35. BNDwidgetState state = BND_DEFAULT;
  36. if (APP->event->hoveredWidget == this)
  37. state = BND_HOVER;
  38. if (APP->event->draggedWidget == this)
  39. state = BND_ACTIVE;
  40. bndMenuItem(args.vg, 0.0, 0.0, box.size.x, box.size.y, state, -1, text.c_str());
  41. Widget::draw(args);
  42. }
  43. };
  44. struct NotificationIcon : widget::Widget {
  45. void draw(const DrawArgs& args) override {
  46. nvgBeginPath(args.vg);
  47. float radius = 4;
  48. nvgCircle(args.vg, radius, radius, radius);
  49. nvgFillColor(args.vg, nvgRGBf(1.0, 0.0, 0.0));
  50. nvgFill(args.vg);
  51. nvgStrokeColor(args.vg, nvgRGBf(0.5, 0.0, 0.0));
  52. nvgStroke(args.vg);
  53. }
  54. };
  55. ////////////////////
  56. // File
  57. ////////////////////
  58. struct FileButton : MenuButton {
  59. void onAction(const ActionEvent& e) override {
  60. ui::Menu* menu = createMenu();
  61. menu->cornerFlags = BND_CORNER_TOP;
  62. menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y));
  63. menu->addChild(createMenuItem("New", RACK_MOD_CTRL_NAME "+N", []() {
  64. APP->patch->loadTemplateDialog();
  65. }));
  66. menu->addChild(createMenuItem("Open", RACK_MOD_CTRL_NAME "+O", []() {
  67. APP->patch->loadDialog();
  68. }));
  69. menu->addChild(createSubmenuItem("Open recent", "", [](ui::Menu* menu) {
  70. for (const std::string& path : settings::recentPatchPaths) {
  71. std::string name = system::getStem(path);
  72. menu->addChild(createMenuItem(name, "", [=]() {
  73. APP->patch->loadPathDialog(path);
  74. }));
  75. }
  76. }, settings::recentPatchPaths.empty()));
  77. menu->addChild(createMenuItem("Save", RACK_MOD_CTRL_NAME "+S", []() {
  78. APP->patch->saveDialog();
  79. }));
  80. menu->addChild(createMenuItem("Save as", RACK_MOD_CTRL_NAME "+Shift+S", []() {
  81. APP->patch->saveAsDialog();
  82. }));
  83. menu->addChild(createMenuItem("Save a copy", "", []() {
  84. APP->patch->saveAsDialog(false);
  85. }));
  86. menu->addChild(createMenuItem("Revert", RACK_MOD_CTRL_NAME "+" RACK_MOD_SHIFT_NAME "+O", []() {
  87. APP->patch->revertDialog();
  88. }, APP->patch->path == ""));
  89. menu->addChild(createMenuItem("Overwrite template", "", []() {
  90. APP->patch->saveTemplateDialog();
  91. }));
  92. menu->addChild(new ui::MenuSeparator);
  93. // Load selection
  94. menu->addChild(createMenuItem("Import selection", "", [=]() {
  95. APP->scene->rack->loadSelectionDialog();
  96. }, false, true));
  97. menu->addChild(new ui::MenuSeparator);
  98. menu->addChild(createMenuItem("Quit", RACK_MOD_CTRL_NAME "+Q", []() {
  99. APP->window->close();
  100. }));
  101. }
  102. };
  103. ////////////////////
  104. // Edit
  105. ////////////////////
  106. struct EditButton : MenuButton {
  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. struct UndoItem : ui::MenuItem {
  112. void step() override {
  113. text = "Undo " + APP->history->getUndoName();
  114. disabled = !APP->history->canUndo();
  115. MenuItem::step();
  116. }
  117. void onAction(const ActionEvent& e) override {
  118. APP->history->undo();
  119. }
  120. };
  121. menu->addChild(createMenuItem<UndoItem>("", RACK_MOD_CTRL_NAME "+Z"));
  122. struct RedoItem : ui::MenuItem {
  123. void step() override {
  124. text = "Redo " + APP->history->getRedoName();
  125. disabled = !APP->history->canRedo();
  126. MenuItem::step();
  127. }
  128. void onAction(const ActionEvent& e) override {
  129. APP->history->redo();
  130. }
  131. };
  132. menu->addChild(createMenuItem<RedoItem>("", RACK_MOD_CTRL_NAME "+" RACK_MOD_SHIFT_NAME "+Z"));
  133. menu->addChild(createMenuItem("Clear cables", "", [=]() {
  134. APP->patch->disconnectDialog();
  135. }));
  136. menu->addChild(new ui::MenuSeparator);
  137. APP->scene->rack->appendSelectionContextMenu(menu);
  138. }
  139. };
  140. ////////////////////
  141. // View
  142. ////////////////////
  143. struct ZoomQuantity : Quantity {
  144. void setValue(float value) override {
  145. APP->scene->rackScroll->setZoom(std::pow(2.f, value));
  146. }
  147. float getValue() override {
  148. return std::log2(APP->scene->rackScroll->getZoom());
  149. }
  150. float getMinValue() override {
  151. return -2.f;
  152. }
  153. float getMaxValue() override {
  154. return 2.f;
  155. }
  156. float getDefaultValue() override {
  157. return 0.0;
  158. }
  159. float getDisplayValue() override {
  160. return std::round(std::pow(2.f, getValue()) * 100);
  161. }
  162. void setDisplayValue(float displayValue) override {
  163. setValue(std::log2(displayValue / 100));
  164. }
  165. std::string getLabel() override {
  166. return "Zoom";
  167. }
  168. std::string getUnit() override {
  169. return "%";
  170. }
  171. };
  172. struct ZoomSlider : ui::Slider {
  173. ZoomSlider() {
  174. quantity = new ZoomQuantity;
  175. }
  176. ~ZoomSlider() {
  177. delete quantity;
  178. }
  179. };
  180. struct CableOpacityQuantity : Quantity {
  181. void setValue(float value) override {
  182. settings::cableOpacity = math::clamp(value, getMinValue(), getMaxValue());
  183. }
  184. float getValue() override {
  185. return settings::cableOpacity;
  186. }
  187. float getDefaultValue() override {
  188. return 0.5;
  189. }
  190. float getDisplayValue() override {
  191. return getValue() * 100;
  192. }
  193. void setDisplayValue(float displayValue) override {
  194. setValue(displayValue / 100);
  195. }
  196. std::string getLabel() override {
  197. return "Cable opacity";
  198. }
  199. std::string getUnit() override {
  200. return "%";
  201. }
  202. };
  203. struct CableOpacitySlider : ui::Slider {
  204. CableOpacitySlider() {
  205. quantity = new CableOpacityQuantity;
  206. }
  207. ~CableOpacitySlider() {
  208. delete quantity;
  209. }
  210. };
  211. struct CableTensionQuantity : Quantity {
  212. void setValue(float value) override {
  213. settings::cableTension = math::clamp(value, getMinValue(), getMaxValue());
  214. }
  215. float getValue() override {
  216. return settings::cableTension;
  217. }
  218. float getDefaultValue() override {
  219. return 0.5;
  220. }
  221. float getDisplayValue() override {
  222. return getValue() * 100;
  223. }
  224. void setDisplayValue(float displayValue) override {
  225. setValue(displayValue / 100);
  226. }
  227. std::string getLabel() override {
  228. return "Cable tension";
  229. }
  230. std::string getUnit() override {
  231. return "%";
  232. }
  233. };
  234. struct CableTensionSlider : ui::Slider {
  235. CableTensionSlider() {
  236. quantity = new CableTensionQuantity;
  237. }
  238. ~CableTensionSlider() {
  239. delete quantity;
  240. }
  241. };
  242. struct RackBrightnessQuantity : Quantity {
  243. void setValue(float value) override {
  244. settings::rackBrightness = math::clamp(value, getMinValue(), getMaxValue());
  245. }
  246. float getValue() override {
  247. return settings::rackBrightness;
  248. }
  249. float getDefaultValue() override {
  250. return 1.0;
  251. }
  252. float getDisplayValue() override {
  253. return getValue() * 100;
  254. }
  255. void setDisplayValue(float displayValue) override {
  256. setValue(displayValue / 100);
  257. }
  258. std::string getUnit() override {
  259. return "%";
  260. }
  261. std::string getLabel() override {
  262. return "Room brightness";
  263. }
  264. int getDisplayPrecision() override {
  265. return 3;
  266. }
  267. };
  268. struct RackBrightnessSlider : ui::Slider {
  269. RackBrightnessSlider() {
  270. quantity = new RackBrightnessQuantity;
  271. }
  272. ~RackBrightnessSlider() {
  273. delete quantity;
  274. }
  275. };
  276. struct HaloBrightnessQuantity : Quantity {
  277. void setValue(float value) override {
  278. settings::haloBrightness = math::clamp(value, getMinValue(), getMaxValue());
  279. }
  280. float getValue() override {
  281. return settings::haloBrightness;
  282. }
  283. float getDefaultValue() override {
  284. return 0.25;
  285. }
  286. float getDisplayValue() override {
  287. return getValue() * 100;
  288. }
  289. void setDisplayValue(float displayValue) override {
  290. setValue(displayValue / 100);
  291. }
  292. std::string getUnit() override {
  293. return "%";
  294. }
  295. std::string getLabel() override {
  296. return "Light bloom";
  297. }
  298. int getDisplayPrecision() override {
  299. return 3;
  300. }
  301. };
  302. struct HaloBrightnessSlider : ui::Slider {
  303. HaloBrightnessSlider() {
  304. quantity = new HaloBrightnessQuantity;
  305. }
  306. ~HaloBrightnessSlider() {
  307. delete quantity;
  308. }
  309. };
  310. struct KnobScrollSensitivityQuantity : Quantity {
  311. void setValue(float value) override {
  312. value = math::clamp(value, getMinValue(), getMaxValue());
  313. settings::knobScrollSensitivity = std::pow(2.f, value);
  314. }
  315. float getValue() override {
  316. return std::log2(settings::knobScrollSensitivity);
  317. }
  318. float getMinValue() override {
  319. return std::log2(1e-4f);
  320. }
  321. float getMaxValue() override {
  322. return std::log2(1e-2f);
  323. }
  324. float getDefaultValue() override {
  325. return std::log2(1e-3f);
  326. }
  327. float getDisplayValue() override {
  328. return std::pow(2.f, getValue() - getDefaultValue());
  329. }
  330. void setDisplayValue(float displayValue) override {
  331. setValue(std::log2(displayValue) + getDefaultValue());
  332. }
  333. std::string getLabel() override {
  334. return "Scroll wheel knob sensitivity";
  335. }
  336. int getDisplayPrecision() override {
  337. return 2;
  338. }
  339. };
  340. struct KnobScrollSensitivitySlider : ui::Slider {
  341. KnobScrollSensitivitySlider() {
  342. quantity = new KnobScrollSensitivityQuantity;
  343. }
  344. ~KnobScrollSensitivitySlider() {
  345. delete quantity;
  346. }
  347. };
  348. struct ViewButton : MenuButton {
  349. void onAction(const ActionEvent& e) override {
  350. ui::Menu* menu = createMenu();
  351. menu->cornerFlags = BND_CORNER_TOP;
  352. menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y));
  353. menu->addChild(createMenuLabel("Window"));
  354. bool fullscreen = APP->window->isFullScreen();
  355. std::string fullscreenText = "F11";
  356. if (fullscreen)
  357. fullscreenText += " " CHECKMARK_STRING;
  358. menu->addChild(createMenuItem("Fullscreen", fullscreenText, [=]() {
  359. APP->window->setFullScreen(!fullscreen);
  360. }));
  361. menu->addChild(createSubmenuItem("Frame rate", string::f("%.0f Hz", settings::frameRateLimit), [=](ui::Menu* menu) {
  362. for (int i = 1; i <= 6; i++) {
  363. double frameRate = APP->window->getMonitorRefreshRate() / i;
  364. menu->addChild(createCheckMenuItem(string::f("%.0f Hz", frameRate), "",
  365. [=]() {return settings::frameRateLimit == frameRate;},
  366. [=]() {settings::frameRateLimit = frameRate;}
  367. ));
  368. }
  369. }));
  370. menu->addChild(new ui::MenuSeparator);
  371. menu->addChild(createMenuLabel("Appearance"));
  372. static const std::vector<std::string> uiThemes = {"dark", "light", "hcdark"};
  373. static const std::vector<std::string> uiThemeLabels = {"Dark", "Light", "High contrast dark"};
  374. menu->addChild(createIndexSubmenuItem("Theme", uiThemeLabels,
  375. [=]() -> size_t {
  376. auto it = std::find(uiThemes.begin(), uiThemes.end(), settings::uiTheme);
  377. if (it == uiThemes.end())
  378. return -1;
  379. return it - uiThemes.begin();
  380. },
  381. [=](size_t i) {
  382. settings::uiTheme = uiThemes[i];
  383. ui::refreshTheme();
  384. }
  385. ));
  386. menu->addChild(createBoolPtrMenuItem("Show tooltips", "", &settings::tooltips));
  387. // Various sliders
  388. ZoomSlider* zoomSlider = new ZoomSlider;
  389. zoomSlider->box.size.x = 250.0;
  390. menu->addChild(zoomSlider);
  391. CableOpacitySlider* cableOpacitySlider = new CableOpacitySlider;
  392. cableOpacitySlider->box.size.x = 250.0;
  393. menu->addChild(cableOpacitySlider);
  394. CableTensionSlider* cableTensionSlider = new CableTensionSlider;
  395. cableTensionSlider->box.size.x = 250.0;
  396. menu->addChild(cableTensionSlider);
  397. RackBrightnessSlider* rackBrightnessSlider = new RackBrightnessSlider;
  398. rackBrightnessSlider->box.size.x = 250.0;
  399. menu->addChild(rackBrightnessSlider);
  400. HaloBrightnessSlider* haloBrightnessSlider = new HaloBrightnessSlider;
  401. haloBrightnessSlider->box.size.x = 250.0;
  402. menu->addChild(haloBrightnessSlider);
  403. // Cable colors
  404. menu->addChild(createSubmenuItem("Cable colors", "", [=](ui::Menu* menu) {
  405. // TODO Subclass Menu to make an auto-refreshing list so user can Ctrl+click to keep menu open.
  406. // Add color items
  407. for (size_t i = 0; i < settings::cableColors.size(); i++) {
  408. NVGcolor color = settings::cableColors[i];
  409. ui::ColorDotMenuItem* item = createSubmenuItem<ui::ColorDotMenuItem>(string::uppercase(color::toHexString(color)), string::f("%d", int(i + 1)), [=](ui::Menu* menu) {
  410. // Helper for launching color dialog
  411. auto selectColor = [](NVGcolor color) {
  412. osdialog_color c = {
  413. uint8_t(color.r * 255.f),
  414. uint8_t(color.g * 255.f),
  415. uint8_t(color.b * 255.f),
  416. uint8_t(color.a * 255.f),
  417. };
  418. osdialog_color_picker(&c, false);
  419. return nvgRGBA(c.r, c.g, c.b, c.a);
  420. };
  421. menu->addChild(createMenuItem("Set color", "", [=]() {
  422. if (i >= settings::cableColors.size())
  423. return;
  424. NVGcolor newColor = selectColor(color);
  425. std::memcpy(&settings::cableColors[i], &newColor, sizeof(newColor));
  426. }, false, true));
  427. menu->addChild(createMenuItem("New color above", "", [=]() {
  428. if (i >= settings::cableColors.size())
  429. return;
  430. settings::cableColors.insert(settings::cableColors.begin() + i, selectColor(color));
  431. }, false, true));
  432. menu->addChild(createMenuItem("New color below", "", [=]() {
  433. if (i >= settings::cableColors.size())
  434. return;
  435. settings::cableColors.insert(settings::cableColors.begin() + i + 1, selectColor(color));
  436. }, false, true));
  437. menu->addChild(createMenuItem("Move up", "", [=]() {
  438. if (i < 1 || i >= settings::cableColors.size())
  439. return;
  440. std::swap(settings::cableColors[i], settings::cableColors[i - 1]);
  441. }, i < 1, true));
  442. menu->addChild(createMenuItem("Move down", "", [=]() {
  443. if (i + 1 >= settings::cableColors.size())
  444. return;
  445. std::swap(settings::cableColors[i], settings::cableColors[i + 1]);
  446. }, i + 1 >= settings::cableColors.size()));
  447. menu->addChild(createMenuItem("Delete", "", [=]() {
  448. if (i >= settings::cableColors.size())
  449. return;
  450. settings::cableColors.erase(settings::cableColors.begin() + i);
  451. }, settings::cableColors.size() <= 1, true));
  452. });
  453. item->color = color;
  454. menu->addChild(item);
  455. }
  456. menu->addChild(createBoolMenuItem("Auto-rotate colors", "",
  457. [=]() -> bool {
  458. return settings::cableAutoRotate;
  459. },
  460. [=](bool s) {
  461. APP->scene->rack->setNextCableColorId(0);
  462. settings::cableAutoRotate = s;
  463. }
  464. ));
  465. menu->addChild(createMenuItem("Restore factory colors", "", [=]() {
  466. if (!osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK_CANCEL, "Overwrite colors with factory defaults?"))
  467. return;
  468. settings::cableColorsReset();
  469. }, false, true));
  470. }));
  471. menu->addChild(new ui::MenuSeparator);
  472. menu->addChild(createMenuLabel("Parameters"));
  473. menu->addChild(createBoolPtrMenuItem("Lock cursor while dragging", "", &settings::allowCursorLock));
  474. static const std::vector<std::string> knobModeLabels = {
  475. "Linear",
  476. "Scaled linear",
  477. "Absolute rotary",
  478. "Relative rotary",
  479. };
  480. static const std::vector<int> knobModes = {0, 2, 3};
  481. menu->addChild(createSubmenuItem("Knob mode", knobModeLabels[settings::knobMode], [=](ui::Menu* menu) {
  482. for (int knobMode : knobModes) {
  483. menu->addChild(createCheckMenuItem(knobModeLabels[knobMode], "",
  484. [=]() {return settings::knobMode == knobMode;},
  485. [=]() {settings::knobMode = (settings::KnobMode) knobMode;}
  486. ));
  487. }
  488. }));
  489. menu->addChild(createBoolPtrMenuItem("Scroll wheel knob control", "", &settings::knobScroll));
  490. KnobScrollSensitivitySlider* knobScrollSensitivitySlider = new KnobScrollSensitivitySlider;
  491. knobScrollSensitivitySlider->box.size.x = 250.0;
  492. menu->addChild(knobScrollSensitivitySlider);
  493. menu->addChild(new ui::MenuSeparator);
  494. menu->addChild(createMenuLabel("Modules"));
  495. menu->addChild(createBoolPtrMenuItem("Lock positions", "", &settings::lockModules));
  496. menu->addChild(createBoolPtrMenuItem("Smart rearrangement", "", &settings::squeezeModules));
  497. menu->addChild(createBoolPtrMenuItem("Use dark panels if available", "", &settings::preferDarkPanels));
  498. }
  499. };
  500. ////////////////////
  501. // Engine
  502. ////////////////////
  503. struct SampleRateItem : ui::MenuItem {
  504. ui::Menu* createChildMenu() override {
  505. ui::Menu* menu = new ui::Menu;
  506. // Auto sample rate
  507. std::string rightText;
  508. if (settings::sampleRate == 0) {
  509. float sampleRate = APP->engine->getSampleRate();
  510. rightText += string::f("(%g kHz) ", sampleRate / 1000.f);
  511. }
  512. menu->addChild(createCheckMenuItem("Auto", rightText,
  513. [=]() {return settings::sampleRate == 0;},
  514. [=]() {settings::sampleRate = 0;}
  515. ));
  516. // Power-of-2 oversample times 44.1kHz or 48kHz
  517. for (int i = -2; i <= 4; i++) {
  518. for (int j = 0; j < 2; j++) {
  519. float oversample = std::pow(2.f, i);
  520. float sampleRate = (j == 0) ? 44100.f : 48000.f;
  521. sampleRate *= oversample;
  522. std::string text = string::f("%g kHz", sampleRate / 1000.f);
  523. std::string rightText;
  524. if (oversample > 1.f) {
  525. rightText += string::f("(%.0fx)", oversample);
  526. }
  527. else if (oversample < 1.f) {
  528. rightText += string::f("(1/%.0fx)", 1.f / oversample);
  529. }
  530. menu->addChild(createCheckMenuItem(text, rightText,
  531. [=]() {return settings::sampleRate == sampleRate;},
  532. [=]() {settings::sampleRate = sampleRate;}
  533. ));
  534. }
  535. }
  536. return menu;
  537. }
  538. };
  539. struct EngineButton : MenuButton {
  540. void onAction(const ActionEvent& e) override {
  541. ui::Menu* menu = createMenu();
  542. menu->cornerFlags = BND_CORNER_TOP;
  543. menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y));
  544. std::string cpuMeterText = "F3";
  545. if (settings::cpuMeter)
  546. cpuMeterText += " " CHECKMARK_STRING;
  547. menu->addChild(createMenuItem("Performance meters", cpuMeterText, [=]() {
  548. settings::cpuMeter ^= true;
  549. }));
  550. menu->addChild(createMenuItem<SampleRateItem>("Sample rate", RIGHT_ARROW));
  551. menu->addChild(createSubmenuItem("Threads", string::f("%d", settings::threadCount), [=](ui::Menu* menu) {
  552. // BUG This assumes SMT is enabled.
  553. int cores = system::getLogicalCoreCount() / 2;
  554. for (int i = 1; i <= 2 * cores; i++) {
  555. std::string rightText;
  556. if (i == cores)
  557. rightText += "(most modules)";
  558. else if (i == 1)
  559. rightText += "(lowest CPU usage)";
  560. menu->addChild(createCheckMenuItem(string::f("%d", i), rightText,
  561. [=]() {return settings::threadCount == i;},
  562. [=]() {settings::threadCount = i;}
  563. ));
  564. }
  565. }));
  566. }
  567. };
  568. ////////////////////
  569. // Plugins
  570. ////////////////////
  571. struct AccountPasswordField : ui::PasswordField {
  572. ui::MenuItem* logInItem;
  573. void onAction(const ActionEvent& e) override {
  574. logInItem->doAction();
  575. }
  576. };
  577. struct LogInItem : ui::MenuItem {
  578. ui::TextField* emailField;
  579. ui::TextField* passwordField;
  580. void onAction(const ActionEvent& e) override {
  581. std::string email = emailField->text;
  582. std::string password = passwordField->text;
  583. std::thread t([=] {
  584. library::logIn(email, password);
  585. library::checkUpdates();
  586. });
  587. t.detach();
  588. e.unconsume();
  589. }
  590. void step() override {
  591. text = "Log in";
  592. rightText = library::loginStatus;
  593. MenuItem::step();
  594. }
  595. };
  596. struct SyncUpdatesItem : ui::MenuItem {
  597. void step() override {
  598. if (library::updateStatus != "") {
  599. text = library::updateStatus;
  600. }
  601. else if (library::isSyncing) {
  602. text = "Updating...";
  603. }
  604. else if (!library::hasUpdates()) {
  605. text = "Up-to-date";
  606. }
  607. else {
  608. text = "Update all";
  609. }
  610. disabled = library::isSyncing || !library::hasUpdates();
  611. MenuItem::step();
  612. }
  613. void onAction(const ActionEvent& e) override {
  614. std::thread t([=] {
  615. library::syncUpdates();
  616. });
  617. t.detach();
  618. e.unconsume();
  619. }
  620. };
  621. struct SyncUpdateItem : ui::MenuItem {
  622. std::string slug;
  623. void setUpdate(const std::string& slug) {
  624. this->slug = slug;
  625. auto it = library::updateInfos.find(slug);
  626. if (it == library::updateInfos.end())
  627. return;
  628. library::UpdateInfo update = it->second;
  629. text = update.name;
  630. }
  631. ui::Menu* createChildMenu() override {
  632. auto it = library::updateInfos.find(slug);
  633. if (it == library::updateInfos.end())
  634. return NULL;
  635. library::UpdateInfo update = it->second;
  636. ui::Menu* menu = new ui::Menu;
  637. if (update.minRackVersion != "") {
  638. menu->addChild(createMenuLabel(string::f("Requires Rack %s+", update.minRackVersion.c_str())));
  639. }
  640. if (update.changelogUrl != "") {
  641. std::string changelogUrl = update.changelogUrl;
  642. menu->addChild(createMenuItem("Changelog", "", [=]() {
  643. system::openBrowser(changelogUrl);
  644. }));
  645. }
  646. if (menu->children.empty()) {
  647. delete menu;
  648. return NULL;
  649. }
  650. return menu;
  651. }
  652. void step() override {
  653. disabled = false;
  654. if (library::isSyncing)
  655. disabled = true;
  656. auto it = library::updateInfos.find(slug);
  657. if (it == library::updateInfos.end()) {
  658. disabled = true;
  659. }
  660. else {
  661. library::UpdateInfo update = it->second;
  662. if (update.minRackVersion != "")
  663. disabled = true;
  664. if (update.downloaded) {
  665. rightText = CHECKMARK_STRING;
  666. disabled = true;
  667. }
  668. else if (slug == library::updateSlug) {
  669. rightText = string::f("%.0f%%", library::updateProgress * 100.f);
  670. }
  671. else {
  672. rightText = "";
  673. plugin::Plugin* p = plugin::getPlugin(slug);
  674. if (p) {
  675. rightText += p->version + " → ";
  676. }
  677. rightText += update.version;
  678. }
  679. }
  680. MenuItem::step();
  681. }
  682. void onAction(const ActionEvent& e) override {
  683. std::thread t([=] {
  684. library::syncUpdate(slug);
  685. });
  686. t.detach();
  687. e.unconsume();
  688. }
  689. };
  690. struct LibraryMenu : ui::Menu {
  691. LibraryMenu() {
  692. refresh();
  693. }
  694. void step() override {
  695. // Refresh menu when appropriate
  696. if (library::refreshRequested) {
  697. library::refreshRequested = false;
  698. refresh();
  699. }
  700. Menu::step();
  701. }
  702. void refresh() {
  703. setChildMenu(NULL);
  704. clearChildren();
  705. if (settings::devMode) {
  706. addChild(createMenuLabel("Disabled in development mode"));
  707. }
  708. else if (!library::isLoggedIn()) {
  709. addChild(createMenuItem("Register VCV account", "", [=]() {
  710. system::openBrowser("https://vcvrack.com/login");
  711. }));
  712. ui::TextField* emailField = new ui::TextField;
  713. emailField->placeholder = "Email";
  714. emailField->box.size.x = 240.0;
  715. addChild(emailField);
  716. AccountPasswordField* passwordField = new AccountPasswordField;
  717. passwordField->placeholder = "Password";
  718. passwordField->box.size.x = 240.0;
  719. passwordField->nextField = emailField;
  720. emailField->nextField = passwordField;
  721. addChild(passwordField);
  722. LogInItem* logInItem = new LogInItem;
  723. logInItem->emailField = emailField;
  724. logInItem->passwordField = passwordField;
  725. passwordField->logInItem = logInItem;
  726. addChild(logInItem);
  727. }
  728. else {
  729. addChild(createMenuItem("Log out", "", [=]() {
  730. library::logOut();
  731. }));
  732. addChild(createMenuItem("Account settings", "", [=]() {
  733. system::openBrowser("https://vcvrack.com/account");
  734. }));
  735. addChild(createMenuItem("Browse VCV Library", "", [=]() {
  736. system::openBrowser("https://library.vcvrack.com/");
  737. }));
  738. SyncUpdatesItem* syncItem = new SyncUpdatesItem;
  739. syncItem->text = "Update all";
  740. addChild(syncItem);
  741. if (!library::updateInfos.empty()) {
  742. addChild(new ui::MenuSeparator);
  743. addChild(createMenuLabel("Updates"));
  744. for (auto& pair : library::updateInfos) {
  745. SyncUpdateItem* updateItem = new SyncUpdateItem;
  746. updateItem->setUpdate(pair.first);
  747. addChild(updateItem);
  748. }
  749. }
  750. }
  751. }
  752. };
  753. struct LibraryButton : MenuButton {
  754. NotificationIcon* notification;
  755. LibraryButton() {
  756. notification = new NotificationIcon;
  757. addChild(notification);
  758. }
  759. void onAction(const ActionEvent& e) override {
  760. ui::Menu* menu = createMenu<LibraryMenu>();
  761. menu->cornerFlags = BND_CORNER_TOP;
  762. menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y));
  763. // Check for updates when menu is opened
  764. if (!settings::devMode) {
  765. std::thread t([&]() {
  766. system::setThreadName("Library");
  767. library::checkUpdates();
  768. });
  769. t.detach();
  770. }
  771. }
  772. void step() override {
  773. notification->box.pos = math::Vec(0, 0);
  774. notification->visible = library::hasUpdates();
  775. // Popup when updates finish downloading
  776. if (library::restartRequested) {
  777. library::restartRequested = false;
  778. if (osdialog_message(OSDIALOG_INFO, OSDIALOG_OK_CANCEL, "All plugins have been downloaded. Close and re-launch Rack to load new updates.")) {
  779. APP->window->close();
  780. }
  781. }
  782. MenuButton::step();
  783. }
  784. };
  785. ////////////////////
  786. // Help
  787. ////////////////////
  788. struct HelpButton : MenuButton {
  789. NotificationIcon* notification;
  790. HelpButton() {
  791. notification = new NotificationIcon;
  792. addChild(notification);
  793. }
  794. void onAction(const ActionEvent& e) override {
  795. ui::Menu* menu = createMenu();
  796. menu->cornerFlags = BND_CORNER_TOP;
  797. menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y));
  798. menu->addChild(createMenuItem("Tips", "", [=]() {
  799. APP->scene->addChild(tipWindowCreate());
  800. }));
  801. menu->addChild(createMenuItem("User manual", "F1", [=]() {
  802. system::openBrowser("https://vcvrack.com/manual");
  803. }));
  804. menu->addChild(createMenuItem("Support", "", [=]() {
  805. system::openBrowser("https://vcvrack.com/support");
  806. }));
  807. menu->addChild(createMenuItem("VCVRack.com", "", [=]() {
  808. system::openBrowser("https://vcvrack.com/");
  809. }));
  810. menu->addChild(new ui::MenuSeparator);
  811. menu->addChild(createMenuItem("Open user folder", "", [=]() {
  812. system::openDirectory(asset::user(""));
  813. }));
  814. menu->addChild(createMenuItem("Changelog", "", [=]() {
  815. system::openBrowser("https://github.com/VCVRack/Rack/blob/v2/CHANGELOG.md");
  816. }));
  817. if (library::isAppUpdateAvailable()) {
  818. menu->addChild(createMenuItem("Update " + APP_NAME, APP_VERSION + " → " + library::appVersion, [=]() {
  819. system::openBrowser(library::appDownloadUrl);
  820. }));
  821. }
  822. else if (!settings::autoCheckUpdates && !settings::devMode) {
  823. menu->addChild(createMenuItem("Check for " + APP_NAME + " update", "", [=]() {
  824. std::thread t([&]() {
  825. library::checkAppUpdate();
  826. });
  827. t.detach();
  828. }, false, true));
  829. }
  830. }
  831. void step() override {
  832. notification->box.pos = math::Vec(0, 0);
  833. notification->visible = library::isAppUpdateAvailable();
  834. MenuButton::step();
  835. }
  836. };
  837. ////////////////////
  838. // MenuBar
  839. ////////////////////
  840. struct InfoLabel : ui::Label {
  841. int frameCount = 0;
  842. double frameDurationTotal = 0.0;
  843. double frameDurationAvg = NAN;
  844. // double uiLastTime = 0.0;
  845. // double uiLastThreadTime = 0.0;
  846. // double uiFrac = 0.0;
  847. void step() override {
  848. // Compute frame rate
  849. double frameDuration = APP->window->getLastFrameDuration();
  850. if (std::isfinite(frameDuration)) {
  851. frameDurationTotal += frameDuration;
  852. frameCount++;
  853. }
  854. if (frameDurationTotal >= 1.0) {
  855. frameDurationAvg = frameDurationTotal / frameCount;
  856. frameDurationTotal = 0.0;
  857. frameCount = 0;
  858. }
  859. // Compute UI thread CPU
  860. // double time = system::getTime();
  861. // double uiDuration = time - uiLastTime;
  862. // if (uiDuration >= 1.0) {
  863. // double threadTime = system::getThreadTime();
  864. // uiFrac = (threadTime - uiLastThreadTime) / uiDuration;
  865. // uiLastThreadTime = threadTime;
  866. // uiLastTime = time;
  867. // }
  868. text = "";
  869. if (box.size.x >= 460) {
  870. double fps = std::isfinite(frameDurationAvg) ? 1.0 / frameDurationAvg : 0.0;
  871. double meterAverage = APP->engine->getMeterAverage();
  872. double meterMax = APP->engine->getMeterMax();
  873. text += string::f("%.1f fps %.1f%% avg %.1f%% max", fps, meterAverage * 100, meterMax * 100);
  874. text += " ";
  875. }
  876. text += APP_NAME + " " + APP_EDITION_NAME + " " + APP_VERSION + " " + APP_OS_NAME + " " + APP_CPU_NAME;
  877. Label::step();
  878. }
  879. };
  880. struct MenuBar : widget::OpaqueWidget {
  881. InfoLabel* infoLabel;
  882. MenuBar() {
  883. const float margin = 5;
  884. box.size.y = BND_WIDGET_HEIGHT + 2 * margin;
  885. ui::SequentialLayout* layout = new ui::SequentialLayout;
  886. layout->margin = math::Vec(margin, margin);
  887. layout->spacing = math::Vec(0, 0);
  888. addChild(layout);
  889. FileButton* fileButton = new FileButton;
  890. fileButton->text = "File";
  891. layout->addChild(fileButton);
  892. EditButton* editButton = new EditButton;
  893. editButton->text = "Edit";
  894. layout->addChild(editButton);
  895. ViewButton* viewButton = new ViewButton;
  896. viewButton->text = "View";
  897. layout->addChild(viewButton);
  898. EngineButton* engineButton = new EngineButton;
  899. engineButton->text = "Engine";
  900. layout->addChild(engineButton);
  901. LibraryButton* libraryButton = new LibraryButton;
  902. libraryButton->text = "Library";
  903. layout->addChild(libraryButton);
  904. HelpButton* helpButton = new HelpButton;
  905. helpButton->text = "Help";
  906. layout->addChild(helpButton);
  907. infoLabel = new InfoLabel;
  908. infoLabel->box.size.x = 600;
  909. infoLabel->alignment = ui::Label::RIGHT_ALIGNMENT;
  910. layout->addChild(infoLabel);
  911. }
  912. void draw(const DrawArgs& args) override {
  913. bndMenuBackground(args.vg, 0.0, 0.0, box.size.x, box.size.y, BND_CORNER_ALL);
  914. bndBevel(args.vg, 0.0, 0.0, box.size.x, box.size.y);
  915. Widget::draw(args);
  916. }
  917. void step() override {
  918. Widget::step();
  919. infoLabel->box.size.x = box.size.x - infoLabel->box.pos.x - 5;
  920. // Setting 50% alpha prevents Label from using the default UI theme color, so set the color manually here.
  921. infoLabel->color = color::alpha(bndGetTheme()->regularTheme.textColor, 0.5);
  922. }
  923. };
  924. } // namespace menuBar
  925. widget::Widget* createMenuBar() {
  926. menuBar::MenuBar* menuBar = new menuBar::MenuBar;
  927. return menuBar;
  928. }
  929. } // namespace app
  930. } // namespace rack