Browse Source

Big cable refactor.

tags/v1.0.0
Andrew Belt 6 years ago
parent
commit
5266641777
15 changed files with 387 additions and 352 deletions
  1. +16
    -6
      include/app/CableContainer.hpp
  2. +7
    -4
      include/app/CableWidget.hpp
  3. +3
    -4
      include/app/PortWidget.hpp
  4. +6
    -6
      include/app/RackWidget.hpp
  5. +2
    -2
      src/Core/Blank.cpp
  6. +97
    -71
      src/app/CableContainer.cpp
  7. +96
    -50
      src/app/CableWidget.cpp
  8. +4
    -4
      src/app/ModuleWidget.cpp
  9. +68
    -52
      src/app/PortWidget.cpp
  10. +1
    -1
      src/app/RackScrollWidget.cpp
  11. +72
    -139
      src/app/RackWidget.cpp
  12. +4
    -4
      src/main.cpp
  13. +7
    -7
      src/settings.cpp
  14. +2
    -0
      src/widgets/Widget.cpp
  15. +2
    -2
      src/window.cpp

+ 16
- 6
include/app/CableContainer.hpp View File

@@ -3,21 +3,31 @@
#include "widgets/TransparentWidget.hpp"
#include "app/CableWidget.hpp"
#include "app/PortWidget.hpp"
#include <map>


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<int, ModuleWidget*> &moduleWidgets);
void draw(NVGcontext *vg) override;
};



+ 7
- 4
include/app/CableWidget.hpp View File

@@ -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 <map>


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<int, ModuleWidget*> &moduleWidgets);
void draw(NVGcontext *vg) override;
void drawPlugs(NVGcontext *vg);
};


+ 3
- 4
include/app/PortWidget.hpp View File

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




+ 6
- 6
include/app/RackWidget.hpp View File

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


+ 2
- 2
src/Core/Blank.cpp View File

@@ -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<ModuleWidget>();
originalBox = m->box;
}
void onDragMove(const event::DragMove &e) override {
ModuleWidget *m = getAncestorOfType<ModuleWidget>();

float newDragX = app()->scene->rackWidget->lastMousePos.x;
float newDragX = app()->scene->rackWidget->mousePos.x;
float deltaX = newDragX - dragX;

Rect newBox = originalBox;


+ 97
- 71
src/app/CableContainer.cpp View File

@@ -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<CableWidget*>(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<CableWidget*> cables;
for (Widget *w : children) {
CableWidget *cw = dynamic_cast<CableWidget*>(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<CableWidget*> cables;
void CableContainer::removeCable(CableWidget *w) {
assert(w->isComplete());
app()->engine->removeCable(w->cable);
removeChild(w);
}

for (Widget *child : children) {
CableWidget *cable = dynamic_cast<CableWidget*>(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<CableWidget*>(*it);
assert(cable);
CableWidget *cw = dynamic_cast<CableWidget*>(*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<CableWidget*>(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<int, ModuleWidget*> &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<CableWidget*>(child);
assert(cable);
cable->drawPlugs(vg);
for (Widget *w : children) {
CableWidget *cw = dynamic_cast<CableWidget*>(w);
assert(cw);
cw->drawPlugs(vg);
}
}



+ 96
- 50
src/app/CableWidget.cpp View File

@@ -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<int, ModuleWidget*> &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<PortWidget*>(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);
}
}
}



+ 4
- 4
src/app/ModuleWidget.cpp View File

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



+ 68
- 52
src/app/PortWidget.cpp View File

@@ -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<float> 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<PortWidget*>(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<PortWidget*>(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;
}
}



+ 1
- 1
src/app/RackScrollWidget.cpp View File

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


+ 72
- 139
src/app/RackWidget.cpp View File

@@ -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<Widget*> 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<CableWidget*>(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<math::Vec> 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) {


+ 4
- 4
src/main.cpp View File

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



+ 7
- 7
src/settings.cpp View File

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


+ 2
- 0
src/widgets/Widget.cpp View File

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


+ 2
- 2
src/window.cpp View File

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


Loading…
Cancel
Save