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.

968 lines
24KB

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