@@ -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()) | |||