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