--- ../Rack/src/app/MenuBar.cpp 2022-01-15 14:44:46.391280963 +0000 +++ MenuBar.cpp 2022-01-24 11:25:15.507061204 +0000 @@ -1,8 +1,33 @@ +/* + * DISTRHO Cardinal Plugin + * Copyright (C) 2021-2022 Filipe Coelho + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 3 of + * the License, or any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * For a full copy of the GNU General Public License see the LICENSE file. + */ + +/** + * This file is an edited version of VCVRack's app/MenuBar.cpp + * Copyright (C) 2016-2021 VCV. + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 3 of + * the License, or (at your option) any later version. + */ + #include #include -#include - #include #include #include @@ -25,6 +50,11 @@ #include #include +#ifdef HAVE_LIBLO +# include +#endif + +#include "../CardinalCommon.hpp" namespace rack { namespace app { @@ -48,79 +78,75 @@ }; -struct NotificationIcon : widget::Widget { - void draw(const DrawArgs& args) override { - nvgBeginPath(args.vg); - float radius = 4; - nvgCircle(args.vg, radius, radius, radius); - nvgFillColor(args.vg, nvgRGBf(1.0, 0.0, 0.0)); - nvgFill(args.vg); - nvgStrokeColor(args.vg, nvgRGBf(0.5, 0.0, 0.0)); - nvgStroke(args.vg); - } -}; - - //////////////////// // File //////////////////// struct FileButton : MenuButton { + const bool isStandalone; + + FileButton(const bool standalone) + : MenuButton(), isStandalone(standalone) {} + void onAction(const ActionEvent& e) override { ui::Menu* menu = createMenu(); menu->cornerFlags = BND_CORNER_TOP; menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y)); menu->addChild(createMenuItem("New", RACK_MOD_CTRL_NAME "+N", []() { - APP->patch->loadTemplateDialog(); + patchUtils::loadTemplateDialog(); })); - menu->addChild(createMenuItem("Open", RACK_MOD_CTRL_NAME "+O", []() { - APP->patch->loadDialog(); + menu->addChild(createMenuItem("Open / Import...", RACK_MOD_CTRL_NAME "+O", []() { + patchUtils::loadDialog(); })); - menu->addChild(createSubmenuItem("Open recent", "", [](ui::Menu* menu) { - for (const std::string& path : settings::recentPatchPaths) { - std::string name = system::getStem(path); - menu->addChild(createMenuItem(name, "", [=]() { - APP->patch->loadPathDialog(path); - })); - } - }, settings::recentPatchPaths.empty())); - menu->addChild(createMenuItem("Save", RACK_MOD_CTRL_NAME "+S", []() { - APP->patch->saveDialog(); + // NOTE: will do nothing if path is empty, intentionally + patchUtils::saveDialog(APP->patch->path); + }, APP->patch->path.empty())); + + menu->addChild(createMenuItem("Save as / Export...", RACK_MOD_CTRL_NAME "+Shift+S", []() { + patchUtils::saveAsDialog(); })); - menu->addChild(createMenuItem("Save as", RACK_MOD_CTRL_NAME "+Shift+S", []() { - APP->patch->saveAsDialog(); - })); +#ifdef HAVE_LIBLO + if (patchUtils::isRemoteConnected()) { + menu->addChild(createMenuItem("Deploy to MOD", "F7", []() { + patchUtils::deployToRemote(); + })); - menu->addChild(createMenuItem("Save a copy", "", []() { - APP->patch->saveAsDialog(false); - })); + const bool autoDeploy = patchUtils::isRemoteAutoDeployed(); + menu->addChild(createCheckMenuItem("Auto deploy to MOD", "", + [=]() {return autoDeploy;}, + [=]() {patchUtils::setRemoteAutoDeploy(!autoDeploy);} + )); + } else { + menu->addChild(createMenuItem("Connect to MOD", "", [this]() { + patchUtils::connectToRemote(); + })); + } +#endif menu->addChild(createMenuItem("Revert", RACK_MOD_CTRL_NAME "+" RACK_MOD_SHIFT_NAME "+O", []() { - APP->patch->revertDialog(); - }, APP->patch->path == "")); - - menu->addChild(createMenuItem("Overwrite template", "", []() { - APP->patch->saveTemplateDialog(); - })); + patchUtils::revertDialog(); + }, APP->patch->path.empty())); menu->addChild(new ui::MenuSeparator); // Load selection menu->addChild(createMenuItem("Import selection", "", [=]() { - APP->scene->rack->loadSelectionDialog(); + patchUtils::loadSelectionDialog(); }, false, true)); - menu->addChild(new ui::MenuSeparator); + if (isStandalone) { + menu->addChild(new ui::MenuSeparator); - menu->addChild(createMenuItem("Quit", RACK_MOD_CTRL_NAME "+Q", []() { - APP->window->close(); - })); + menu->addChild(createMenuItem("Quit", RACK_MOD_CTRL_NAME "+Q", []() { + APP->window->close(); + })); + }; } }; @@ -166,7 +192,7 @@ menu->addChild(new ui::MenuSeparator); - APP->scene->rack->appendSelectionContextMenu(menu); + patchUtils::appendSelectionContextMenu(menu); } }; @@ -256,7 +282,7 @@ return settings::cableTension; } float getDefaultValue() override { - return 0.5; + return 0.75; } float getDisplayValue() override { return getValue() * 100; @@ -421,28 +447,9 @@ haloBrightnessSlider->box.size.x = 250.0; menu->addChild(haloBrightnessSlider); - double frameRate = APP->window->getMonitorRefreshRate() / settings::frameSwapInterval; - menu->addChild(createSubmenuItem("Frame rate", string::f("%.0f Hz", frameRate), [=](ui::Menu* menu) { - for (int i = 1; i <= 6; i++) { - double frameRate = APP->window->getMonitorRefreshRate() / i; - menu->addChild(createCheckMenuItem(string::f("%.0f Hz", frameRate), "", - [=]() {return settings::frameSwapInterval == i;}, - [=]() {settings::frameSwapInterval = i;} - )); - } - })); - - bool fullscreen = APP->window->isFullScreen(); - std::string fullscreenText = "F11"; - if (fullscreen) - fullscreenText += " " CHECKMARK_STRING; - menu->addChild(createMenuItem("Fullscreen", fullscreenText, [=]() { - APP->window->setFullScreen(!fullscreen); - })); - menu->addChild(new ui::MenuSeparator); - menu->addChild(createBoolPtrMenuItem("Lock cursor while dragging params", "", &settings::allowCursorLock)); + // menu->addChild(createBoolPtrMenuItem("Hide cursor while dragging", "", &settings::allowCursorLock)); static const std::vector knobModeLabels = { "Linear", @@ -467,6 +474,21 @@ menu->addChild(knobScrollSensitivitySlider); menu->addChild(createBoolPtrMenuItem("Lock module positions", "", &settings::lockModules)); + + static const std::vector rateLimitLabels = { + "None", + "2x", + "4x", + }; + static const std::vector rateLimits = {0, 1, 2}; + menu->addChild(createSubmenuItem("Update rate limit", rateLimitLabels[settings::rateLimit], [=](ui::Menu* menu) { + for (int rateLimit : rateLimits) { + menu->addChild(createCheckMenuItem(rateLimitLabels[rateLimit], "", + [=]() {return settings::rateLimit == rateLimit;}, + [=]() {settings::rateLimit = rateLimit;} + )); + } + })); } }; @@ -476,47 +498,6 @@ //////////////////// -struct SampleRateItem : ui::MenuItem { - ui::Menu* createChildMenu() override { - ui::Menu* menu = new ui::Menu; - - // Auto sample rate - std::string rightText; - if (settings::sampleRate == 0) { - float sampleRate = APP->engine->getSampleRate(); - rightText += string::f("(%g kHz) ", sampleRate / 1000.f); - } - menu->addChild(createCheckMenuItem("Auto", rightText, - [=]() {return settings::sampleRate == 0;}, - [=]() {settings::sampleRate = 0;} - )); - - // Power-of-2 oversample times 44.1kHz or 48kHz - for (int i = -2; i <= 4; i++) { - for (int j = 0; j < 2; j++) { - float oversample = std::pow(2.f, i); - float sampleRate = (j == 0) ? 44100.f : 48000.f; - sampleRate *= oversample; - - std::string text = string::f("%g kHz", sampleRate / 1000.f); - std::string rightText; - if (oversample > 1.f) { - rightText += string::f("(%.0fx)", oversample); - } - else if (oversample < 1.f) { - rightText += string::f("(1/%.0fx)", 1.f / oversample); - } - menu->addChild(createCheckMenuItem(text, rightText, - [=]() {return settings::sampleRate == sampleRate;}, - [=]() {settings::sampleRate = sampleRate;} - )); - } - } - return menu; - } -}; - - struct EngineButton : MenuButton { void onAction(const ActionEvent& e) override { ui::Menu* menu = createMenu(); @@ -529,269 +510,6 @@ menu->addChild(createMenuItem("Performance meters", cpuMeterText, [=]() { settings::cpuMeter ^= true; })); - - menu->addChild(createMenuItem("Sample rate", RIGHT_ARROW)); - - menu->addChild(createSubmenuItem("Threads", string::f("%d", settings::threadCount), [=](ui::Menu* menu) { - // BUG This assumes SMT is enabled. - int cores = system::getLogicalCoreCount() / 2; - - for (int i = 1; i <= 2 * cores; i++) { - std::string rightText; - if (i == cores) - rightText += "(most modules)"; - else if (i == 1) - rightText += "(lowest CPU usage)"; - menu->addChild(createCheckMenuItem(string::f("%d", i), rightText, - [=]() {return settings::threadCount == i;}, - [=]() {settings::threadCount = i;} - )); - } - })); - } -}; - - -//////////////////// -// Plugins -//////////////////// - - -struct AccountPasswordField : ui::PasswordField { - ui::MenuItem* logInItem; - void onAction(const ActionEvent& e) override { - logInItem->doAction(); - } -}; - - -struct LogInItem : ui::MenuItem { - ui::TextField* emailField; - ui::TextField* passwordField; - - void onAction(const ActionEvent& e) override { - std::string email = emailField->text; - std::string password = passwordField->text; - std::thread t([=] { - library::logIn(email, password); - library::checkUpdates(); - }); - t.detach(); - e.unconsume(); - } - - void step() override { - text = "Log in"; - rightText = library::loginStatus; - MenuItem::step(); - } -}; - - -struct SyncUpdatesItem : ui::MenuItem { - void step() override { - if (library::updateStatus != "") { - text = library::updateStatus; - } - else if (library::isSyncing) { - text = "Updating..."; - } - else if (!library::hasUpdates()) { - text = "Up-to-date"; - } - else { - text = "Update all"; - } - - disabled = library::isSyncing || !library::hasUpdates(); - MenuItem::step(); - } - - void onAction(const ActionEvent& e) override { - std::thread t([=] { - library::syncUpdates(); - }); - t.detach(); - e.unconsume(); - } -}; - - -struct SyncUpdateItem : ui::MenuItem { - std::string slug; - - void setUpdate(const std::string& slug) { - this->slug = slug; - - auto it = library::updateInfos.find(slug); - if (it == library::updateInfos.end()) - return; - library::UpdateInfo update = it->second; - - text = update.name; - } - - ui::Menu* createChildMenu() override { - auto it = library::updateInfos.find(slug); - if (it == library::updateInfos.end()) - return NULL; - library::UpdateInfo update = it->second; - - if (update.changelogUrl == "") - return NULL; - - ui::Menu* menu = new ui::Menu; - - std::string changelogUrl = update.changelogUrl; - menu->addChild(createMenuItem("Changelog", "", [=]() { - system::openBrowser(changelogUrl); - })); - - return menu; - } - - void step() override { - disabled = library::isSyncing; - - auto it = library::updateInfos.find(slug); - if (it != library::updateInfos.end()) { - library::UpdateInfo update = it->second; - - if (update.downloaded) { - rightText = CHECKMARK_STRING; - disabled = true; - } - else if (slug == library::updateSlug) { - rightText = string::f("%.0f%%", library::updateProgress * 100.f); - } - else { - rightText = ""; - plugin::Plugin* p = plugin::getPlugin(slug); - if (p) { - rightText += p->version + " → "; - } - rightText += update.version; - } - } - - MenuItem::step(); - } - - void onAction(const ActionEvent& e) override { - std::thread t([=] { - library::syncUpdate(slug); - }); - t.detach(); - e.unconsume(); - } -}; - - -struct LibraryMenu : ui::Menu { - LibraryMenu() { - refresh(); - } - - void step() override { - // Refresh menu when appropriate - if (library::refreshRequested) { - library::refreshRequested = false; - refresh(); - } - Menu::step(); - } - - void refresh() { - setChildMenu(NULL); - clearChildren(); - - if (settings::devMode) { - addChild(createMenuLabel("Disabled in development mode")); - } - else if (!library::isLoggedIn()) { - addChild(createMenuItem("Register VCV account", "", [=]() { - system::openBrowser("https://vcvrack.com/login"); - })); - - ui::TextField* emailField = new ui::TextField; - emailField->placeholder = "Email"; - emailField->box.size.x = 240.0; - addChild(emailField); - - AccountPasswordField* passwordField = new AccountPasswordField; - passwordField->placeholder = "Password"; - passwordField->box.size.x = 240.0; - passwordField->nextField = emailField; - emailField->nextField = passwordField; - addChild(passwordField); - - LogInItem* logInItem = new LogInItem; - logInItem->emailField = emailField; - logInItem->passwordField = passwordField; - passwordField->logInItem = logInItem; - addChild(logInItem); - } - else { - addChild(createMenuItem("Log out", "", [=]() { - library::logOut(); - })); - - addChild(createMenuItem("Browse VCV Library", "", [=]() { - system::openBrowser("https://library.vcvrack.com/"); - })); - - SyncUpdatesItem* syncItem = new SyncUpdatesItem; - syncItem->text = "Update all"; - addChild(syncItem); - - if (!library::updateInfos.empty()) { - addChild(new ui::MenuSeparator); - addChild(createMenuLabel("Updates")); - - for (auto& pair : library::updateInfos) { - SyncUpdateItem* updateItem = new SyncUpdateItem; - updateItem->setUpdate(pair.first); - addChild(updateItem); - } - } - } - } -}; - - -struct LibraryButton : MenuButton { - NotificationIcon* notification; - - LibraryButton() { - notification = new NotificationIcon; - addChild(notification); - } - - void onAction(const ActionEvent& e) override { - ui::Menu* menu = createMenu(); - menu->cornerFlags = BND_CORNER_TOP; - menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y)); - // Check for updates when menu is opened - std::thread t([&]() { - system::setThreadName("Library"); - library::checkUpdates(); - }); - t.detach(); - } - - void step() override { - notification->box.pos = math::Vec(0, 0); - notification->visible = library::hasUpdates(); - - // Popup when updates finish downloading - if (library::restartRequested) { - library::restartRequested = false; - if (osdialog_message(OSDIALOG_INFO, OSDIALOG_OK_CANCEL, "All plugins have been downloaded. Close and re-launch Rack to load new updates.")) { - APP->window->close(); - } - } - - MenuButton::step(); } }; @@ -802,63 +520,24 @@ struct HelpButton : MenuButton { - NotificationIcon* notification; - - HelpButton() { - notification = new NotificationIcon; - addChild(notification); - } - void onAction(const ActionEvent& e) override { ui::Menu* menu = createMenu(); menu->cornerFlags = BND_CORNER_TOP; menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y)); - menu->addChild(createMenuItem("Tips", "", [=]() { - APP->scene->addChild(tipWindowCreate()); - })); - - menu->addChild(createMenuItem("User manual", "F1", [=]() { + menu->addChild(createMenuItem("Rack User manual", "F1", [=]() { system::openBrowser("https://vcvrack.com/manual/"); })); - menu->addChild(createMenuItem("VCVRack.com", "", [=]() { - system::openBrowser("https://vcvrack.com/"); + menu->addChild(createMenuItem("Cardinal Project page", "", [=]() { + system::openBrowser("https://github.com/DISTRHO/Cardinal/"); })); - menu->addChild(createMenuItem("Open user folder", "", [=]() { - system::openDirectory(asset::user("")); - })); - - if (library::isAppUpdateAvailable()) { - menu->addChild(new ui::MenuSeparator); - - menu->addChild(createMenuItem("Update " + APP_NAME, APP_VERSION + " → " + library::appVersion, [=]() { - system::openBrowser(library::appDownloadUrl); - })); - - menu->addChild(createMenuItem("Review changelog", "", [=]() { - system::openBrowser(library::appChangelogUrl); - })); - } - else if (!settings::autoCheckUpdates && !settings::devMode) { - menu->addChild(createMenuItem("Check for " + APP_NAME + " update", "", [=]() { - std::thread t([&]() { - library::checkAppUpdate(); - }); - t.detach(); - }, false, true)); - } - menu->addChild(new ui::MenuSeparator); - menu->addChild(createMenuLabel(APP_NAME + " " + APP_EDITION_NAME + " " + APP_VERSION)); - } + menu->addChild(createMenuLabel(APP_EDITION + " " + APP_EDITION_NAME)); - void step() override { - notification->box.pos = math::Vec(0, 0); - notification->visible = library::isAppUpdateAvailable(); - MenuButton::step(); + menu->addChild(createMenuLabel("Rack " + APP_VERSION + " Compatible")); } }; @@ -908,7 +587,9 @@ struct MenuBar : widget::OpaqueWidget { MeterLabel* meterLabel; - MenuBar() { + MenuBar(const bool isStandalone) + : widget::OpaqueWidget() + { const float margin = 5; box.size.y = BND_WIDGET_HEIGHT + 2 * margin; @@ -917,7 +598,7 @@ layout->spacing = math::Vec(0, 0); addChild(layout); - FileButton* fileButton = new FileButton; + FileButton* fileButton = new FileButton(isStandalone); fileButton->text = "File"; layout->addChild(fileButton); @@ -933,10 +614,6 @@ engineButton->text = "Engine"; layout->addChild(engineButton); - LibraryButton* libraryButton = new LibraryButton; - libraryButton->text = "Library"; - layout->addChild(libraryButton); - HelpButton* helpButton = new HelpButton; helpButton->text = "Help"; layout->addChild(helpButton); @@ -971,7 +648,11 @@ widget::Widget* createMenuBar() { - menuBar::MenuBar* menuBar = new menuBar::MenuBar; + return new widget::Widget; +} + +widget::Widget* createMenuBar(const bool isStandalone) { + menuBar::MenuBar* menuBar = new menuBar::MenuBar(isStandalone); return menuBar; }