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.

1023 lines
25KB

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