#include #include #include #include #include #include #include #include #include #include namespace rack { namespace app { CableWidget::CableWidget() { color = color::BLACK_TRANSPARENT; } CableWidget::~CableWidget() { setCable(NULL); } 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 <=v0.6.0, patches used JSON objects. Just ignore them if so and use the existing cable color. if (json_is_string(colorJ)) color = color::fromHexString(json_string_value(colorJ)); } } static void drawPlug(NVGcontext* vg, math::Vec pos, NVGcolor color) { NVGcolor colorOutline = nvgLerpRGBA(color, nvgRGBf(0.0, 0.0, 0.0), 0.5); // Plug solid nvgBeginPath(vg); nvgCircle(vg, pos.x, pos.y, 9); nvgFillColor(vg, color); nvgFill(vg); // Border nvgStrokeWidth(vg, 1.0); nvgStrokeColor(vg, colorOutline); nvgStroke(vg); // Hole nvgBeginPath(vg); nvgCircle(vg, pos.x, pos.y, 5); nvgFillColor(vg, nvgRGBf(0.0, 0.0, 0.0)); nvgFill(vg); } static void drawCable(NVGcontext* vg, 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(vg); // This power scaling looks more linear than actual linear scaling nvgGlobalAlpha(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(vg, NVG_ROUND); // Shadow math::Vec pos4 = pos3.plus(slump.mult(0.08)); nvgBeginPath(vg); nvgMoveTo(vg, pos1.x, pos1.y); nvgQuadTo(vg, pos4.x, pos4.y, pos2.x, pos2.y); nvgStrokeColor(vg, colorShadow); nvgStrokeWidth(vg, thickness); nvgStroke(vg); // Cable outline nvgBeginPath(vg); nvgMoveTo(vg, pos1.x, pos1.y); nvgQuadTo(vg, pos3.x, pos3.y, pos2.x, pos2.y); nvgStrokeColor(vg, colorOutline); nvgStrokeWidth(vg, thickness); nvgStroke(vg); // Cable solid nvgStrokeColor(vg, color); nvgStrokeWidth(vg, thickness - 2); nvgStroke(vg); nvgRestore(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(); drawCable(args.vg, outputPos, inputPos, color, thickness, tension, opacity); } void CableWidget::drawPlugs(const DrawArgs& args) { math::Vec outputPos = getOutputPos(); math::Vec inputPos = getInputPos(); // Draw plug if the cable is on top, or if the cable is incomplete if (!isComplete() || APP->scene->rack->getTopCable(outputPort) == this) { drawPlug(args.vg, outputPos, color); if (isComplete()) { // Draw plug light nvgSave(args.vg); nvgTranslate(args.vg, outputPos.x - 4, outputPos.y - 4); outputPort->plugLight->draw(args); nvgRestore(args.vg); } } if (!isComplete() || APP->scene->rack->getTopCable(inputPort) == this) { drawPlug(args.vg, inputPos, color); if (isComplete()) { nvgSave(args.vg); nvgTranslate(args.vg, inputPos.x - 4, inputPos.y - 4); inputPort->plugLight->draw(args); nvgRestore(args.vg); } } } engine::Cable* CableWidget::releaseCable() { engine::Cable* cable = this->cable; this->cable = NULL; return cable; } } // namespace app } // namespace rack