Browse Source

Allow multiple incomplete cables. Grabbing a plug moves it to the top. Serialize plug order to patch.

tags/v2.6.0
Andrew Belt 6 months ago
parent
commit
d8edf64b8a
5 changed files with 236 additions and 123 deletions
  1. +15
    -1
      include/app/CableWidget.hpp
  2. +13
    -7
      include/app/RackWidget.hpp
  3. +27
    -17
      src/app/CableWidget.cpp
  4. +84
    -48
      src/app/PortWidget.cpp
  5. +97
    -50
      src/app/RackWidget.cpp

+ 15
- 1
include/app/CableWidget.hpp View File

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


+ 13
- 7
include/app/RackWidget.hpp View File

@@ -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<CableWidget*> getCables();
/** Returns all cables attached to 2 ports. */
std::vector<CableWidget*> getCompleteCables();
/** Returns all cables attached to port, complete or not. */
/** Returns all cables attached to less than 2 ports. */
std::vector<CableWidget*> getIncompleteCables();
/** Returns all cables attached to the port, complete or not. */
std::vector<CableWidget*> getCablesOnPort(PortWidget* port);
/** Returns all complete cables attached to the port. */
std::vector<CableWidget*> getCompleteCablesOnPort(PortWidget* port);
/** Returns but does not advance the next cable color. */
int getNextCableColorId();


+ 27
- 17
src/app/CableWidget.cpp View File

@@ -34,17 +34,17 @@ struct PlugLight : componentlibrary::TRedGreenBlueLight<app::MultiLightWidget> {


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<float> 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


+ 84
- 48
src/app/PortWidget.cpp View File

@@ -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<CableWidget*> 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<CableColorItem>("Set color");
@@ -342,6 +344,12 @@ void PortWidget::step() {


void PortWidget::draw(const DrawArgs& args) {
PortWidget* draggedPw = dynamic_cast<PortWidget*>(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<CableWidget*> 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<PortWidget*>(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<PortWidget*>(e.origin);
if (pw) {
createTooltip();
}
// Check if dragging from another port, which implies that a cable is being dragged
PortWidget* pwOrigin = dynamic_cast<PortWidget*>(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<PortWidget*>(e.origin);
if (!originPort)
PortWidget* pwOrigin = dynamic_cast<PortWidget*>(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;
}
}



+ 97
- 50
src/app/RackWidget.cpp View File

@@ -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<Widget*, int> 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<Widget*, int> 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<ModuleWidget*> 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<CableWidget*>(*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<PlugWidget*>(*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<CableWidget*>(*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<CableWidget*> RackWidget::getCables() {
std::vector<CableWidget*> cws;
cws.reserve(internal->cableContainer->children.size());
for (widget::Widget* w : internal->cableContainer->children) {
CableWidget* cw = dynamic_cast<CableWidget*>(w);
assert(cw);
cws.push_back(cw);
}
return cws;
}

std::vector<CableWidget*> RackWidget::getCompleteCables() {
std::vector<CableWidget*> 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<CableWidget*>(w);
@@ -1514,15 +1548,27 @@ std::vector<CableWidget*> RackWidget::getCompleteCables() {
return cws;
}

std::vector<CableWidget*> RackWidget::getCablesOnPort(PortWidget* port) {
assert(port);
std::vector<CableWidget*> RackWidget::getIncompleteCables() {
std::vector<CableWidget*> cws;
for (widget::Widget* w : internal->cableContainer->children) {
CableWidget* cw = dynamic_cast<CableWidget*>(w);
assert(cw);
if (cw->inputPort == port || cw->outputPort == port) {
if (!cw->isComplete())
cws.push_back(cw);
}
return cws;
}

std::vector<CableWidget*> RackWidget::getCablesOnPort(PortWidget* port) {
assert(port);
std::vector<CableWidget*> cws;
for (widget::Widget* w : internal->plugContainer->children) {
PlugWidget* plug = dynamic_cast<PlugWidget*>(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<CableWidget*> RackWidget::getCablesOnPort(PortWidget* port) {
std::vector<CableWidget*> RackWidget::getCompleteCablesOnPort(PortWidget* port) {
assert(port);
std::vector<CableWidget*> cws;
for (widget::Widget* w : internal->cableContainer->children) {
CableWidget* cw = dynamic_cast<CableWidget*>(w);
assert(cw);
for (widget::Widget* w : internal->plugContainer->children) {
PlugWidget* plug = dynamic_cast<PlugWidget*>(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;
}


Loading…
Cancel
Save