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.

900 lines
22KB

  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 FrameRateValueItem : ui::MenuItem {
  289. int frameSwapInterval;
  290. void onAction(const event::Action& e) override {
  291. settings::frameSwapInterval = frameSwapInterval;
  292. }
  293. };
  294. struct FrameRateItem : ui::MenuItem {
  295. ui::Menu* createChildMenu() override {
  296. ui::Menu* menu = new ui::Menu;
  297. for (int i = 1; i <= 6; i++) {
  298. float frameRate = APP->window->getMonitorRefreshRate() / i;
  299. FrameRateValueItem* item = new FrameRateValueItem;
  300. item->frameSwapInterval = i;
  301. item->text = string::f("%.0f Hz", frameRate);
  302. item->rightText += CHECKMARK(settings::frameSwapInterval == i);
  303. menu->addChild(item);
  304. }
  305. return menu;
  306. }
  307. };
  308. struct FullscreenItem : ui::MenuItem {
  309. void onAction(const event::Action& e) override {
  310. APP->window->setFullScreen(!APP->window->isFullScreen());
  311. }
  312. };
  313. struct ViewButton : MenuButton {
  314. void onAction(const event::Action& e) override {
  315. ui::Menu* menu = createMenu();
  316. menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y));
  317. menu->box.size.x = box.size.x;
  318. ParamTooltipItem* paramTooltipItem = new ParamTooltipItem;
  319. paramTooltipItem->text = "Parameter tooltips";
  320. paramTooltipItem->rightText = CHECKMARK(settings::paramTooltip);
  321. menu->addChild(paramTooltipItem);
  322. LockModulesItem* lockModulesItem = new LockModulesItem;
  323. lockModulesItem->text = "Lock modules";
  324. lockModulesItem->rightText = CHECKMARK(settings::lockModules);
  325. menu->addChild(lockModulesItem);
  326. CursorLockItem* cursorLockItem = new CursorLockItem;
  327. cursorLockItem->text = "Lock cursor while dragging";
  328. cursorLockItem->rightText = CHECKMARK(settings::allowCursorLock);
  329. menu->addChild(cursorLockItem);
  330. ZoomSlider* zoomSlider = new ZoomSlider;
  331. zoomSlider->box.size.x = 200.0;
  332. menu->addChild(zoomSlider);
  333. CableOpacitySlider* cableOpacitySlider = new CableOpacitySlider;
  334. cableOpacitySlider->box.size.x = 200.0;
  335. menu->addChild(cableOpacitySlider);
  336. CableTensionSlider* cableTensionSlider = new CableTensionSlider;
  337. cableTensionSlider->box.size.x = 200.0;
  338. menu->addChild(cableTensionSlider);
  339. FrameRateItem* frameRateItem = new FrameRateItem;
  340. frameRateItem->text = "Frame rate";
  341. menu->addChild(frameRateItem);
  342. FullscreenItem* fullscreenItem = new FullscreenItem;
  343. fullscreenItem->text = "Fullscreen";
  344. fullscreenItem->rightText = "F11";
  345. if (APP->window->isFullScreen())
  346. fullscreenItem->rightText = CHECKMARK_STRING " " + fullscreenItem->rightText;
  347. menu->addChild(fullscreenItem);
  348. }
  349. };
  350. ////////////////////
  351. // Engine
  352. ////////////////////
  353. struct CpuMeterItem : ui::MenuItem {
  354. void onAction(const event::Action& e) override {
  355. settings::cpuMeter ^= true;
  356. }
  357. };
  358. struct EnginePauseItem : ui::MenuItem {
  359. void onAction(const event::Action& e) override {
  360. APP->engine->setPaused(!APP->engine->isPaused());
  361. }
  362. };
  363. struct SampleRateValueItem : ui::MenuItem {
  364. float sampleRate;
  365. void onAction(const event::Action& e) override {
  366. settings::sampleRate = sampleRate;
  367. APP->engine->setPaused(false);
  368. }
  369. };
  370. struct SampleRateItem : ui::MenuItem {
  371. ui::Menu* createChildMenu() override {
  372. ui::Menu* menu = new ui::Menu;
  373. EnginePauseItem* enginePauseItem = new EnginePauseItem;
  374. enginePauseItem->text = "Pause";
  375. enginePauseItem->rightText = CHECKMARK(APP->engine->isPaused());
  376. menu->addChild(enginePauseItem);
  377. for (int i = 0; i <= 4; i++) {
  378. for (int j = 0; j < 2; j++) {
  379. int oversample = 1 << i;
  380. float sampleRate = (j == 0) ? 44100.f : 48000.f;
  381. sampleRate *= oversample;
  382. SampleRateValueItem* item = new SampleRateValueItem;
  383. item->sampleRate = sampleRate;
  384. item->text = string::f("%g kHz", sampleRate / 1000.0);
  385. if (oversample > 1)
  386. item->rightText += string::f("(%dx)", oversample);
  387. item->rightText += " ";
  388. item->rightText += CHECKMARK(settings::sampleRate == sampleRate);
  389. menu->addChild(item);
  390. }
  391. }
  392. return menu;
  393. }
  394. };
  395. struct RealTimeItem : ui::MenuItem {
  396. void onAction(const event::Action& e) override {
  397. settings::realTime ^= true;
  398. }
  399. };
  400. struct ThreadCountValueItem : ui::MenuItem {
  401. int threadCount;
  402. void setThreadCount(int threadCount) {
  403. this->threadCount = threadCount;
  404. text = string::f("%d", threadCount);
  405. if (threadCount == system::getLogicalCoreCount() / 2)
  406. text += " (most modules)";
  407. else if (threadCount == 1)
  408. text += " (lowest CPU usage)";
  409. rightText = CHECKMARK(settings::threadCount == threadCount);
  410. }
  411. void onAction(const event::Action& e) override {
  412. settings::threadCount = threadCount;
  413. }
  414. };
  415. struct ThreadCountItem : ui::MenuItem {
  416. ui::Menu* createChildMenu() override {
  417. ui::Menu* menu = new ui::Menu;
  418. RealTimeItem* realTimeItem = new RealTimeItem;
  419. realTimeItem->text = "Real-time priority";
  420. realTimeItem->rightText = CHECKMARK(settings::realTime);
  421. menu->addChild(realTimeItem);
  422. int coreCount = system::getLogicalCoreCount();
  423. for (int i = 1; i <= coreCount; i++) {
  424. ThreadCountValueItem* item = new ThreadCountValueItem;
  425. item->setThreadCount(i);
  426. menu->addChild(item);
  427. }
  428. return menu;
  429. }
  430. };
  431. struct EngineButton : MenuButton {
  432. void onAction(const event::Action& e) override {
  433. ui::Menu* menu = createMenu();
  434. menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y));
  435. menu->box.size.x = box.size.x;
  436. CpuMeterItem* cpuMeterItem = new CpuMeterItem;
  437. cpuMeterItem->text = "CPU meter";
  438. cpuMeterItem->rightText = "F3 ";
  439. cpuMeterItem->rightText += CHECKMARK(settings::cpuMeter);
  440. menu->addChild(cpuMeterItem);
  441. SampleRateItem* sampleRateItem = new SampleRateItem;
  442. sampleRateItem->text = "Sample rate";
  443. sampleRateItem->rightText = RIGHT_ARROW;
  444. menu->addChild(sampleRateItem);
  445. ThreadCountItem* threadCount = new ThreadCountItem;
  446. threadCount->text = "Threads";
  447. threadCount->rightText = RIGHT_ARROW;
  448. menu->addChild(threadCount);
  449. }
  450. };
  451. ////////////////////
  452. // Plugins
  453. ////////////////////
  454. static bool isLoggingIn = false;
  455. struct AccountEmailField : ui::TextField {
  456. ui::TextField* passwordField;
  457. void onSelectKey(const event::SelectKey& e) override {
  458. if (e.action == GLFW_PRESS && e.key == GLFW_KEY_TAB) {
  459. APP->event->setSelected(passwordField);
  460. e.consume(this);
  461. }
  462. if (!e.getTarget())
  463. ui::TextField::onSelectKey(e);
  464. }
  465. };
  466. struct AccountPasswordField : ui::PasswordField {
  467. ui::MenuItem* logInItem;
  468. void onSelectKey(const event::SelectKey& e) override {
  469. if (e.action == GLFW_PRESS && (e.key == GLFW_KEY_ENTER || e.key == GLFW_KEY_KP_ENTER)) {
  470. logInItem->doAction();
  471. e.consume(this);
  472. }
  473. if (!e.getTarget())
  474. ui::PasswordField::onSelectKey(e);
  475. }
  476. };
  477. struct LogInItem : ui::MenuItem {
  478. ui::TextField* emailField;
  479. ui::TextField* passwordField;
  480. void onAction(const event::Action& e) override {
  481. isLoggingIn = true;
  482. std::string email = emailField->text;
  483. std::string password = passwordField->text;
  484. std::thread t([ = ] {
  485. plugin::logIn(email, password);
  486. isLoggingIn = false;
  487. });
  488. t.detach();
  489. e.consume(NULL);
  490. }
  491. void step() override {
  492. disabled = isLoggingIn;
  493. text = "Log in";
  494. rightText = plugin::loginStatus;
  495. MenuItem::step();
  496. }
  497. };
  498. struct SyncItem : ui::MenuItem {
  499. void step() override {
  500. disabled = true;
  501. if (plugin::updateStatus != "") {
  502. text = plugin::updateStatus;
  503. }
  504. else if (plugin::isSyncing()) {
  505. text = "Updating...";
  506. }
  507. else if (!plugin::hasUpdates()) {
  508. text = "Up-to-date";
  509. }
  510. else {
  511. text = "Update all";
  512. disabled = false;
  513. }
  514. MenuItem::step();
  515. }
  516. void onAction(const event::Action& e) override {
  517. std::thread t([ = ] {
  518. plugin::syncUpdates();
  519. });
  520. t.detach();
  521. e.consume(NULL);
  522. }
  523. };
  524. struct PluginSyncItem : ui::MenuItem {
  525. plugin::Update* update;
  526. void setUpdate(plugin::Update* update) {
  527. this->update = update;
  528. text = update->pluginName;
  529. plugin::Plugin* p = plugin::getPlugin(update->pluginSlug);
  530. if (p) {
  531. rightText += "v" + p->version + " → ";
  532. }
  533. rightText += "v" + update->version;
  534. }
  535. ui::Menu* createChildMenu() override {
  536. if (update->changelogUrl != "") {
  537. ui::Menu* menu = new ui::Menu;
  538. UrlItem* changelogUrl = new UrlItem;
  539. changelogUrl->text = "Changelog";
  540. changelogUrl->url = update->changelogUrl;
  541. menu->addChild(changelogUrl);
  542. return menu;
  543. }
  544. return NULL;
  545. }
  546. void step() override {
  547. disabled = plugin::isSyncing();
  548. if (update->progress >= 1) {
  549. rightText = CHECKMARK_STRING;
  550. disabled = true;
  551. }
  552. else if (update->progress > 0) {
  553. rightText = string::f("%.0f%%", update->progress * 100.f);
  554. }
  555. MenuItem::step();
  556. }
  557. void onAction(const event::Action& e) override {
  558. std::thread t([ = ] {
  559. plugin::syncUpdate(update);
  560. });
  561. t.detach();
  562. e.consume(NULL);
  563. }
  564. };
  565. struct LogOutItem : ui::MenuItem {
  566. void onAction(const event::Action& e) override {
  567. plugin::logOut();
  568. }
  569. };
  570. struct LibraryMenu : ui::Menu {
  571. bool loggedIn = false;
  572. LibraryMenu() {
  573. refresh();
  574. }
  575. void step() override {
  576. // Refresh menu when appropriate
  577. if (!loggedIn && plugin::isLoggedIn())
  578. refresh();
  579. Menu::step();
  580. }
  581. void refresh() {
  582. setChildMenu(NULL);
  583. clearChildren();
  584. if (settings::devMode) {
  585. addChild(createMenuLabel("Disabled in development mode"));
  586. }
  587. else if (!plugin::isLoggedIn()) {
  588. UrlItem* registerItem = new UrlItem;
  589. registerItem->text = "Register VCV account";
  590. registerItem->url = "https://vcvrack.com/";
  591. addChild(registerItem);
  592. AccountEmailField* emailField = new AccountEmailField;
  593. emailField->placeholder = "Email";
  594. emailField->box.size.x = 240.0;
  595. addChild(emailField);
  596. AccountPasswordField* passwordField = new AccountPasswordField;
  597. passwordField->placeholder = "Password";
  598. passwordField->box.size.x = 240.0;
  599. emailField->passwordField = passwordField;
  600. addChild(passwordField);
  601. LogInItem* logInItem = new LogInItem;
  602. logInItem->emailField = emailField;
  603. logInItem->passwordField = passwordField;
  604. passwordField->logInItem = logInItem;
  605. addChild(logInItem);
  606. }
  607. else {
  608. loggedIn = true;
  609. UrlItem* manageItem = new UrlItem;
  610. manageItem->text = "Manage plugins";
  611. manageItem->url = "https://vcvrack.com/plugins.html";
  612. addChild(manageItem);
  613. LogOutItem* logOutItem = new LogOutItem;
  614. logOutItem->text = "Log out";
  615. addChild(logOutItem);
  616. SyncItem* syncItem = new SyncItem;
  617. syncItem->text = "Update all";
  618. addChild(syncItem);
  619. if (plugin::hasUpdates()) {
  620. addChild(new ui::MenuEntry);
  621. ui::MenuLabel* updatesLabel = new ui::MenuLabel;
  622. updatesLabel->text = "Updates";
  623. addChild(updatesLabel);
  624. for (plugin::Update& update : plugin::updates) {
  625. PluginSyncItem* updateItem = new PluginSyncItem;
  626. updateItem->setUpdate(&update);
  627. addChild(updateItem);
  628. }
  629. }
  630. }
  631. }
  632. };
  633. struct LibraryButton : MenuButton {
  634. NotificationIcon* notification;
  635. LibraryButton() {
  636. notification = new NotificationIcon;
  637. addChild(notification);
  638. }
  639. void onAction(const event::Action& e) override {
  640. ui::Menu* menu = createMenu<LibraryMenu>();
  641. menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y));
  642. menu->box.size.x = box.size.x;
  643. }
  644. void step() override {
  645. notification->box.pos = math::Vec(0, 0);
  646. notification->visible = plugin::hasUpdates();
  647. // Popup when updates finish downloading
  648. if (plugin::restartRequested) {
  649. plugin::restartRequested = false;
  650. if (osdialog_message(OSDIALOG_INFO, OSDIALOG_OK_CANCEL, "All plugins have been downloaded. Close and re-launch Rack to load new updates.")) {
  651. APP->window->close();
  652. }
  653. }
  654. MenuButton::step();
  655. }
  656. };
  657. ////////////////////
  658. // Help
  659. ////////////////////
  660. struct UpdateItem : ui::MenuItem {
  661. ui::Menu* createChildMenu() override {
  662. ui::Menu* menu = new ui::Menu;
  663. UrlItem* changelogUrl = new UrlItem;
  664. changelogUrl->text = "Changelog";
  665. changelogUrl->url = updater::changelogUrl;
  666. menu->addChild(changelogUrl);
  667. return menu;
  668. }
  669. void step() override {
  670. if (updater::progress > 0) {
  671. rightText = string::f("%.0f%%", updater::progress * 100.f);
  672. }
  673. MenuItem::step();
  674. }
  675. void onAction(const event::Action& e) override {
  676. std::thread t([ = ] {
  677. updater::update();
  678. });
  679. t.detach();
  680. e.consume(NULL);
  681. }
  682. };
  683. struct HelpButton : MenuButton {
  684. NotificationIcon* notification;
  685. HelpButton() {
  686. notification = new NotificationIcon;
  687. addChild(notification);
  688. }
  689. void onAction(const event::Action& e) override {
  690. ui::Menu* menu = createMenu();
  691. menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y));
  692. menu->box.size.x = box.size.x;
  693. if (updater::isUpdateAvailable()) {
  694. UpdateItem* updateItem = new UpdateItem;
  695. updateItem->text = "Update " + APP_NAME;
  696. updateItem->rightText = APP_VERSION + " → " + updater::version;
  697. menu->addChild(updateItem);
  698. }
  699. UrlItem* manualItem = new UrlItem;
  700. manualItem->text = "Manual";
  701. manualItem->rightText = "F1";
  702. manualItem->url = "https://vcvrack.com/manual/";
  703. menu->addChild(manualItem);
  704. UrlItem* websiteItem = new UrlItem;
  705. websiteItem->text = "VCVRack.com";
  706. websiteItem->url = "https://vcvrack.com/";
  707. menu->addChild(websiteItem);
  708. FolderItem* folderItem = new FolderItem;
  709. folderItem->text = "Open user folder";
  710. folderItem->path = asset::user("");
  711. menu->addChild(folderItem);
  712. }
  713. void step() override {
  714. notification->box.pos = math::Vec(0, 0);
  715. notification->visible = updater::isUpdateAvailable();
  716. MenuButton::step();
  717. }
  718. };
  719. ////////////////////
  720. // MenuBar
  721. ////////////////////
  722. void MenuBar::draw(const DrawArgs& args) {
  723. bndMenuBackground(args.vg, 0.0, 0.0, box.size.x, box.size.y, BND_CORNER_ALL);
  724. bndBevel(args.vg, 0.0, 0.0, box.size.x, box.size.y);
  725. Widget::draw(args);
  726. }
  727. MenuBar* createMenuBar() {
  728. MenuBar* menuBar = new MenuBar;
  729. const float margin = 5;
  730. menuBar->box.size.y = BND_WIDGET_HEIGHT + 2 * margin;
  731. ui::SequentialLayout* layout = new ui::SequentialLayout;
  732. layout->box.pos = math::Vec(margin, margin);
  733. layout->spacing = math::Vec(0, 0);
  734. menuBar->addChild(layout);
  735. FileButton* fileButton = new FileButton;
  736. fileButton->text = "File";
  737. layout->addChild(fileButton);
  738. EditButton* editButton = new EditButton;
  739. editButton->text = "Edit";
  740. layout->addChild(editButton);
  741. ViewButton* viewButton = new ViewButton;
  742. viewButton->text = "View";
  743. layout->addChild(viewButton);
  744. EngineButton* engineButton = new EngineButton;
  745. engineButton->text = "Engine";
  746. layout->addChild(engineButton);
  747. LibraryButton* libraryButton = new LibraryButton;
  748. libraryButton->text = "Library";
  749. layout->addChild(libraryButton);
  750. HelpButton* helpButton = new HelpButton;
  751. helpButton->text = "Help";
  752. layout->addChild(helpButton);
  753. return menuBar;
  754. }
  755. } // namespace app
  756. } // namespace rack