diff --git a/include/app/MenuBar.hpp b/include/app/MenuBar.hpp index 6949d57a..5b4e7b1f 100644 --- a/include/app/MenuBar.hpp +++ b/include/app/MenuBar.hpp @@ -8,10 +8,12 @@ namespace app { struct MenuBar : widget::OpaqueWidget { - MenuBar(); void draw(const DrawArgs &args) override; }; +MenuBar *createMenuBar(); + + } // namespace app } // namespace rack diff --git a/include/app/common.hpp b/include/app/common.hpp index 3695a86b..d6482850 100644 --- a/include/app/common.hpp +++ b/include/app/common.hpp @@ -12,9 +12,10 @@ namespace rack { namespace app { -extern const char APP_NAME[]; -extern const char APP_VERSION[]; -extern const char API_URL[]; +extern std::string APP_NAME; +extern std::string APP_VERSION; +extern std::string APP_NEW_VERSION; +extern std::string API_URL; static const float SVG_DPI = 75.0; static const float MM_PER_IN = 25.4; @@ -47,5 +48,8 @@ static const math::Vec RACK_OFFSET = RACK_GRID_SIZE.mult(math::Vec(2000, 100)); static const math::Vec BUS_BOARD_GRID_SIZE = math::Vec(RACK_GRID_WIDTH * 20, RACK_GRID_HEIGHT); +void init(); + + } // namespace app } // namespace rack diff --git a/include/settings.hpp b/include/settings.hpp index 0183b1a3..ec6ca2c2 100644 --- a/include/settings.hpp +++ b/include/settings.hpp @@ -34,7 +34,6 @@ extern int threadCount; extern bool paramTooltip; extern bool cpuMeter; extern bool lockModules; -extern bool checkVersion; extern float frameRateLimit; extern bool frameRateSync; extern bool skipLoadOnLaunch; diff --git a/src/app/MenuBar.cpp b/src/app/MenuBar.cpp index 4762cf60..c193d900 100644 --- a/src/app/MenuBar.cpp +++ b/src/app/MenuBar.cpp @@ -34,6 +34,29 @@ struct MenuButton : ui::Button { if (APP->event->draggedWidget == this) state = BND_ACTIVE; bndMenuItem(args.vg, 0.0, 0.0, box.size.x, box.size.y, state, -1, text.c_str()); + Widget::draw(args); + } +}; + +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); + } +}; + +struct UrlItem : ui::MenuItem { + std::string url; + void onAction(const event::Action &e) override { + std::thread t([=]() { + system::openBrowser(url); + }); + t.detach(); } }; @@ -421,15 +444,6 @@ struct EngineButton : MenuButton { static bool isLoggingIn = false; -struct RegisterItem : ui::MenuItem { - void onAction(const event::Action &e) override { - std::thread t([]() { - system::openBrowser("https://vcvrack.com/"); - }); - t.detach(); - } -}; - struct AccountEmailField : ui::TextField { ui::TextField *passwordField; void onSelectKey(const event::SelectKey &e) override { @@ -478,15 +492,6 @@ struct LogInItem : ui::MenuItem { } }; -struct ManageItem : ui::MenuItem { - void onAction(const event::Action &e) override { - std::thread t([]() { - system::openBrowser("https://vcvrack.com/plugins.html"); - }); - t.detach(); - } -}; - struct SyncItem : ui::MenuItem { void onAction(const event::Action &e) override { std::thread t([=]() { @@ -496,16 +501,6 @@ struct SyncItem : ui::MenuItem { } }; -struct UpdateItem : ui::MenuItem { - std::string changelogUrl; - void onAction(const event::Action &e) override { - std::thread t([=]() { - system::openBrowser(changelogUrl); - }); - t.detach(); - } -}; - #if 0 struct SyncButton : ui::Button { bool checked = false; @@ -589,8 +584,9 @@ struct PluginsMenu : ui::Menu { } if (plugin::isLoggedIn()) { - ManageItem *manageItem = new ManageItem; + UrlItem *manageItem = new UrlItem; manageItem->text = "Manage"; + manageItem->url = "https://vcvrack.com/plugins.html"; addChild(manageItem); LogOutItem *logOutItem = new LogOutItem; @@ -610,22 +606,23 @@ struct PluginsMenu : ui::Menu { addChild(updatesLabel); for (const plugin::Update &update : plugin::updates) { - UpdateItem *updateItem = new UpdateItem; + UrlItem *updateItem = new UrlItem; updateItem->text = update.pluginSlug; plugin::Plugin *p = plugin::getPlugin(update.pluginSlug); if (p) { updateItem->rightText += "v" + p->version + " → "; } updateItem->rightText += "v" + update.version; - updateItem->changelogUrl = update.changelogUrl; + updateItem->url = update.changelogUrl; updateItem->disabled = update.changelogUrl.empty(); addChild(updateItem); } } } else { - RegisterItem *registerItem = new RegisterItem; + UrlItem *registerItem = new UrlItem; registerItem->text = "Register VCV account"; + registerItem->url = "https://vcvrack.com/"; addChild(registerItem); AccountEmailField *emailField = new AccountEmailField; @@ -649,24 +646,23 @@ struct PluginsMenu : ui::Menu { }; struct PluginsButton : MenuButton { + NotificationIcon *notification; + + PluginsButton() { + notification = new NotificationIcon; + addChild(notification); + } + void onAction(const event::Action &e) override { ui::Menu *menu = createMenu(); menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y)); menu->box.size.x = box.size.x; } - void draw(const DrawArgs &args) override { - MenuButton::draw(args); - - if (0) { - // Notification circle - nvgBeginPath(args.vg); - nvgCircle(args.vg, 4, 2, 4.0); - 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); - } + void step() override { + notification->box.pos = math::Vec(0, 0); + notification->visible = false; + MenuButton::step(); } }; @@ -674,26 +670,6 @@ struct PluginsButton : MenuButton { // Help //////////////////// -struct ManualItem : ui::MenuItem { - void onAction(const event::Action &e) override { - std::thread t(system::openBrowser, "https://vcvrack.com/manual/"); - t.detach(); - } -}; - -struct WebsiteItem : ui::MenuItem { - void onAction(const event::Action &e) override { - std::thread t(system::openBrowser, "https://vcvrack.com/"); - t.detach(); - } -}; - -struct CheckVersionItem : ui::MenuItem { - void onAction(const event::Action &e) override { - settings::checkVersion ^= true; - } -}; - struct UserFolderItem : ui::MenuItem { void onAction(const event::Action &e) override { std::thread t(system::openFolder, asset::user("")); @@ -702,43 +678,75 @@ struct UserFolderItem : ui::MenuItem { }; struct HelpButton : MenuButton { + NotificationIcon *notification; + + HelpButton() { + notification = new NotificationIcon; + addChild(notification); + } + void onAction(const event::Action &e) override { ui::Menu *menu = createMenu(); menu->box.pos = getAbsoluteOffset(math::Vec(0, box.size.y)); menu->box.size.x = box.size.x; - ManualItem *manualItem = new ManualItem; + UrlItem *manualItem = new UrlItem; manualItem->text = "Manual"; manualItem->rightText = "F1"; + manualItem->url = "https://vcvrack.com/manual/"; menu->addChild(manualItem); - WebsiteItem *websiteItem = new WebsiteItem; + UrlItem *websiteItem = new UrlItem; websiteItem->text = "VCVRack.com"; + websiteItem->url = "https://vcvrack.com/"; menu->addChild(websiteItem); - CheckVersionItem *checkVersionItem = new CheckVersionItem; - checkVersionItem->text = "Check version on launch"; - checkVersionItem->rightText = CHECKMARK(settings::checkVersion); - menu->addChild(checkVersionItem); + if (hasUpdate()) { + UrlItem *updateItem = new UrlItem; + updateItem->text = "Update " + APP_NAME; + updateItem->rightText = APP_VERSION + " → " + APP_NEW_VERSION; + updateItem->url = "https://vcvrack.com/"; + menu->addChild(updateItem); + } UserFolderItem *folderItem = new UserFolderItem; folderItem->text = "Open user folder"; menu->addChild(folderItem); } + + void step() override { + notification->box.pos = math::Vec(0, 0); + notification->visible = hasUpdate(); + MenuButton::step(); + } + + bool hasUpdate() { + return !APP_NEW_VERSION.empty() && APP_NEW_VERSION != APP_VERSION; + } }; //////////////////// // MenuBar //////////////////// -MenuBar::MenuBar() { +void MenuBar::draw(const DrawArgs &args) { + bndMenuBackground(args.vg, 0.0, 0.0, box.size.x, box.size.y, BND_CORNER_ALL); + bndBevel(args.vg, 0.0, 0.0, box.size.x, box.size.y); + + Widget::draw(args); +} + + +MenuBar *createMenuBar() { + MenuBar *menuBar = new MenuBar; + const float margin = 5; - box.size.y = BND_WIDGET_HEIGHT + 2*margin; + menuBar->box.size.y = BND_WIDGET_HEIGHT + 2*margin; ui::SequentialLayout *layout = new ui::SequentialLayout; layout->box.pos = math::Vec(margin, margin); layout->spacing = math::Vec(0, 0); - addChild(layout); + menuBar->addChild(layout); FileButton *fileButton = new FileButton; fileButton->text = "File"; @@ -763,13 +771,8 @@ MenuBar::MenuBar() { HelpButton *helpButton = new HelpButton; helpButton->text = "Help"; layout->addChild(helpButton); -} -void MenuBar::draw(const DrawArgs &args) { - bndMenuBackground(args.vg, 0.0, 0.0, box.size.x, box.size.y, BND_CORNER_ALL); - bndBevel(args.vg, 0.0, 0.0, box.size.x, box.size.y); - - Widget::draw(args); + return menuBar; } diff --git a/src/app/Scene.cpp b/src/app/Scene.cpp index 4c5543a2..e24c0deb 100644 --- a/src/app/Scene.cpp +++ b/src/app/Scene.cpp @@ -21,7 +21,7 @@ Scene::Scene() { rack = rackScroll->rackWidget; - menuBar = new MenuBar; + menuBar = createMenuBar(); addChild(menuBar); rackScroll->box.pos.y = menuBar->box.size.y; @@ -46,24 +46,6 @@ void Scene::step() { settings::save(asset::user("settings.json")); } - // Request latest version from server - if (!settings::devMode && checkVersion && !checkedVersion) { - std::thread t(&Scene::runCheckVersion, this); - t.detach(); - checkedVersion = true; - } - - // Version popup message - if (!latestVersion.empty()) { - std::string versionMessage = string::f("Rack v%s is available.\n\nYou have Rack v%s.\n\nClose Rack and download new version on the website?", latestVersion.c_str(), app::APP_VERSION); - if (osdialog_message(OSDIALOG_INFO, OSDIALOG_OK_CANCEL, versionMessage.c_str())) { - std::thread t(system::openBrowser, "https://vcvrack.com/"); - t.detach(); - APP->window->close(); - } - latestVersion = ""; - } - Widget::step(); } @@ -166,23 +148,6 @@ void Scene::onPathDrop(const event::PathDrop &e) { } } -void Scene::runCheckVersion() { - std::string versionUrl = app::API_URL; - versionUrl += "/version"; - json_t *versionResJ = network::requestJson(network::METHOD_GET, versionUrl, NULL); - - if (versionResJ) { - json_t *versionJ = json_object_get(versionResJ, "version"); - if (versionJ) { - std::string version = json_string_value(versionJ); - if (version != app::APP_VERSION) { - latestVersion = version; - } - } - json_decref(versionResJ); - } -} - } // namespace app } // namespace rack diff --git a/src/app/common.cpp b/src/app/common.cpp index e56db1ee..2f23cd10 100644 --- a/src/app/common.cpp +++ b/src/app/common.cpp @@ -1,14 +1,43 @@ #include "app/common.hpp" +#include "settings.hpp" +#include "network.hpp" +#include namespace rack { namespace app { -const char APP_NAME[] = "VCV Rack"; -const char APP_VERSION[] = TOSTRING(VERSION); -const char API_URL[] = "https://api.vcvrack.com"; +std::string APP_NAME = "VCV Rack"; +std::string APP_VERSION = TOSTRING(VERSION); +std::string APP_NEW_VERSION; +std::string API_URL = "https://api.vcvrack.com"; +static void checkVersion() { + std::string versionUrl = app::API_URL + "/version2"; + json_t *versionResJ = network::requestJson(network::METHOD_GET, versionUrl, NULL); + if (!versionResJ) { + WARN("Request for version failed"); + return; + } + DEFER({ + json_decref(versionResJ); + }); + + json_t *versionJ = json_object_get(versionResJ, "version"); + if (versionJ) + APP_NEW_VERSION = json_string_value(versionJ); +} + +void init() { + // TODO + if (!settings::devMode || 1) { + std::thread t([] { + checkVersion(); + }); + t.detach(); + } +} } // namespace app diff --git a/src/main.cpp b/src/main.cpp index f02e2e34..d1e2e38b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -48,7 +48,7 @@ int main(int argc, char *argv[]) { #if defined ARCH_WIN // Windows global mutex to prevent multiple instances // Handle will be closed by Windows when the process ends - HANDLE instanceMutex = CreateMutexA(NULL, true, app::APP_NAME); + HANDLE instanceMutex = CreateMutexA(NULL, true, app::APP_NAME.c_str()); if (GetLastError() == ERROR_ALREADY_EXISTS) { osdialog_message(OSDIALOG_ERROR, OSDIALOG_OK, "Rack is already running. Multiple Rack instances are not supported."); exit(1); @@ -104,7 +104,7 @@ int main(int argc, char *argv[]) { #endif // Log environment - INFO("%s v%s", app::APP_NAME, app::APP_VERSION); + INFO("%s v%s", app::APP_NAME.c_str(), app::APP_VERSION.c_str()); INFO("%s", system::getOperatingSystemInfo().c_str()); if (settings::devMode) INFO("Development mode"); @@ -139,6 +139,7 @@ int main(int argc, char *argv[]) { keyboard::init(); gamepad::init(); plugin::init(); + app::init(); if (!settings::headless) { ui::init(); windowInit(); diff --git a/src/patch.cpp b/src/patch.cpp index 7a20524f..10252aed 100644 --- a/src/patch.cpp +++ b/src/patch.cpp @@ -230,7 +230,7 @@ json_t *PatchManager::toJson() { json_t *rootJ = json_object(); // version - json_t *versionJ = json_string(app::APP_VERSION); + json_t *versionJ = json_string(app::APP_VERSION.c_str()); json_object_set_new(rootJ, "version", versionJ); // Merge with RackWidget JSON @@ -251,7 +251,7 @@ void PatchManager::fromJson(json_t *rootJ) { if (versionJ) version = json_string_value(versionJ); if (version != app::APP_VERSION) { - INFO("Patch was made with Rack v%s, current Rack version is v%s", version.c_str(), app::APP_VERSION); + INFO("Patch was made with Rack v%s, current Rack version is v%s", version.c_str(), app::APP_VERSION.c_str()); } // Detect old patches with ModuleWidget::params/inputs/outputs indices. diff --git a/src/plugin.cpp b/src/plugin.cpp index 0e5276f3..b513f51c 100644 --- a/src/plugin.cpp +++ b/src/plugin.cpp @@ -166,8 +166,7 @@ static bool syncUpdate(const Update &update) { std::string arch = "lin"; #endif - std::string downloadUrl = app::API_URL; - downloadUrl += "/download"; + std::string downloadUrl = app::API_URL + "/download"; downloadUrl += "?token=" + network::encodeUrl(settings::token); downloadUrl += "&slug=" + network::encodeUrl(update.pluginSlug); downloadUrl += "&version=" + network::encodeUrl(update.version); @@ -350,8 +349,7 @@ void logIn(const std::string &email, const std::string &password) { json_t *reqJ = json_object(); json_object_set(reqJ, "email", json_string(email.c_str())); json_object_set(reqJ, "password", json_string(password.c_str())); - std::string url = app::API_URL; - url += "/token"; + std::string url = app::API_URL + "/token"; json_t *resJ = network::requestJson(network::METHOD_POST, url, reqJ); json_decref(reqJ); @@ -391,8 +389,7 @@ void queryUpdates() { // Get user's plugins list json_t *pluginsReqJ = json_object(); json_object_set(pluginsReqJ, "token", json_string(settings::token.c_str())); - std::string pluginsUrl = app::API_URL; - pluginsUrl += "/plugins"; + std::string pluginsUrl = app::API_URL + "/plugins"; json_t *pluginsResJ = network::requestJson(network::METHOD_GET, pluginsUrl, pluginsReqJ); json_decref(pluginsReqJ); if (!pluginsResJ) { @@ -410,8 +407,7 @@ void queryUpdates() { } // Get community manifests - std::string manifestsUrl = app::API_URL; - manifestsUrl += "/community/manifests"; + std::string manifestsUrl = app::API_URL + "/community/manifests"; json_t *manifestsResJ = network::requestJson(network::METHOD_GET, manifestsUrl, NULL); if (!manifestsResJ) { WARN("Request for community manifests failed"); diff --git a/src/settings.cpp b/src/settings.cpp index 9f7c80f2..4c26e5d2 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -29,7 +29,6 @@ int threadCount = 1; bool paramTooltip = false; bool cpuMeter = false; bool lockModules = false; -bool checkVersion = true; float frameRateLimit = 70.0; bool frameRateSync = true; bool skipLoadOnLaunch = false; @@ -76,8 +75,6 @@ json_t *toJson() { json_object_set_new(rootJ, "lockModules", json_boolean(lockModules)); - json_object_set_new(rootJ, "checkVersion", json_boolean(checkVersion)); - json_object_set_new(rootJ, "frameRateLimit", json_real(frameRateLimit)); json_object_set_new(rootJ, "frameRateSync", json_boolean(frameRateSync)); @@ -174,10 +171,6 @@ void fromJson(json_t *rootJ) { if (lockModulesJ) lockModules = json_boolean_value(lockModulesJ); - json_t *checkVersionJ = json_object_get(rootJ, "checkVersion"); - if (checkVersionJ) - checkVersion = json_boolean_value(checkVersionJ); - json_t *frameRateLimitJ = json_object_get(rootJ, "frameRateLimit"); if (frameRateLimitJ) frameRateLimit = json_number_value(frameRateLimitJ); diff --git a/src/window.cpp b/src/window.cpp index c215aaac..43356b15 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -327,10 +327,7 @@ void Window::run() { gamepad::step(); // Set window title - std::string windowTitle; - windowTitle = app::APP_NAME; - windowTitle += " v"; - windowTitle += app::APP_VERSION; + std::string windowTitle = app::APP_NAME + " v" + app::APP_VERSION; if (!APP->patch->path.empty()) { windowTitle += " - "; if (!APP->history->isSaved())