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.

1190 lines
34KB

  1. #include <thread>
  2. #include <utility>
  3. #include <algorithm>
  4. #include <osdialog.h>
  5. #include <app/MenuBar.hpp>
  6. #include <app/TipWindow.hpp>
  7. #include <widget/OpaqueWidget.hpp>
  8. #include <ui/Button.hpp>
  9. #include <ui/MenuItem.hpp>
  10. #include <ui/MenuSeparator.hpp>
  11. #include <ui/SequentialLayout.hpp>
  12. #include <ui/Slider.hpp>
  13. #include <ui/TextField.hpp>
  14. #include <ui/ProgressBar.hpp>
  15. #include <ui/Label.hpp>
  16. #include <engine/Engine.hpp>
  17. #include <window/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. ////////////////////
  56. // File
  57. ////////////////////
  58. struct FileButton : MenuButton {
  59. void onAction(const ActionEvent& e) override {
  60. ui::Menu* menu = createMenu();
  61. menu->cornerFlags = BND_CORNER_TOP;
  62. menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y));
  63. menu->addChild(createMenuItem(string::translate("MenuBar.file.new"), widget::getKeyCommandName(GLFW_KEY_N, RACK_MOD_CTRL), []() {
  64. APP->patch->loadTemplateDialog();
  65. }));
  66. menu->addChild(createMenuItem(string::translate("MenuBar.file.open"), widget::getKeyCommandName(GLFW_KEY_O, RACK_MOD_CTRL), []() {
  67. APP->patch->loadDialog();
  68. }));
  69. menu->addChild(createSubmenuItem(string::translate("MenuBar.file.openRecent"), "", [](ui::Menu* menu) {
  70. for (const std::string& path : settings::recentPatchPaths) {
  71. std::string name = system::getStem(path);
  72. menu->addChild(createMenuItem(name, "", [=]() {
  73. APP->patch->loadPathDialog(path);
  74. }));
  75. }
  76. }, settings::recentPatchPaths.empty()));
  77. menu->addChild(createMenuItem(string::translate("MenuBar.file.save"), widget::getKeyCommandName(GLFW_KEY_S, RACK_MOD_CTRL), []() {
  78. APP->patch->saveDialog();
  79. }));
  80. menu->addChild(createMenuItem(string::translate("MenuBar.file.saveAs"), widget::getKeyCommandName(GLFW_KEY_S, RACK_MOD_CTRL | GLFW_MOD_SHIFT), []() {
  81. APP->patch->saveAsDialog();
  82. }));
  83. menu->addChild(createMenuItem(string::translate("MenuBar.file.saveCopy"), "", []() {
  84. APP->patch->saveAsDialog(false);
  85. }));
  86. menu->addChild(createMenuItem(string::translate("MenuBar.file.revert"), widget::getKeyCommandName(GLFW_KEY_O, RACK_MOD_CTRL | GLFW_MOD_SHIFT), []() {
  87. APP->patch->revertDialog();
  88. }, APP->patch->path == ""));
  89. menu->addChild(createMenuItem(string::translate("MenuBar.file.overwriteTemplate"), "", []() {
  90. APP->patch->saveTemplateDialog();
  91. }));
  92. menu->addChild(new ui::MenuSeparator);
  93. // Load selection
  94. menu->addChild(createMenuItem(string::translate("MenuBar.file.importSelection"), "", [=]() {
  95. APP->scene->rack->loadSelectionDialog();
  96. }, false, true));
  97. menu->addChild(new ui::MenuSeparator);
  98. menu->addChild(createMenuItem(string::translate("MenuBar.file.quit"), widget::getKeyCommandName(GLFW_KEY_Q, RACK_MOD_CTRL), []() {
  99. APP->window->close();
  100. }));
  101. }
  102. };
  103. ////////////////////
  104. // Edit
  105. ////////////////////
  106. struct EditButton : MenuButton {
  107. void onAction(const ActionEvent& e) override {
  108. ui::Menu* menu = createMenu();
  109. menu->cornerFlags = BND_CORNER_TOP;
  110. menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y));
  111. struct UndoItem : ui::MenuItem {
  112. void step() override {
  113. bool canUndo = APP->history->canUndo();
  114. text = canUndo ? string::f(string::translate("MenuBar.edit.undoAction"), APP->history->getUndoName()) : string::translate("MenuBar.edit.undo");
  115. disabled = !canUndo;
  116. MenuItem::step();
  117. }
  118. void onAction(const ActionEvent& e) override {
  119. APP->history->undo();
  120. }
  121. };
  122. menu->addChild(createMenuItem<UndoItem>("", widget::getKeyCommandName(GLFW_KEY_Z, RACK_MOD_CTRL)));
  123. struct RedoItem : ui::MenuItem {
  124. void step() override {
  125. bool canRedo = APP->history->canRedo();
  126. text = canRedo ? string::f(string::translate("MenuBar.edit.redoAction"), APP->history->getRedoName()) : string::translate("MenuBar.edit.redo");
  127. disabled = !canRedo;
  128. MenuItem::step();
  129. }
  130. void onAction(const ActionEvent& e) override {
  131. APP->history->redo();
  132. }
  133. };
  134. menu->addChild(createMenuItem<RedoItem>("", widget::getKeyCommandName(GLFW_KEY_Z, RACK_MOD_CTRL | GLFW_MOD_SHIFT)));
  135. menu->addChild(createMenuItem(string::translate("MenuBar.edit.clearCables"), "", [=]() {
  136. APP->patch->disconnectDialog();
  137. }));
  138. menu->addChild(new ui::MenuSeparator);
  139. APP->scene->rack->appendSelectionContextMenu(menu);
  140. }
  141. };
  142. ////////////////////
  143. // View
  144. ////////////////////
  145. struct ZoomQuantity : Quantity {
  146. void setValue(float value) override {
  147. APP->scene->rackScroll->setZoom(std::pow(2.f, value));
  148. }
  149. float getValue() override {
  150. return std::log2(APP->scene->rackScroll->getZoom());
  151. }
  152. float getMinValue() override {
  153. return -2.f;
  154. }
  155. float getMaxValue() override {
  156. return 2.f;
  157. }
  158. float getDefaultValue() override {
  159. return 0.0;
  160. }
  161. float getDisplayValue() override {
  162. return std::round(std::pow(2.f, getValue()) * 100);
  163. }
  164. void setDisplayValue(float displayValue) override {
  165. setValue(std::log2(displayValue / 100));
  166. }
  167. std::string getLabel() override {
  168. return string::translate("MenuBar.view.zoom");
  169. }
  170. std::string getUnit() override {
  171. return "%";
  172. }
  173. };
  174. struct ZoomSlider : ui::Slider {
  175. ZoomSlider() {
  176. quantity = new ZoomQuantity;
  177. }
  178. ~ZoomSlider() {
  179. delete quantity;
  180. }
  181. };
  182. struct CableOpacityQuantity : Quantity {
  183. void setValue(float value) override {
  184. settings::cableOpacity = math::clamp(value, getMinValue(), getMaxValue());
  185. }
  186. float getValue() override {
  187. return settings::cableOpacity;
  188. }
  189. float getDefaultValue() override {
  190. return 0.5;
  191. }
  192. float getDisplayValue() override {
  193. return getValue() * 100;
  194. }
  195. void setDisplayValue(float displayValue) override {
  196. setValue(displayValue / 100);
  197. }
  198. std::string getLabel() override {
  199. return string::translate("MenuBar.view.cableOpacity");
  200. }
  201. std::string getUnit() override {
  202. return "%";
  203. }
  204. };
  205. struct CableOpacitySlider : ui::Slider {
  206. CableOpacitySlider() {
  207. quantity = new CableOpacityQuantity;
  208. }
  209. ~CableOpacitySlider() {
  210. delete quantity;
  211. }
  212. };
  213. struct CableTensionQuantity : Quantity {
  214. void setValue(float value) override {
  215. settings::cableTension = math::clamp(value, getMinValue(), getMaxValue());
  216. }
  217. float getValue() override {
  218. return settings::cableTension;
  219. }
  220. float getDefaultValue() override {
  221. return 0.5;
  222. }
  223. float getDisplayValue() override {
  224. return getValue() * 100;
  225. }
  226. void setDisplayValue(float displayValue) override {
  227. setValue(displayValue / 100);
  228. }
  229. std::string getLabel() override {
  230. return string::translate("MenuBar.view.cableTension");
  231. }
  232. std::string getUnit() override {
  233. return "%";
  234. }
  235. };
  236. struct CableTensionSlider : ui::Slider {
  237. CableTensionSlider() {
  238. quantity = new CableTensionQuantity;
  239. }
  240. ~CableTensionSlider() {
  241. delete quantity;
  242. }
  243. };
  244. struct RackBrightnessQuantity : Quantity {
  245. void setValue(float value) override {
  246. settings::rackBrightness = math::clamp(value, getMinValue(), getMaxValue());
  247. }
  248. float getValue() override {
  249. return settings::rackBrightness;
  250. }
  251. float getDefaultValue() override {
  252. return 1.0;
  253. }
  254. float getDisplayValue() override {
  255. return getValue() * 100;
  256. }
  257. void setDisplayValue(float displayValue) override {
  258. setValue(displayValue / 100);
  259. }
  260. std::string getUnit() override {
  261. return "%";
  262. }
  263. std::string getLabel() override {
  264. return string::translate("MenuBar.view.roomBrightness");
  265. }
  266. int getDisplayPrecision() override {
  267. return 3;
  268. }
  269. };
  270. struct RackBrightnessSlider : ui::Slider {
  271. RackBrightnessSlider() {
  272. quantity = new RackBrightnessQuantity;
  273. }
  274. ~RackBrightnessSlider() {
  275. delete quantity;
  276. }
  277. };
  278. struct HaloBrightnessQuantity : Quantity {
  279. void setValue(float value) override {
  280. settings::haloBrightness = math::clamp(value, getMinValue(), getMaxValue());
  281. }
  282. float getValue() override {
  283. return settings::haloBrightness;
  284. }
  285. float getDefaultValue() override {
  286. return 0.25;
  287. }
  288. float getDisplayValue() override {
  289. return getValue() * 100;
  290. }
  291. void setDisplayValue(float displayValue) override {
  292. setValue(displayValue / 100);
  293. }
  294. std::string getUnit() override {
  295. return "%";
  296. }
  297. std::string getLabel() override {
  298. return string::translate("MenuBar.view.lightBloom");
  299. }
  300. int getDisplayPrecision() override {
  301. return 3;
  302. }
  303. };
  304. struct HaloBrightnessSlider : ui::Slider {
  305. HaloBrightnessSlider() {
  306. quantity = new HaloBrightnessQuantity;
  307. }
  308. ~HaloBrightnessSlider() {
  309. delete quantity;
  310. }
  311. };
  312. struct KnobScrollSensitivityQuantity : Quantity {
  313. void setValue(float value) override {
  314. value = math::clamp(value, getMinValue(), getMaxValue());
  315. settings::knobScrollSensitivity = std::pow(2.f, value);
  316. }
  317. float getValue() override {
  318. return std::log2(settings::knobScrollSensitivity);
  319. }
  320. float getMinValue() override {
  321. return std::log2(1e-4f);
  322. }
  323. float getMaxValue() override {
  324. return std::log2(1e-2f);
  325. }
  326. float getDefaultValue() override {
  327. return std::log2(1e-3f);
  328. }
  329. float getDisplayValue() override {
  330. return std::pow(2.f, getValue() - getDefaultValue());
  331. }
  332. void setDisplayValue(float displayValue) override {
  333. setValue(std::log2(displayValue) + getDefaultValue());
  334. }
  335. std::string getLabel() override {
  336. return string::translate("MenuBar.view.wheelSensitivity");
  337. }
  338. int getDisplayPrecision() override {
  339. return 2;
  340. }
  341. };
  342. struct KnobScrollSensitivitySlider : ui::Slider {
  343. KnobScrollSensitivitySlider() {
  344. quantity = new KnobScrollSensitivityQuantity;
  345. }
  346. ~KnobScrollSensitivitySlider() {
  347. delete quantity;
  348. }
  349. };
  350. struct ViewButton : MenuButton {
  351. void onAction(const ActionEvent& e) override {
  352. ui::Menu* menu = createMenu();
  353. menu->cornerFlags = BND_CORNER_TOP;
  354. menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y));
  355. menu->addChild(createMenuLabel(string::translate("MenuBar.view.window")));
  356. bool fullscreen = APP->window->isFullScreen();
  357. std::string fullscreenText = widget::getKeyCommandName(GLFW_KEY_F11, 0);
  358. if (fullscreen)
  359. fullscreenText += " " CHECKMARK_STRING;
  360. menu->addChild(createMenuItem(string::translate("MenuBar.view.fullscreen"), fullscreenText, [=]() {
  361. APP->window->setFullScreen(!fullscreen);
  362. }));
  363. menu->addChild(createSubmenuItem(string::translate("MenuBar.view.frameRate"), string::f("%.0f Hz", settings::frameRateLimit), [=](ui::Menu* menu) {
  364. for (int i = 1; i <= 6; i++) {
  365. double frameRate = APP->window->getMonitorRefreshRate() / i;
  366. menu->addChild(createCheckMenuItem(string::f("%.0f Hz", frameRate), "",
  367. [=]() {return settings::frameRateLimit == frameRate;},
  368. [=]() {settings::frameRateLimit = frameRate;}
  369. ));
  370. }
  371. }));
  372. menu->addChild(createIndexPtrSubmenuItem(string::translate("MenuBar.view.pixelRatio"), {
  373. string::translate("MenuBar.view.pixelRatio.auto"),
  374. "100%", "200%", "300%"
  375. }, &settings::pixelRatio));
  376. ZoomSlider* zoomSlider = new ZoomSlider;
  377. zoomSlider->box.size.x = 250.0;
  378. menu->addChild(zoomSlider);
  379. menu->addChild(createMenuItem(string::translate("MenuBar.view.zoomFit"), widget::getKeyCommandName(GLFW_KEY_F4, 0), [=]() {
  380. APP->scene->rackScroll->zoomToModules();
  381. }));
  382. menu->addChild(createIndexPtrSubmenuItem(string::translate("MenuBar.view.mouseWheelZoom"), {
  383. string::f(string::translate("MenuBar.view.mouseWheelZoom.scroll"), RACK_MOD_CTRL_NAME),
  384. string::f(string::translate("MenuBar.view.mouseWheelZoom.zoom"), RACK_MOD_CTRL_NAME)
  385. }, &settings::mouseWheelZoom));
  386. menu->addChild(new ui::MenuSeparator);
  387. menu->addChild(createMenuLabel(string::translate("MenuBar.view.appearance")));
  388. static const std::vector<std::string> uiThemes = {"dark", "light", "hcdark"};
  389. menu->addChild(createIndexSubmenuItem(string::translate("MenuBar.view.uiTheme"), {
  390. string::translate("MenuBar.view.appearance.dark"),
  391. string::translate("MenuBar.view.appearance.light"),
  392. string::translate("MenuBar.view.appearance.hcdark")
  393. }, [=]() -> size_t {
  394. auto it = std::find(uiThemes.begin(), uiThemes.end(), settings::uiTheme);
  395. if (it == uiThemes.end())
  396. return -1;
  397. return it - uiThemes.begin();
  398. }, [=](size_t i) {
  399. settings::uiTheme = uiThemes[i];
  400. ui::refreshTheme();
  401. }));
  402. menu->addChild(createBoolPtrMenuItem(string::translate("MenuBar.view.showTooltips"), "", &settings::tooltips));
  403. // Various sliders
  404. CableOpacitySlider* cableOpacitySlider = new CableOpacitySlider;
  405. cableOpacitySlider->box.size.x = 250.0;
  406. menu->addChild(cableOpacitySlider);
  407. CableTensionSlider* cableTensionSlider = new CableTensionSlider;
  408. cableTensionSlider->box.size.x = 250.0;
  409. menu->addChild(cableTensionSlider);
  410. RackBrightnessSlider* rackBrightnessSlider = new RackBrightnessSlider;
  411. rackBrightnessSlider->box.size.x = 250.0;
  412. menu->addChild(rackBrightnessSlider);
  413. HaloBrightnessSlider* haloBrightnessSlider = new HaloBrightnessSlider;
  414. haloBrightnessSlider->box.size.x = 250.0;
  415. menu->addChild(haloBrightnessSlider);
  416. // Cable colors
  417. menu->addChild(createSubmenuItem(string::translate("MenuBar.view.cableColors"), "", [=](ui::Menu* menu) {
  418. // TODO Subclass Menu to make an auto-refreshing list so user can Ctrl+click to keep menu open.
  419. // Add color items
  420. for (size_t i = 0; i < settings::cableColors.size(); i++) {
  421. NVGcolor color = settings::cableColors[i];
  422. std::string label = get(settings::cableLabels, i);
  423. std::string labelFallback = (label != "") ? label : string::f("Color #%lld", (long long) (i + 1));
  424. ui::ColorDotMenuItem* item = createSubmenuItem<ui::ColorDotMenuItem>(labelFallback, "", [=](ui::Menu* menu) {
  425. // Helper for launching color dialog
  426. auto selectColor = [](NVGcolor& color) -> bool {
  427. osdialog_color c = {
  428. uint8_t(color.r * 255.f),
  429. uint8_t(color.g * 255.f),
  430. uint8_t(color.b * 255.f),
  431. uint8_t(color.a * 255.f),
  432. };
  433. if (!osdialog_color_picker(&c, false))
  434. return false;
  435. color = nvgRGBA(c.r, c.g, c.b, c.a);
  436. return true;
  437. };
  438. menu->addChild(createMenuItem(string::translate("MenuBar.view.cableColors.setLabel"), "", [=]() {
  439. if (i >= settings::cableColors.size())
  440. return;
  441. char* s = osdialog_prompt(OSDIALOG_INFO, "", label.c_str());
  442. if (!s)
  443. return;
  444. settings::cableLabels.resize(settings::cableColors.size());
  445. settings::cableLabels[i] = s;
  446. free(s);
  447. }, false, true));
  448. menu->addChild(createMenuItem(string::translate("MenuBar.view.cableColors.setColor"), "", [=]() {
  449. if (i >= settings::cableColors.size())
  450. return;
  451. NVGcolor newColor = color;
  452. if (!selectColor(newColor))
  453. return;
  454. std::memcpy(&settings::cableColors[i], &newColor, sizeof(newColor));
  455. }, false, true));
  456. menu->addChild(createMenuItem(string::translate("MenuBar.view.cableColors.newColorAbove"), "", [=]() {
  457. if (i >= settings::cableColors.size())
  458. return;
  459. NVGcolor newColor = color;
  460. if (!selectColor(newColor))
  461. return;
  462. settings::cableLabels.resize(settings::cableColors.size());
  463. settings::cableColors.insert(settings::cableColors.begin() + i, newColor);
  464. settings::cableLabels.insert(settings::cableLabels.begin() + i, "");
  465. }, false, true));
  466. menu->addChild(createMenuItem(string::translate("MenuBar.view.cableColors.newColorBelow"), "", [=]() {
  467. if (i >= settings::cableColors.size())
  468. return;
  469. NVGcolor newColor = color;
  470. if (!selectColor(newColor))
  471. return;
  472. settings::cableLabels.resize(settings::cableColors.size());
  473. settings::cableColors.insert(settings::cableColors.begin() + i + 1, newColor);
  474. settings::cableLabels.insert(settings::cableLabels.begin() + i + 1, "");
  475. }, false, true));
  476. menu->addChild(createMenuItem(string::translate("MenuBar.view.cableColors.moveUp"), "", [=]() {
  477. if (i < 1 || i >= settings::cableColors.size())
  478. return;
  479. settings::cableLabels.resize(settings::cableColors.size());
  480. std::swap(settings::cableColors[i], settings::cableColors[i - 1]);
  481. std::swap(settings::cableLabels[i], settings::cableLabels[i - 1]);
  482. }, i < 1, true));
  483. menu->addChild(createMenuItem(string::translate("MenuBar.view.cableColors.moveDown"), "", [=]() {
  484. if (i + 1 >= settings::cableColors.size())
  485. return;
  486. settings::cableLabels.resize(settings::cableColors.size());
  487. std::swap(settings::cableColors[i], settings::cableColors[i + 1]);
  488. std::swap(settings::cableLabels[i], settings::cableLabels[i + 1]);
  489. }, i + 1 >= settings::cableColors.size()));
  490. menu->addChild(createMenuItem(string::translate("MenuBar.view.cableColors.delete"), "", [=]() {
  491. if (i >= settings::cableColors.size())
  492. return;
  493. settings::cableLabels.resize(settings::cableColors.size());
  494. settings::cableColors.erase(settings::cableColors.begin() + i);
  495. settings::cableLabels.erase(settings::cableLabels.begin() + i);
  496. }, settings::cableColors.size() <= 1, true));
  497. });
  498. item->color = color;
  499. menu->addChild(item);
  500. }
  501. menu->addChild(createBoolMenuItem(string::translate("MenuBar.view.cableColors.autoRotate"), "",
  502. [=]() -> bool {
  503. return settings::cableAutoRotate;
  504. },
  505. [=](bool s) {
  506. settings::cableAutoRotate = s;
  507. }
  508. ));
  509. menu->addChild(createMenuItem(string::translate("MenuBar.view.cableColors.restoreFactory"), "", [=]() {
  510. if (!osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK_CANCEL, string::translate("MenuBar.view.cableColors.overwriteFactory").c_str()))
  511. return;
  512. settings::resetCables();
  513. }, false, true));
  514. }));
  515. menu->addChild(new ui::MenuSeparator);
  516. menu->addChild(createMenuLabel(string::translate("MenuBar.view.parameters")));
  517. menu->addChild(createBoolPtrMenuItem(string::translate("MenuBar.view.lockCursor"), "", &settings::allowCursorLock));
  518. static const std::vector<std::string> knobModeLabels = {
  519. string::translate("MenuBar.view.knob.linear"),
  520. string::translate("MenuBar.view.knob.scaledLinear"),
  521. string::translate("MenuBar.view.knob.absRotary"),
  522. string::translate("MenuBar.view.knob.relRotary"),
  523. };
  524. static const std::vector<int> knobModes = {0, 2, 3};
  525. menu->addChild(createSubmenuItem(string::translate("MenuBar.view.knob"), knobModeLabels[settings::knobMode], [=](ui::Menu* menu) {
  526. for (int knobMode : knobModes) {
  527. menu->addChild(createCheckMenuItem(knobModeLabels[knobMode], "",
  528. [=]() {return settings::knobMode == knobMode;},
  529. [=]() {settings::knobMode = (settings::KnobMode) knobMode;}
  530. ));
  531. }
  532. }));
  533. menu->addChild(createBoolPtrMenuItem(string::translate("MenuBar.view.knobScroll"), "", &settings::knobScroll));
  534. KnobScrollSensitivitySlider* knobScrollSensitivitySlider = new KnobScrollSensitivitySlider;
  535. knobScrollSensitivitySlider->box.size.x = 250.0;
  536. menu->addChild(knobScrollSensitivitySlider);
  537. menu->addChild(new ui::MenuSeparator);
  538. menu->addChild(createMenuLabel(string::translate("MenuBar.view.modules")));
  539. menu->addChild(createBoolPtrMenuItem(string::translate("MenuBar.view.lockModules"), "", &settings::lockModules));
  540. menu->addChild(createBoolPtrMenuItem(string::translate("MenuBar.view.squeezeModules"), "", &settings::squeezeModules));
  541. menu->addChild(createBoolPtrMenuItem(string::translate("MenuBar.view.preferDarkPanels"), "", &settings::preferDarkPanels));
  542. }
  543. };
  544. ////////////////////
  545. // Engine
  546. ////////////////////
  547. struct SampleRateItem : ui::MenuItem {
  548. ui::Menu* createChildMenu() override {
  549. ui::Menu* menu = new ui::Menu;
  550. // Auto sample rate
  551. std::string rightText;
  552. if (settings::sampleRate == 0) {
  553. float sampleRate = APP->engine->getSampleRate();
  554. rightText += string::f("(%g kHz) ", sampleRate / 1000.f);
  555. }
  556. menu->addChild(createCheckMenuItem(string::translate("MenuBar.engine.sampleRate.auto"), rightText,
  557. [=]() {return settings::sampleRate == 0;},
  558. [=]() {settings::sampleRate = 0;}
  559. ));
  560. // Power-of-2 oversample times 44.1kHz or 48kHz
  561. for (int i = -2; i <= 4; i++) {
  562. for (int j = 0; j < 2; j++) {
  563. float oversample = std::pow(2.f, i);
  564. float sampleRate = (j == 0) ? 44100.f : 48000.f;
  565. sampleRate *= oversample;
  566. std::string text = string::f("%g kHz", sampleRate / 1000.f);
  567. std::string rightText;
  568. if (oversample > 1.f) {
  569. rightText += string::f("(%.0fx)", oversample);
  570. }
  571. else if (oversample < 1.f) {
  572. rightText += string::f("(1/%.0fx)", 1.f / oversample);
  573. }
  574. menu->addChild(createCheckMenuItem(text, rightText,
  575. [=]() {return settings::sampleRate == sampleRate;},
  576. [=]() {settings::sampleRate = sampleRate;}
  577. ));
  578. }
  579. }
  580. return menu;
  581. }
  582. };
  583. struct EngineButton : MenuButton {
  584. void onAction(const ActionEvent& e) override {
  585. ui::Menu* menu = createMenu();
  586. menu->cornerFlags = BND_CORNER_TOP;
  587. menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y));
  588. std::string cpuMeterText = widget::getKeyCommandName(GLFW_KEY_F3, 0);
  589. if (settings::cpuMeter)
  590. cpuMeterText += " " CHECKMARK_STRING;
  591. menu->addChild(createMenuItem(string::translate("MenuBar.engine.cpuMeter"), cpuMeterText, [=]() {
  592. settings::cpuMeter ^= true;
  593. }));
  594. menu->addChild(createMenuItem<SampleRateItem>(string::translate("MenuBar.engine.sampleRate"), RIGHT_ARROW));
  595. menu->addChild(createSubmenuItem(string::translate("MenuBar.engine.threads"), string::f("%d", settings::threadCount), [=](ui::Menu* menu) {
  596. // BUG This assumes SMT is enabled.
  597. int cores = system::getLogicalCoreCount() / 2;
  598. for (int i = 1; i <= 2 * cores; i++) {
  599. std::string rightText;
  600. if (i == cores)
  601. rightText += string::translate("MenuBar.engine.threads.most");
  602. else if (i == 1)
  603. rightText += string::translate("MenuBar.engine.threads.lowest");
  604. menu->addChild(createCheckMenuItem(string::f("%d", i), rightText,
  605. [=]() {return settings::threadCount == i;},
  606. [=]() {settings::threadCount = i;}
  607. ));
  608. }
  609. }));
  610. }
  611. };
  612. ////////////////////
  613. // Plugins
  614. ////////////////////
  615. struct AccountPasswordField : ui::PasswordField {
  616. ui::MenuItem* logInItem;
  617. void onAction(const ActionEvent& e) override {
  618. logInItem->doAction();
  619. }
  620. };
  621. struct LogInItem : ui::MenuItem {
  622. ui::TextField* emailField;
  623. ui::TextField* passwordField;
  624. void onAction(const ActionEvent& e) override {
  625. std::string email = emailField->text;
  626. std::string password = passwordField->text;
  627. std::thread t([=] {
  628. library::logIn(email, password);
  629. library::checkUpdates();
  630. });
  631. t.detach();
  632. e.unconsume();
  633. }
  634. void step() override {
  635. text = string::translate("MenuBar.library.login");
  636. rightText = library::loginStatus;
  637. MenuItem::step();
  638. }
  639. };
  640. struct SyncUpdatesItem : ui::MenuItem {
  641. void step() override {
  642. if (library::updateStatus != "") {
  643. text = library::updateStatus;
  644. }
  645. else if (library::isSyncing) {
  646. text = string::translate("MenuBar.library.updating");
  647. }
  648. else if (!library::hasUpdates()) {
  649. text = string::translate("MenuBar.library.upToDate");
  650. }
  651. else {
  652. text = string::translate("MenuBar.library.updateAll");
  653. }
  654. disabled = library::isSyncing || !library::hasUpdates();
  655. MenuItem::step();
  656. }
  657. void onAction(const ActionEvent& e) override {
  658. std::thread t([=] {
  659. library::syncUpdates();
  660. });
  661. t.detach();
  662. e.unconsume();
  663. }
  664. };
  665. struct SyncUpdateItem : ui::MenuItem {
  666. std::string slug;
  667. void setUpdate(const std::string& slug) {
  668. this->slug = slug;
  669. auto it = library::updateInfos.find(slug);
  670. if (it == library::updateInfos.end())
  671. return;
  672. library::UpdateInfo update = it->second;
  673. text = update.name;
  674. }
  675. ui::Menu* createChildMenu() override {
  676. auto it = library::updateInfos.find(slug);
  677. if (it == library::updateInfos.end())
  678. return NULL;
  679. library::UpdateInfo update = it->second;
  680. ui::Menu* menu = new ui::Menu;
  681. if (update.minRackVersion != "") {
  682. menu->addChild(createMenuLabel(string::f(string::translate("MenuBar.library.requiresRack"), update.minRackVersion)));
  683. }
  684. if (update.changelogUrl != "") {
  685. std::string changelogUrl = update.changelogUrl;
  686. menu->addChild(createMenuItem(string::translate("MenuBar.library.changelog"), "", [=]() {
  687. system::openBrowser(changelogUrl);
  688. }));
  689. }
  690. if (menu->children.empty()) {
  691. delete menu;
  692. return NULL;
  693. }
  694. return menu;
  695. }
  696. void step() override {
  697. disabled = false;
  698. if (library::isSyncing)
  699. disabled = true;
  700. auto it = library::updateInfos.find(slug);
  701. if (it == library::updateInfos.end()) {
  702. disabled = true;
  703. }
  704. else {
  705. library::UpdateInfo update = it->second;
  706. if (update.minRackVersion != "")
  707. disabled = true;
  708. if (update.downloaded) {
  709. rightText = CHECKMARK_STRING;
  710. disabled = true;
  711. }
  712. else if (slug == library::updateSlug) {
  713. rightText = string::f("%.0f%%", library::updateProgress * 100.f);
  714. }
  715. else {
  716. rightText = "";
  717. plugin::Plugin* p = plugin::getPlugin(slug);
  718. if (p) {
  719. rightText += p->version + " → ";
  720. }
  721. rightText += update.version;
  722. }
  723. }
  724. MenuItem::step();
  725. }
  726. void onAction(const ActionEvent& e) override {
  727. std::thread t([=] {
  728. library::syncUpdate(slug);
  729. });
  730. t.detach();
  731. e.unconsume();
  732. }
  733. };
  734. struct LibraryMenu : ui::Menu {
  735. LibraryMenu() {
  736. refresh();
  737. }
  738. void step() override {
  739. // Refresh menu when appropriate
  740. if (library::refreshRequested) {
  741. library::refreshRequested = false;
  742. refresh();
  743. }
  744. Menu::step();
  745. }
  746. void refresh() {
  747. setChildMenu(NULL);
  748. clearChildren();
  749. if (settings::devMode) {
  750. addChild(createMenuLabel(string::translate("MenuBar.library.devMode")));
  751. }
  752. else if (!library::isLoggedIn()) {
  753. addChild(createMenuItem(string::translate("MenuBar.library.register"), "", [=]() {
  754. system::openBrowser("https://vcvrack.com/login");
  755. }));
  756. ui::TextField* emailField = new ui::TextField;
  757. emailField->placeholder = string::translate("MenuBar.library.email");
  758. emailField->box.size.x = 240.0;
  759. addChild(emailField);
  760. AccountPasswordField* passwordField = new AccountPasswordField;
  761. passwordField->placeholder = string::translate("MenuBar.library.password");
  762. passwordField->box.size.x = 240.0;
  763. passwordField->nextField = emailField;
  764. emailField->nextField = passwordField;
  765. addChild(passwordField);
  766. LogInItem* logInItem = new LogInItem;
  767. logInItem->emailField = emailField;
  768. logInItem->passwordField = passwordField;
  769. passwordField->logInItem = logInItem;
  770. addChild(logInItem);
  771. }
  772. else {
  773. addChild(createMenuItem(string::translate("MenuBar.library.logOut"), "", [=]() {
  774. library::logOut();
  775. }));
  776. addChild(createMenuItem(string::translate("MenuBar.library.account"), "", [=]() {
  777. system::openBrowser("https://vcvrack.com/account");
  778. }));
  779. addChild(createMenuItem(string::translate("MenuBar.library.browse"), "", [=]() {
  780. system::openBrowser("https://library.vcvrack.com/");
  781. }));
  782. SyncUpdatesItem* syncItem = new SyncUpdatesItem;
  783. syncItem->text = string::translate("MenuBar.library.updateAll");
  784. addChild(syncItem);
  785. if (!library::updateInfos.empty()) {
  786. addChild(new ui::MenuSeparator);
  787. addChild(createMenuLabel(string::translate("MenuBar.library.updates")));
  788. for (auto& pair : library::updateInfos) {
  789. SyncUpdateItem* updateItem = new SyncUpdateItem;
  790. updateItem->setUpdate(pair.first);
  791. addChild(updateItem);
  792. }
  793. }
  794. }
  795. }
  796. };
  797. struct LibraryButton : MenuButton {
  798. NotificationIcon* notification;
  799. LibraryButton() {
  800. notification = new NotificationIcon;
  801. addChild(notification);
  802. }
  803. void onAction(const ActionEvent& e) override {
  804. ui::Menu* menu = createMenu<LibraryMenu>();
  805. menu->cornerFlags = BND_CORNER_TOP;
  806. menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y));
  807. // Check for updates when menu is opened
  808. if (!settings::devMode) {
  809. std::thread t([&]() {
  810. system::setThreadName(string::translate("MenuBar.library"));
  811. library::checkUpdates();
  812. });
  813. t.detach();
  814. }
  815. }
  816. void step() override {
  817. notification->box.pos = math::Vec(0, 0);
  818. notification->visible = library::hasUpdates();
  819. // Popup when updates finish downloading
  820. if (library::restartRequested) {
  821. library::restartRequested = false;
  822. if (osdialog_message(OSDIALOG_INFO, OSDIALOG_OK_CANCEL, string::translate("MenuBar.library.restart").c_str())) {
  823. APP->window->close();
  824. settings::restart = true;
  825. }
  826. }
  827. MenuButton::step();
  828. }
  829. };
  830. ////////////////////
  831. // Help
  832. ////////////////////
  833. struct HelpButton : MenuButton {
  834. NotificationIcon* notification;
  835. HelpButton() {
  836. notification = new NotificationIcon;
  837. addChild(notification);
  838. }
  839. void onAction(const ActionEvent& e) override {
  840. ui::Menu* menu = createMenu();
  841. menu->cornerFlags = BND_CORNER_TOP;
  842. menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y));
  843. menu->addChild(createSubmenuItem("🌐 " + string::translate("MenuBar.help.language"), "", [=](ui::Menu* menu) {
  844. appendLanguageMenu(menu);
  845. }));
  846. menu->addChild(createMenuItem(string::translate("MenuBar.help.tips"), "", [=]() {
  847. APP->scene->addChild(tipWindowCreate());
  848. }));
  849. menu->addChild(createMenuItem(string::translate("MenuBar.help.manual"), widget::getKeyCommandName(GLFW_KEY_F1, 0), [=]() {
  850. system::openBrowser("https://vcvrack.com/manual");
  851. }));
  852. menu->addChild(createMenuItem(string::translate("MenuBar.help.support"), "", [=]() {
  853. system::openBrowser("https://vcvrack.com/support");
  854. }));
  855. menu->addChild(createMenuItem("VCVRack.com", "", [=]() {
  856. system::openBrowser("https://vcvrack.com/");
  857. }));
  858. menu->addChild(new ui::MenuSeparator);
  859. menu->addChild(createMenuItem(string::translate("MenuBar.help.userFolder"), "", [=]() {
  860. system::openDirectory(asset::user(""));
  861. }));
  862. menu->addChild(createMenuItem(string::translate("MenuBar.help.changelog"), "", [=]() {
  863. system::openBrowser("https://github.com/VCVRack/Rack/blob/v2/CHANGELOG.md");
  864. }));
  865. if (library::isAppUpdateAvailable()) {
  866. menu->addChild(createMenuItem(string::f(string::translate("MenuBar.help.update"), APP_NAME), APP_VERSION + " → " + library::appVersion, [=]() {
  867. system::openBrowser(library::appDownloadUrl);
  868. }));
  869. }
  870. else if (!settings::autoCheckUpdates && !settings::devMode) {
  871. menu->addChild(createMenuItem(string::f(string::translate("MenuBar.help.checkUpdate"), APP_NAME), "", [=]() {
  872. std::thread t([&]() {
  873. library::checkAppUpdate();
  874. });
  875. t.detach();
  876. }, false, true));
  877. }
  878. }
  879. void step() override {
  880. notification->box.pos = math::Vec(0, 0);
  881. notification->visible = library::isAppUpdateAvailable();
  882. MenuButton::step();
  883. }
  884. };
  885. ////////////////////
  886. // MenuBar
  887. ////////////////////
  888. struct InfoLabel : ui::Label {
  889. int frameCount = 0;
  890. double frameDurationTotal = 0.0;
  891. double frameDurationAvg = NAN;
  892. // double uiLastTime = 0.0;
  893. // double uiLastThreadTime = 0.0;
  894. // double uiFrac = 0.0;
  895. void step() override {
  896. // Compute frame rate
  897. double frameDuration = APP->window->getLastFrameDuration();
  898. if (std::isfinite(frameDuration)) {
  899. frameDurationTotal += frameDuration;
  900. frameCount++;
  901. }
  902. if (frameDurationTotal >= 1.0) {
  903. frameDurationAvg = frameDurationTotal / frameCount;
  904. frameDurationTotal = 0.0;
  905. frameCount = 0;
  906. }
  907. // Compute UI thread CPU
  908. // double time = system::getTime();
  909. // double uiDuration = time - uiLastTime;
  910. // if (uiDuration >= 1.0) {
  911. // double threadTime = system::getThreadTime();
  912. // uiFrac = (threadTime - uiLastThreadTime) / uiDuration;
  913. // uiLastThreadTime = threadTime;
  914. // uiLastTime = time;
  915. // }
  916. text = "";
  917. if (box.size.x >= 460) {
  918. double fps = std::isfinite(frameDurationAvg) ? 1.0 / frameDurationAvg : 0.0;
  919. double meterAverage = APP->engine->getMeterAverage();
  920. double meterMax = APP->engine->getMeterMax();
  921. text += string::f(string::translate("MenuBar.infoLabel"), fps, meterAverage * 100, meterMax * 100);
  922. text += " ";
  923. }
  924. text += APP_NAME + " " + APP_EDITION_NAME + " " + APP_VERSION + " " + APP_OS_NAME + " " + APP_CPU_NAME;
  925. Label::step();
  926. }
  927. };
  928. struct MenuBar : widget::OpaqueWidget {
  929. InfoLabel* infoLabel;
  930. MenuBar() {
  931. const float margin = 5;
  932. box.size.y = BND_WIDGET_HEIGHT + 2 * margin;
  933. ui::SequentialLayout* layout = new ui::SequentialLayout;
  934. layout->margin = math::Vec(margin, margin);
  935. layout->spacing = math::Vec(0, 0);
  936. addChild(layout);
  937. FileButton* fileButton = new FileButton;
  938. fileButton->text = string::translate("MenuBar.file");
  939. layout->addChild(fileButton);
  940. EditButton* editButton = new EditButton;
  941. editButton->text = string::translate("MenuBar.edit");
  942. layout->addChild(editButton);
  943. ViewButton* viewButton = new ViewButton;
  944. viewButton->text = string::translate("MenuBar.view");
  945. layout->addChild(viewButton);
  946. EngineButton* engineButton = new EngineButton;
  947. engineButton->text = string::translate("MenuBar.engine");
  948. layout->addChild(engineButton);
  949. LibraryButton* libraryButton = new LibraryButton;
  950. libraryButton->text = string::translate("MenuBar.library");
  951. layout->addChild(libraryButton);
  952. HelpButton* helpButton = new HelpButton;
  953. helpButton->text = string::translate("MenuBar.help");
  954. layout->addChild(helpButton);
  955. infoLabel = new InfoLabel;
  956. infoLabel->box.size.x = 600;
  957. infoLabel->alignment = ui::Label::RIGHT_ALIGNMENT;
  958. layout->addChild(infoLabel);
  959. }
  960. void draw(const DrawArgs& args) override {
  961. bndMenuBackground(args.vg, 0.0, 0.0, box.size.x, box.size.y, BND_CORNER_ALL);
  962. bndBevel(args.vg, 0.0, 0.0, box.size.x, box.size.y);
  963. Widget::draw(args);
  964. }
  965. void step() override {
  966. Widget::step();
  967. infoLabel->box.size.x = box.size.x - infoLabel->box.pos.x - 5;
  968. // Setting 50% alpha prevents Label from using the default UI theme color, so set the color manually here.
  969. infoLabel->color = color::alpha(bndGetTheme()->regularTheme.textColor, 0.5);
  970. }
  971. };
  972. } // namespace menuBar
  973. widget::Widget* createMenuBar() {
  974. menuBar::MenuBar* menuBar = new menuBar::MenuBar;
  975. return menuBar;
  976. }
  977. void appendLanguageMenu(ui::Menu* menu) {
  978. for (const std::string& language : string::getLanguages()) {
  979. menu->addChild(createCheckMenuItem(string::translate("language", language), "", [=]() {
  980. return settings::language == language;
  981. }, [=]() {
  982. if (settings::language == language)
  983. return;
  984. settings::language = language;
  985. // Request restart
  986. std::string msg = string::f(string::translate("MenuBar.help.language.restart"), string::translate("language"));
  987. if (osdialog_message(OSDIALOG_INFO, OSDIALOG_OK_CANCEL, msg.c_str())) {
  988. APP->window->close();
  989. settings::restart = true;
  990. }
  991. }));
  992. }
  993. }
  994. } // namespace app
  995. } // namespace rack