From 5266641777549f163449841fda64cb4f72543704 Mon Sep 17 00:00:00 2001 From: Andrew Belt Date: Tue, 22 Jan 2019 15:15:16 -0500 Subject: [PATCH] Big cable refactor. --- include/app/CableContainer.hpp | 22 +++- include/app/CableWidget.hpp | 11 +- include/app/PortWidget.hpp | 7 +- include/app/RackWidget.hpp | 12 +- src/Core/Blank.cpp | 4 +- src/app/CableContainer.cpp | 168 +++++++++++++++----------- src/app/CableWidget.cpp | 146 +++++++++++++++-------- src/app/ModuleWidget.cpp | 8 +- src/app/PortWidget.cpp | 120 +++++++++++-------- src/app/RackScrollWidget.cpp | 2 +- src/app/RackWidget.cpp | 211 +++++++++++---------------------- src/main.cpp | 8 +- src/settings.cpp | 14 +-- src/widgets/Widget.cpp | 2 + src/window.cpp | 4 +- 15 files changed, 387 insertions(+), 352 deletions(-) diff --git a/include/app/CableContainer.hpp b/include/app/CableContainer.hpp index c47da41d..a629cc51 100644 --- a/include/app/CableContainer.hpp +++ b/include/app/CableContainer.hpp @@ -3,21 +3,31 @@ #include "widgets/TransparentWidget.hpp" #include "app/CableWidget.hpp" #include "app/PortWidget.hpp" +#include namespace rack { struct CableContainer : TransparentWidget { - CableWidget *activeCable = NULL; + CableWidget *incompleteCable = NULL; + + ~CableContainer(); + void clear(); + /** Removes all complete cables connected to the port */ + void clearPort(PortWidget *port); + /** Adds a complete cable and adds it to the Engine. + Ownership rules work like add/removeChild() + */ + void addCable(CableWidget *w); + void removeCable(CableWidget *w); /** Takes ownership of `w` and adds it as a child if it isn't already */ - void setActiveCable(CableWidget *w); - /** "Drops" the cable onto the port, making an engine connection if successful */ - void commitActiveCable(); - void removeTopCable(PortWidget *port); - void removeAllCables(PortWidget *port); + void setIncompleteCable(CableWidget *w); + CableWidget *releaseIncompleteCable(); /** Returns the most recently added cable connected to the given Port, i.e. the top of the stack */ CableWidget *getTopCable(PortWidget *port); + json_t *toJson(); + void fromJson(json_t *rootJ, const std::map &moduleWidgets); void draw(NVGcontext *vg) override; }; diff --git a/include/app/CableWidget.hpp b/include/app/CableWidget.hpp index 8cc1c8db..9418f1c5 100644 --- a/include/app/CableWidget.hpp +++ b/include/app/CableWidget.hpp @@ -2,7 +2,9 @@ #include "app/common.hpp" #include "widgets/OpaqueWidget.hpp" #include "app/PortWidget.hpp" +#include "app/ModuleWidget.hpp" #include "engine/Cable.hpp" +#include namespace rack { @@ -13,17 +15,18 @@ struct CableWidget : OpaqueWidget { PortWidget *inputPort = NULL; PortWidget *hoveredOutputPort = NULL; PortWidget *hoveredInputPort = NULL; - Cable *cable = NULL; + Cable *cable; NVGcolor color; CableWidget(); ~CableWidget(); - /** Synchronizes the plugged state of the widget to the owned cable */ - void updateCable(); + bool isComplete(); + void setOutputPort(PortWidget *outputPort); + void setInputPort(PortWidget *inputPort); math::Vec getOutputPos(); math::Vec getInputPos(); json_t *toJson(); - void fromJson(json_t *rootJ); + void fromJson(json_t *rootJ, const std::map &moduleWidgets); void draw(NVGcontext *vg) override; void drawPlugs(NVGcontext *vg); }; diff --git a/include/app/PortWidget.hpp b/include/app/PortWidget.hpp index d6edf56e..2dd73a36 100644 --- a/include/app/PortWidget.hpp +++ b/include/app/PortWidget.hpp @@ -13,10 +13,10 @@ struct PortWidget : OpaqueWidget { int portId; enum Type { - INPUT, - OUTPUT + OUTPUT, + INPUT }; - Type type = INPUT; + Type type; MultiLightWidget *plugLight; PortWidget(); @@ -29,7 +29,6 @@ struct PortWidget : OpaqueWidget { void onDragDrop(const event::DragDrop &e) override; void onDragEnter(const event::DragEnter &e) override; void onDragLeave(const event::DragLeave &e) override; - void setHovered(); }; diff --git a/include/app/RackWidget.hpp b/include/app/RackWidget.hpp index 75b64010..4f0bddb8 100644 --- a/include/app/RackWidget.hpp +++ b/include/app/RackWidget.hpp @@ -15,8 +15,10 @@ struct RackWidget : OpaqueWidget { Widget *moduleContainer; // Only put CableWidgets in here CableContainer *cableContainer; - std::string lastPath; - math::Vec lastMousePos; + /** The currently loaded patch file path */ + std::string patchPath; + /** The last mouse position in the RackWidget */ + math::Vec mousePos; RackWidget(); ~RackWidget(); @@ -37,8 +39,6 @@ struct RackWidget : OpaqueWidget { void load(std::string filename); json_t *toJson(); void fromJson(json_t *rootJ); - /** Creates a module and adds it to the rack */ - ModuleWidget *moduleFromJson(json_t *moduleJ); void pastePresetClipboard(); void addModule(ModuleWidget *m); @@ -46,9 +46,9 @@ struct RackWidget : OpaqueWidget { /** Removes the module and transfers ownership to the caller */ void removeModule(ModuleWidget *m); /** Sets a module's box if non-colliding. Returns true if set */ - bool requestModuleBox(ModuleWidget *m, math::Rect box); + bool requestModuleBox(ModuleWidget *m, math::Rect requestedBox); /** Moves a module to the closest non-colliding position */ - bool requestModuleBoxNearest(ModuleWidget *m, math::Rect box); + bool requestModuleBoxNearest(ModuleWidget *m, math::Rect requestedBox); ModuleWidget *getModule(int moduleId); void step() override; diff --git a/src/Core/Blank.cpp b/src/Core/Blank.cpp index 6a2ce965..c22e9855 100644 --- a/src/Core/Blank.cpp +++ b/src/Core/Blank.cpp @@ -39,14 +39,14 @@ struct ModuleResizeHandle : Widget { } } void onDragStart(const event::DragStart &e) override { - dragX = app()->scene->rackWidget->lastMousePos.x; + dragX = app()->scene->rackWidget->mousePos.x; ModuleWidget *m = getAncestorOfType(); originalBox = m->box; } void onDragMove(const event::DragMove &e) override { ModuleWidget *m = getAncestorOfType(); - float newDragX = app()->scene->rackWidget->lastMousePos.x; + float newDragX = app()->scene->rackWidget->mousePos.x; float deltaX = newDragX - dragX; Rect newBox = originalBox; diff --git a/src/app/CableContainer.cpp b/src/app/CableContainer.cpp index a4691609..235821ad 100644 --- a/src/app/CableContainer.cpp +++ b/src/app/CableContainer.cpp @@ -1,106 +1,132 @@ #include "app/CableContainer.hpp" +#include "app.hpp" +#include "engine/Engine.hpp" + namespace rack { -void CableContainer::setActiveCable(CableWidget *w) { - if (activeCable) { - removeChild(activeCable); - delete activeCable; - activeCable = NULL; - } - if (w) { - if (w->parent == NULL) - addChild(w); - activeCable = w; - } +CableContainer::~CableContainer() { + clear(); } -void CableContainer::commitActiveCable() { - if (!activeCable) - return; - - if (activeCable->hoveredOutputPort) { - activeCable->outputPort = activeCable->hoveredOutputPort; - activeCable->hoveredOutputPort = NULL; +void CableContainer::clear() { + for (Widget *w : children) { + CableWidget *cw = dynamic_cast(w); + assert(cw); + if (cw != incompleteCable) + app()->engine->removeCable(cw->cable); } - if (activeCable->hoveredInputPort) { - activeCable->inputPort = activeCable->hoveredInputPort; - activeCable->hoveredInputPort = NULL; - } - activeCable->updateCable(); + incompleteCable = NULL; + clearChildren(); +} - // Did it successfully connect? - if (activeCable->cable) { - // Make it permanent - activeCable = NULL; - } - else { - // Remove it - setActiveCable(NULL); +void CableContainer::clearPort(PortWidget *port) { + assert(port); + // Collect cables to remove + std::list cables; + for (Widget *w : children) { + CableWidget *cw = dynamic_cast(w); + assert(cw); + if (cw->inputPort == port || cw->outputPort == port) { + cables.push_back(cw); + } } -} -void CableContainer::removeTopCable(PortWidget *port) { - CableWidget *cable = getTopCable(port); - if (cable) { - removeChild(cable); - delete cable; + // Remove and delete the cables + for (CableWidget *cw : cables) { + if (cw == incompleteCable) { + incompleteCable = NULL; + removeChild(cw); + } + else { + removeCable(cw); + } + delete cw; } } -void CableContainer::removeAllCables(PortWidget *port) { - // As a convenience, de-hover the active cable so we don't attach them once it is dropped. - if (activeCable) { - if (activeCable->hoveredInputPort == port) - activeCable->hoveredInputPort = NULL; - if (activeCable->hoveredOutputPort == port) - activeCable->hoveredOutputPort = NULL; - } +void CableContainer::addCable(CableWidget *w) { + assert(w->isComplete()); + app()->engine->addCable(w->cable); + addChild(w); +} - // Build a list of CableWidgets to delete - std::list cables; +void CableContainer::removeCable(CableWidget *w) { + assert(w->isComplete()); + app()->engine->removeCable(w->cable); + removeChild(w); +} - for (Widget *child : children) { - CableWidget *cable = dynamic_cast(child); - assert(cable); - if (!cable || cable->inputPort == port || cable->outputPort == port) { - if (activeCable == cable) { - activeCable = NULL; - } - // We can't delete from this list while we're iterating it, so add it to the deletion list. - cables.push_back(cable); - } +void CableContainer::setIncompleteCable(CableWidget *w) { + if (incompleteCable) { + removeChild(incompleteCable); + delete incompleteCable; + incompleteCable = NULL; } - - // Once we're done building the list, actually delete them - for (CableWidget *cable : cables) { - removeChild(cable); - delete cable; + if (w) { + addChild(w); + incompleteCable = w; } } +CableWidget *CableContainer::releaseIncompleteCable() { + CableWidget *cw = incompleteCable; + removeChild(incompleteCable); + incompleteCable = NULL; + return cw; +} + CableWidget *CableContainer::getTopCable(PortWidget *port) { for (auto it = children.rbegin(); it != children.rend(); it++) { - CableWidget *cable = dynamic_cast(*it); - assert(cable); + CableWidget *cw = dynamic_cast(*it); + assert(cw); // Ignore incomplete cables - if (!(cable->inputPort && cable->outputPort)) + if (!cw->isComplete()) continue; - if (cable->inputPort == port || cable->outputPort == port) - return cable; + if (cw->inputPort == port || cw->outputPort == port) + return cw; } return NULL; } +json_t *CableContainer::toJson() { + json_t *rootJ = json_array(); + for (Widget *w : children) { + CableWidget *cw = dynamic_cast(w); + assert(cw); + + // Only serialize complete cables + if (!cw->isComplete()) + continue; + + json_array_append_new(rootJ, cw->toJson()); + } + return rootJ; +} + +void CableContainer::fromJson(json_t *rootJ, const std::map &moduleWidgets) { + size_t cableIndex; + json_t *cableJ; + json_array_foreach(rootJ, cableIndex, cableJ) { + CableWidget *cw = new CableWidget; + cw->fromJson(cableJ, moduleWidgets); + if (!cw->isComplete()) { + delete cw; + continue; + } + addCable(cw); + } +} + void CableContainer::draw(NVGcontext *vg) { Widget::draw(vg); // Cable plugs - for (Widget *child : children) { - CableWidget *cable = dynamic_cast(child); - assert(cable); - cable->drawPlugs(vg); + for (Widget *w : children) { + CableWidget *cw = dynamic_cast(w); + assert(cw); + cw->drawPlugs(vg); } } diff --git a/src/app/CableWidget.cpp b/src/app/CableWidget.cpp index bf0b77ad..928de06f 100644 --- a/src/app/CableWidget.cpp +++ b/src/app/CableWidget.cpp @@ -1,6 +1,5 @@ #include "app/CableWidget.hpp" #include "app/Scene.hpp" -#include "engine/Engine.hpp" #include "componentlibrary.hpp" #include "window.hpp" #include "event.hpp" @@ -80,10 +79,6 @@ static const NVGcolor cableColors[] = { nvgRGB(0xc9, 0x18, 0x47), // red nvgRGB(0x0c, 0x8e, 0x15), // green nvgRGB(0x09, 0x86, 0xad), // blue - // nvgRGB(0x44, 0x44, 0x44), // black - // nvgRGB(0x66, 0x66, 0x66), // gray - // nvgRGB(0x88, 0x88, 0x88), // light gray - // nvgRGB(0xaa, 0xaa, 0xaa), // white }; static int lastCableColorId = -1; @@ -91,35 +86,33 @@ static int lastCableColorId = -1; CableWidget::CableWidget() { lastCableColorId = (lastCableColorId + 1) % LENGTHOF(cableColors); color = cableColors[lastCableColorId]; + + cable = new Cable; } CableWidget::~CableWidget() { - outputPort = NULL; - inputPort = NULL; - updateCable(); + delete cable; } -void CableWidget::updateCable() { - if (inputPort && outputPort) { - // Check correct types - assert(inputPort->type == PortWidget::INPUT); - assert(outputPort->type == PortWidget::OUTPUT); +bool CableWidget::isComplete() { + return outputPort && inputPort; +} - if (!cable) { - cable = new Cable; - cable->outputModule = outputPort->module; - cable->outputId = outputPort->portId; - cable->inputModule = inputPort->module; - cable->inputId = inputPort->portId; - app()->engine->addCable(cable); - } +void CableWidget::setOutputPort(PortWidget *outputPort) { + this->outputPort = outputPort; + if (outputPort) { + assert(outputPort->type == PortWidget::OUTPUT); + cable->outputModule = outputPort->module; + cable->outputId = outputPort->portId; } - else { - if (cable) { - app()->engine->removeCable(cable); - delete cable; - cable = NULL; - } +} + +void CableWidget::setInputPort(PortWidget *inputPort) { + this->inputPort = inputPort; + if (inputPort) { + assert(inputPort->type == PortWidget::INPUT); + cable->inputModule = inputPort->module; + cable->inputId = inputPort->portId; } } @@ -131,7 +124,7 @@ math::Vec CableWidget::getOutputPos() { return hoveredOutputPort->getRelativeOffset(hoveredOutputPort->box.zeroPos().getCenter(), app()->scene->rackWidget); } else { - return app()->scene->rackWidget->lastMousePos; + return app()->scene->rackWidget->mousePos; } } @@ -143,18 +136,67 @@ math::Vec CableWidget::getInputPos() { return hoveredInputPort->getRelativeOffset(hoveredInputPort->box.zeroPos().getCenter(), app()->scene->rackWidget); } else { - return app()->scene->rackWidget->lastMousePos; + return app()->scene->rackWidget->mousePos; } } json_t *CableWidget::toJson() { + assert(isComplete()); json_t *rootJ = json_object(); + + // This is just here for fun. It is not used in fromJson() + json_object_set_new(rootJ, "id", json_integer(cable->id)); + + json_object_set_new(rootJ, "outputModuleId", json_integer(cable->outputModule->id)); + json_object_set_new(rootJ, "outputId", json_integer(cable->outputId)); + json_object_set_new(rootJ, "inputModuleId", json_integer(cable->inputModule->id)); + json_object_set_new(rootJ, "inputId", json_integer(cable->inputId)); + std::string s = color::toHexString(color); json_object_set_new(rootJ, "color", json_string(s.c_str())); + return rootJ; } -void CableWidget::fromJson(json_t *rootJ) { +void CableWidget::fromJson(json_t *rootJ, const std::map &moduleWidgets) { + int outputModuleId = json_integer_value(json_object_get(rootJ, "outputModuleId")); + int outputId = json_integer_value(json_object_get(rootJ, "outputId")); + int inputModuleId = json_integer_value(json_object_get(rootJ, "inputModuleId")); + int inputId = json_integer_value(json_object_get(rootJ, "inputId")); + + // Get module widgets + auto outputModuleIt = moduleWidgets.find(outputModuleId); + auto inputModuleIt = moduleWidgets.find(inputModuleId); + if (outputModuleIt == moduleWidgets.end() || inputModuleIt == moduleWidgets.end()) + return; + + ModuleWidget *outputModule = outputModuleIt->second; + ModuleWidget *inputModule = inputModuleIt->second; + + // Set ports + // TODO + if (false /*legacy && legacy <= 1*/) { + // Before 0.6, the index of the "ports" array was the index of the PortWidget in the `outputs` and `inputs` vector. + setOutputPort(outputModule->outputs[outputId]); + setInputPort(inputModule->inputs[inputId]); + } + else { + for (PortWidget *port : outputModule->outputs) { + if (port->portId == outputId) { + setOutputPort(port); + break; + } + } + for (PortWidget *port : inputModule->inputs) { + if (port->portId == inputId) { + setInputPort(port); + break; + } + } + } + if (!isComplete()) + return; + json_t *colorJ = json_object_get(rootJ, "color"); if (colorJ) { // v0.6.0 and earlier patches use JSON objects. Just ignore them if so and use the existing cable color. @@ -167,13 +209,12 @@ void CableWidget::draw(NVGcontext *vg) { float opacity = settings::cableOpacity; float tension = settings::cableTension; - CableWidget *activeCable = app()->scene->rackWidget->cableContainer->activeCable; - if (activeCable) { - // Draw as opaque if the cable is active - if (activeCable == this) - opacity = 1.0; + if (!isComplete()) { + // Draw opaque if the cable is incomplete + opacity = 1.0; } else { + // Draw opaque if mouse is hovering over a connected port PortWidget *hoveredPort = dynamic_cast(app()->event->hoveredWidget); if (hoveredPort && (hoveredPort == outputPort || hoveredPort == inputPort)) opacity = 1.0; @@ -181,6 +222,7 @@ void CableWidget::draw(NVGcontext *vg) { float thickness = 5; if (cable && cable->outputModule) { + // Increase thickness if output port is polyphonic Output *output = &cable->outputModule->outputs[cable->outputId]; if (output->channels != 1) { thickness = 7; @@ -196,23 +238,27 @@ void CableWidget::drawPlugs(NVGcontext *vg) { // TODO Figure out a way to draw plugs first and cables last, and cut the plug portion of the cable off. math::Vec outputPos = getOutputPos(); math::Vec inputPos = getInputPos(); - drawPlug(vg, outputPos, color); - drawPlug(vg, inputPos, color); - // Draw plug light - // TODO - // Only draw this when light is on top of the plug stack - if (outputPort) { - nvgSave(vg); - nvgTranslate(vg, outputPos.x - 4, outputPos.y - 4); - outputPort->plugLight->draw(vg); - nvgRestore(vg); + // Draw plug if the cable is on top, or if the cable is incomplete + if (!isComplete() || app()->scene->rackWidget->cableContainer->getTopCable(outputPort) == this) { + drawPlug(vg, outputPos, color); + if (outputPort) { + // Draw plug light + nvgSave(vg); + nvgTranslate(vg, outputPos.x - 4, outputPos.y - 4); + outputPort->plugLight->draw(vg); + nvgRestore(vg); + } } - if (inputPort) { - nvgSave(vg); - nvgTranslate(vg, inputPos.x - 4, inputPos.y - 4); - inputPort->plugLight->draw(vg); - nvgRestore(vg); + + if (!isComplete() || app()->scene->rackWidget->cableContainer->getTopCable(inputPort) == this) { + drawPlug(vg, inputPos, color); + if (inputPort) { + nvgSave(vg); + nvgTranslate(vg, inputPos.x - 4, inputPos.y - 4); + inputPort->plugLight->draw(vg); + nvgRestore(vg); + } } } diff --git a/src/app/ModuleWidget.cpp b/src/app/ModuleWidget.cpp index 64089859..d2dedd86 100644 --- a/src/app/ModuleWidget.cpp +++ b/src/app/ModuleWidget.cpp @@ -253,7 +253,7 @@ void ModuleWidget::onHoverKey(const event::HoverKey &e) { void ModuleWidget::onDragStart(const event::DragStart &e) { oldPos = box.pos; - dragPos = app()->scene->rackWidget->lastMousePos.minus(box.pos); + dragPos = app()->scene->rackWidget->mousePos.minus(box.pos); } void ModuleWidget::onDragEnd(const event::DragEnd &e) { @@ -270,7 +270,7 @@ void ModuleWidget::onDragEnd(const event::DragEnd &e) { void ModuleWidget::onDragMove(const event::DragMove &e) { if (!settings::lockModules) { math::Rect newBox = box; - newBox.pos = app()->scene->rackWidget->lastMousePos.minus(dragPos); + newBox.pos = app()->scene->rackWidget->mousePos.minus(dragPos); app()->scene->rackWidget->requestModuleBoxNearest(this, newBox); } } @@ -502,10 +502,10 @@ void ModuleWidget::saveDialog() { void ModuleWidget::disconnect() { for (PortWidget *input : inputs) { - app()->scene->rackWidget->cableContainer->removeAllCables(input); + app()->scene->rackWidget->cableContainer->clearPort(input); } for (PortWidget *output : outputs) { - app()->scene->rackWidget->cableContainer->removeAllCables(output); + app()->scene->rackWidget->cableContainer->clearPort(output); } } diff --git a/src/app/PortWidget.cpp b/src/app/PortWidget.cpp index 9f6190bc..46d5d7d1 100644 --- a/src/app/PortWidget.cpp +++ b/src/app/PortWidget.cpp @@ -26,9 +26,8 @@ PortWidget::~PortWidget() { // plugLight is not a child and is thus owned by the PortWidget, so we need to delete it here delete plugLight; // HACK - // See ModuleWidget::~ModuleWidget for description if (module) - app()->scene->rackWidget->cableContainer->removeAllCables(this); + app()->scene->rackWidget->cableContainer->clearPort(this); } void PortWidget::step() { @@ -36,22 +35,22 @@ void PortWidget::step() { return; std::vector values(2); - if (type == INPUT) { - values[0] = module->inputs[portId].plugLights[0].getBrightness(); - values[1] = module->inputs[portId].plugLights[1].getBrightness(); - } - else { + if (type == OUTPUT) { values[0] = module->outputs[portId].plugLights[0].getBrightness(); values[1] = module->outputs[portId].plugLights[1].getBrightness(); } + else { + values[0] = module->inputs[portId].plugLights[0].getBrightness(); + values[1] = module->inputs[portId].plugLights[1].getBrightness(); + } plugLight->setValues(values); } void PortWidget::draw(NVGcontext *vg) { - CableWidget *activeCable = app()->scene->rackWidget->cableContainer->activeCable; - if (activeCable) { + CableWidget *cw = app()->scene->rackWidget->cableContainer->incompleteCable; + if (cw) { // Dim the PortWidget if the active cable cannot plug into this PortWidget - if (type == INPUT ? activeCable->inputPort : activeCable->outputPort) + if (type == OUTPUT ? cw->outputPort : cw->inputPort) nvgGlobalAlpha(vg, 0.5); } Widget::draw(vg); @@ -59,59 +58,87 @@ void PortWidget::draw(NVGcontext *vg) { void PortWidget::onButton(const event::Button &e) { if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_RIGHT) { - app()->scene->rackWidget->cableContainer->removeTopCable(this); - - // HACK - // Update hovered*PortWidget of active cable if applicable - // event::DragEnter eDragEnter; - // onDragEnter(eDragEnter); + CableWidget *cw = app()->scene->rackWidget->cableContainer->getTopCable(this); + if (cw) { + app()->scene->rackWidget->cableContainer->removeCable(cw); + delete cw; + } } e.consume(this); } void PortWidget::onDragStart(const event::DragStart &e) { - // Try to grab cable on top of stack - CableWidget *cable = NULL; + CableWidget *cw = NULL; if (type == OUTPUT && (app()->window->getMods() & WINDOW_MOD_MASK) == WINDOW_MOD_CTRL) { // Keep cable NULL } else { - cable = app()->scene->rackWidget->cableContainer->getTopCable(this); + // Grab cable on top of stack + cw = app()->scene->rackWidget->cableContainer->getTopCable(this); } - if (cable) { - // Disconnect existing cable - (type == INPUT ? cable->inputPort : cable->outputPort) = NULL; - cable->updateCable(); + if (cw) { + // Disconnect and reuse existing cable + app()->scene->rackWidget->cableContainer->removeCable(cw); + if (type == OUTPUT) + cw->setOutputPort(NULL); + else + cw->setInputPort(NULL); } else { // Create a new cable - cable = new CableWidget; - (type == INPUT ? cable->inputPort : cable->outputPort) = this; + cw = new CableWidget; + if (type == OUTPUT) + cw->setOutputPort(this); + else + cw->setInputPort(this); } - app()->scene->rackWidget->cableContainer->setActiveCable(cable); + app()->scene->rackWidget->cableContainer->setIncompleteCable(cw); } void PortWidget::onDragEnd(const event::DragEnd &e) { // FIXME // If the source PortWidget is deleted, this will be called, removing the cable - app()->scene->rackWidget->cableContainer->commitActiveCable(); + CableWidget *cw = app()->scene->rackWidget->cableContainer->releaseIncompleteCable(); + if (cw->isComplete()) { + app()->scene->rackWidget->cableContainer->addCable(cw); + } + else { + delete cw; + } } void PortWidget::onDragDrop(const event::DragDrop &e) { - PortWidget *originPort = dynamic_cast(e.origin); - if (!originPort) - return; + // Reject ports if this is an input port and something is already plugged into it + if (type == INPUT) { + if (app()->scene->rackWidget->cableContainer->getTopCable(this)) + return; + } - setHovered(); + CableWidget *cw = app()->scene->rackWidget->cableContainer->incompleteCable; + if (cw) { + cw->hoveredOutputPort = cw->hoveredInputPort = NULL; + if (type == OUTPUT) + cw->setOutputPort(this); + else + cw->setInputPort(this); + } } void PortWidget::onDragEnter(const event::DragEnter &e) { - PortWidget *originPort = dynamic_cast(e.origin); - if (!originPort) - return; + // Reject ports if this is an input port and something is already plugged into it + if (type == INPUT) { + if (app()->scene->rackWidget->cableContainer->getTopCable(this)) + return; + } - setHovered(); + CableWidget *cw = app()->scene->rackWidget->cableContainer->incompleteCable; + if (cw) { + if (type == OUTPUT) + cw->hoveredOutputPort = this; + else + cw->hoveredInputPort = this; + } } void PortWidget::onDragLeave(const event::DragLeave &e) { @@ -119,23 +146,12 @@ void PortWidget::onDragLeave(const event::DragLeave &e) { if (!originPort) return; - CableWidget *activeCable = app()->scene->rackWidget->cableContainer->activeCable; - if (activeCable) { - (type == INPUT ? activeCable->hoveredInputPort : activeCable->hoveredOutputPort) = NULL; - } -} - -void PortWidget::setHovered() { - // Reject ports if this is an input port and something is already plugged into it - if (type == INPUT) { - CableWidget *topCable = app()->scene->rackWidget->cableContainer->getTopCable(this); - if (topCable) - return; - } - - CableWidget *activeCable = app()->scene->rackWidget->cableContainer->activeCable; - if (activeCable) { - (type == INPUT ? activeCable->hoveredInputPort : activeCable->hoveredOutputPort) = this; + CableWidget *cw = app()->scene->rackWidget->cableContainer->incompleteCable; + if (cw) { + if (type == OUTPUT) + cw->hoveredOutputPort = NULL; + else + cw->hoveredInputPort = NULL; } } diff --git a/src/app/RackScrollWidget.cpp b/src/app/RackScrollWidget.cpp index 3c0ff415..90959ff6 100644 --- a/src/app/RackScrollWidget.cpp +++ b/src/app/RackScrollWidget.cpp @@ -11,7 +11,7 @@ void RackScrollWidget::step() { math::Vec pos = app()->window->mousePos; math::Rect viewport = getViewport(box.zeroPos()); // Scroll rack if dragging cable near the edge of the screen - if (app()->scene->rackWidget->cableContainer->activeCable) { + if (app()->scene->rackWidget->cableContainer->incompleteCable) { float margin = 20.0; float speed = 15.0; if (pos.x <= viewport.pos.x + margin) diff --git a/src/app/RackWidget.cpp b/src/app/RackWidget.cpp index 6215a328..4e24fb01 100644 --- a/src/app/RackWidget.cpp +++ b/src/app/RackWidget.cpp @@ -16,6 +16,30 @@ namespace rack { +static ModuleWidget *moduleFromJson(json_t *moduleJ) { + // Get slugs + json_t *pluginSlugJ = json_object_get(moduleJ, "plugin"); + if (!pluginSlugJ) + return NULL; + json_t *modelSlugJ = json_object_get(moduleJ, "model"); + if (!modelSlugJ) + return NULL; + std::string pluginSlug = json_string_value(pluginSlugJ); + std::string modelSlug = json_string_value(modelSlugJ); + + // Get Model + Model *model = plugin::getModel(pluginSlug, modelSlug); + if (!model) + return NULL; + + // Create ModuleWidget + ModuleWidget *moduleWidget = model->createModuleWidget(); + assert(moduleWidget); + moduleWidget->fromJson(moduleJ); + return moduleWidget; +} + + struct ModuleContainer : Widget { void draw(NVGcontext *vg) override { // Draw shadows behind each ModuleWidget first, so the shadow doesn't overlap the front of other ModuleWidgets. @@ -58,8 +82,6 @@ RackWidget::~RackWidget() { } void RackWidget::clear() { - cableContainer->activeCable = NULL; - cableContainer->clearChildren(); // Remove ModuleWidgets std::list widgets = moduleContainer->children; for (Widget *w : widgets) { @@ -67,27 +89,27 @@ void RackWidget::clear() { assert(moduleWidget); removeModule(moduleWidget); } - - app()->scene->scrollWidget->offset = math::Vec(0, 0); + assert(cableContainer->children.empty()); } void RackWidget::reset() { if (osdialog_message(OSDIALOG_INFO, OSDIALOG_OK_CANCEL, "Clear patch and start over?")) { clear(); + app()->scene->scrollWidget->offset = math::Vec(0, 0); // Fails silently if file does not exist load(asset::user("template.vcv")); - lastPath = ""; + patchPath = ""; } } void RackWidget::loadDialog() { std::string dir; - if (lastPath.empty()) { + if (patchPath.empty()) { dir = asset::user("patches"); system::createDirectory(dir); } else { - dir = string::directory(lastPath); + dir = string::directory(patchPath); } osdialog_filters *filters = osdialog_filters_parse(PATCH_FILTERS.c_str()); @@ -105,12 +127,12 @@ void RackWidget::loadDialog() { }); load(path); - lastPath = path; + patchPath = path; } void RackWidget::saveDialog() { - if (!lastPath.empty()) { - save(lastPath); + if (!patchPath.empty()) { + save(patchPath); } else { saveAsDialog(); @@ -120,13 +142,13 @@ void RackWidget::saveDialog() { void RackWidget::saveAsDialog() { std::string dir; std::string filename; - if (lastPath.empty()) { + if (patchPath.empty()) { dir = asset::user("patches"); system::createDirectory(dir); } else { - dir = string::directory(lastPath); - filename = string::filename(lastPath); + dir = string::directory(patchPath); + filename = string::filename(patchPath); } osdialog_filters *filters = osdialog_filters_parse(PATCH_FILTERS.c_str()); @@ -150,7 +172,7 @@ void RackWidget::saveAsDialog() { } save(pathStr); - lastPath = pathStr; + patchPath = pathStr; } void RackWidget::saveTemplate() { @@ -203,14 +225,15 @@ void RackWidget::load(std::string filename) { }); clear(); + app()->scene->scrollWidget->offset = math::Vec(0, 0); fromJson(rootJ); } void RackWidget::revert() { - if (lastPath.empty()) + if (patchPath.empty()) return; if (osdialog_message(OSDIALOG_INFO, OSDIALOG_OK_CANCEL, "Revert patch to the last saved state?")) { - load(lastPath); + load(patchPath); } } @@ -218,7 +241,7 @@ void RackWidget::disconnect() { if (!osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK_CANCEL, "Remove all patch cables?")) return; - cableContainer->removeAllCables(NULL); + cableContainer->clear(); } json_t *RackWidget::toJson() { @@ -249,34 +272,7 @@ json_t *RackWidget::toJson() { json_object_set_new(rootJ, "modules", modulesJ); // cables - json_t *cablesJ = json_array(); - for (Widget *w : cableContainer->children) { - CableWidget *cableWidget = dynamic_cast(w); - assert(cableWidget); - - PortWidget *outputPort = cableWidget->outputPort; - PortWidget *inputPort = cableWidget->inputPort; - // Only serialize CableWidgets connected on both ends - if (!(outputPort && inputPort)) - continue; - - Cable *cable = cableWidget->cable; - assert(cable); - // cable - json_t *cableJ = cableWidget->toJson(); - - assert(outputPort->module); - assert(inputPort->module); - - json_object_set_new(cableJ, "id", json_integer(cable->id)); - json_object_set_new(cableJ, "outputModuleId", json_integer(outputPort->module->id)); - json_object_set_new(cableJ, "outputId", json_integer(outputPort->portId)); - json_object_set_new(cableJ, "inputModuleId", json_integer(inputPort->module->id)); - json_object_set_new(cableJ, "inputId", json_integer(inputPort->portId)); - - json_array_append_new(cablesJ, cableJ); - } - json_object_set_new(rootJ, "cables", cablesJ); + json_object_set_new(rootJ, "cables", cableContainer->toJson()); return rootJ; } @@ -363,55 +359,8 @@ void RackWidget::fromJson(json_t *rootJ) { // Before 1.0, cables were called wires if (!cablesJ) cablesJ = json_object_get(rootJ, "wires"); - assert(cablesJ); - size_t cableIndex; - json_t *cableJ; - json_array_foreach(cablesJ, cableIndex, cableJ) { - int outputModuleId = json_integer_value(json_object_get(cableJ, "outputModuleId")); - int outputId = json_integer_value(json_object_get(cableJ, "outputId")); - int inputModuleId = json_integer_value(json_object_get(cableJ, "inputModuleId")); - int inputId = json_integer_value(json_object_get(cableJ, "inputId")); - - // Get module widgets - ModuleWidget *outputModuleWidget = moduleWidgets[outputModuleId]; - if (!outputModuleWidget) continue; - ModuleWidget *inputModuleWidget = moduleWidgets[inputModuleId]; - if (!inputModuleWidget) continue; - - // Get port widgets - PortWidget *outputPort = NULL; - PortWidget *inputPort = NULL; - if (legacy && legacy <= 1) { - // Before 0.6, the index of the "ports" array was the index of the PortWidget in the `outputs` and `inputs` vector. - outputPort = outputModuleWidget->outputs[outputId]; - inputPort = inputModuleWidget->inputs[inputId]; - } - else { - for (PortWidget *port : outputModuleWidget->outputs) { - if (port->portId == outputId) { - outputPort = port; - break; - } - } - for (PortWidget *port : inputModuleWidget->inputs) { - if (port->portId == inputId) { - inputPort = port; - break; - } - } - } - if (!outputPort || !inputPort) - continue; - - // Create CableWidget - CableWidget *cableWidget = new CableWidget; - cableWidget->fromJson(cableJ); - cableWidget->outputPort = outputPort; - cableWidget->inputPort = inputPort; - cableWidget->updateCable(); - // Add cable to rack - cableContainer->addChild(cableWidget); - } + if (cablesJ) + cableContainer->fromJson(cablesJ, moduleWidgets); // Display a message if we have something to say if (!message.empty()) { @@ -419,29 +368,6 @@ void RackWidget::fromJson(json_t *rootJ) { } } -ModuleWidget *RackWidget::moduleFromJson(json_t *moduleJ) { - // Get slugs - json_t *pluginSlugJ = json_object_get(moduleJ, "plugin"); - if (!pluginSlugJ) - return NULL; - json_t *modelSlugJ = json_object_get(moduleJ, "model"); - if (!modelSlugJ) - return NULL; - std::string pluginSlug = json_string_value(pluginSlugJ); - std::string modelSlug = json_string_value(modelSlugJ); - - // Get Model - Model *model = plugin::getModel(pluginSlug, modelSlug); - if (!model) - return NULL; - - // Create ModuleWidget - ModuleWidget *moduleWidget = model->createModuleWidget(); - assert(moduleWidget); - moduleWidget->fromJson(moduleJ); - return moduleWidget; -} - void RackWidget::pastePresetClipboard() { const char *moduleJson = glfwGetClipboardString(app()->window->win); if (!moduleJson) { @@ -457,7 +383,7 @@ void RackWidget::pastePresetClipboard() { addModule(moduleWidget); // Set moduleWidget position math::Rect newBox = moduleWidget->box; - newBox.pos = lastMousePos.minus(newBox.size.div(2)); + newBox.pos = mousePos.minus(newBox.size.div(2)); requestModuleBoxNearest(moduleWidget, newBox); } else { @@ -468,17 +394,18 @@ void RackWidget::pastePresetClipboard() { void RackWidget::addModule(ModuleWidget *m) { // Add module to ModuleContainer assert(m); - assert(m->module); moduleContainer->addChild(m); - // Add module to Engine - app()->engine->addModule(m->module); + if (m->module) { + // Add module to Engine + app()->engine->addModule(m->module); + } } void RackWidget::addModuleAtMouse(ModuleWidget *m) { assert(m); // Move module nearest to the mouse position - m->box.pos = lastMousePos.minus(m->box.size.div(2)); + m->box.pos = mousePos.minus(m->box.size.div(2)); requestModuleBoxNearest(m, m->box); addModule(m); } @@ -487,32 +414,37 @@ void RackWidget::removeModule(ModuleWidget *m) { // Disconnect cables m->disconnect(); - // Remove module from Engine - assert(m->module); - app()->engine->removeModule(m->module); + if (m->module) { + // Remove module from Engine + app()->engine->removeModule(m->module); + } // Remove module from ModuleContainer moduleContainer->removeChild(m); } -bool RackWidget::requestModuleBox(ModuleWidget *m, math::Rect box) { - if (box.pos.x < 0 || box.pos.y < 0) +bool RackWidget::requestModuleBox(ModuleWidget *m, math::Rect requestedBox) { + // Check bounds + if (requestedBox.pos.x < 0 || requestedBox.pos.y < 0) return false; - for (Widget *child2 : moduleContainer->children) { - if (m == child2) continue; - if (box.intersects(child2->box)) { + // Check intersection with other modules + for (Widget *m2 : moduleContainer->children) { + if (m == m2) continue; + if (requestedBox.intersects(m2->box)) { return false; } } - m->box = box; + + // Accept requested position + m->box = requestedBox; return true; } -bool RackWidget::requestModuleBoxNearest(ModuleWidget *m, math::Rect box) { +bool RackWidget::requestModuleBoxNearest(ModuleWidget *m, math::Rect requestedBox) { // Create possible positions - int x0 = std::round(box.pos.x / RACK_GRID_WIDTH); - int y0 = std::round(box.pos.y / RACK_GRID_HEIGHT); + int x0 = std::round(requestedBox.pos.x / RACK_GRID_WIDTH); + int y0 = std::round(requestedBox.pos.y / RACK_GRID_HEIGHT); std::vector positions; for (int y = std::max(0, y0 - 8); y < y0 + 8; y++) { for (int x = std::max(0, x0 - 400); x < x0 + 400; x++) { @@ -521,17 +453,18 @@ bool RackWidget::requestModuleBoxNearest(ModuleWidget *m, math::Rect box) { } // Sort possible positions by distance to the requested position - std::sort(positions.begin(), positions.end(), [box](math::Vec a, math::Vec b) { - return a.minus(box.pos).norm() < b.minus(box.pos).norm(); + std::sort(positions.begin(), positions.end(), [requestedBox](math::Vec a, math::Vec b) { + return a.minus(requestedBox.pos).norm() < b.minus(requestedBox.pos).norm(); }); // Find a position that does not collide for (math::Vec position : positions) { - math::Rect newBox = box; + math::Rect newBox = requestedBox; newBox.pos = position; if (requestModuleBox(m, newBox)) return true; } + // We failed to find a box with this brute force algorithm. return false; } @@ -602,12 +535,12 @@ void RackWidget::onHover(const event::Hover &e) { } OpaqueWidget::onHover(e); - lastMousePos = e.pos; + mousePos = e.pos; } void RackWidget::onDragHover(const event::DragHover &e) { OpaqueWidget::onDragHover(e); - lastMousePos = e.pos; + mousePos = e.pos; } void RackWidget::onButton(const event::Button &e) { diff --git a/src/main.cpp b/src/main.cpp index e5789b1a..581bc904 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -93,19 +93,19 @@ int main(int argc, char *argv[]) { settings::save(asset::user("settings.json")); settings::skipLoadOnLaunch = false; if (oldSkipLoadOnLaunch && osdialog_message(OSDIALOG_INFO, OSDIALOG_YES_NO, "Rack has recovered from a crash, possibly caused by a faulty module in your patch. Clear your patch and start over?")) { - app()->scene->rackWidget->lastPath = ""; + app()->scene->rackWidget->patchPath = ""; } else { // Load autosave - std::string oldLastPath = app()->scene->rackWidget->lastPath; + std::string oldLastPath = app()->scene->rackWidget->patchPath; app()->scene->rackWidget->load(asset::user("autosave.vcv")); - app()->scene->rackWidget->lastPath = oldLastPath; + app()->scene->rackWidget->patchPath = oldLastPath; } } else { // Load patch app()->scene->rackWidget->load(patchFile); - app()->scene->rackWidget->lastPath = patchFile; + app()->scene->rackWidget->patchPath = patchFile; } INFO("Initialized app"); diff --git a/src/settings.cpp b/src/settings.cpp index 7fc9c178..b1751359 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -52,9 +52,9 @@ static json_t *settingsToJson() { json_t *sampleRateJ = json_real(app()->engine->getSampleRate()); json_object_set_new(rootJ, "sampleRate", sampleRateJ); - // lastPath - json_t *lastPathJ = json_string(app()->scene->rackWidget->lastPath.c_str()); - json_object_set_new(rootJ, "lastPath", lastPathJ); + // patchPath + json_t *patchPathJ = json_string(app()->scene->rackWidget->patchPath.c_str()); + json_object_set_new(rootJ, "patchPath", patchPathJ); // skipLoadOnLaunch if (skipLoadOnLaunch) { @@ -125,10 +125,10 @@ static void settingsFromJson(json_t *rootJ) { app()->engine->setSampleRate(sampleRate); } - // lastPath - json_t *lastPathJ = json_object_get(rootJ, "lastPath"); - if (lastPathJ) - app()->scene->rackWidget->lastPath = json_string_value(lastPathJ); + // patchPath + json_t *patchPathJ = json_object_get(rootJ, "patchPath"); + if (patchPathJ) + app()->scene->rackWidget->patchPath = json_string_value(patchPathJ); // skipLoadOnLaunch json_t *skipLoadOnLaunchJ = json_object_get(rootJ, "skipLoadOnLaunch"); diff --git a/src/widgets/Widget.cpp b/src/widgets/Widget.cpp index dd781778..786d6844 100644 --- a/src/widgets/Widget.cpp +++ b/src/widgets/Widget.cpp @@ -47,12 +47,14 @@ math::Rect Widget::getViewport(math::Rect r) { } void Widget::addChild(Widget *child) { + assert(child); assert(!child->parent); child->parent = this; children.push_back(child); } void Widget::removeChild(Widget *child) { + assert(child); // Make sure `this` is the child's parent assert(child->parent == this); // Prepare to remove widget from the event state diff --git a/src/window.cpp b/src/window.cpp index ae6f60d2..77757c8b 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -316,9 +316,9 @@ void Window::run() { windowTitle = APP_NAME; windowTitle += " "; windowTitle += APP_VERSION; - if (!app()->scene->rackWidget->lastPath.empty()) { + if (!app()->scene->rackWidget->patchPath.empty()) { windowTitle += " - "; - windowTitle += string::filename(app()->scene->rackWidget->lastPath); + windowTitle += string::filename(app()->scene->rackWidget->patchPath); } if (windowTitle != internal->lastWindowTitle) { glfwSetWindowTitle(win, windowTitle.c_str());