diff --git a/include/app/CableWidget.hpp b/include/app/CableWidget.hpp index 9c2efd50..a071f004 100644 --- a/include/app/CableWidget.hpp +++ b/include/app/CableWidget.hpp @@ -10,6 +10,9 @@ namespace rack { namespace app { +struct CableWidget; + + struct PlugWidget : widget::Widget { struct Internal; Internal* internal; @@ -19,8 +22,9 @@ struct PlugWidget : widget::Widget { void step() override; PRIVATE void setColor(NVGcolor color); PRIVATE void setAngle(float angle); - PRIVATE void setPortWidget(PortWidget* portWidget); PRIVATE void setTop(bool top); + CableWidget* getCable(); + engine::Port::Type getType(); }; @@ -41,6 +45,7 @@ struct CableWidget : widget::Widget { CableWidget(); ~CableWidget(); + /** Returns whether cable is connected to 2 ports. */ bool isComplete(); /** Based on the input/output ports, re-creates the cable and removes/adds it to the Engine. */ void updateCable(); @@ -50,6 +55,15 @@ struct CableWidget : widget::Widget { */ void setCable(engine::Cable* cable); engine::Cable* getCable(); + PlugWidget*& getPlug(engine::Port::Type type) { + return type == engine::Port::INPUT ? inputPlug : outputPlug; + } + PortWidget*& getPort(engine::Port::Type type) { + return type == engine::Port::INPUT ? inputPort : outputPort; + } + PortWidget*& getHoveredPort(engine::Port::Type type) { + return type == engine::Port::INPUT ? hoveredInputPort : hoveredOutputPort; + } math::Vec getInputPos(); math::Vec getOutputPos(); void mergeJson(json_t* rootJ); diff --git a/include/app/RackWidget.hpp b/include/app/RackWidget.hpp index 1b62c4e8..dbc6eb58 100644 --- a/include/app/RackWidget.hpp +++ b/include/app/RackWidget.hpp @@ -115,23 +115,29 @@ struct RackWidget : widget::OpaqueWidget { void clearCablesAction(); /** Removes all cables connected to the port */ void clearCablesOnPort(PortWidget* port); - /** Adds a complete cable and adopts ownership. + /** Adds a cable and adopts ownership. */ void addCable(CableWidget* cw); /** Removes cable and releases ownership to caller. */ void removeCable(CableWidget* cw); - CableWidget* getIncompleteCable(); - /** Takes ownership of `cw` and adds it as a child if it isn't already. */ - void setIncompleteCable(CableWidget* cw); - CableWidget* releaseIncompleteCable(); - /** Returns the most recently added complete cable connected to the given Port, i.e. the top of the stack. */ + /** Returns the top incomplete cable. Use getIncompleteCables() instead. */ + DEPRECATED CableWidget* getIncompleteCable(); + /** Returns the topmost plug stacked on the port. */ + PlugWidget* getTopPlug(PortWidget* port); + /** Returns the cable with the topmost plug stacked on the port. */ CableWidget* getTopCable(PortWidget* port); CableWidget* getCable(int64_t cableId); CableWidget* getCable(PortWidget* outputPort, PortWidget* inputPort); + /** Returns all cables, complete and incomplete. */ + std::vector getCables(); + /** Returns all cables attached to 2 ports. */ std::vector getCompleteCables(); - /** Returns all cables attached to port, complete or not. */ + /** Returns all cables attached to less than 2 ports. */ + std::vector getIncompleteCables(); + /** Returns all cables attached to the port, complete or not. */ std::vector getCablesOnPort(PortWidget* port); + /** Returns all complete cables attached to the port. */ std::vector getCompleteCablesOnPort(PortWidget* port); /** Returns but does not advance the next cable color. */ int getNextCableColorId(); diff --git a/src/app/CableWidget.cpp b/src/app/CableWidget.cpp index 4b8aafff..ce3b6b8a 100644 --- a/src/app/CableWidget.cpp +++ b/src/app/CableWidget.cpp @@ -34,17 +34,17 @@ struct PlugLight : componentlibrary::TRedGreenBlueLight { struct PlugWidget::Internal { + CableWidget* cableWidget; + engine::Port::Type type; + /** Initially pointing upward. */ float angle = 0.5f * M_PI; - PortWidget* portWidget = NULL; widget::FramebufferWidget* fb; widget::TransformWidget* plugTransform; TintWidget* plugTint; widget::SvgWidget* plug; - widget::SvgWidget* plugPort; - app::MultiLightWidget* plugLight; }; @@ -87,8 +87,10 @@ PlugWidget::~PlugWidget() { void PlugWidget::step() { std::vector values(3); - if (internal->portWidget && internal->plugLight->isVisible()) { - engine::Port* port = internal->portWidget->getPort(); + + PortWidget* pw = internal->cableWidget->getPort(internal->type); + if (pw && internal->plugLight->isVisible()) { + engine::Port* port = pw->getPort(); if (port) { for (int i = 0; i < 3; i++) { values[i] = port->plugLights[i].getBrightness(); @@ -116,14 +118,18 @@ void PlugWidget::setAngle(float angle) { internal->fb->setDirty(); } -void PlugWidget::setPortWidget(PortWidget* portWidget) { - internal->portWidget = portWidget; -} - void PlugWidget::setTop(bool top) { internal->plugLight->setVisible(top); } +CableWidget* PlugWidget::getCable() { + return internal->cableWidget; +} + +engine::Port::Type PlugWidget::getType() { + return internal->type; +} + struct CableWidget::Internal { }; @@ -134,7 +140,12 @@ CableWidget::CableWidget() { color = color::BLACK_TRANSPARENT; outputPlug = new PlugWidget; + outputPlug->internal->cableWidget = this; + outputPlug->internal->type = engine::Port::OUTPUT; + inputPlug = new PlugWidget; + inputPlug->internal->cableWidget = this; + inputPlug->internal->type = engine::Port::INPUT; } @@ -267,20 +278,18 @@ void CableWidget::step() { colorOpaque.a = 1.f; // Draw output plug - bool outputTop = !isComplete() || APP->scene->rack->getTopCable(outputPort) == this; outputPlug->setPosition(outputPos); - outputPlug->setTop(outputTop); + // bool outputTop = isComplete() && APP->scene->rack->getTopCable(outputPort) == this; + // outputPlug->setTop(outputTop); outputPlug->setAngle(slump.minus(outputPos).arg()); outputPlug->setColor(colorOpaque); - outputPlug->setPortWidget(outputPort); // Draw input plug - bool inputTop = !isComplete() || APP->scene->rack->getTopCable(inputPort) == this; inputPlug->setPosition(inputPos); - inputPlug->setTop(inputTop); + // bool inputTop = isComplete() && APP->scene->rack->getTopCable(inputPort) == this; + // inputPlug->setTop(inputTop); inputPlug->setAngle(slump.minus(inputPos).arg()); inputPlug->setColor(colorOpaque); - inputPlug->setPortWidget(inputPort); Widget::step(); } @@ -329,8 +338,9 @@ void CableWidget::drawLayer(const DrawArgs& args, int layer) { // The endpoints are off-center math::Vec slump = getSlumpPos(outputPos, inputPos); - outputPos = outputPos.plus(slump.minus(outputPos).normalize().mult(13.0)); - inputPos = inputPos.plus(slump.minus(inputPos).normalize().mult(13.0)); + float dist = 14.f; + outputPos = outputPos.plus(slump.minus(outputPos).normalize().mult(dist)); + inputPos = inputPos.plus(slump.minus(inputPos).normalize().mult(dist)); nvgLineCap(args.vg, NVG_ROUND); // Avoids glitches when cable is bent diff --git a/src/app/PortWidget.cpp b/src/app/PortWidget.cpp index e5506e57..01e27af9 100644 --- a/src/app/PortWidget.cpp +++ b/src/app/PortWidget.cpp @@ -48,7 +48,7 @@ struct PortTooltip : ui::Tooltip { text += string::f("%d: ", i + 1); text += string::f("% .3fV", math::normalizeZero(v)); } - // Connected to + // From/To std::vector cables = APP->scene->rack->getCompleteCablesOnPort(portWidget); for (auto it = cables.rbegin(); it != cables.rend(); it++) { CableWidget* cable = *it; @@ -136,6 +136,8 @@ struct PortCableItem : ui::ColorDotMenuItem { ui::Menu* createChildMenu() override { ui::Menu* menu = new ui::Menu; + // menu->addChild(createMenuLabel(string::f("ID: %ld", cw->cable->id))); + for (NVGcolor color : settings::cableColors) { // Include extra leading spaces for the color circle CableColorItem* item = createMenuItem("Set color"); @@ -342,6 +344,12 @@ void PortWidget::step() { void PortWidget::draw(const DrawArgs& args) { + PortWidget* draggedPw = dynamic_cast(APP->event->getDraggedWidget()); + if (draggedPw) { + // TODO + } + // TODO Reimplement this +#if 0 CableWidget* cw = APP->scene->rack->getIncompleteCable(); if (cw) { // Dim the PortWidget if the active cable cannot plug into this PortWidget @@ -349,6 +357,7 @@ void PortWidget::draw(const DrawArgs& args) { nvgTint(args.vg, nvgRGBf(0.33, 0.33, 0.33)); } } +#endif Widget::draw(args); } @@ -426,32 +435,35 @@ void PortWidget::onDragStart(const DragStartEvent& e) { h->setCable(cw); APP->history->push(h); - // Disconnect and reuse existing cable - APP->scene->rack->removeCable(cw); - if (type == engine::Port::OUTPUT) - cw->outputPort = NULL; - else - cw->inputPort = NULL; + // Reuse existing cable + cw->getPort(type) = NULL; cw->updateCable(); } } + // If not using existing cable, create new cable if (!cw) { - // Create a new cable cw = new CableWidget; // Set color cw->color = APP->scene->rack->getNextCableColor(); // Set port - if (type == engine::Port::OUTPUT) - cw->outputPort = this; - else - cw->inputPort = this; + cw->getPort(type) = this; cw->updateCable(); } - APP->scene->rack->setIncompleteCable(cw); + // Add cable to rack if not already added + if (!cw->getParent()) { + APP->scene->rack->addCable(cw); + } + else { + // Move grabbed plug to top of stack + PlugWidget* plug = cw->getPlug(type); + assert(plug); + APP->scene->rack->getPlugContainer()->removeChild(plug); + APP->scene->rack->getPlugContainer()->addChild(plug); + } } @@ -459,20 +471,31 @@ void PortWidget::onDragEnd(const DragEndEvent& e) { if (e.button != GLFW_MOUSE_BUTTON_LEFT) return; - CableWidget* cw = APP->scene->rack->releaseIncompleteCable(); - if (!cw) + std::vector cws = APP->scene->rack->getIncompleteCables(); + if (cws.empty()) return; - if (cw->isComplete()) { - APP->scene->rack->addCable(cw); + history::ComplexAction* h = new history::ComplexAction; - // history::CableAdd - history::CableAdd* h = new history::CableAdd; - h->setCable(cw); - APP->history->push(h); + for (CableWidget* cw : cws) { + if (cw->isComplete()) { + // history::CableAdd + history::CableAdd* hAdd = new history::CableAdd; + hAdd->setCable(cw); + h->push(hAdd); + } + else { + APP->scene->rack->removeCable(cw); + delete cw; + } + } + + // Push history + if (h->isEmpty()) { + delete h; } else { - delete cw; + APP->history->push(h); } } @@ -481,18 +504,28 @@ void PortWidget::onDragDrop(const DragDropEvent& e) { if (e.button != GLFW_MOUSE_BUTTON_LEFT) return; + PortWidget* pwOrigin = dynamic_cast(e.origin); + if (!pwOrigin) + return; + // HACK: Only delete tooltip if we're not (normal) dragging it. - if (e.origin == this) + if (pwOrigin == this) { createTooltip(); + } - CableWidget* cw = APP->scene->rack->getIncompleteCable(); - if (cw) { - cw->hoveredOutputPort = cw->hoveredInputPort = NULL; - if (type == engine::Port::OUTPUT && cw->inputPort && !APP->scene->rack->getCable(this, cw->inputPort)) { - cw->outputPort = this; + for (CableWidget* cw : APP->scene->rack->getIncompleteCables()) { + cw->hoveredOutputPort = NULL; + cw->hoveredInputPort = NULL; + if (type == engine::Port::OUTPUT) { + // Check that similar cable doesn't exist + if (cw->inputPort && !APP->scene->rack->getCable(this, cw->inputPort)) { + cw->outputPort = this; + } } - if (type == engine::Port::INPUT && cw->outputPort && !APP->scene->rack->getCable(cw->outputPort, this)) { - cw->inputPort = this; + else { + if (cw->outputPort && !APP->scene->rack->getCable(cw->outputPort, this)) { + cw->inputPort = this; + } } cw->updateCable(); } @@ -503,18 +536,25 @@ void PortWidget::onDragEnter(const DragEnterEvent& e) { if (e.button != GLFW_MOUSE_BUTTON_LEFT) return; - PortWidget* pw = dynamic_cast(e.origin); - if (pw) { - createTooltip(); - } + // Check if dragging from another port, which implies that a cable is being dragged + PortWidget* pwOrigin = dynamic_cast(e.origin); + if (!pwOrigin) + return; - CableWidget* cw = APP->scene->rack->getIncompleteCable(); - if (cw) { - if (type == engine::Port::OUTPUT && cw->inputPort && !APP->scene->rack->getCable(this, cw->inputPort)) { - cw->hoveredOutputPort = this; + createTooltip(); + + // Make all incomplete cables hover this port + for (CableWidget* cw : APP->scene->rack->getIncompleteCables()) { + if (type == engine::Port::OUTPUT) { + // Check that similar cable doesn't exist + if (cw->inputPort && !APP->scene->rack->getCable(this, cw->inputPort)) { + cw->hoveredOutputPort = this; + } } - if (type == engine::Port::INPUT && cw->outputPort && !APP->scene->rack->getCable(cw->outputPort, this)) { - cw->hoveredInputPort = this; + else { + if (cw->outputPort && !APP->scene->rack->getCable(cw->outputPort, this)) { + cw->hoveredInputPort = this; + } } } } @@ -526,16 +566,12 @@ void PortWidget::onDragLeave(const DragLeaveEvent& e) { if (e.button != GLFW_MOUSE_BUTTON_LEFT) return; - PortWidget* originPort = dynamic_cast(e.origin); - if (!originPort) + PortWidget* pwOrigin = dynamic_cast(e.origin); + if (!pwOrigin) return; - CableWidget* cw = APP->scene->rack->getIncompleteCable(); - if (cw) { - if (type == engine::Port::OUTPUT) - cw->hoveredOutputPort = NULL; - if (type == engine::Port::INPUT) - cw->hoveredInputPort = NULL; + for (CableWidget* cw : APP->scene->rack->getIncompleteCables()) { + cw->getHoveredPort(type) = NULL; } } diff --git a/src/app/RackWidget.cpp b/src/app/RackWidget.cpp index 93563048..a184c745 100644 --- a/src/app/RackWidget.cpp +++ b/src/app/RackWidget.cpp @@ -28,7 +28,6 @@ struct RackWidget::Internal { widget::Widget* moduleContainer = NULL; widget::Widget* plugContainer = NULL; widget::Widget* cableContainer = NULL; - CableWidget* incompleteCable = NULL; int nextCableColorId = 0; /** The last mouse position in the RackWidget */ math::Vec mousePos; @@ -289,6 +288,13 @@ void RackWidget::mergeJson(json_t* rootJ) { json_object_set_new(moduleJ, "pos", posJ); } + // Calculate plug orders + std::map plugOrders; + int plugOrder = 1; + for (Widget* w : internal->plugContainer->children) { + plugOrders[w] = plugOrder++; + } + // cables json_t* cablesJ = json_object_get(rootJ, "cables"); if (!cablesJ) @@ -308,6 +314,20 @@ void RackWidget::mergeJson(json_t* rootJ) { } cw->mergeJson(cableJ); + + // inputPlugOrder + auto plugOrderIt = plugOrders.find(cw->inputPlug); + if (plugOrderIt != plugOrders.end()) { + int inputPlugOrder = plugOrderIt->second; + json_object_set_new(cableJ, "inputPlugOrder", json_integer(inputPlugOrder)); + } + + // outputPlugOrder + plugOrderIt = plugOrders.find(cw->outputPlug); + if (plugOrderIt != plugOrders.end()) { + int outputPlugOrder = plugOrderIt->second; + json_object_set_new(cableJ, "outputPlugOrder", json_integer(outputPlugOrder)); + } } } @@ -369,6 +389,8 @@ void RackWidget::fromJson(json_t* rootJ) { updateExpanders(); + std::map plugOrders; + // cables json_t* cablesJ = json_object_get(rootJ, "cables"); // In <=v0.6, cables were called wires @@ -409,7 +431,24 @@ void RackWidget::fromJson(json_t* rootJ) { continue; } addCable(cw); + + // inputPlugOrder + json_t* inputPlugOrderJ = json_object_get(cableJ, "inputPlugOrder"); + if (inputPlugOrderJ) { + plugOrders[cw->inputPlug] = json_integer_value(inputPlugOrderJ); + } + + // outputPlugOrder + json_t* outputPlugOrderJ = json_object_get(cableJ, "outputPlugOrder"); + if (outputPlugOrderJ) { + plugOrders[cw->outputPlug] = json_integer_value(outputPlugOrderJ); + } } + + // Reorder plugs, approximately O(n log(n) log(n)) + internal->plugContainer->children.sort([&](Widget* w1, Widget* w2) { + return get(plugOrders, w1, 0) < get(plugOrders, w2, 0); + }); } struct PasteJsonResult { @@ -680,7 +719,6 @@ std::vector RackWidget::getModules() { assert(mw); mws.push_back(mw); } - mws.shrink_to_fit(); return mws; } @@ -1010,8 +1048,8 @@ json_t* RackWidget::selectionToJson(bool cables) { if (cables) { // cables json_t* cablesJ = json_array(); + // Only add complete cables to JSON for (CableWidget* cw : getCompleteCables()) { - // Only add cables attached on both ends to selected modules engine::Cable* cable = cw->getCable(); if (!cable || !cable->inputModule || !cable->outputModule) continue; @@ -1394,8 +1432,7 @@ void RackWidget::appendSelectionContextMenu(ui::Menu* menu) { } void RackWidget::clearCables() { - internal->incompleteCable = NULL; - // Since cables manage plugs, all plugs are removed from plugContainer + // Since cables manage plugs, all plugs will be removed from plugContainer internal->cableContainer->clearChildren(); } @@ -1421,61 +1458,46 @@ void RackWidget::clearCablesAction() { void RackWidget::clearCablesOnPort(PortWidget* port) { for (CableWidget* cw : getCablesOnPort(port)) { - // Check if cable is connected to port - if (cw == internal->incompleteCable) { - internal->incompleteCable = NULL; - internal->cableContainer->removeChild(cw); - } - else { - removeCable(cw); - } + removeCable(cw); delete cw; } } void RackWidget::addCable(CableWidget* cw) { - assert(cw->isComplete()); internal->cableContainer->addChild(cw); } void RackWidget::removeCable(CableWidget* cw) { - assert(cw->isComplete()); internal->cableContainer->removeChild(cw); } CableWidget* RackWidget::getIncompleteCable() { - return internal->incompleteCable; -} - -void RackWidget::setIncompleteCable(CableWidget* cw) { - if (internal->incompleteCable) { - internal->cableContainer->removeChild(internal->incompleteCable); - delete internal->incompleteCable; - internal->incompleteCable = NULL; - } - if (cw) { - internal->cableContainer->addChild(cw); - internal->incompleteCable = cw; + for (auto it = internal->cableContainer->children.rbegin(); it != internal->cableContainer->children.rend(); it++) { + CableWidget* cw = dynamic_cast(*it); + assert(cw); + if (!cw->isComplete()) + return cw; } + return NULL; } -CableWidget* RackWidget::releaseIncompleteCable() { - if (!internal->incompleteCable) - return NULL; - - CableWidget* cw = internal->incompleteCable; - internal->cableContainer->removeChild(internal->incompleteCable); - internal->incompleteCable = NULL; - return cw; +PlugWidget* RackWidget::getTopPlug(PortWidget* port) { + assert(port); + for (auto it = internal->plugContainer->children.rbegin(); it != internal->plugContainer->children.rend(); it++) { + PlugWidget* plug = dynamic_cast(*it); + assert(plug); + CableWidget* cw = plug->getCable(); + PortWidget* port2 = cw->getPort(plug->getType()); + if (port2 == port) + return plug; + } + return NULL; } CableWidget* RackWidget::getTopCable(PortWidget* port) { - for (auto it = internal->cableContainer->children.rbegin(); it != internal->cableContainer->children.rend(); it++) { - CableWidget* cw = dynamic_cast(*it); - assert(cw); - if (cw->inputPort == port || cw->outputPort == port) - return cw; - } + PlugWidget* plug = getTopPlug(port); + if (plug) + return plug->getCable(); return NULL; } @@ -1501,8 +1523,20 @@ CableWidget* RackWidget::getCable(PortWidget* outputPort, PortWidget* inputPort) return NULL; } +std::vector RackWidget::getCables() { + std::vector cws; + cws.reserve(internal->cableContainer->children.size()); + for (widget::Widget* w : internal->cableContainer->children) { + CableWidget* cw = dynamic_cast(w); + assert(cw); + cws.push_back(cw); + } + return cws; +} + std::vector RackWidget::getCompleteCables() { std::vector cws; + // Assume that most cables are complete, so pre-allocate and shrink vector. cws.reserve(internal->cableContainer->children.size()); for (widget::Widget* w : internal->cableContainer->children) { CableWidget* cw = dynamic_cast(w); @@ -1514,15 +1548,27 @@ std::vector RackWidget::getCompleteCables() { return cws; } -std::vector RackWidget::getCablesOnPort(PortWidget* port) { - assert(port); +std::vector RackWidget::getIncompleteCables() { std::vector cws; for (widget::Widget* w : internal->cableContainer->children) { CableWidget* cw = dynamic_cast(w); assert(cw); - if (cw->inputPort == port || cw->outputPort == port) { + if (!cw->isComplete()) + cws.push_back(cw); + } + return cws; +} + +std::vector RackWidget::getCablesOnPort(PortWidget* port) { + assert(port); + std::vector cws; + for (widget::Widget* w : internal->plugContainer->children) { + PlugWidget* plug = dynamic_cast(w); + assert(plug); + CableWidget* cw = plug->getCable(); + PortWidget* port2 = cw->getPort(plug->getType()); + if (port2 == port) cws.push_back(cw); - } } return cws; } @@ -1530,14 +1576,15 @@ std::vector RackWidget::getCablesOnPort(PortWidget* port) { std::vector RackWidget::getCompleteCablesOnPort(PortWidget* port) { assert(port); std::vector cws; - for (widget::Widget* w : internal->cableContainer->children) { - CableWidget* cw = dynamic_cast(w); - assert(cw); + for (widget::Widget* w : internal->plugContainer->children) { + PlugWidget* plug = dynamic_cast(w); + assert(plug); + CableWidget* cw = plug->getCable(); if (!cw->isComplete()) continue; - if (cw->inputPort == port || cw->outputPort == port) { + PortWidget* port2 = cw->getPort(plug->getType()); + if (port2 == port) cws.push_back(cw); - } } return cws; }