| @@ -12,6 +12,7 @@ struct Knob : ParamWidget { | |||||
| /** Multiplier for mouse movement to adjust knob value */ | /** Multiplier for mouse movement to adjust knob value */ | ||||
| float speed = 1.0; | float speed = 1.0; | ||||
| float oldValue = 0.f; | float oldValue = 0.f; | ||||
| bool smooth = true; | |||||
| /** Enable snapping at integer values */ | /** Enable snapping at integer values */ | ||||
| bool snap = false; | bool snap = false; | ||||
| float snapValue = NAN; | float snapValue = NAN; | ||||
| @@ -16,11 +16,13 @@ struct ParamWidget : OpaqueWidget { | |||||
| ~ParamWidget(); | ~ParamWidget(); | ||||
| void step() override; | void step() override; | ||||
| /** For legacy patch loading */ | |||||
| void fromJson(json_t *rootJ); | |||||
| void draw(NVGcontext *vg) override; | |||||
| void onButton(const event::Button &e) override; | void onButton(const event::Button &e) override; | ||||
| void onEnter(const event::Enter &e) override; | void onEnter(const event::Enter &e) override; | ||||
| void onLeave(const event::Leave &e) override; | void onLeave(const event::Leave &e) override; | ||||
| /** For legacy patch loading */ | |||||
| void fromJson(json_t *rootJ); | |||||
| }; | }; | ||||
| @@ -52,6 +52,7 @@ struct Key { | |||||
| int mods; | int mods; | ||||
| }; | }; | ||||
| // Events | |||||
| struct Text { | struct Text { | ||||
| /** Unicode code point of the character */ | /** Unicode code point of the character */ | ||||
| @@ -183,7 +183,7 @@ void CableWidget::draw(NVGcontext *vg) { | |||||
| if (cable && cable->outputModule) { | if (cable && cable->outputModule) { | ||||
| Output *output = &cable->outputModule->outputs[cable->outputId]; | Output *output = &cable->outputModule->outputs[cable->outputId]; | ||||
| if (output->numChannels != 1) { | if (output->numChannels != 1) { | ||||
| thickness = 9; | |||||
| thickness = 7; | |||||
| } | } | ||||
| } | } | ||||
| @@ -28,9 +28,9 @@ void Knob::onButton(const event::Button &e) { | |||||
| void Knob::onDragStart(const event::DragStart &e) { | void Knob::onDragStart(const event::DragStart &e) { | ||||
| if (paramQuantity) { | if (paramQuantity) { | ||||
| oldValue = paramQuantity->getValue(); | |||||
| oldValue = paramQuantity->getSmoothValue(); | |||||
| if (snap) { | if (snap) { | ||||
| snapValue = oldValue; | |||||
| snapValue = paramQuantity->getValue(); | |||||
| } | } | ||||
| } | } | ||||
| @@ -41,7 +41,7 @@ void Knob::onDragEnd(const event::DragEnd &e) { | |||||
| app()->window->cursorUnlock(); | app()->window->cursorUnlock(); | ||||
| if (paramQuantity) { | if (paramQuantity) { | ||||
| float newValue = paramQuantity->getValue(); | |||||
| float newValue = paramQuantity->getSmoothValue(); | |||||
| if (oldValue != newValue) { | if (oldValue != newValue) { | ||||
| // Push ParamChange history action | // Push ParamChange history action | ||||
| history::ParamChange *h = new history::ParamChange; | history::ParamChange *h = new history::ParamChange; | ||||
| @@ -75,9 +75,12 @@ void Knob::onDragMove(const event::DragMove &e) { | |||||
| snapValue = math::clamp(snapValue, paramQuantity->getMinValue(), paramQuantity->getMaxValue()); | snapValue = math::clamp(snapValue, paramQuantity->getMinValue(), paramQuantity->getMaxValue()); | ||||
| paramQuantity->setValue(std::round(snapValue)); | paramQuantity->setValue(std::round(snapValue)); | ||||
| } | } | ||||
| else { | |||||
| else if (smooth) { | |||||
| paramQuantity->setSmoothValue(paramQuantity->getSmoothValue() + delta); | paramQuantity->setSmoothValue(paramQuantity->getSmoothValue() + delta); | ||||
| } | } | ||||
| else { | |||||
| paramQuantity->setValue(paramQuantity->getValue() + delta); | |||||
| } | |||||
| } | } | ||||
| ParamWidget::onDragMove(e); | ParamWidget::onDragMove(e); | ||||
| @@ -93,6 +93,22 @@ void ParamWidget::step() { | |||||
| OpaqueWidget::step(); | OpaqueWidget::step(); | ||||
| } | } | ||||
| void ParamWidget::draw(NVGcontext *vg) { | |||||
| Widget::draw(vg); | |||||
| if (paramQuantity) { | |||||
| nvgBeginPath(vg); | |||||
| nvgRect(vg, | |||||
| box.size.x - 12, box.size.y - 12, | |||||
| 12, 12); | |||||
| nvgFillColor(vg, nvgRGBAf(1, 0, 1, 0.9)); | |||||
| nvgFill(vg); | |||||
| std::string mapText = string::f("%d", paramQuantity->paramId); | |||||
| bndLabel(vg, box.size.x - 17.0, box.size.y - 16.0, INFINITY, INFINITY, -1, mapText.c_str()); | |||||
| } | |||||
| } | |||||
| void ParamWidget::fromJson(json_t *rootJ) { | void ParamWidget::fromJson(json_t *rootJ) { | ||||
| json_t *valueJ = json_object_get(rootJ, "value"); | json_t *valueJ = json_object_get(rootJ, "value"); | ||||
| if (valueJ) { | if (valueJ) { | ||||
| @@ -24,7 +24,7 @@ void SVGSwitch::addFrame(std::shared_ptr<SVG> svg) { | |||||
| void SVGSwitch::onChange(const event::Change &e) { | void SVGSwitch::onChange(const event::Change &e) { | ||||
| if (!frames.empty() && paramQuantity) { | if (!frames.empty() && paramQuantity) { | ||||
| int index = (int) paramQuantity->getValue(); | |||||
| int index = (int) std::round(paramQuantity->getValue()); | |||||
| index = math::clamp(index, 0, (int) frames.size() - 1); | index = math::clamp(index, 0, (int) frames.size() - 1); | ||||
| sw->setSVG(frames[index]); | sw->setSVG(frames[index]); | ||||
| fb->dirty = true; | fb->dirty = true; | ||||
| @@ -16,23 +16,23 @@ | |||||
| #include <osdialog.h> | #include <osdialog.h> | ||||
| #ifdef ARCH_WIN | #ifdef ARCH_WIN | ||||
| #include <Windows.h> | |||||
| #include <Windows.h> | |||||
| #endif | #endif | ||||
| using namespace rack; | using namespace rack; | ||||
| int main(int argc, char *argv[]) { | int main(int argc, char *argv[]) { | ||||
| #ifdef ARCH_WIN | |||||
| // Windows global mutex to prevent multiple instances | |||||
| // Handle will be closed by Windows when the process ends | |||||
| HANDLE instanceMutex = CreateMutex(NULL, true, 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); | |||||
| } | |||||
| (void) instanceMutex; | |||||
| #endif | |||||
| #ifdef ARCH_WIN | |||||
| // Windows global mutex to prevent multiple instances | |||||
| // Handle will be closed by Windows when the process ends | |||||
| HANDLE instanceMutex = CreateMutex(NULL, true, 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); | |||||
| } | |||||
| (void) instanceMutex; | |||||
| #endif | |||||
| bool devMode = false; | bool devMode = false; | ||||
| std::string patchFile; | std::string patchFile; | ||||
| @@ -59,7 +59,6 @@ int main(int argc, char *argv[]) { | |||||
| } | } | ||||
| // Initialize environment | // Initialize environment | ||||
| random::init(); | |||||
| asset::init(devMode); | asset::init(devMode); | ||||
| logger::init(devMode); | logger::init(devMode); | ||||
| @@ -70,6 +69,7 @@ int main(int argc, char *argv[]) { | |||||
| INFO("System directory: %s", asset::systemDir.c_str()); | INFO("System directory: %s", asset::systemDir.c_str()); | ||||
| INFO("User directory: %s", asset::userDir.c_str()); | INFO("User directory: %s", asset::userDir.c_str()); | ||||
| random::init(); | |||||
| midi::init(); | midi::init(); | ||||
| rtmidiInit(); | rtmidiInit(); | ||||
| bridgeInit(); | bridgeInit(); | ||||
| @@ -77,6 +77,7 @@ int main(int argc, char *argv[]) { | |||||
| gamepad::init(); | gamepad::init(); | ||||
| ui::init(); | ui::init(); | ||||
| plugin::init(devMode); | plugin::init(devMode); | ||||
| INFO("Initialized environment") | |||||
| // Initialize app | // Initialize app | ||||
| appInit(); | appInit(); | ||||
| @@ -104,21 +105,25 @@ int main(int argc, char *argv[]) { | |||||
| app()->scene->rackWidget->load(patchFile); | app()->scene->rackWidget->load(patchFile); | ||||
| app()->scene->rackWidget->lastPath = patchFile; | app()->scene->rackWidget->lastPath = patchFile; | ||||
| } | } | ||||
| INFO("Initialized app") | |||||
| app()->engine->start(); | app()->engine->start(); | ||||
| app()->window->run(); | app()->window->run(); | ||||
| INFO("Window closed"); | |||||
| app()->engine->stop(); | app()->engine->stop(); | ||||
| // Destroy app | // Destroy app | ||||
| app()->scene->rackWidget->save(asset::user("autosave.vcv")); | app()->scene->rackWidget->save(asset::user("autosave.vcv")); | ||||
| settings::save(asset::user("settings.json")); | settings::save(asset::user("settings.json")); | ||||
| appDestroy(); | appDestroy(); | ||||
| INFO("Cleaned up app") | |||||
| // Destroy environment | // Destroy environment | ||||
| plugin::destroy(); | plugin::destroy(); | ||||
| ui::destroy(); | ui::destroy(); | ||||
| bridgeDestroy(); | bridgeDestroy(); | ||||
| midi::destroy(); | midi::destroy(); | ||||
| INFO("Cleaned up environment") | |||||
| logger::destroy(); | logger::destroy(); | ||||
| return 0; | return 0; | ||||