diff --git a/include/app/ModuleWidget.hpp b/include/app/ModuleWidget.hpp index 7a13abf9..1d75483e 100644 --- a/include/app/ModuleWidget.hpp +++ b/include/app/ModuleWidget.hpp @@ -2,7 +2,6 @@ #include "app/common.hpp" #include "widgets/OpaqueWidget.hpp" #include "ui/Menu.hpp" -#include "app/SVGPanel.hpp" #include "app/Port.hpp" #include "app/ParamWidget.hpp" #include "plugin/Model.hpp" @@ -12,19 +11,15 @@ namespace rack { -struct SVGPanel; -struct Port; - - struct ModuleWidget : OpaqueWidget { Model *model = NULL; /** Owns the module pointer */ Module *module = NULL; - SVGPanel *panel = NULL; + Widget *panel = NULL; + std::vector params; std::vector inputs; std::vector outputs; - std::vector params; ModuleWidget(Module *module); ~ModuleWidget(); @@ -39,13 +34,11 @@ struct ModuleWidget : OpaqueWidget { void copyClipboard(); void pasteClipboard(); - void save(std::string filename); void load(std::string filename); + void save(std::string filename); void loadDialog(); void saveDialog(); - virtual void create(); - virtual void _delete(); /** Disconnects cables from all ports Called when the user clicks Disconnect Cables in the context menu. */ diff --git a/include/app/MomentarySwitch.hpp b/include/app/MomentarySwitch.hpp index 9a3308f0..e2d14d3e 100644 --- a/include/app/MomentarySwitch.hpp +++ b/include/app/MomentarySwitch.hpp @@ -10,8 +10,6 @@ namespace rack { Consider using SVGButton if the switch simply changes the state of your Module when clicked. */ struct MomentarySwitch : virtual ParamWidget { - /** Don't randomize state */ - void randomize() override {} void onDragStart(event::DragStart &e) override; void onDragEnd(event::DragEnd &e) override; }; diff --git a/include/app/ParamWidget.hpp b/include/app/ParamWidget.hpp index 658571d6..b9a6138f 100644 --- a/include/app/ParamWidget.hpp +++ b/include/app/ParamWidget.hpp @@ -17,8 +17,6 @@ struct ParamWidget : OpaqueWidget { /** For legacy patch loading */ void fromJson(json_t *rootJ); - virtual void reset(); - virtual void randomize(); void onButton(event::Button &e) override; }; diff --git a/include/app/RackWidget.hpp b/include/app/RackWidget.hpp index 7d4829cb..fd42c628 100644 --- a/include/app/RackWidget.hpp +++ b/include/app/RackWidget.hpp @@ -53,6 +53,7 @@ struct RackWidget : OpaqueWidget { void draw(NVGcontext *vg) override; void onHover(event::Hover &e) override; + void onDragHover(event::DragHover &e) override; void onButton(event::Button &e) override; void onZoom(event::Zoom &e) override; }; diff --git a/include/engine/Module.hpp b/include/engine/Module.hpp index 0316c27e..dd506ddc 100644 --- a/include/engine/Module.hpp +++ b/include/engine/Module.hpp @@ -39,6 +39,11 @@ struct Module { lights.resize(numLights); } + json_t *toJson(); + void fromJson(json_t *rootJ); + void reset(); + void randomize(); + /** Advances the module by 1 audio frame with duration 1.0 / gSampleRate Override this method to read inputs and params, and to write outputs and lights. */ @@ -51,9 +56,6 @@ struct Module { /** Called when user clicks Randomize in the module context menu */ virtual void onRandomize() {} - json_t *toJson(); - void fromJson(json_t *rootJ); - /** Override these to store extra internal data in the "data" property of the module's JSON object */ virtual json_t *dataToJson() { return NULL; } virtual void dataFromJson(json_t *root) {} diff --git a/include/engine/Param.hpp b/include/engine/Param.hpp index b973942c..b1df1b92 100644 --- a/include/engine/Param.hpp +++ b/include/engine/Param.hpp @@ -34,6 +34,8 @@ struct Param { json_t *toJson(); void fromJson(json_t *rootJ); + void reset(); + void randomize(); }; diff --git a/src/app/ModuleWidget.cpp b/src/app/ModuleWidget.cpp index a4225ac4..7cac7f6b 100644 --- a/src/app/ModuleWidget.cpp +++ b/src/app/ModuleWidget.cpp @@ -3,6 +3,7 @@ #include "system.hpp" #include "asset.hpp" #include "app/Scene.hpp" +#include "app/SVGPanel.hpp" #include "helpers.hpp" #include "context.hpp" #include "settings.hpp" @@ -56,11 +57,13 @@ void ModuleWidget::setPanel(std::shared_ptr svg) { panel = NULL; } - panel = new SVGPanel; - panel->setBackground(svg); - addChild(panel); - - box.size = panel->box.size; + { + SVGPanel *panel = new SVGPanel; + panel->setBackground(svg); + addChild(panel); + box.size = panel->box.size; + this->panel = panel; + } } @@ -123,10 +126,14 @@ void ModuleWidget::fromJson(json_t *rootJ) { void ModuleWidget::copyClipboard() { json_t *moduleJ = toJson(); + DEFER({ + json_decref(moduleJ); + }); char *moduleJson = json_dumps(moduleJ, JSON_INDENT(2) | JSON_REAL_PRECISION(9)); + DEFER({ + free(moduleJson); + }); glfwSetClipboardString(context()->window->win, moduleJson); - free(moduleJson); - json_decref(moduleJ); } void ModuleWidget::pasteClipboard() { @@ -138,50 +145,61 @@ void ModuleWidget::pasteClipboard() { json_error_t error; json_t *moduleJ = json_loads(moduleJson, 0, &error); - if (moduleJ) { - fromJson(moduleJ); - json_decref(moduleJ); - } - else { + if (!moduleJ) { WARN("JSON parsing error at %s %d:%d %s", error.source, error.line, error.column, error.text); + return; } + DEFER({ + json_decref(moduleJ); + }); + + fromJson(moduleJ); } void ModuleWidget::load(std::string filename) { INFO("Loading preset %s", filename.c_str()); + FILE *file = fopen(filename.c_str(), "r"); if (!file) { - // Exit silently + WARN("Could not load patch file %s", filename.c_str()); return; } + DEFER({ + fclose(file); + }); json_error_t error; json_t *moduleJ = json_loadf(file, 0, &error); - if (moduleJ) { - fromJson(moduleJ); - json_decref(moduleJ); - } - else { - std::string message = string::f("JSON parsing error at %s %d:%d %s", error.source, error.line, error.column, error.text); + if (!moduleJ) { + std::string message = string::f("File is not a valid patch file. JSON parsing error at %s %d:%d %s", error.source, error.line, error.column, error.text); osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK, message.c_str()); + return; } + DEFER({ + json_decref(moduleJ); + }); - fclose(file); + fromJson(moduleJ); } void ModuleWidget::save(std::string filename) { INFO("Saving preset %s", filename.c_str()); + json_t *moduleJ = toJson(); - if (!moduleJ) - return; + assert(moduleJ); + DEFER({ + json_decref(moduleJ); + }); FILE *file = fopen(filename.c_str(), "w"); - if (file) { - json_dumpf(moduleJ, file, JSON_INDENT(2) | JSON_REAL_PRECISION(9)); - fclose(file); + if (!file) { + WARN("Could not write to patch file %s", filename.c_str()); } + DEFER({ + fclose(file); + }); - json_decref(moduleJ); + json_dumpf(moduleJ, file, JSON_INDENT(2) | JSON_REAL_PRECISION(9)); } void ModuleWidget::loadDialog() { @@ -189,12 +207,20 @@ void ModuleWidget::loadDialog() { system::createDirectory(dir); osdialog_filters *filters = osdialog_filters_parse(PRESET_FILTERS.c_str()); + DEFER({ + osdialog_filters_free(filters); + }); + char *path = osdialog_file(OSDIALOG_OPEN, dir.c_str(), NULL, filters); - if (path) { - load(path); - free(path); + if (!path) { + // No path selected + return; } - osdialog_filters_free(filters); + DEFER({ + free(path); + }); + + load(path); } void ModuleWidget::saveDialog() { @@ -202,19 +228,26 @@ void ModuleWidget::saveDialog() { system::createDirectory(dir); osdialog_filters *filters = osdialog_filters_parse(PRESET_FILTERS.c_str()); - char *path = osdialog_file(OSDIALOG_SAVE, dir.c_str(), "Untitled.vcvm", filters); + DEFER({ + osdialog_filters_free(filters); + }); - if (path) { - std::string pathStr = path; + char *path = osdialog_file(OSDIALOG_SAVE, dir.c_str(), "Untitled.vcvm", filters); + if (!path) { + // No path selected + return; + } + DEFER({ free(path); - std::string extension = string::extension(pathStr); - if (extension.empty()) { - pathStr += ".vcvm"; - } + }); - save(pathStr); + std::string pathStr = path; + std::string extension = string::extension(pathStr); + if (extension.empty()) { + pathStr += ".vcvm"; } - osdialog_filters_free(filters); + + save(pathStr); } void ModuleWidget::disconnect() { @@ -226,25 +259,13 @@ void ModuleWidget::disconnect() { } } -void ModuleWidget::create() { -} - -void ModuleWidget::_delete() { -} - void ModuleWidget::reset() { - for (ParamWidget *param : params) { - param->reset(); - } if (module) { context()->engine->resetModule(module); } } void ModuleWidget::randomize() { - for (ParamWidget *param : params) { - param->randomize(); - } if (module) { context()->engine->randomizeModule(module); } @@ -300,9 +321,7 @@ void ModuleWidget::onHover(event::Hover &e) { // Instead of checking key-down events, delete the module even if key-repeat hasn't fired yet and the cursor is hovering over the widget. if (glfwGetKey(context()->window->win, GLFW_KEY_DELETE) == GLFW_PRESS || glfwGetKey(context()->window->win, GLFW_KEY_BACKSPACE) == GLFW_PRESS) { if (!context()->window->isModPressed() && !context()->window->isShiftPressed()) { - context()->scene->rackWidget->deleteModule(this); - delete this; - // e.target = this; + requestedDelete = true; return; } } @@ -365,7 +384,6 @@ void ModuleWidget::onHoverKey(event::HoverKey &e) { void ModuleWidget::onDragStart(event::DragStart &e) { dragPos = context()->scene->rackWidget->lastMousePos.minus(box.pos); - e.target = this; } void ModuleWidget::onDragEnd(event::DragEnd &e) { @@ -375,6 +393,7 @@ void ModuleWidget::onDragMove(event::DragMove &e) { if (!settings::lockModules) { math::Rect newBox = box; newBox.pos = context()->scene->rackWidget->lastMousePos.minus(dragPos); + DEBUG("%f %f", newBox.pos.x, newBox.pos.y); context()->scene->rackWidget->requestModuleBoxNearest(this, newBox); } } @@ -382,6 +401,10 @@ void ModuleWidget::onDragMove(event::DragMove &e) { struct ModuleDisconnectItem : MenuItem { ModuleWidget *moduleWidget; + ModuleDisconnectItem() { + text = "Disconnect cables"; + rightText = WINDOW_MOD_KEY_NAME "+U"; + } void onAction(event::Action &e) override { moduleWidget->disconnect(); } @@ -389,6 +412,10 @@ struct ModuleDisconnectItem : MenuItem { struct ModuleResetItem : MenuItem { ModuleWidget *moduleWidget; + ModuleResetItem() { + text = "Initialize"; + rightText = WINDOW_MOD_KEY_NAME "+I"; + } void onAction(event::Action &e) override { moduleWidget->reset(); } @@ -396,6 +423,10 @@ struct ModuleResetItem : MenuItem { struct ModuleRandomizeItem : MenuItem { ModuleWidget *moduleWidget; + ModuleRandomizeItem() { + text = "Randomize"; + rightText = WINDOW_MOD_KEY_NAME "+R"; + } void onAction(event::Action &e) override { moduleWidget->randomize(); } @@ -403,6 +434,10 @@ struct ModuleRandomizeItem : MenuItem { struct ModuleCopyItem : MenuItem { ModuleWidget *moduleWidget; + ModuleCopyItem() { + text = "Copy preset"; + rightText = WINDOW_MOD_KEY_NAME "+C"; + } void onAction(event::Action &e) override { moduleWidget->copyClipboard(); } @@ -410,6 +445,10 @@ struct ModuleCopyItem : MenuItem { struct ModulePasteItem : MenuItem { ModuleWidget *moduleWidget; + ModulePasteItem() { + text = "Paste preset"; + rightText = WINDOW_MOD_KEY_NAME "+V"; + } void onAction(event::Action &e) override { moduleWidget->pasteClipboard(); } @@ -417,6 +456,9 @@ struct ModulePasteItem : MenuItem { struct ModuleSaveItem : MenuItem { ModuleWidget *moduleWidget; + ModuleSaveItem() { + text = "Save preset"; + } void onAction(event::Action &e) override { moduleWidget->saveDialog(); } @@ -424,6 +466,9 @@ struct ModuleSaveItem : MenuItem { struct ModuleLoadItem : MenuItem { ModuleWidget *moduleWidget; + ModuleLoadItem() { + text = "Load preset"; + } void onAction(event::Action &e) override { moduleWidget->loadDialog(); } @@ -431,6 +476,10 @@ struct ModuleLoadItem : MenuItem { struct ModuleCloneItem : MenuItem { ModuleWidget *moduleWidget; + ModuleCloneItem() { + text = "Duplicate"; + rightText = WINDOW_MOD_KEY_NAME "+D"; + } void onAction(event::Action &e) override { context()->scene->rackWidget->cloneModule(moduleWidget); } @@ -438,6 +487,10 @@ struct ModuleCloneItem : MenuItem { struct ModuleDeleteItem : MenuItem { ModuleWidget *moduleWidget; + ModuleDeleteItem() { + text = "Delete"; + rightText = "Backspace/Delete"; + } void onAction(event::Action &e) override { context()->scene->rackWidget->deleteModule(moduleWidget); delete moduleWidget; @@ -452,54 +505,38 @@ Menu *ModuleWidget::createContextMenu() { menu->addChild(menuLabel); ModuleResetItem *resetItem = new ModuleResetItem; - resetItem->text = "Initialize"; - resetItem->rightText = WINDOW_MOD_KEY_NAME "+I"; resetItem->moduleWidget = this; menu->addChild(resetItem); ModuleRandomizeItem *randomizeItem = new ModuleRandomizeItem; - randomizeItem->text = "Randomize"; - randomizeItem->rightText = WINDOW_MOD_KEY_NAME "+R"; randomizeItem->moduleWidget = this; menu->addChild(randomizeItem); ModuleDisconnectItem *disconnectItem = new ModuleDisconnectItem; - disconnectItem->text = "Disconnect cables"; - disconnectItem->rightText = WINDOW_MOD_KEY_NAME "+U"; disconnectItem->moduleWidget = this; menu->addChild(disconnectItem); ModuleCloneItem *cloneItem = new ModuleCloneItem; - cloneItem->text = "Duplicate"; - cloneItem->rightText = WINDOW_MOD_KEY_NAME "+D"; cloneItem->moduleWidget = this; menu->addChild(cloneItem); ModuleCopyItem *copyItem = new ModuleCopyItem; - copyItem->text = "Copy preset"; - copyItem->rightText = WINDOW_MOD_KEY_NAME "+C"; copyItem->moduleWidget = this; menu->addChild(copyItem); ModulePasteItem *pasteItem = new ModulePasteItem; - pasteItem->text = "Paste preset"; - pasteItem->rightText = WINDOW_MOD_KEY_NAME "+V"; pasteItem->moduleWidget = this; menu->addChild(pasteItem); ModuleLoadItem *loadItem = new ModuleLoadItem; - loadItem->text = "Load preset"; loadItem->moduleWidget = this; menu->addChild(loadItem); ModuleSaveItem *saveItem = new ModuleSaveItem; - saveItem->text = "Save preset"; saveItem->moduleWidget = this; menu->addChild(saveItem); ModuleDeleteItem *deleteItem = new ModuleDeleteItem; - deleteItem->text = "Delete"; - deleteItem->rightText = "Backspace/Delete"; deleteItem->moduleWidget = this; menu->addChild(deleteItem); diff --git a/src/app/ParamWidget.cpp b/src/app/ParamWidget.cpp index 8eda1c3b..2b59175f 100644 --- a/src/app/ParamWidget.cpp +++ b/src/app/ParamWidget.cpp @@ -13,31 +13,17 @@ void ParamWidget::fromJson(json_t *rootJ) { } } -void ParamWidget::reset() { - if (quantity) { - // Infinite params should not be reset - if (quantity->isBounded()) +void ParamWidget::onButton(event::Button &e) { + // Right click to reset + if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_RIGHT) { + if (quantity) quantity->reset(); + // Here's another way of doing it, but either works. + // dynamic_cast(quantity)->getParam()->reset(); + return; } -} - -void ParamWidget::randomize() { - if (quantity) { - // Infinite params should not be randomized - if (quantity->isBounded()) { - quantity->setScaledValue(random::uniform()); - } - } -} -void ParamWidget::onButton(event::Button &e) { OpaqueWidget::onButton(e); - if (e.target == this) { - if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_RIGHT) { - if (quantity) - quantity->reset(); - } - } } diff --git a/src/app/RackWidget.cpp b/src/app/RackWidget.cpp index c4ac4acc..3886d142 100644 --- a/src/app/RackWidget.cpp +++ b/src/app/RackWidget.cpp @@ -17,13 +17,14 @@ namespace rack { struct ModuleContainer : Widget { void draw(NVGcontext *vg) override { - // Draw shadows behind each ModuleWidget first, so the shadow doesn't overlap the front. + // Draw shadows behind each ModuleWidget first, so the shadow doesn't overlap the front of other ModuleWidgets. for (Widget *child : children) { if (!child->visible) continue; nvgSave(vg); nvgTranslate(vg, child->box.pos.x, child->box.pos.y); ModuleWidget *w = dynamic_cast(child); + assert(w); w->drawShadow(vg); nvgRestore(vg); } @@ -415,16 +416,14 @@ void RackWidget::pastePresetClipboard() { void RackWidget::addModule(ModuleWidget *m) { moduleContainer->addChild(m); - m->create(); } void RackWidget::deleteModule(ModuleWidget *m) { - m->_delete(); moduleContainer->removeChild(m); } void RackWidget::cloneModule(ModuleWidget *m) { - // JSON serialization is the most straightforward way to do this + // JSON serialization is the obvious way to do this json_t *moduleJ = m->toJson(); ModuleWidget *clonedModuleWidget = moduleFromJson(moduleJ); json_decref(moduleJ); @@ -509,6 +508,11 @@ void RackWidget::onHover(event::Hover &e) { lastMousePos = e.pos; } +void RackWidget::onDragHover(event::DragHover &e) { + OpaqueWidget::onDragHover(e); + lastMousePos = e.pos; +} + void RackWidget::onButton(event::Button &e) { OpaqueWidget::onButton(e); if (e.target == this) { diff --git a/src/app/Toolbar.cpp b/src/app/Toolbar.cpp index 61437fd3..df855b56 100644 --- a/src/app/Toolbar.cpp +++ b/src/app/Toolbar.cpp @@ -336,7 +336,7 @@ struct ManageItem : MenuItem { struct SyncItem : MenuItem { SyncItem() { - text = "Sync"; + text = "Sync plugins"; disabled = true; } void onAction(event::Action &e) override { diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp index 0718a289..b96fefc4 100644 --- a/src/engine/Engine.cpp +++ b/src/engine/Engine.cpp @@ -93,11 +93,11 @@ static void Engine_step(Engine *engine) { // Events if (engine->internal->resetModule) { - engine->internal->resetModule->onReset(); + engine->internal->resetModule->reset(); engine->internal->resetModule = NULL; } if (engine->internal->randomizeModule) { - engine->internal->randomizeModule->onRandomize(); + engine->internal->randomizeModule->randomize(); engine->internal->randomizeModule = NULL; } diff --git a/src/engine/Module.cpp b/src/engine/Module.cpp index cd9cba03..a6589582 100644 --- a/src/engine/Module.cpp +++ b/src/engine/Module.cpp @@ -50,5 +50,19 @@ void Module::fromJson(json_t *rootJ) { } } +void Module::reset() { + for (Param ¶m : params) { + param.reset(); + } + onReset(); +} + +void Module::randomize() { + for (Param ¶m : params) { + param.randomize(); + } + onRandomize(); +} + } // namespace rack diff --git a/src/engine/Param.cpp b/src/engine/Param.cpp index c38aaa7d..7aed2cb6 100644 --- a/src/engine/Param.cpp +++ b/src/engine/Param.cpp @@ -1,4 +1,6 @@ #include "engine/Param.hpp" +#include "random.hpp" +#include "math.hpp" namespace rack { @@ -20,5 +22,17 @@ void Param::fromJson(json_t *rootJ) { value = json_number_value(valueJ); } +void Param::reset() { + if (std::isfinite(minValue) && std::isfinite(maxValue)) { + value = defaultValue; + } +} + +void Param::randomize() { + if (std::isfinite(minValue) && std::isfinite(maxValue)) { + value = math::rescale(random::uniform(), 0.f, 1.f, minValue, maxValue); + } +} + } // namespace rack