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.

1013 lines
25KB

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