#include #include #include #include #include #include #include #include #include #include #include #include #include namespace rack { namespace app { struct TintWidget : widget::Widget { NVGcolor color = color::WHITE; void draw(const DrawArgs& args) override { nvgTint(args.vg, color); Widget::draw(args); } }; struct PlugLight : componentlibrary::TRedGreenBlueLight { PlugLight() { box.size = math::Vec(9, 9); } }; struct PlugWidget : widget::Widget { float angle = 0.5f * M_PI; PortWidget* portWidget = NULL; widget::FramebufferWidget* fb; widget::TransformWidget* plugTransform; TintWidget* plugTint; widget::SvgWidget* plug; widget::SvgWidget* plugPort; app::MultiLightWidget* plugLight; PlugWidget() { fb = new widget::FramebufferWidget; addChild(fb); plugTransform = new widget::TransformWidget; fb->addChild(plugTransform); plugTint = new TintWidget; plugTransform->addChild(plugTint); plug = new widget::SvgWidget; plug->setSvg(window::Svg::load(asset::system("res/ComponentLibrary/Plug.svg"))); plugTint->addChild(plug); plugTransform->setSize(plug->getSize()); plugTransform->setPosition(plug->getSize().mult(-0.5)); plugTint->setSize(plug->getSize()); plugPort = new widget::SvgWidget; plugPort->setSvg(window::Svg::load(asset::system("res/ComponentLibrary/PlugPort.svg"))); plugPort->setPosition(plugPort->getSize().mult(-0.5)); fb->addChild(plugPort); plugLight = new PlugLight; plugLight->setPosition(plugLight->getSize().mult(-0.5)); addChild(plugLight); setSize(plug->getSize()); } void step() override { std::vector values(3); if (portWidget && plugLight->isVisible()) { engine::Port* port = portWidget->getPort(); if (port) { for (int i = 0; i < 3; i++) { values[i] = port->plugLights[i].getBrightness(); } } } plugLight->setBrightnesses(values); Widget::step(); } void setColor(NVGcolor color) { if (color::isEqual(color, plugTint->color)) return; plugTint->color = color; fb->setDirty(); } void setAngle(float angle) { if (angle == this->angle) return; this->angle = angle; plugTransform->identity(); plugTransform->rotate(angle - 0.5f * M_PI, plug->getSize().div(2)); fb->setDirty(); } void setPortWidget(PortWidget* portWidget) { this->portWidget = portWidget; } void setTop(bool top) { plugLight->setVisible(top); } }; struct CableWidget::Internal { }; CableWidget::CableWidget() { internal = new Internal; color = color::BLACK_TRANSPARENT; inputPlug = new PlugWidget; addChild(inputPlug); outputPlug = new PlugWidget; addChild(outputPlug); } CableWidget::~CableWidget() { setCable(NULL); delete internal; } 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; } if (cable) { app::ModuleWidget* outputMw = APP->scene->rack->getModule(cable->outputModule->id); if (!outputMw) throw Exception("Cable cannot find output ModuleWidget %lld", (long long) cable->outputModule->id); outputPort = outputMw->getOutput(cable->outputId); if (!outputPort) throw Exception("Cable cannot find output port %d", cable->outputId); app::ModuleWidget* inputMw = APP->scene->rack->getModule(cable->inputModule->id); if (!inputMw) throw Exception("Cable cannot find input ModuleWidget %lld", (long long) cable->inputModule->id); inputPort = inputMw->getInput(cable->inputId); if (!inputPort) throw Exception("Cable cannot find input port %d", cable->inputId); this->cable = cable; } else { outputPort = NULL; inputPort = NULL; } } engine::Cable* CableWidget::getCable() { return cable; } 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->getMousePos(); } } 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->getMousePos(); } } void CableWidget::mergeJson(json_t* rootJ) { std::string s = color::toHexString(color); json_object_set_new(rootJ, "color", json_string(s.c_str())); } void CableWidget::fromJson(json_t* rootJ) { json_t* colorJ = json_object_get(rootJ, "color"); if (colorJ && json_is_string(colorJ)) { color = color::fromHexString(json_string_value(colorJ)); } else { // In scene->rack->getNextCableColor(); } } static math::Vec getSlumpPos(math::Vec pos1, math::Vec pos2) { float dist = pos1.minus(pos2).norm(); math::Vec avg = pos1.plus(pos2).div(2); // Lower average point as distance increases avg.y += (1.0 - settings::cableTension) * (150.0 + 1.0 * dist); return avg; } void CableWidget::step() { math::Vec outputPos = getOutputPos(); math::Vec inputPos = getInputPos(); math::Vec slump = getSlumpPos(outputPos, inputPos); NVGcolor colorOpaque = color; colorOpaque.a = 1.f; // Draw output plug bool outputTop = !isComplete() || APP->scene->rack->getTopCable(outputPort) == this; outputPlug->setPosition(outputPos); outputPlug->setTop(outputTop); outputPlug->setAngle(slump.minus(outputPos).arg()); outputPlug->setColor(colorOpaque); outputPlug->setPortWidget(outputPort); // Draw input plug bool inputTop = !isComplete() || APP->scene->rack->getTopCable(inputPort) == this; inputPlug->setPosition(inputPos); inputPlug->setTop(inputTop); inputPlug->setAngle(slump.minus(inputPos).arg()); inputPlug->setColor(colorOpaque); inputPlug->setPortWidget(inputPort); Widget::step(); } void CableWidget::draw(const DrawArgs& args) { // Draw plugs Widget::draw(args); } void CableWidget::drawLayer(const DrawArgs& args, int layer) { // Cable shadow and cable if (layer == 2 || layer == 3) { float opacity = settings::cableOpacity; bool thick = false; if (isComplete()) { engine::Output* output = &cable->outputModule->outputs[cable->outputId]; // Increase thickness if output port is polyphonic if (output->isPolyphonic()) { thick = true; } // 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->getChannels() == 0) { opacity *= 0.5; } } else { // Draw opaque if the cable is incomplete opacity = 1.0; } if (opacity <= 0.0) return; nvgAlpha(args.vg, std::pow(opacity, 1.5)); math::Vec outputPos = getOutputPos(); math::Vec inputPos = getInputPos(); float thickness = thick ? 9.0 : 6.0; // The endpoints are off-center math::Vec slump = getSlumpPos(outputPos, inputPos); outputPos = outputPos.plus(slump.minus(outputPos).normalize().mult(13.0)); inputPos = inputPos.plus(slump.minus(inputPos).normalize().mult(13.0)); nvgLineCap(args.vg, NVG_ROUND); // Avoids glitches when cable is bent nvgLineJoin(args.vg, NVG_ROUND); if (layer == 2) { // Draw cable shadow math::Vec shadowSlump = slump.plus(math::Vec(0, 30)); nvgBeginPath(args.vg); nvgMoveTo(args.vg, VEC_ARGS(outputPos)); nvgQuadTo(args.vg, VEC_ARGS(shadowSlump), VEC_ARGS(inputPos)); NVGcolor shadowColor = nvgRGBAf(0, 0, 0, 0.10); nvgStrokeColor(args.vg, shadowColor); nvgStrokeWidth(args.vg, thickness - 1.0); nvgStroke(args.vg); } else if (layer == 3) { // Draw cable outline nvgBeginPath(args.vg); nvgMoveTo(args.vg, VEC_ARGS(outputPos)); nvgQuadTo(args.vg, VEC_ARGS(slump), VEC_ARGS(inputPos)); // nvgStrokePaint(args.vg, nvgLinearGradient(args.vg, VEC_ARGS(outputPos), VEC_ARGS(inputPos), color::mult(color, 0.5), color)); nvgStrokeColor(args.vg, color::mult(color, 0.8)); nvgStrokeWidth(args.vg, thickness); nvgStroke(args.vg); // Draw cable nvgStrokeColor(args.vg, color::mult(color, 0.95)); nvgStrokeWidth(args.vg, thickness - 1.0); nvgStroke(args.vg); } } Widget::drawLayer(args, layer); } engine::Cable* CableWidget::releaseCable() { engine::Cable* cable = this->cable; this->cable = NULL; return cable; } } // namespace app } // namespace rack