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.

871 lines
21KB

  1. #include <app/MenuBar.hpp>
  2. #include <window.hpp>
  3. #include <engine/Engine.hpp>
  4. #include <asset.hpp>
  5. #include <ui/Button.hpp>
  6. #include <ui/MenuItem.hpp>
  7. #include <ui/SequentialLayout.hpp>
  8. #include <ui/Slider.hpp>
  9. #include <ui/TextField.hpp>
  10. #include <ui/PasswordField.hpp>
  11. #include <ui/ProgressBar.hpp>
  12. #include <app.hpp>
  13. #include <settings.hpp>
  14. #include <helpers.hpp>
  15. #include <system.hpp>
  16. #include <plugin.hpp>
  17. #include <patch.hpp>
  18. #include <updater.hpp>
  19. #include <osdialog.h>
  20. #include <thread>
  21. namespace rack {
  22. namespace app {
  23. struct MenuButton : ui::Button {
  24. void step() override {
  25. box.size.x = bndLabelWidth(APP->window->vg, -1, text.c_str()) + 1.0;
  26. Widget::step();
  27. }
  28. void draw(const DrawArgs& args) override {
  29. BNDwidgetState state = BND_DEFAULT;
  30. if (APP->event->hoveredWidget == this)
  31. state = BND_HOVER;
  32. if (APP->event->draggedWidget == this)
  33. state = BND_ACTIVE;
  34. bndMenuItem(args.vg, 0.0, 0.0, box.size.x, box.size.y, state, -1, text.c_str());
  35. Widget::draw(args);
  36. }
  37. };
  38. struct NotificationIcon : widget::Widget {
  39. void draw(const DrawArgs& args) override {
  40. nvgBeginPath(args.vg);
  41. float radius = 4;
  42. nvgCircle(args.vg, radius, radius, radius);
  43. nvgFillColor(args.vg, nvgRGBf(1.0, 0.0, 0.0));
  44. nvgFill(args.vg);
  45. nvgStrokeColor(args.vg, nvgRGBf(0.5, 0.0, 0.0));
  46. nvgStroke(args.vg);
  47. }
  48. };
  49. struct UrlItem : ui::MenuItem {
  50. std::string url;
  51. void onAction(const event::Action& e) override {
  52. std::thread t([ = ] {
  53. system::openBrowser(url);
  54. });
  55. t.detach();
  56. }
  57. };
  58. struct FolderItem : ui::MenuItem {
  59. std::string path;
  60. void onAction(const event::Action& e) override {
  61. std::thread t([ = ] {
  62. system::openFolder(path);
  63. });
  64. t.detach();
  65. }
  66. };
  67. ////////////////////
  68. // File
  69. ////////////////////
  70. struct NewItem : ui::MenuItem {
  71. void onAction(const event::Action& e) override {
  72. APP->patch->resetDialog();
  73. }
  74. };
  75. struct OpenItem : ui::MenuItem {
  76. void onAction(const event::Action& e) override {
  77. APP->patch->loadDialog();
  78. }
  79. };
  80. struct SaveItem : ui::MenuItem {
  81. void onAction(const event::Action& e) override {
  82. APP->patch->saveDialog();
  83. }
  84. };
  85. struct SaveAsItem : ui::MenuItem {
  86. void onAction(const event::Action& e) override {
  87. APP->patch->saveAsDialog();
  88. }
  89. };
  90. struct SaveTemplateItem : ui::MenuItem {
  91. void onAction(const event::Action& e) override {
  92. APP->patch->saveTemplateDialog();
  93. }
  94. };
  95. struct RevertItem : ui::MenuItem {
  96. void onAction(const event::Action& e) override {
  97. APP->patch->revertDialog();
  98. }
  99. };
  100. struct QuitItem : ui::MenuItem {
  101. void onAction(const event::Action& e) override {
  102. APP->window->close();
  103. }
  104. };
  105. struct FileButton : MenuButton {
  106. void onAction(const event::Action& e) override {
  107. ui::Menu* menu = createMenu();
  108. menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y));
  109. menu->box.size.x = box.size.x;
  110. NewItem* newItem = new NewItem;
  111. newItem->text = "New";
  112. newItem->rightText = RACK_MOD_CTRL_NAME "+N";
  113. menu->addChild(newItem);
  114. OpenItem* openItem = new OpenItem;
  115. openItem->text = "Open";
  116. openItem->rightText = RACK_MOD_CTRL_NAME "+O";
  117. menu->addChild(openItem);
  118. SaveItem* saveItem = new SaveItem;
  119. saveItem->text = "Save";
  120. saveItem->rightText = RACK_MOD_CTRL_NAME "+S";
  121. menu->addChild(saveItem);
  122. SaveAsItem* saveAsItem = new SaveAsItem;
  123. saveAsItem->text = "Save as";
  124. saveAsItem->rightText = RACK_MOD_CTRL_NAME "+Shift+S";
  125. menu->addChild(saveAsItem);
  126. SaveTemplateItem* saveTemplateItem = new SaveTemplateItem;
  127. saveTemplateItem->text = "Save template";
  128. menu->addChild(saveTemplateItem);
  129. RevertItem* revertItem = new RevertItem;
  130. revertItem->text = "Revert";
  131. revertItem->rightText = RACK_MOD_CTRL_NAME "+" RACK_MOD_SHIFT_NAME "+O";
  132. menu->addChild(revertItem);
  133. QuitItem* quitItem = new QuitItem;
  134. quitItem->text = "Quit";
  135. quitItem->rightText = RACK_MOD_CTRL_NAME "+Q";
  136. menu->addChild(quitItem);
  137. }
  138. };
  139. ////////////////////
  140. // Edit
  141. ////////////////////
  142. struct UndoItem : ui::MenuItem {
  143. void onAction(const event::Action& e) override {
  144. APP->history->undo();
  145. }
  146. };
  147. struct RedoItem : ui::MenuItem {
  148. void onAction(const event::Action& e) override {
  149. APP->history->redo();
  150. }
  151. };
  152. struct DisconnectCablesItem : ui::MenuItem {
  153. void onAction(const event::Action& e) override {
  154. APP->patch->disconnectDialog();
  155. }
  156. };
  157. struct EditButton : MenuButton {
  158. void onAction(const event::Action& e) override {
  159. ui::Menu* menu = createMenu();
  160. menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y));
  161. menu->box.size.x = box.size.x;
  162. UndoItem* undoItem = new UndoItem;
  163. undoItem->text = "Undo " + APP->history->getUndoName();
  164. undoItem->rightText = RACK_MOD_CTRL_NAME "+Z";
  165. undoItem->disabled = !APP->history->canUndo();
  166. menu->addChild(undoItem);
  167. RedoItem* redoItem = new RedoItem;
  168. redoItem->text = "Redo " + APP->history->getRedoName();
  169. redoItem->rightText = RACK_MOD_CTRL_NAME "+" RACK_MOD_SHIFT_NAME "+Z";
  170. redoItem->disabled = !APP->history->canRedo();
  171. menu->addChild(redoItem);
  172. DisconnectCablesItem* disconnectCablesItem = new DisconnectCablesItem;
  173. disconnectCablesItem->text = "Clear cables";
  174. menu->addChild(disconnectCablesItem);
  175. }
  176. };
  177. ////////////////////
  178. // View
  179. ////////////////////
  180. struct ZoomQuantity : Quantity {
  181. void setValue(float value) override {
  182. settings::zoom = value;
  183. }
  184. float getValue() override {
  185. return settings::zoom;
  186. }
  187. float getMinValue() override {
  188. return -2.0;
  189. }
  190. float getMaxValue() override {
  191. return 2.0;
  192. }
  193. float getDefaultValue() override {
  194. return 0.0;
  195. }
  196. float getDisplayValue() override {
  197. return std::round(std::pow(2.f, getValue()) * 100);
  198. }
  199. void setDisplayValue(float displayValue) override {
  200. setValue(std::log2(displayValue / 100));
  201. }
  202. std::string getLabel() override {
  203. return "Zoom";
  204. }
  205. std::string getUnit() override {
  206. return "%";
  207. }
  208. };
  209. struct ZoomSlider : ui::Slider {
  210. ZoomSlider() {
  211. quantity = new ZoomQuantity;
  212. }
  213. ~ZoomSlider() {
  214. delete quantity;
  215. }
  216. };
  217. struct CableOpacityQuantity : Quantity {
  218. void setValue(float value) override {
  219. settings::cableOpacity = math::clamp(value, getMinValue(), getMaxValue());
  220. }
  221. float getValue() override {
  222. return settings::cableOpacity;
  223. }
  224. float getDefaultValue() override {
  225. return 0.5;
  226. }
  227. float getDisplayValue() override {
  228. return getValue() * 100;
  229. }
  230. void setDisplayValue(float displayValue) override {
  231. setValue(displayValue / 100);
  232. }
  233. std::string getLabel() override {
  234. return "Cable opacity";
  235. }
  236. std::string getUnit() override {
  237. return "%";
  238. }
  239. };
  240. struct CableOpacitySlider : ui::Slider {
  241. CableOpacitySlider() {
  242. quantity = new CableOpacityQuantity;
  243. }
  244. ~CableOpacitySlider() {
  245. delete quantity;
  246. }
  247. };
  248. struct CableTensionQuantity : Quantity {
  249. void setValue(float value) override {
  250. settings::cableTension = math::clamp(value, getMinValue(), getMaxValue());
  251. }
  252. float getValue() override {
  253. return settings::cableTension;
  254. }
  255. float getDefaultValue() override {
  256. return 0.5;
  257. }
  258. std::string getLabel() override {
  259. return "Cable tension";
  260. }
  261. int getDisplayPrecision() override {
  262. return 2;
  263. }
  264. };
  265. struct CableTensionSlider : ui::Slider {
  266. CableTensionSlider() {
  267. quantity = new CableTensionQuantity;
  268. }
  269. ~CableTensionSlider() {
  270. delete quantity;
  271. }
  272. };
  273. struct ParamTooltipItem : ui::MenuItem {
  274. void onAction(const event::Action& e) override {
  275. settings::paramTooltip ^= true;
  276. }
  277. };
  278. struct LockModulesItem : ui::MenuItem {
  279. void onAction(const event::Action& e) override {
  280. settings::lockModules ^= true;
  281. }
  282. };
  283. struct CursorLockItem : ui::MenuItem {
  284. void onAction(const event::Action& e) override {
  285. settings::allowCursorLock ^= true;
  286. }
  287. };
  288. struct FullscreenItem : ui::MenuItem {
  289. void onAction(const event::Action& e) override {
  290. APP->window->setFullScreen(!APP->window->isFullScreen());
  291. }
  292. };
  293. struct ViewButton : MenuButton {
  294. void onAction(const event::Action& e) override {
  295. ui::Menu* menu = createMenu();
  296. menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y));
  297. menu->box.size.x = box.size.x;
  298. ParamTooltipItem* paramTooltipItem = new ParamTooltipItem;
  299. paramTooltipItem->text = "Parameter tooltips";
  300. paramTooltipItem->rightText = CHECKMARK(settings::paramTooltip);
  301. menu->addChild(paramTooltipItem);
  302. LockModulesItem* lockModulesItem = new LockModulesItem;
  303. lockModulesItem->text = "Lock modules";
  304. lockModulesItem->rightText = CHECKMARK(settings::lockModules);
  305. menu->addChild(lockModulesItem);
  306. CursorLockItem* cursorLockItem = new CursorLockItem;
  307. cursorLockItem->text = "Lock cursor while dragging";
  308. cursorLockItem->rightText = CHECKMARK(settings::allowCursorLock);
  309. menu->addChild(cursorLockItem);
  310. ZoomSlider* zoomSlider = new ZoomSlider;
  311. zoomSlider->box.size.x = 200.0;
  312. menu->addChild(zoomSlider);
  313. CableOpacitySlider* cableOpacitySlider = new CableOpacitySlider;
  314. cableOpacitySlider->box.size.x = 200.0;
  315. menu->addChild(cableOpacitySlider);
  316. CableTensionSlider* cableTensionSlider = new CableTensionSlider;
  317. cableTensionSlider->box.size.x = 200.0;
  318. menu->addChild(cableTensionSlider);
  319. FullscreenItem* fullscreenItem = new FullscreenItem;
  320. fullscreenItem->text = "Fullscreen";
  321. fullscreenItem->rightText = "F11";
  322. if (APP->window->isFullScreen())
  323. fullscreenItem->rightText = CHECKMARK_STRING " " + fullscreenItem->rightText;
  324. menu->addChild(fullscreenItem);
  325. }
  326. };
  327. ////////////////////
  328. // Engine
  329. ////////////////////
  330. struct CpuMeterItem : ui::MenuItem {
  331. void onAction(const event::Action& e) override {
  332. settings::cpuMeter ^= true;
  333. }
  334. };
  335. struct EnginePauseItem : ui::MenuItem {
  336. void onAction(const event::Action& e) override {
  337. APP->engine->setPaused(!APP->engine->isPaused());
  338. }
  339. };
  340. struct SampleRateValueItem : ui::MenuItem {
  341. float sampleRate;
  342. void onAction(const event::Action& e) override {
  343. settings::sampleRate = sampleRate;
  344. APP->engine->setPaused(false);
  345. }
  346. };
  347. struct SampleRateItem : ui::MenuItem {
  348. ui::Menu* createChildMenu() override {
  349. ui::Menu* menu = new ui::Menu;
  350. EnginePauseItem* enginePauseItem = new EnginePauseItem;
  351. enginePauseItem->text = "Pause";
  352. enginePauseItem->rightText = CHECKMARK(APP->engine->isPaused());
  353. menu->addChild(enginePauseItem);
  354. for (int i = 0; i <= 4; i++) {
  355. for (int j = 0; j < 2; j++) {
  356. int oversample = 1 << i;
  357. float sampleRate = (j == 0) ? 44100.f : 48000.f;
  358. sampleRate *= oversample;
  359. SampleRateValueItem* item = new SampleRateValueItem;
  360. item->sampleRate = sampleRate;
  361. item->text = string::f("%g kHz", sampleRate / 1000.0);
  362. if (oversample > 1)
  363. item->rightText += string::f("(%dx)", oversample);
  364. item->rightText += " ";
  365. item->rightText += CHECKMARK(settings::sampleRate == sampleRate);
  366. menu->addChild(item);
  367. }
  368. }
  369. return menu;
  370. }
  371. };
  372. struct RealTimeItem : ui::MenuItem {
  373. void onAction(const event::Action& e) override {
  374. settings::realTime ^= true;
  375. }
  376. };
  377. struct ThreadCountValueItem : ui::MenuItem {
  378. int threadCount;
  379. void setThreadCount(int threadCount) {
  380. this->threadCount = threadCount;
  381. text = string::f("%d", threadCount);
  382. if (threadCount == system::getLogicalCoreCount() / 2)
  383. text += " (most modules)";
  384. else if (threadCount == 1)
  385. text += " (lowest CPU usage)";
  386. rightText = CHECKMARK(settings::threadCount == threadCount);
  387. }
  388. void onAction(const event::Action& e) override {
  389. settings::threadCount = threadCount;
  390. }
  391. };
  392. struct ThreadCountItem : ui::MenuItem {
  393. ui::Menu* createChildMenu() override {
  394. ui::Menu* menu = new ui::Menu;
  395. RealTimeItem* realTimeItem = new RealTimeItem;
  396. realTimeItem->text = "Real-time priority";
  397. realTimeItem->rightText = CHECKMARK(settings::realTime);
  398. menu->addChild(realTimeItem);
  399. int coreCount = system::getLogicalCoreCount();
  400. for (int i = 1; i <= coreCount; i++) {
  401. ThreadCountValueItem* item = new ThreadCountValueItem;
  402. item->setThreadCount(i);
  403. menu->addChild(item);
  404. }
  405. return menu;
  406. }
  407. };
  408. struct EngineButton : MenuButton {
  409. void onAction(const event::Action& e) override {
  410. ui::Menu* menu = createMenu();
  411. menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y));
  412. menu->box.size.x = box.size.x;
  413. CpuMeterItem* cpuMeterItem = new CpuMeterItem;
  414. cpuMeterItem->text = "CPU meter";
  415. cpuMeterItem->rightText = CHECKMARK(settings::cpuMeter);
  416. menu->addChild(cpuMeterItem);
  417. SampleRateItem* sampleRateItem = new SampleRateItem;
  418. sampleRateItem->text = "Sample rate";
  419. sampleRateItem->rightText = RIGHT_ARROW;
  420. menu->addChild(sampleRateItem);
  421. ThreadCountItem* threadCount = new ThreadCountItem;
  422. threadCount->text = "Threads";
  423. threadCount->rightText = RIGHT_ARROW;
  424. menu->addChild(threadCount);
  425. }
  426. };
  427. ////////////////////
  428. // Plugins
  429. ////////////////////
  430. static bool isLoggingIn = false;
  431. struct AccountEmailField : ui::TextField {
  432. ui::TextField* passwordField;
  433. void onSelectKey(const event::SelectKey& e) override {
  434. if (e.action == GLFW_PRESS && e.key == GLFW_KEY_TAB) {
  435. APP->event->setSelected(passwordField);
  436. e.consume(this);
  437. }
  438. if (!e.getTarget())
  439. ui::TextField::onSelectKey(e);
  440. }
  441. };
  442. struct AccountPasswordField : ui::PasswordField {
  443. ui::MenuItem* logInItem;
  444. void onSelectKey(const event::SelectKey& e) override {
  445. if (e.action == GLFW_PRESS && (e.key == GLFW_KEY_ENTER || e.key == GLFW_KEY_KP_ENTER)) {
  446. logInItem->doAction();
  447. e.consume(this);
  448. }
  449. if (!e.getTarget())
  450. ui::PasswordField::onSelectKey(e);
  451. }
  452. };
  453. struct LogInItem : ui::MenuItem {
  454. ui::TextField* emailField;
  455. ui::TextField* passwordField;
  456. void onAction(const event::Action& e) override {
  457. isLoggingIn = true;
  458. std::string email = emailField->text;
  459. std::string password = passwordField->text;
  460. std::thread t([ = ] {
  461. plugin::logIn(email, password);
  462. isLoggingIn = false;
  463. });
  464. t.detach();
  465. e.consume(NULL);
  466. }
  467. void step() override {
  468. disabled = isLoggingIn;
  469. text = "Log in";
  470. rightText = plugin::loginStatus;
  471. MenuItem::step();
  472. }
  473. };
  474. struct SyncItem : ui::MenuItem {
  475. void step() override {
  476. disabled = true;
  477. if (plugin::updateStatus != "") {
  478. text = plugin::updateStatus;
  479. }
  480. else if (plugin::isSyncing()) {
  481. text = "Updating...";
  482. }
  483. else if (!plugin::hasUpdates()) {
  484. text = "Up-to-date";
  485. }
  486. else {
  487. text = "Update all";
  488. disabled = false;
  489. }
  490. MenuItem::step();
  491. }
  492. void onAction(const event::Action& e) override {
  493. std::thread t([ = ] {
  494. plugin::syncUpdates();
  495. });
  496. t.detach();
  497. e.consume(NULL);
  498. }
  499. };
  500. struct PluginSyncItem : ui::MenuItem {
  501. plugin::Update* update;
  502. void setUpdate(plugin::Update* update) {
  503. this->update = update;
  504. text = update->pluginName;
  505. plugin::Plugin* p = plugin::getPlugin(update->pluginSlug);
  506. if (p) {
  507. rightText += "v" + p->version + " → ";
  508. }
  509. rightText += "v" + update->version;
  510. }
  511. ui::Menu* createChildMenu() override {
  512. if (update->changelogUrl != "") {
  513. ui::Menu* menu = new ui::Menu;
  514. UrlItem* changelogUrl = new UrlItem;
  515. changelogUrl->text = "Changelog";
  516. changelogUrl->url = update->changelogUrl;
  517. menu->addChild(changelogUrl);
  518. return menu;
  519. }
  520. return NULL;
  521. }
  522. void step() override {
  523. disabled = plugin::isSyncing();
  524. if (update->progress >= 1) {
  525. rightText = CHECKMARK_STRING;
  526. disabled = true;
  527. }
  528. else if (update->progress > 0) {
  529. rightText = string::f("%.0f%%", update->progress * 100.f);
  530. }
  531. MenuItem::step();
  532. }
  533. void onAction(const event::Action& e) override {
  534. std::thread t([ = ] {
  535. plugin::syncUpdate(update);
  536. });
  537. t.detach();
  538. e.consume(NULL);
  539. }
  540. };
  541. struct LogOutItem : ui::MenuItem {
  542. void onAction(const event::Action& e) override {
  543. plugin::logOut();
  544. }
  545. };
  546. struct LibraryMenu : ui::Menu {
  547. bool loggedIn = false;
  548. LibraryMenu() {
  549. refresh();
  550. }
  551. void step() override {
  552. // Refresh menu when appropriate
  553. if (!loggedIn && plugin::isLoggedIn())
  554. refresh();
  555. Menu::step();
  556. }
  557. void refresh() {
  558. setChildMenu(NULL);
  559. clearChildren();
  560. if (settings::devMode) {
  561. addChild(createMenuLabel("Disabled in development mode"));
  562. }
  563. else if (!plugin::isLoggedIn()) {
  564. UrlItem* registerItem = new UrlItem;
  565. registerItem->text = "Register VCV account";
  566. registerItem->url = "https://vcvrack.com/";
  567. addChild(registerItem);
  568. AccountEmailField* emailField = new AccountEmailField;
  569. emailField->placeholder = "Email";
  570. emailField->box.size.x = 240.0;
  571. addChild(emailField);
  572. AccountPasswordField* passwordField = new AccountPasswordField;
  573. passwordField->placeholder = "Password";
  574. passwordField->box.size.x = 240.0;
  575. emailField->passwordField = passwordField;
  576. addChild(passwordField);
  577. LogInItem* logInItem = new LogInItem;
  578. logInItem->emailField = emailField;
  579. logInItem->passwordField = passwordField;
  580. passwordField->logInItem = logInItem;
  581. addChild(logInItem);
  582. }
  583. else {
  584. loggedIn = true;
  585. UrlItem* manageItem = new UrlItem;
  586. manageItem->text = "Manage plugins";
  587. manageItem->url = "https://vcvrack.com/plugins.html";
  588. addChild(manageItem);
  589. LogOutItem* logOutItem = new LogOutItem;
  590. logOutItem->text = "Log out";
  591. addChild(logOutItem);
  592. SyncItem* syncItem = new SyncItem;
  593. syncItem->text = "Update all";
  594. addChild(syncItem);
  595. if (plugin::hasUpdates()) {
  596. addChild(new ui::MenuEntry);
  597. ui::MenuLabel* updatesLabel = new ui::MenuLabel;
  598. updatesLabel->text = "Updates";
  599. addChild(updatesLabel);
  600. for (plugin::Update& update : plugin::updates) {
  601. PluginSyncItem* updateItem = new PluginSyncItem;
  602. updateItem->setUpdate(&update);
  603. addChild(updateItem);
  604. }
  605. }
  606. }
  607. }
  608. };
  609. struct LibraryButton : MenuButton {
  610. NotificationIcon* notification;
  611. LibraryButton() {
  612. notification = new NotificationIcon;
  613. addChild(notification);
  614. }
  615. void onAction(const event::Action& e) override {
  616. ui::Menu* menu = createMenu<LibraryMenu>();
  617. menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y));
  618. menu->box.size.x = box.size.x;
  619. }
  620. void step() override {
  621. notification->box.pos = math::Vec(0, 0);
  622. notification->visible = plugin::hasUpdates();
  623. // Popup when updates finish downloading
  624. if (plugin::restartRequested) {
  625. plugin::restartRequested = false;
  626. if (osdialog_message(OSDIALOG_INFO, OSDIALOG_OK_CANCEL, "All plugins have been downloaded. Close and re-launch Rack to load new updates.")) {
  627. APP->window->close();
  628. }
  629. }
  630. MenuButton::step();
  631. }
  632. };
  633. ////////////////////
  634. // Help
  635. ////////////////////
  636. struct UpdateItem : ui::MenuItem {
  637. ui::Menu* createChildMenu() override {
  638. ui::Menu* menu = new ui::Menu;
  639. UrlItem* changelogUrl = new UrlItem;
  640. changelogUrl->text = "Changelog";
  641. changelogUrl->url = updater::changelogUrl;
  642. menu->addChild(changelogUrl);
  643. return menu;
  644. }
  645. void step() override {
  646. if (updater::progress > 0) {
  647. rightText = string::f("%.0f%%", updater::progress * 100.f);
  648. }
  649. MenuItem::step();
  650. }
  651. void onAction(const event::Action& e) override {
  652. std::thread t([ = ] {
  653. updater::update();
  654. });
  655. t.detach();
  656. e.consume(NULL);
  657. }
  658. };
  659. struct HelpButton : MenuButton {
  660. NotificationIcon* notification;
  661. HelpButton() {
  662. notification = new NotificationIcon;
  663. addChild(notification);
  664. }
  665. void onAction(const event::Action& e) override {
  666. ui::Menu* menu = createMenu();
  667. menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y));
  668. menu->box.size.x = box.size.x;
  669. if (updater::isUpdateAvailable()) {
  670. UpdateItem* updateItem = new UpdateItem;
  671. updateItem->text = "Update " + APP_NAME;
  672. updateItem->rightText = APP_VERSION + " → " + updater::version;
  673. menu->addChild(updateItem);
  674. }
  675. UrlItem* manualItem = new UrlItem;
  676. manualItem->text = "Manual";
  677. manualItem->rightText = "F1";
  678. manualItem->url = "https://vcvrack.com/manual/";
  679. menu->addChild(manualItem);
  680. UrlItem* websiteItem = new UrlItem;
  681. websiteItem->text = "VCVRack.com";
  682. websiteItem->url = "https://vcvrack.com/";
  683. menu->addChild(websiteItem);
  684. FolderItem* folderItem = new FolderItem;
  685. folderItem->text = "Open user folder";
  686. folderItem->path = asset::user("");
  687. menu->addChild(folderItem);
  688. }
  689. void step() override {
  690. notification->box.pos = math::Vec(0, 0);
  691. notification->visible = updater::isUpdateAvailable();
  692. MenuButton::step();
  693. }
  694. };
  695. ////////////////////
  696. // MenuBar
  697. ////////////////////
  698. void MenuBar::draw(const DrawArgs& args) {
  699. bndMenuBackground(args.vg, 0.0, 0.0, box.size.x, box.size.y, BND_CORNER_ALL);
  700. bndBevel(args.vg, 0.0, 0.0, box.size.x, box.size.y);
  701. Widget::draw(args);
  702. }
  703. MenuBar* createMenuBar() {
  704. MenuBar* menuBar = new MenuBar;
  705. const float margin = 5;
  706. menuBar->box.size.y = BND_WIDGET_HEIGHT + 2 * margin;
  707. ui::SequentialLayout* layout = new ui::SequentialLayout;
  708. layout->box.pos = math::Vec(margin, margin);
  709. layout->spacing = math::Vec(0, 0);
  710. menuBar->addChild(layout);
  711. FileButton* fileButton = new FileButton;
  712. fileButton->text = "File";
  713. layout->addChild(fileButton);
  714. EditButton* editButton = new EditButton;
  715. editButton->text = "Edit";
  716. layout->addChild(editButton);
  717. ViewButton* viewButton = new ViewButton;
  718. viewButton->text = "View";
  719. layout->addChild(viewButton);
  720. EngineButton* engineButton = new EngineButton;
  721. engineButton->text = "Engine";
  722. layout->addChild(engineButton);
  723. LibraryButton* libraryButton = new LibraryButton;
  724. libraryButton->text = "Library";
  725. layout->addChild(libraryButton);
  726. HelpButton* helpButton = new HelpButton;
  727. helpButton->text = "Help";
  728. layout->addChild(helpButton);
  729. return menuBar;
  730. }
  731. } // namespace app
  732. } // namespace rack