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