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.

804 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 onAction(const event::Action &e) override {
  425. std::thread t([=]() {
  426. plugin::syncUpdates();
  427. });
  428. t.detach();
  429. e.consume(NULL);
  430. }
  431. };
  432. struct PluginSyncItem : ui::MenuItem {
  433. plugin::Update *update;
  434. void setUpdate(plugin::Update *update) {
  435. this->update = update;
  436. text = update->pluginSlug;
  437. plugin::Plugin *p = plugin::getPlugin(update->pluginSlug);
  438. if (p) {
  439. rightText += "v" + p->version + " → ";
  440. }
  441. rightText += "v" + update->version;
  442. }
  443. ui::Menu *createChildMenu() override {
  444. if (update->changelogUrl != "") {
  445. ui::Menu *menu = new ui::Menu;
  446. UrlItem *changelogUrl = new UrlItem;
  447. changelogUrl->text = "Changelog";
  448. changelogUrl->url = update->changelogUrl;
  449. menu->addChild(changelogUrl);
  450. return menu;
  451. }
  452. return NULL;
  453. }
  454. void step() override {
  455. if (update->progress >= 1) {
  456. rightText = CHECKMARK_STRING;
  457. }
  458. else if (update->progress > 0) {
  459. rightText = string::f("%.0f%%", update->progress * 100.f);
  460. }
  461. MenuItem::step();
  462. }
  463. void onAction(const event::Action &e) override {
  464. std::thread t([=]() {
  465. plugin::syncUpdate(update);
  466. });
  467. t.detach();
  468. e.consume(NULL);
  469. }
  470. };
  471. #if 0
  472. struct SyncButton : ui::Button {
  473. bool checked = false;
  474. /** Updates are available */
  475. bool available = false;
  476. /** Plugins have been updated */
  477. bool completed = false;
  478. void step() override {
  479. // Check for plugin update on first step()
  480. if (!checked) {
  481. std::thread t([this]() {
  482. if (plugin::sync(true))
  483. available = true;
  484. });
  485. t.detach();
  486. checked = true;
  487. }
  488. // Display message if we've completed updates
  489. if (completed) {
  490. if (osdialog_message(OSDIALOG_INFO, OSDIALOG_OK_CANCEL, "All plugins have been updated. Close Rack and re-launch it to load new updates.")) {
  491. APP->window->close();
  492. }
  493. completed = false;
  494. }
  495. }
  496. void onAction(const event::Action &e) override {
  497. available = false;
  498. std::thread t([this]() {
  499. if (plugin::sync(false))
  500. completed = true;
  501. });
  502. t.detach();
  503. }
  504. };
  505. #endif
  506. struct LogOutItem : ui::MenuItem {
  507. void onAction(const event::Action &e) override {
  508. plugin::logOut();
  509. }
  510. };
  511. struct PluginsMenu : ui::Menu {
  512. bool loggedIn = false;
  513. PluginsMenu() {
  514. refresh();
  515. }
  516. void step() override {
  517. if (!loggedIn && plugin::isLoggedIn())
  518. refresh();
  519. Menu::step();
  520. }
  521. void refresh() {
  522. clearChildren();
  523. if (!plugin::isLoggedIn()) {
  524. UrlItem *registerItem = new UrlItem;
  525. registerItem->text = "Register VCV account";
  526. registerItem->url = "https://vcvrack.com/";
  527. addChild(registerItem);
  528. AccountEmailField *emailField = new AccountEmailField;
  529. emailField->placeholder = "Email";
  530. emailField->box.size.x = 240.0;
  531. addChild(emailField);
  532. AccountPasswordField *passwordField = new AccountPasswordField;
  533. passwordField->placeholder = "Password";
  534. passwordField->box.size.x = 240.0;
  535. emailField->passwordField = passwordField;
  536. addChild(passwordField);
  537. LogInItem *logInItem = new LogInItem;
  538. logInItem->emailField = emailField;
  539. logInItem->passwordField = passwordField;
  540. passwordField->logInItem = logInItem;
  541. addChild(logInItem);
  542. }
  543. else {
  544. loggedIn = true;
  545. UrlItem *manageItem = new UrlItem;
  546. manageItem->text = "VCV Store";
  547. manageItem->url = "https://vcvrack.com/plugins.html";
  548. addChild(manageItem);
  549. LogOutItem *logOutItem = new LogOutItem;
  550. logOutItem->text = "Log out";
  551. addChild(logOutItem);
  552. SyncItem *syncItem = new SyncItem;
  553. syncItem->text = "Update all";
  554. syncItem->disabled = plugin::updates.empty();
  555. addChild(syncItem);
  556. if (!plugin::updates.empty()) {
  557. addChild(new ui::MenuEntry);
  558. ui::MenuLabel *updatesLabel = new ui::MenuLabel;
  559. updatesLabel->text = "Updates";
  560. addChild(updatesLabel);
  561. for (plugin::Update &update : plugin::updates) {
  562. PluginSyncItem *updateItem = new PluginSyncItem;
  563. updateItem->setUpdate(&update);
  564. addChild(updateItem);
  565. }
  566. }
  567. }
  568. }
  569. };
  570. struct PluginsButton : MenuButton {
  571. NotificationIcon *notification;
  572. PluginsButton() {
  573. notification = new NotificationIcon;
  574. addChild(notification);
  575. }
  576. void onAction(const event::Action &e) override {
  577. ui::Menu *menu = createMenu<PluginsMenu>();
  578. menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y));
  579. menu->box.size.x = box.size.x;
  580. }
  581. void step() override {
  582. notification->box.pos = math::Vec(0, 0);
  583. notification->visible = false;
  584. MenuButton::step();
  585. }
  586. };
  587. ////////////////////
  588. // Help
  589. ////////////////////
  590. struct UserFolderItem : ui::MenuItem {
  591. void onAction(const event::Action &e) override {
  592. std::thread t(system::openFolder, asset::user(""));
  593. t.detach();
  594. }
  595. };
  596. struct HelpButton : MenuButton {
  597. NotificationIcon *notification;
  598. HelpButton() {
  599. notification = new NotificationIcon;
  600. addChild(notification);
  601. }
  602. void onAction(const event::Action &e) override {
  603. ui::Menu *menu = createMenu();
  604. menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y));
  605. menu->box.size.x = box.size.x;
  606. UrlItem *manualItem = new UrlItem;
  607. manualItem->text = "Manual";
  608. manualItem->rightText = "F1";
  609. manualItem->url = "https://vcvrack.com/manual/";
  610. menu->addChild(manualItem);
  611. UrlItem *websiteItem = new UrlItem;
  612. websiteItem->text = "VCVRack.com";
  613. websiteItem->url = "https://vcvrack.com/";
  614. menu->addChild(websiteItem);
  615. if (hasUpdate()) {
  616. UrlItem *updateItem = new UrlItem;
  617. updateItem->text = "Update " + APP_NAME;
  618. updateItem->rightText = APP_VERSION + " → " + APP_VERSION_UPDATE;
  619. updateItem->url = "https://vcvrack.com/";
  620. menu->addChild(updateItem);
  621. }
  622. UserFolderItem *folderItem = new UserFolderItem;
  623. folderItem->text = "Open user folder";
  624. menu->addChild(folderItem);
  625. }
  626. void step() override {
  627. notification->box.pos = math::Vec(0, 0);
  628. notification->visible = hasUpdate();
  629. MenuButton::step();
  630. }
  631. bool hasUpdate() {
  632. return !APP_VERSION_UPDATE.empty() && APP_VERSION_UPDATE != APP_VERSION;
  633. }
  634. };
  635. ////////////////////
  636. // MenuBar
  637. ////////////////////
  638. void MenuBar::draw(const DrawArgs &args) {
  639. bndMenuBackground(args.vg, 0.0, 0.0, box.size.x, box.size.y, BND_CORNER_ALL);
  640. bndBevel(args.vg, 0.0, 0.0, box.size.x, box.size.y);
  641. Widget::draw(args);
  642. }
  643. MenuBar *createMenuBar() {
  644. MenuBar *menuBar = new MenuBar;
  645. const float margin = 5;
  646. menuBar->box.size.y = BND_WIDGET_HEIGHT + 2*margin;
  647. ui::SequentialLayout *layout = new ui::SequentialLayout;
  648. layout->box.pos = math::Vec(margin, margin);
  649. layout->spacing = math::Vec(0, 0);
  650. menuBar->addChild(layout);
  651. FileButton *fileButton = new FileButton;
  652. fileButton->text = "File";
  653. layout->addChild(fileButton);
  654. EditButton *editButton = new EditButton;
  655. editButton->text = "Edit";
  656. layout->addChild(editButton);
  657. ViewButton *viewButton = new ViewButton;
  658. viewButton->text = "View";
  659. layout->addChild(viewButton);
  660. EngineButton *engineButton = new EngineButton;
  661. engineButton->text = "Engine";
  662. layout->addChild(engineButton);
  663. PluginsButton *pluginsButton = new PluginsButton;
  664. pluginsButton->text = "Plugins";
  665. layout->addChild(pluginsButton);
  666. HelpButton *helpButton = new HelpButton;
  667. helpButton->text = "Help";
  668. layout->addChild(helpButton);
  669. return menuBar;
  670. }
  671. } // namespace app
  672. } // namespace rack