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.

813 lines
20KB

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