@@ -1,37 +0,0 @@ | |||||
#pragma once | |||||
#include "app/common.hpp" | |||||
#include "widgets/TransparentWidget.hpp" | |||||
#include "app/CableWidget.hpp" | |||||
#include "app/PortWidget.hpp" | |||||
#include <map> | |||||
namespace rack { | |||||
struct CableContainer : TransparentWidget { | |||||
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 setIncompleteCable(CableWidget *w); | |||||
CableWidget *releaseIncompleteCable(); | |||||
/** Returns the most recently added complete cable connected to the given Port, i.e. the top of the stack */ | |||||
CableWidget *getTopCable(PortWidget *port); | |||||
CableWidget *getCable(int cableId); | |||||
json_t *toJson(); | |||||
void fromJson(json_t *rootJ, const std::map<int, ModuleWidget*> &moduleWidgets); | |||||
void draw(NVGcontext *vg) override; | |||||
}; | |||||
} // namespace rack |
@@ -2,8 +2,9 @@ | |||||
#include "app/common.hpp" | #include "app/common.hpp" | ||||
#include "widgets/OpaqueWidget.hpp" | #include "widgets/OpaqueWidget.hpp" | ||||
#include "widgets/FramebufferWidget.hpp" | #include "widgets/FramebufferWidget.hpp" | ||||
#include "app/CableContainer.hpp" | |||||
#include "app/ModuleWidget.hpp" | #include "app/ModuleWidget.hpp" | ||||
#include "app/CableWidget.hpp" | |||||
#include "app/PortWidget.hpp" | |||||
namespace rack { | namespace rack { | ||||
@@ -12,13 +13,33 @@ namespace rack { | |||||
struct RackWidget : OpaqueWidget { | struct RackWidget : OpaqueWidget { | ||||
FramebufferWidget *rails; | FramebufferWidget *rails; | ||||
Widget *moduleContainer; | Widget *moduleContainer; | ||||
CableContainer *cableContainer; | |||||
Widget *cableContainer; | |||||
CableWidget *incompleteCable = NULL; | |||||
/** The last mouse position in the RackWidget */ | /** The last mouse position in the RackWidget */ | ||||
math::Vec mousePos; | math::Vec mousePos; | ||||
RackWidget(); | RackWidget(); | ||||
~RackWidget(); | ~RackWidget(); | ||||
void step() override; | |||||
void draw(NVGcontext *vg) override; | |||||
void onHover(const event::Hover &e) override; | |||||
void onDragHover(const event::DragHover &e) override; | |||||
void onButton(const event::Button &e) override; | |||||
void onZoom(const event::Zoom &e) override; | |||||
/** Completely clear the rack's modules and cables */ | |||||
void clear(); | |||||
json_t *toJson(); | |||||
void fromJson(json_t *rootJ); | |||||
void pastePresetClipboard(); | |||||
// Module methods | |||||
/** Adds a module and adds it to the Engine | |||||
Ownership rules work like add/removeChild() | |||||
*/ | |||||
void addModule(ModuleWidget *mw); | void addModule(ModuleWidget *mw); | ||||
void addModuleAtMouse(ModuleWidget *mw); | void addModuleAtMouse(ModuleWidget *mw); | ||||
/** Removes the module and transfers ownership to the caller */ | /** Removes the module and transfers ownership to the caller */ | ||||
@@ -29,19 +50,22 @@ struct RackWidget : OpaqueWidget { | |||||
bool requestModuleBoxNearest(ModuleWidget *mw, math::Rect requestedBox); | bool requestModuleBoxNearest(ModuleWidget *mw, math::Rect requestedBox); | ||||
ModuleWidget *getModule(int moduleId); | ModuleWidget *getModule(int moduleId); | ||||
/** Completely clear the rack's modules and cables */ | |||||
void clear(); | |||||
json_t *toJson(); | |||||
void fromJson(json_t *rootJ); | |||||
void pastePresetClipboard(); | |||||
// Cable methods | |||||
void step() override; | |||||
void draw(NVGcontext *vg) override; | |||||
void onHover(const event::Hover &e) override; | |||||
void onDragHover(const event::DragHover &e) override; | |||||
void onButton(const event::Button &e) override; | |||||
void onZoom(const event::Zoom &e) override; | |||||
void clearCables(); | |||||
/** Removes all complete cables connected to the port */ | |||||
void clearCablesOnPort(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 setIncompleteCable(CableWidget *w); | |||||
CableWidget *releaseIncompleteCable(); | |||||
/** Returns the most recently added complete cable connected to the given Port, i.e. the top of the stack */ | |||||
CableWidget *getTopCable(PortWidget *port); | |||||
CableWidget *getCable(int cableId); | |||||
}; | }; | ||||
@@ -64,7 +64,6 @@ | |||||
#include "app/SVGSlider.hpp" | #include "app/SVGSlider.hpp" | ||||
#include "app/SVGSwitch.hpp" | #include "app/SVGSwitch.hpp" | ||||
#include "app/Toolbar.hpp" | #include "app/Toolbar.hpp" | ||||
#include "app/CableContainer.hpp" | |||||
#include "app/CableWidget.hpp" | #include "app/CableWidget.hpp" | ||||
#include "engine/Engine.hpp" | #include "engine/Engine.hpp" | ||||
@@ -1,141 +0,0 @@ | |||||
#include "app/CableContainer.hpp" | |||||
#include "app.hpp" | |||||
#include "engine/Engine.hpp" | |||||
namespace rack { | |||||
CableContainer::~CableContainer() { | |||||
clear(); | |||||
} | |||||
void CableContainer::clear() { | |||||
for (Widget *w : children) { | |||||
CableWidget *cw = dynamic_cast<CableWidget*>(w); | |||||
assert(cw); | |||||
if (cw != incompleteCable) | |||||
app()->engine->removeCable(cw->cable); | |||||
} | |||||
incompleteCable = NULL; | |||||
clearChildren(); | |||||
} | |||||
void CableContainer::clearPort(PortWidget *port) { | |||||
assert(port); | |||||
std::list<Widget*> childrenCopy = children; | |||||
for (Widget *w : childrenCopy) { | |||||
CableWidget *cw = dynamic_cast<CableWidget*>(w); | |||||
assert(cw); | |||||
// Check if cable is connected to port | |||||
if (cw->inputPort == port || cw->outputPort == port) { | |||||
if (cw == incompleteCable) { | |||||
incompleteCable = NULL; | |||||
removeChild(cw); | |||||
} | |||||
else { | |||||
removeCable(cw); | |||||
} | |||||
delete cw; | |||||
} | |||||
} | |||||
} | |||||
void CableContainer::addCable(CableWidget *w) { | |||||
assert(w->isComplete()); | |||||
app()->engine->addCable(w->cable); | |||||
addChild(w); | |||||
} | |||||
void CableContainer::removeCable(CableWidget *w) { | |||||
assert(w->isComplete()); | |||||
app()->engine->removeCable(w->cable); | |||||
removeChild(w); | |||||
} | |||||
void CableContainer::setIncompleteCable(CableWidget *w) { | |||||
if (incompleteCable) { | |||||
removeChild(incompleteCable); | |||||
delete incompleteCable; | |||||
incompleteCable = NULL; | |||||
} | |||||
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 *cw = dynamic_cast<CableWidget*>(*it); | |||||
assert(cw); | |||||
// Ignore incomplete cables | |||||
if (!cw->isComplete()) | |||||
continue; | |||||
if (cw->inputPort == port || cw->outputPort == port) | |||||
return cw; | |||||
} | |||||
return NULL; | |||||
} | |||||
CableWidget *CableContainer::getCable(int cableId) { | |||||
for (Widget *w : children) { | |||||
CableWidget *cw = dynamic_cast<CableWidget*>(w); | |||||
assert(cw); | |||||
if (cw->cable->id == cableId) | |||||
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) { | |||||
// Create a unserialize cable | |||||
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 *w : children) { | |||||
CableWidget *cw = dynamic_cast<CableWidget*>(w); | |||||
assert(cw); | |||||
cw->drawPlugs(vg); | |||||
} | |||||
} | |||||
} // namespace rack |
@@ -240,7 +240,7 @@ void CableWidget::drawPlugs(NVGcontext *vg) { | |||||
math::Vec inputPos = getInputPos(); | math::Vec inputPos = getInputPos(); | ||||
// Draw plug if the cable is on top, or if the cable is incomplete | // Draw plug if the cable is on top, or if the cable is incomplete | ||||
if (!isComplete() || app()->scene->rackWidget->cableContainer->getTopCable(outputPort) == this) { | |||||
if (!isComplete() || app()->scene->rackWidget->getTopCable(outputPort) == this) { | |||||
drawPlug(vg, outputPos, color); | drawPlug(vg, outputPos, color); | ||||
if (outputPort) { | if (outputPort) { | ||||
// Draw plug light | // Draw plug light | ||||
@@ -251,7 +251,7 @@ void CableWidget::drawPlugs(NVGcontext *vg) { | |||||
} | } | ||||
} | } | ||||
if (!isComplete() || app()->scene->rackWidget->cableContainer->getTopCable(inputPort) == this) { | |||||
if (!isComplete() || app()->scene->rackWidget->getTopCable(inputPort) == this) { | |||||
drawPlug(vg, inputPos, color); | drawPlug(vg, inputPos, color); | ||||
if (inputPort) { | if (inputPort) { | ||||
nvgSave(vg); | nvgSave(vg); | ||||
@@ -529,10 +529,10 @@ void ModuleWidget::saveDialog() { | |||||
void ModuleWidget::disconnect() { | void ModuleWidget::disconnect() { | ||||
for (PortWidget *input : inputs) { | for (PortWidget *input : inputs) { | ||||
app()->scene->rackWidget->cableContainer->clearPort(input); | |||||
app()->scene->rackWidget->clearCablesOnPort(input); | |||||
} | } | ||||
for (PortWidget *output : outputs) { | for (PortWidget *output : outputs) { | ||||
app()->scene->rackWidget->cableContainer->clearPort(output); | |||||
app()->scene->rackWidget->clearCablesOnPort(output); | |||||
} | } | ||||
} | } | ||||
@@ -28,7 +28,7 @@ PortWidget::~PortWidget() { | |||||
delete plugLight; | delete plugLight; | ||||
// HACK | // HACK | ||||
if (module) | if (module) | ||||
app()->scene->rackWidget->cableContainer->clearPort(this); | |||||
app()->scene->rackWidget->clearCablesOnPort(this); | |||||
} | } | ||||
void PortWidget::step() { | void PortWidget::step() { | ||||
@@ -48,7 +48,7 @@ void PortWidget::step() { | |||||
} | } | ||||
void PortWidget::draw(NVGcontext *vg) { | void PortWidget::draw(NVGcontext *vg) { | ||||
CableWidget *cw = app()->scene->rackWidget->cableContainer->incompleteCable; | |||||
CableWidget *cw = app()->scene->rackWidget->incompleteCable; | |||||
if (cw) { | if (cw) { | ||||
// Dim the PortWidget if the active cable cannot plug into this PortWidget | // Dim the PortWidget if the active cable cannot plug into this PortWidget | ||||
if (type == OUTPUT ? cw->outputPort : cw->inputPort) | if (type == OUTPUT ? cw->outputPort : cw->inputPort) | ||||
@@ -59,14 +59,14 @@ void PortWidget::draw(NVGcontext *vg) { | |||||
void PortWidget::onButton(const event::Button &e) { | void PortWidget::onButton(const event::Button &e) { | ||||
if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_RIGHT) { | if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_RIGHT) { | ||||
CableWidget *cw = app()->scene->rackWidget->cableContainer->getTopCable(this); | |||||
CableWidget *cw = app()->scene->rackWidget->getTopCable(this); | |||||
if (cw) { | if (cw) { | ||||
// history::CableRemove | // history::CableRemove | ||||
history::CableRemove *h = new history::CableRemove; | history::CableRemove *h = new history::CableRemove; | ||||
h->setCable(cw); | h->setCable(cw); | ||||
app()->history->push(h); | app()->history->push(h); | ||||
app()->scene->rackWidget->cableContainer->removeCable(cw); | |||||
app()->scene->rackWidget->removeCable(cw); | |||||
delete cw; | delete cw; | ||||
} | } | ||||
} | } | ||||
@@ -80,7 +80,7 @@ void PortWidget::onDragStart(const event::DragStart &e) { | |||||
} | } | ||||
else { | else { | ||||
// Grab cable on top of stack | // Grab cable on top of stack | ||||
cw = app()->scene->rackWidget->cableContainer->getTopCable(this); | |||||
cw = app()->scene->rackWidget->getTopCable(this); | |||||
} | } | ||||
if (cw) { | if (cw) { | ||||
@@ -90,7 +90,7 @@ void PortWidget::onDragStart(const event::DragStart &e) { | |||||
app()->history->push(h); | app()->history->push(h); | ||||
// Disconnect and reuse existing cable | // Disconnect and reuse existing cable | ||||
app()->scene->rackWidget->cableContainer->removeCable(cw); | |||||
app()->scene->rackWidget->removeCable(cw); | |||||
if (type == OUTPUT) | if (type == OUTPUT) | ||||
cw->setOutput(NULL); | cw->setOutput(NULL); | ||||
else | else | ||||
@@ -104,15 +104,15 @@ void PortWidget::onDragStart(const event::DragStart &e) { | |||||
else | else | ||||
cw->setInput(this); | cw->setInput(this); | ||||
} | } | ||||
app()->scene->rackWidget->cableContainer->setIncompleteCable(cw); | |||||
app()->scene->rackWidget->setIncompleteCable(cw); | |||||
} | } | ||||
void PortWidget::onDragEnd(const event::DragEnd &e) { | void PortWidget::onDragEnd(const event::DragEnd &e) { | ||||
// FIXME | // FIXME | ||||
// If the source PortWidget is deleted, this will be called, removing the cable | // If the source PortWidget is deleted, this will be called, removing the cable | ||||
CableWidget *cw = app()->scene->rackWidget->cableContainer->releaseIncompleteCable(); | |||||
CableWidget *cw = app()->scene->rackWidget->releaseIncompleteCable(); | |||||
if (cw->isComplete()) { | if (cw->isComplete()) { | ||||
app()->scene->rackWidget->cableContainer->addCable(cw); | |||||
app()->scene->rackWidget->addCable(cw); | |||||
// history::CableAdd | // history::CableAdd | ||||
history::CableAdd *h = new history::CableAdd; | history::CableAdd *h = new history::CableAdd; | ||||
@@ -127,11 +127,11 @@ void PortWidget::onDragEnd(const event::DragEnd &e) { | |||||
void PortWidget::onDragDrop(const event::DragDrop &e) { | void PortWidget::onDragDrop(const event::DragDrop &e) { | ||||
// Reject ports if this is an input port and something is already plugged into it | // Reject ports if this is an input port and something is already plugged into it | ||||
if (type == INPUT) { | if (type == INPUT) { | ||||
if (app()->scene->rackWidget->cableContainer->getTopCable(this)) | |||||
if (app()->scene->rackWidget->getTopCable(this)) | |||||
return; | return; | ||||
} | } | ||||
CableWidget *cw = app()->scene->rackWidget->cableContainer->incompleteCable; | |||||
CableWidget *cw = app()->scene->rackWidget->incompleteCable; | |||||
if (cw) { | if (cw) { | ||||
cw->hoveredOutputPort = cw->hoveredInputPort = NULL; | cw->hoveredOutputPort = cw->hoveredInputPort = NULL; | ||||
if (type == OUTPUT) | if (type == OUTPUT) | ||||
@@ -144,11 +144,11 @@ void PortWidget::onDragDrop(const event::DragDrop &e) { | |||||
void PortWidget::onDragEnter(const event::DragEnter &e) { | void PortWidget::onDragEnter(const event::DragEnter &e) { | ||||
// Reject ports if this is an input port and something is already plugged into it | // Reject ports if this is an input port and something is already plugged into it | ||||
if (type == INPUT) { | if (type == INPUT) { | ||||
if (app()->scene->rackWidget->cableContainer->getTopCable(this)) | |||||
if (app()->scene->rackWidget->getTopCable(this)) | |||||
return; | return; | ||||
} | } | ||||
CableWidget *cw = app()->scene->rackWidget->cableContainer->incompleteCable; | |||||
CableWidget *cw = app()->scene->rackWidget->incompleteCable; | |||||
if (cw) { | if (cw) { | ||||
if (type == OUTPUT) | if (type == OUTPUT) | ||||
cw->hoveredOutputPort = this; | cw->hoveredOutputPort = this; | ||||
@@ -162,7 +162,7 @@ void PortWidget::onDragLeave(const event::DragLeave &e) { | |||||
if (!originPort) | if (!originPort) | ||||
return; | return; | ||||
CableWidget *cw = app()->scene->rackWidget->cableContainer->incompleteCable; | |||||
CableWidget *cw = app()->scene->rackWidget->incompleteCable; | |||||
if (cw) { | if (cw) { | ||||
if (type == OUTPUT) | if (type == OUTPUT) | ||||
cw->hoveredOutputPort = NULL; | cw->hoveredOutputPort = NULL; | ||||
@@ -11,7 +11,7 @@ void RackScrollWidget::step() { | |||||
math::Vec pos = app()->window->mousePos; | math::Vec pos = app()->window->mousePos; | ||||
math::Rect viewport = getViewport(box.zeroPos()); | math::Rect viewport = getViewport(box.zeroPos()); | ||||
// Scroll rack if dragging cable near the edge of the screen | // Scroll rack if dragging cable near the edge of the screen | ||||
if (app()->scene->rackWidget->cableContainer->incompleteCable) { | |||||
if (app()->scene->rackWidget->incompleteCable) { | |||||
float margin = 20.0; | float margin = 20.0; | ||||
float speed = 15.0; | float speed = 15.0; | ||||
if (pos.x <= viewport.pos.x + margin) | if (pos.x <= viewport.pos.x + margin) | ||||
@@ -1,4 +1,5 @@ | |||||
#include "app/RackWidget.hpp" | #include "app/RackWidget.hpp" | ||||
#include "widgets/TransparentWidget.hpp" | |||||
#include "app/RackRail.hpp" | #include "app/RackRail.hpp" | ||||
#include "app/Scene.hpp" | #include "app/Scene.hpp" | ||||
#include "app/ModuleBrowser.hpp" | #include "app/ModuleBrowser.hpp" | ||||
@@ -44,12 +45,11 @@ struct ModuleContainer : Widget { | |||||
void draw(NVGcontext *vg) override { | void draw(NVGcontext *vg) override { | ||||
// Draw shadows behind each ModuleWidget first, so the shadow doesn't overlap the front of other ModuleWidgets. | // Draw shadows behind each ModuleWidget first, so the shadow doesn't overlap the front of other ModuleWidgets. | ||||
for (Widget *child : children) { | for (Widget *child : children) { | ||||
if (!child->visible) | |||||
continue; | |||||
nvgSave(vg); | |||||
nvgTranslate(vg, child->box.pos.x, child->box.pos.y); | |||||
ModuleWidget *w = dynamic_cast<ModuleWidget*>(child); | ModuleWidget *w = dynamic_cast<ModuleWidget*>(child); | ||||
assert(w); | assert(w); | ||||
nvgSave(vg); | |||||
nvgTranslate(vg, child->box.pos.x, child->box.pos.y); | |||||
w->drawShadow(vg); | w->drawShadow(vg); | ||||
nvgRestore(vg); | nvgRestore(vg); | ||||
} | } | ||||
@@ -59,6 +59,20 @@ struct ModuleContainer : Widget { | |||||
}; | }; | ||||
struct CableContainer : TransparentWidget { | |||||
void draw(NVGcontext *vg) override { | |||||
Widget::draw(vg); | |||||
// Draw cable plugs | |||||
for (Widget *w : children) { | |||||
CableWidget *cw = dynamic_cast<CableWidget*>(w); | |||||
assert(cw); | |||||
cw->drawPlugs(vg); | |||||
} | |||||
} | |||||
}; | |||||
RackWidget::RackWidget() { | RackWidget::RackWidget() { | ||||
rails = new FramebufferWidget; | rails = new FramebufferWidget; | ||||
rails->box.size = math::Vec(); | rails->box.size = math::Vec(); | ||||
@@ -81,7 +95,81 @@ RackWidget::~RackWidget() { | |||||
clear(); | clear(); | ||||
} | } | ||||
void RackWidget::step() { | |||||
// Expand size to fit modules | |||||
math::Vec moduleSize = moduleContainer->getChildrenBoundingBox().getBottomRight(); | |||||
// We assume that the size is reset by a parent before calling step(). Otherwise it will grow unbounded. | |||||
box.size = box.size.max(moduleSize); | |||||
// Adjust size and position of rails | |||||
Widget *rail = rails->children.front(); | |||||
math::Rect bound = getViewport(math::Rect(math::Vec(), box.size)); | |||||
if (!rails->box.contains(bound)) { | |||||
math::Vec cellMargin = math::Vec(20, 1); | |||||
rails->box.pos = bound.pos.div(RACK_GRID_SIZE).floor().minus(cellMargin).mult(RACK_GRID_SIZE); | |||||
rails->box.size = bound.size.plus(cellMargin.mult(RACK_GRID_SIZE).mult(2)); | |||||
rails->dirty = true; | |||||
rail->box.size = rails->box.size; | |||||
} | |||||
Widget::step(); | |||||
} | |||||
void RackWidget::draw(NVGcontext *vg) { | |||||
Widget::draw(vg); | |||||
} | |||||
void RackWidget::onHover(const event::Hover &e) { | |||||
// Scroll with arrow keys | |||||
float arrowSpeed = 30.0; | |||||
if ((app()->window->getMods() & WINDOW_MOD_MASK) == (WINDOW_MOD_CTRL |GLFW_MOD_SHIFT)) | |||||
arrowSpeed /= 16.0; | |||||
else if ((app()->window->getMods() & WINDOW_MOD_MASK) == WINDOW_MOD_CTRL) | |||||
arrowSpeed *= 4.0; | |||||
else if ((app()->window->getMods() & WINDOW_MOD_MASK) == GLFW_MOD_SHIFT) | |||||
arrowSpeed /= 4.0; | |||||
ScrollWidget *scrollWidget = app()->scene->scrollWidget; | |||||
if (glfwGetKey(app()->window->win, GLFW_KEY_LEFT) == GLFW_PRESS) { | |||||
scrollWidget->offset.x -= arrowSpeed; | |||||
} | |||||
if (glfwGetKey(app()->window->win, GLFW_KEY_RIGHT) == GLFW_PRESS) { | |||||
scrollWidget->offset.x += arrowSpeed; | |||||
} | |||||
if (glfwGetKey(app()->window->win, GLFW_KEY_UP) == GLFW_PRESS) { | |||||
scrollWidget->offset.y -= arrowSpeed; | |||||
} | |||||
if (glfwGetKey(app()->window->win, GLFW_KEY_DOWN) == GLFW_PRESS) { | |||||
scrollWidget->offset.y += arrowSpeed; | |||||
} | |||||
OpaqueWidget::onHover(e); | |||||
mousePos = e.pos; | |||||
} | |||||
void RackWidget::onDragHover(const event::DragHover &e) { | |||||
OpaqueWidget::onDragHover(e); | |||||
mousePos = e.pos; | |||||
} | |||||
void RackWidget::onButton(const event::Button &e) { | |||||
OpaqueWidget::onButton(e); | |||||
if (e.getConsumed() == this) { | |||||
if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_RIGHT) { | |||||
app()->scene->moduleBrowser->visible = true; | |||||
} | |||||
} | |||||
} | |||||
void RackWidget::onZoom(const event::Zoom &e) { | |||||
rails->box.size = math::Vec(); | |||||
OpaqueWidget::onZoom(e); | |||||
} | |||||
void RackWidget::clear() { | void RackWidget::clear() { | ||||
// This isn't required because removing all ModuleWidgets should remove all cables, but do it just in case. | |||||
clearCables(); | |||||
// Remove ModuleWidgets | // Remove ModuleWidgets | ||||
std::list<Widget*> widgets = moduleContainer->children; | std::list<Widget*> widgets = moduleContainer->children; | ||||
for (Widget *w : widgets) { | for (Widget *w : widgets) { | ||||
@@ -89,7 +177,6 @@ void RackWidget::clear() { | |||||
assert(moduleWidget); | assert(moduleWidget); | ||||
removeModule(moduleWidget); | removeModule(moduleWidget); | ||||
} | } | ||||
assert(cableContainer->children.empty()); | |||||
} | } | ||||
json_t *RackWidget::toJson() { | json_t *RackWidget::toJson() { | ||||
@@ -116,7 +203,18 @@ json_t *RackWidget::toJson() { | |||||
json_object_set_new(rootJ, "modules", modulesJ); | json_object_set_new(rootJ, "modules", modulesJ); | ||||
// cables | // cables | ||||
json_object_set_new(rootJ, "cables", cableContainer->toJson()); | |||||
json_t *cablesJ = json_array(); | |||||
for (Widget *w : cableContainer->children) { | |||||
CableWidget *cw = dynamic_cast<CableWidget*>(w); | |||||
assert(cw); | |||||
// Only serialize complete cables | |||||
if (!cw->isComplete()) | |||||
continue; | |||||
json_array_append_new(cablesJ, cw->toJson()); | |||||
} | |||||
json_object_set_new(rootJ, "cables", cablesJ); | |||||
return rootJ; | return rootJ; | ||||
} | } | ||||
@@ -174,8 +272,19 @@ void RackWidget::fromJson(json_t *rootJ) { | |||||
// Before 1.0, cables were called wires | // Before 1.0, cables were called wires | ||||
if (!cablesJ) | if (!cablesJ) | ||||
cablesJ = json_object_get(rootJ, "wires"); | cablesJ = json_object_get(rootJ, "wires"); | ||||
if (cablesJ) | |||||
cableContainer->fromJson(cablesJ, moduleWidgets); | |||||
assert(cablesJ); | |||||
size_t cableIndex; | |||||
json_t *cableJ; | |||||
json_array_foreach(cablesJ, cableIndex, cableJ) { | |||||
// Create a unserialize cable | |||||
CableWidget *cw = new CableWidget; | |||||
cw->fromJson(cableJ, moduleWidgets); | |||||
if (!cw->isComplete()) { | |||||
delete cw; | |||||
continue; | |||||
} | |||||
addCable(cw); | |||||
} | |||||
} | } | ||||
void RackWidget::pastePresetClipboard() { | void RackWidget::pastePresetClipboard() { | ||||
@@ -288,76 +397,90 @@ ModuleWidget *RackWidget::getModule(int moduleId) { | |||||
return NULL; | return NULL; | ||||
} | } | ||||
void RackWidget::step() { | |||||
// Expand size to fit modules | |||||
math::Vec moduleSize = moduleContainer->getChildrenBoundingBox().getBottomRight(); | |||||
// We assume that the size is reset by a parent before calling step(). Otherwise it will grow unbounded. | |||||
box.size = box.size.max(moduleSize); | |||||
// Adjust size and position of rails | |||||
Widget *rail = rails->children.front(); | |||||
math::Rect bound = getViewport(math::Rect(math::Vec(), box.size)); | |||||
if (!rails->box.contains(bound)) { | |||||
math::Vec cellMargin = math::Vec(20, 1); | |||||
rails->box.pos = bound.pos.div(RACK_GRID_SIZE).floor().minus(cellMargin).mult(RACK_GRID_SIZE); | |||||
rails->box.size = bound.size.plus(cellMargin.mult(RACK_GRID_SIZE).mult(2)); | |||||
rails->dirty = true; | |||||
rail->box.size = rails->box.size; | |||||
void RackWidget::clearCables() { | |||||
for (Widget *w : cableContainer->children) { | |||||
CableWidget *cw = dynamic_cast<CableWidget*>(w); | |||||
assert(cw); | |||||
if (cw != incompleteCable) | |||||
app()->engine->removeCable(cw->cable); | |||||
} | } | ||||
incompleteCable = NULL; | |||||
cableContainer->clearChildren(); | |||||
} | |||||
Widget::step(); | |||||
void RackWidget::clearCablesOnPort(PortWidget *port) { | |||||
assert(port); | |||||
std::list<Widget*> childrenCopy = cableContainer->children; | |||||
for (Widget *w : childrenCopy) { | |||||
CableWidget *cw = dynamic_cast<CableWidget*>(w); | |||||
assert(cw); | |||||
// Check if cable is connected to port | |||||
if (cw->inputPort == port || cw->outputPort == port) { | |||||
if (cw == incompleteCable) { | |||||
incompleteCable = NULL; | |||||
cableContainer->removeChild(cw); | |||||
} | |||||
else { | |||||
removeCable(cw); | |||||
} | |||||
delete cw; | |||||
} | |||||
} | |||||
} | } | ||||
void RackWidget::draw(NVGcontext *vg) { | |||||
Widget::draw(vg); | |||||
void RackWidget::addCable(CableWidget *w) { | |||||
assert(w->isComplete()); | |||||
app()->engine->addCable(w->cable); | |||||
cableContainer->addChild(w); | |||||
} | } | ||||
void RackWidget::onHover(const event::Hover &e) { | |||||
// Scroll with arrow keys | |||||
float arrowSpeed = 30.0; | |||||
if ((app()->window->getMods() & WINDOW_MOD_MASK) == (WINDOW_MOD_CTRL |GLFW_MOD_SHIFT)) | |||||
arrowSpeed /= 16.0; | |||||
else if ((app()->window->getMods() & WINDOW_MOD_MASK) == WINDOW_MOD_CTRL) | |||||
arrowSpeed *= 4.0; | |||||
else if ((app()->window->getMods() & WINDOW_MOD_MASK) == GLFW_MOD_SHIFT) | |||||
arrowSpeed /= 4.0; | |||||
void RackWidget::removeCable(CableWidget *w) { | |||||
assert(w->isComplete()); | |||||
app()->engine->removeCable(w->cable); | |||||
cableContainer->removeChild(w); | |||||
} | |||||
ScrollWidget *scrollWidget = app()->scene->scrollWidget; | |||||
if (glfwGetKey(app()->window->win, GLFW_KEY_LEFT) == GLFW_PRESS) { | |||||
scrollWidget->offset.x -= arrowSpeed; | |||||
} | |||||
if (glfwGetKey(app()->window->win, GLFW_KEY_RIGHT) == GLFW_PRESS) { | |||||
scrollWidget->offset.x += arrowSpeed; | |||||
void RackWidget::setIncompleteCable(CableWidget *w) { | |||||
if (incompleteCable) { | |||||
cableContainer->removeChild(incompleteCable); | |||||
delete incompleteCable; | |||||
incompleteCable = NULL; | |||||
} | } | ||||
if (glfwGetKey(app()->window->win, GLFW_KEY_UP) == GLFW_PRESS) { | |||||
scrollWidget->offset.y -= arrowSpeed; | |||||
} | |||||
if (glfwGetKey(app()->window->win, GLFW_KEY_DOWN) == GLFW_PRESS) { | |||||
scrollWidget->offset.y += arrowSpeed; | |||||
if (w) { | |||||
cableContainer->addChild(w); | |||||
incompleteCable = w; | |||||
} | } | ||||
OpaqueWidget::onHover(e); | |||||
mousePos = e.pos; | |||||
} | } | ||||
void RackWidget::onDragHover(const event::DragHover &e) { | |||||
OpaqueWidget::onDragHover(e); | |||||
mousePos = e.pos; | |||||
CableWidget *RackWidget::releaseIncompleteCable() { | |||||
CableWidget *cw = incompleteCable; | |||||
cableContainer->removeChild(incompleteCable); | |||||
incompleteCable = NULL; | |||||
return cw; | |||||
} | } | ||||
void RackWidget::onButton(const event::Button &e) { | |||||
OpaqueWidget::onButton(e); | |||||
if (e.getConsumed() == this) { | |||||
if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_RIGHT) { | |||||
app()->scene->moduleBrowser->visible = true; | |||||
} | |||||
CableWidget *RackWidget::getTopCable(PortWidget *port) { | |||||
for (auto it = cableContainer->children.rbegin(); it != cableContainer->children.rend(); it++) { | |||||
CableWidget *cw = dynamic_cast<CableWidget*>(*it); | |||||
assert(cw); | |||||
// Ignore incomplete cables | |||||
if (!cw->isComplete()) | |||||
continue; | |||||
if (cw->inputPort == port || cw->outputPort == port) | |||||
return cw; | |||||
} | } | ||||
return NULL; | |||||
} | } | ||||
void RackWidget::onZoom(const event::Zoom &e) { | |||||
rails->box.size = math::Vec(); | |||||
OpaqueWidget::onZoom(e); | |||||
CableWidget *RackWidget::getCable(int cableId) { | |||||
for (Widget *w : cableContainer->children) { | |||||
CableWidget *cw = dynamic_cast<CableWidget*>(w); | |||||
assert(cw); | |||||
if (cw->cable->id == cableId) | |||||
return cw; | |||||
} | |||||
return NULL; | |||||
} | } | ||||
@@ -124,8 +124,8 @@ void CableAdd::setCable(CableWidget *cw) { | |||||
} | } | ||||
void CableAdd::undo() { | void CableAdd::undo() { | ||||
CableWidget *cw = app()->scene->rackWidget->cableContainer->getCable(cableId); | |||||
app()->scene->rackWidget->cableContainer->removeCable(cw); | |||||
CableWidget *cw = app()->scene->rackWidget->getCable(cableId); | |||||
app()->scene->rackWidget->removeCable(cw); | |||||
delete cw; | delete cw; | ||||
} | } | ||||
@@ -147,7 +147,7 @@ void CableAdd::redo() { | |||||
cw->color = color; | cw->color = color; | ||||
app()->scene->rackWidget->cableContainer->addCable(cw); | |||||
app()->scene->rackWidget->addCable(cw); | |||||
} | } | ||||
@@ -167,7 +167,7 @@ void PatchManager::disconnectDialog() { | |||||
if (!osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK_CANCEL, "Remove all patch cables?")) | if (!osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK_CANCEL, "Remove all patch cables?")) | ||||
return; | return; | ||||
app()->scene->rackWidget->cableContainer->clear(); | |||||
app()->scene->rackWidget->clear(); | |||||
} | } | ||||
json_t *PatchManager::toJson() { | json_t *PatchManager::toJson() { | ||||