#include #include #include #include #include #include #include #include #include #include namespace rack { namespace app { struct CableWidget::Internal { std::shared_ptr plugPortSvg; }; CableWidget::CableWidget() { internal = new Internal; color = color::BLACK_TRANSPARENT; internal->plugPortSvg = Svg::load(asset::system("res/ComponentLibrary/PlugPort.svg")); } CableWidget::~CableWidget() { setCable(NULL); delete internal; } void CableWidget::setNextCableColor() { if (!settings::cableColors.empty()) { int id = APP->scene->rack->nextCableColorId++; APP->scene->rack->nextCableColorId %= settings::cableColors.size(); color = settings::cableColors[id]; } } bool CableWidget::isComplete() { return outputPort && inputPort; } void CableWidget::updateCable() { if (cable) { APP->engine->removeCable(cable); delete cable; cable = NULL; } if (inputPort && outputPort) { cable = new engine::Cable; cable->inputModule = inputPort->module; cable->inputId = inputPort->portId; cable->outputModule = outputPort->module; cable->outputId = outputPort->portId; APP->engine->addCable(cable); } } void CableWidget::setCable(engine::Cable* cable) { if (this->cable) { APP->engine->removeCable(this->cable); delete this->cable; this->cable = NULL; } this->cable = cable; if (cable) { app::ModuleWidget* outputModule = APP->scene->rack->getModule(cable->outputModule->id); assert(outputModule); outputPort = outputModule->getOutput(cable->outputId); assert(outputPort); app::ModuleWidget* inputModule = APP->scene->rack->getModule(cable->inputModule->id); assert(inputModule); inputPort = inputModule->getInput(cable->inputId); assert(inputPort); } else { outputPort = NULL; inputPort = NULL; } } math::Vec CableWidget::getInputPos() { if (inputPort) { return inputPort->getRelativeOffset(inputPort->box.zeroPos().getCenter(), APP->scene->rack); } else if (hoveredInputPort) { return hoveredInputPort->getRelativeOffset(hoveredInputPort->box.zeroPos().getCenter(), APP->scene->rack); } else { return APP->scene->rack->mousePos; } } math::Vec CableWidget::getOutputPos() { if (outputPort) { return outputPort->getRelativeOffset(outputPort->box.zeroPos().getCenter(), APP->scene->rack); } else if (hoveredOutputPort) { return hoveredOutputPort->getRelativeOffset(hoveredOutputPort->box.zeroPos().getCenter(), APP->scene->rack); } else { return APP->scene->rack->mousePos; } } json_t* CableWidget::toJson() { json_t* rootJ = json_object(); 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) { json_t* colorJ = json_object_get(rootJ, "color"); if (colorJ) { // In plugPortSvg = that->internal->plugPortSvg; math::Vec plugPortSize = plugPortSvg->getSize(); nvgTranslate(args.vg, VEC_ARGS(plugPortSize.div(2).neg())); plugPortSvg->draw(args.vg); nvgRestore(args.vg); } static void CableWidget_drawCable(CableWidget* that, const widget::Widget::DrawArgs& args, math::Vec pos1, math::Vec pos2, NVGcolor color, float thickness, float tension, float opacity) { NVGcolor colorShadow = nvgRGBAf(0, 0, 0, 0.10); NVGcolor colorOutline = nvgLerpRGBA(color, nvgRGBf(0.0, 0.0, 0.0), 0.5); // Cable if (opacity > 0.0) { nvgSave(args.vg); // This power scaling looks more linear than actual linear scaling nvgAlpha(args.vg, std::pow(opacity, 1.5)); float dist = pos1.minus(pos2).norm(); math::Vec slump; slump.y = (1.0 - tension) * (150.0 + 1.0 * dist); math::Vec pos3 = pos1.plus(pos2).div(2).plus(slump); // Adjust pos1 and pos2 to not draw over the plug pos1 = pos1.plus(pos3.minus(pos1).normalize().mult(9)); pos2 = pos2.plus(pos3.minus(pos2).normalize().mult(9)); nvgLineJoin(args.vg, NVG_ROUND); // Shadow math::Vec pos4 = pos3.plus(slump.mult(0.08)); nvgBeginPath(args.vg); nvgMoveTo(args.vg, pos1.x, pos1.y); nvgQuadTo(args.vg, pos4.x, pos4.y, pos2.x, pos2.y); nvgStrokeColor(args.vg, colorShadow); nvgStrokeWidth(args.vg, thickness); nvgStroke(args.vg); // Cable outline nvgBeginPath(args.vg); nvgMoveTo(args.vg, pos1.x, pos1.y); nvgQuadTo(args.vg, pos3.x, pos3.y, pos2.x, pos2.y); nvgStrokeColor(args.vg, colorOutline); nvgStrokeWidth(args.vg, thickness); nvgStroke(args.vg); // Cable solid nvgStrokeColor(args.vg, color); nvgStrokeWidth(args.vg, thickness - 2); nvgStroke(args.vg); nvgRestore(args.vg); } } void CableWidget::draw(const DrawArgs& args) { float opacity = settings::cableOpacity; float tension = settings::cableTension; float thickness = 5; if (isComplete()) { engine::Output* output = &cable->outputModule->outputs[cable->outputId]; // Increase thickness if output port is polyphonic if (output->channels > 1) { thickness = 9; } // Draw opaque if mouse is hovering over a connected port Widget* hoveredWidget = APP->event->hoveredWidget; if (outputPort == hoveredWidget || inputPort == hoveredWidget) { opacity = 1.0; } // Draw translucent cable if not active (i.e. 0 channels) else if (output->channels == 0) { opacity *= 0.5; } } else { // Draw opaque if the cable is incomplete opacity = 1.0; } math::Vec outputPos = getOutputPos(); math::Vec inputPos = getInputPos(); CableWidget_drawCable(this, args, outputPos, inputPos, color, thickness, tension, opacity); } void CableWidget::drawPlugs(const DrawArgs& args) { // Draw output plug math::Vec outputPos = getOutputPos(); bool outputTop = !isComplete() || APP->scene->rack->getTopCable(outputPort) == this; CableWidget_drawPlug(this, args, outputPos, color, outputTop); if (outputTop && isComplete()) { // Draw output plug light nvgSave(args.vg); LightWidget* plugLight = outputPort->getPlugLight(); math::Vec plugPos = outputPos.minus(plugLight->getSize().div(2)); nvgTranslate(args.vg, VEC_ARGS(plugPos)); plugLight->draw(args); nvgRestore(args.vg); } // Draw input plug math::Vec inputPos = getInputPos(); bool inputTop = !isComplete() || APP->scene->rack->getTopCable(inputPort) == this; CableWidget_drawPlug(this, args, inputPos, color, inputTop); if (inputTop && isComplete()) { // Draw input plug light nvgSave(args.vg); LightWidget* plugLight = inputPort->getPlugLight(); math::Vec plugPos = inputPos.minus(plugLight->getSize().div(2)); nvgTranslate(args.vg, VEC_ARGS(plugPos)); plugLight->draw(args); nvgRestore(args.vg); } } engine::Cable* CableWidget::releaseCable() { engine::Cable* cable = this->cable; this->cable = NULL; return cable; } } // namespace app } // namespace rack