#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::Internal { CableWidget* cableWidget; engine::Port::Type type; /** Initially pointing upward. */ float angle = 0.5f * M_PI; widget::FramebufferWidget* fb; widget::TransformWidget* plugTransform; TintWidget* plugTint; widget::SvgWidget* plug; widget::SvgWidget* plugPort; app::MultiLightWidget* plugLight; }; PlugWidget::PlugWidget() { internal = new Internal; internal->fb = new widget::FramebufferWidget; addChild(internal->fb); internal->plugTransform = new widget::TransformWidget; internal->fb->addChild(internal->plugTransform); internal->plugTint = new TintWidget; internal->plugTransform->addChild(internal->plugTint); internal->plug = new widget::SvgWidget; internal->plug->setSvg(window::Svg::load(asset::system("res/ComponentLibrary/Plug.svg"))); internal->plugTint->addChild(internal->plug); internal->plugTransform->setSize(internal->plug->getSize()); internal->plugTransform->setPosition(internal->plug->getSize().mult(-0.5)); internal->plugTint->setSize(internal->plug->getSize()); internal->plugPort = new widget::SvgWidget; internal->plugPort->setSvg(window::Svg::load(asset::system("res/ComponentLibrary/PlugPort.svg"))); internal->plugPort->setPosition(internal->plugPort->getSize().mult(-0.5)); internal->fb->addChild(internal->plugPort); internal->plugLight = new PlugLight; internal->plugLight->setPosition(internal->plugLight->getSize().mult(-0.5)); addChild(internal->plugLight); setSize(internal->plug->getSize()); } PlugWidget::~PlugWidget() { delete internal; } void PlugWidget::step() { std::vector values(3); PortWidget* pw = internal->cableWidget->getPort(internal->type); if (pw && internal->plugLight->isVisible()) { engine::Port* port = pw->getPort(); if (port) { for (int i = 0; i < 3; i++) { values[i] = port->plugLights[i].getBrightness(); } } } internal->plugLight->setBrightnesses(values); Widget::step(); } void PlugWidget::setColor(NVGcolor color) { if (color::isEqual(color, internal->plugTint->color)) return; internal->plugTint->color = color; internal->fb->setDirty(); } void PlugWidget::setAngle(float angle) { if (angle == internal->angle) return; internal->angle = angle; internal->plugTransform->identity(); internal->plugTransform->rotate(angle - 0.5f * M_PI, internal->plug->getSize().div(2)); internal->fb->setDirty(); } void PlugWidget::setTop(bool top) { internal->plugLight->setVisible(top); } CableWidget* PlugWidget::getCable() { return internal->cableWidget; } engine::Port::Type PlugWidget::getType() { return internal->type; } struct CableWidget::Internal { /** For making history consistent when disconnecting and reconnecting cable. */ int64_t cableId = -1; }; CableWidget::CableWidget() { internal = new Internal; color = color::BLACK_TRANSPARENT; outputPlug = new PlugWidget; outputPlug->internal->cableWidget = this; outputPlug->internal->type = engine::Port::OUTPUT; inputPlug = new PlugWidget; inputPlug->internal->cableWidget = this; inputPlug->internal->type = engine::Port::INPUT; } CableWidget::~CableWidget() { delete outputPlug; delete inputPlug; 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->id = internal->cableId; cable->inputModule = inputPort->module; cable->inputId = inputPort->portId; cable->outputModule = outputPort->module; cable->outputId = outputPort->portId; APP->engine->addCable(cable); internal->cableId = cable->id; } } void CableWidget::setCable(engine::Cable* cable) { if (this->cable) { APP->engine->removeCable(this->cable); delete this->cable; this->cable = NULL; internal->cableId = -1; } 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; internal->cableId = cable->id; } 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 outputPlug->setPosition(outputPos); bool outputTop = outputPort && (APP->scene->rack->getTopPlug(outputPort) == outputPlug); outputPlug->setTop(outputTop); outputPlug->setAngle(slump.minus(outputPos).arg()); outputPlug->setColor(colorOpaque); // Draw input plug inputPlug->setPosition(inputPos); bool inputTop = inputPort && (APP->scene->rack->getTopPlug(inputPort) == inputPlug); inputPlug->setTop(inputTop); inputPlug->setAngle(slump.minus(inputPos).arg()); inputPlug->setColor(colorOpaque); Widget::step(); } void CableWidget::draw(const DrawArgs& args) { CableWidget::drawLayer(args, 0); } void CableWidget::drawLayer(const DrawArgs& args, int layer) { // Cable shadow and cable 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); float dist = 14.f; outputPos = outputPos.plus(slump.minus(outputPos).normalize().mult(dist)); inputPos = inputPos.plus(slump.minus(inputPos).normalize().mult(dist)); nvgLineCap(args.vg, NVG_ROUND); // Avoids glitches when cable is bent nvgLineJoin(args.vg, NVG_ROUND); if (layer == -1) { // 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 == 0) { // 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; internal->cableId = -1; return cable; } void CableWidget::onAdd(const AddEvent& e) { Widget* plugContainer = APP->scene->rack->getPlugContainer(); plugContainer->addChild(outputPlug); plugContainer->addChild(inputPlug); Widget::onAdd(e); } void CableWidget::onRemove(const RemoveEvent& e) { Widget* plugContainer = APP->scene->rack->getPlugContainer(); plugContainer->removeChild(outputPlug); plugContainer->removeChild(inputPlug); Widget::onRemove(e); } } // namespace app } // namespace rack