| @@ -8,10 +8,12 @@ namespace app { | |||
| struct MenuBar : widget::OpaqueWidget { | |||
| MenuBar(); | |||
| void draw(const DrawArgs &args) override; | |||
| }; | |||
| MenuBar *createMenuBar(); | |||
| } // namespace app | |||
| } // namespace rack | |||
| @@ -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 | |||
| @@ -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; | |||
| @@ -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<PluginsMenu>(); | |||
| 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; | |||
| } | |||
| @@ -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 | |||
| @@ -1,14 +1,43 @@ | |||
| #include "app/common.hpp" | |||
| #include "settings.hpp" | |||
| #include "network.hpp" | |||
| #include <thread> | |||
| 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 | |||
| @@ -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(); | |||
| @@ -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. | |||
| @@ -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"); | |||
| @@ -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); | |||
| @@ -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()) | |||