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.

1144 lines
28KB

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