| @@ -39,6 +39,7 @@ struct PortWidget : widget::OpaqueWidget { | |||
| void onDragDrop(const DragDropEvent& e) override; | |||
| void onDragEnter(const DragEnterEvent& e) override; | |||
| void onDragLeave(const DragLeaveEvent& e) override; | |||
| void onContextDestroy(const ContextDestroyEvent& e) override; | |||
| }; | |||
| @@ -0,0 +1,213 @@ | |||
| <?xml version="1.0" encoding="UTF-8" standalone="no"?> | |||
| <!-- Generator: Adobe Illustrator 25.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> | |||
| <svg | |||
| version="1.0" | |||
| id="svg15246" | |||
| x="0px" | |||
| y="0px" | |||
| width="15.8003px" | |||
| height="15.8003px" | |||
| viewBox="0 0 15.8003 15.8003" | |||
| enable-background="new 0 0 15.8003 15.8003" | |||
| xml:space="preserve" | |||
| sodipodi:docname="PlugPort.svg" | |||
| inkscape:version="1.1 (c4e8f9ed74, 2021-05-24)" | |||
| xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | |||
| xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | |||
| xmlns="http://www.w3.org/2000/svg" | |||
| xmlns:svg="http://www.w3.org/2000/svg"><defs | |||
| id="defs157" /> | |||
| <linearGradient | |||
| id="SVGID_2_" | |||
| gradientUnits="userSpaceOnUse" | |||
| x1="355.22113" | |||
| y1="-3759.56128" | |||
| x2="377.73505" | |||
| y2="-3759.56128" | |||
| gradientTransform="matrix(0 1 -1 0 -4696.65137 578.8092)"> | |||
| <stop | |||
| offset="0.00559" | |||
| style="stop-color:#FFFFFF" | |||
| id="stop11" /> | |||
| <stop | |||
| offset="1" | |||
| style="stop-color:#454545" | |||
| id="stop13" /> | |||
| </linearGradient> | |||
| <linearGradient | |||
| id="SVGID_3_" | |||
| gradientUnits="userSpaceOnUse" | |||
| x1="-8019.91748" | |||
| y1="2332.14722" | |||
| x2="-8002.08936" | |||
| y2="2332.14722" | |||
| gradientTransform="matrix(0 -1 1 0 -3269.23706 -7065.71631)"> | |||
| <stop | |||
| offset="0.00559" | |||
| style="stop-color:#FFFFFF" | |||
| id="stop20" /> | |||
| <stop | |||
| offset="1" | |||
| style="stop-color:#454545" | |||
| id="stop22" /> | |||
| </linearGradient> | |||
| <linearGradient | |||
| id="SVGID_4_" | |||
| gradientUnits="userSpaceOnUse" | |||
| x1="-6757.92383" | |||
| y1="3834.65601" | |||
| x2="-6757.92383" | |||
| y2="3849.91846" | |||
| gradientTransform="matrix(-1 0 0 1 -7695.01367 -2897)"> | |||
| <stop | |||
| offset="0" | |||
| style="stop-color:#FFFEFE" | |||
| id="stop31" /> | |||
| <stop | |||
| offset="1" | |||
| style="stop-color:#5E5E5E" | |||
| id="stop33" /> | |||
| </linearGradient> | |||
| <linearGradient | |||
| id="SVGID_5_" | |||
| gradientUnits="userSpaceOnUse" | |||
| x1="-6757.92383" | |||
| y1="3848.08325" | |||
| x2="-6757.92383" | |||
| y2="3836.49146" | |||
| gradientTransform="matrix(-1 0 0 1 -7695.01367 -2897)"> | |||
| <stop | |||
| offset="0" | |||
| style="stop-color:#FFFEFE" | |||
| id="stop40" /> | |||
| <stop | |||
| offset="1" | |||
| style="stop-color:#383636" | |||
| id="stop42" /> | |||
| </linearGradient> | |||
| <sodipodi:namedview | |||
| bordercolor="#666666" | |||
| borderopacity="1.0" | |||
| fit-margin-bottom="0" | |||
| fit-margin-left="0" | |||
| fit-margin-right="0" | |||
| fit-margin-top="0" | |||
| id="base" | |||
| inkscape:current-layer="svg15246" | |||
| inkscape:cx="9.7740969" | |||
| inkscape:cy="20.981046" | |||
| inkscape:document-units="mm" | |||
| inkscape:pageopacity="0.0" | |||
| inkscape:pageshadow="2" | |||
| inkscape:snap-bbox="true" | |||
| inkscape:snap-bbox-midpoints="true" | |||
| inkscape:snap-nodes="false" | |||
| inkscape:snap-others="false" | |||
| inkscape:window-height="882" | |||
| inkscape:window-maximized="0" | |||
| inkscape:window-width="1600" | |||
| inkscape:window-x="0" | |||
| inkscape:window-y="18" | |||
| inkscape:zoom="9.7707237" | |||
| pagecolor="#ffffff" | |||
| showgrid="false" | |||
| units="px" | |||
| inkscape:pagecheckerboard="0"> | |||
| </sodipodi:namedview> | |||
| <linearGradient | |||
| id="SVGID_11_" | |||
| gradientUnits="userSpaceOnUse" | |||
| x1="-8066.65869" | |||
| y1="2299.03101" | |||
| x2="-8048.83008" | |||
| y2="2299.03101" | |||
| gradientTransform="matrix(0 -1 1 0 -3269.23706 -7065.71631)"> | |||
| <stop | |||
| offset="0" | |||
| style="stop-color:#8C8B8B" | |||
| id="stop93" /> | |||
| <stop | |||
| offset="1" | |||
| style="stop-color:#454545" | |||
| id="stop95" /> | |||
| </linearGradient> | |||
| <linearGradient | |||
| id="SVGID_16_" | |||
| gradientUnits="userSpaceOnUse" | |||
| x1="-2831.54297" | |||
| y1="3096.30811" | |||
| x2="-2831.54297" | |||
| y2="3080.50781" | |||
| gradientTransform="matrix(1 0 0 1 2839.44287 -3080.50781)"> | |||
| <stop | |||
| offset="0" | |||
| style="stop-color:#FFFEFE" | |||
| id="stop138" /> | |||
| <stop | |||
| offset="1" | |||
| style="stop-color:#0A0A0A" | |||
| id="stop140" /> | |||
| </linearGradient> | |||
| <circle | |||
| opacity="0.32" | |||
| fill="url(#SVGID_16_)" | |||
| cx="7.90013" | |||
| cy="7.90013" | |||
| r="7.90013" | |||
| id="circle143" /> | |||
| <circle | |||
| fill="#E0E0E0" | |||
| cx="7.90013" | |||
| cy="7.90013" | |||
| r="6.46479" | |||
| id="circle145" /> | |||
| <linearGradient | |||
| id="SVGID_17_" | |||
| gradientUnits="userSpaceOnUse" | |||
| x1="-2802.25293" | |||
| y1="3043.80615" | |||
| x2="-2813.42529" | |||
| y2="3043.80615" | |||
| gradientTransform="matrix(-3.491481e-15 1 -1 -3.491481e-15 3051.7063 2815.73926)"> | |||
| <stop | |||
| offset="0" | |||
| style="stop-color:#FFFEFE" | |||
| id="stop147" /> | |||
| <stop | |||
| offset="1" | |||
| style="stop-color:#706C6C" | |||
| id="stop149" /> | |||
| </linearGradient> | |||
| <circle | |||
| fill="url(#SVGID_17_)" | |||
| cx="7.90013" | |||
| cy="7.90013" | |||
| r="5.58624" | |||
| id="circle152" /> | |||
| <circle | |||
| cx="7.90013" | |||
| cy="7.90013" | |||
| r="4.82294" | |||
| id="circle154" /> | |||
| </svg> | |||
| @@ -4,6 +4,7 @@ | |||
| #include <window.hpp> | |||
| #include <context.hpp> | |||
| #include <patch.hpp> | |||
| #include <asset.hpp> | |||
| #include <settings.hpp> | |||
| #include <engine/Engine.hpp> | |||
| #include <engine/Port.hpp> | |||
| @@ -13,12 +14,20 @@ namespace rack { | |||
| namespace app { | |||
| struct CableWidget::Internal { | |||
| std::shared_ptr<Svg> 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() { | |||
| @@ -115,36 +124,43 @@ void CableWidget::fromJson(json_t* rootJ) { | |||
| } | |||
| } | |||
| static void drawPlug(NVGcontext* vg, math::Vec pos, NVGcolor color) { | |||
| static void CableWidget_drawPlug(CableWidget* that, const widget::Widget::DrawArgs& args, math::Vec pos, NVGcolor color, bool top) { | |||
| if (!top) | |||
| return; | |||
| NVGcolor colorOutline = nvgLerpRGBA(color, nvgRGBf(0.0, 0.0, 0.0), 0.5); | |||
| nvgSave(args.vg); | |||
| nvgTranslate(args.vg, pos.x, pos.y); | |||
| // Plug solid | |||
| nvgBeginPath(vg); | |||
| nvgCircle(vg, pos.x, pos.y, 9); | |||
| nvgFillColor(vg, color); | |||
| nvgFill(vg); | |||
| nvgBeginPath(args.vg); | |||
| nvgCircle(args.vg, 0.0, 0.0, 9); | |||
| nvgFillColor(args.vg, color); | |||
| nvgFill(args.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); | |||
| nvgStrokeWidth(args.vg, 1.0); | |||
| nvgStrokeColor(args.vg, colorOutline); | |||
| nvgStroke(args.vg); | |||
| // Port | |||
| std::shared_ptr<Svg> 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 drawCable(NVGcontext* vg, math::Vec pos1, math::Vec pos2, NVGcolor color, float thickness, float tension, float opacity) { | |||
| 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(vg); | |||
| nvgSave(args.vg); | |||
| // This power scaling looks more linear than actual linear scaling | |||
| nvgGlobalAlpha(vg, std::pow(opacity, 1.5)); | |||
| nvgGlobalAlpha(args.vg, std::pow(opacity, 1.5)); | |||
| float dist = pos1.minus(pos2).norm(); | |||
| math::Vec slump; | |||
| @@ -155,31 +171,31 @@ static void drawCable(NVGcontext* vg, math::Vec pos1, math::Vec pos2, NVGcolor c | |||
| pos1 = pos1.plus(pos3.minus(pos1).normalize().mult(9)); | |||
| pos2 = pos2.plus(pos3.minus(pos2).normalize().mult(9)); | |||
| nvgLineJoin(vg, NVG_ROUND); | |||
| nvgLineJoin(args.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); | |||
| 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(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); | |||
| 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(vg, color); | |||
| nvgStrokeWidth(vg, thickness - 2); | |||
| nvgStroke(vg); | |||
| nvgStrokeColor(args.vg, color); | |||
| nvgStrokeWidth(args.vg, thickness - 2); | |||
| nvgStroke(args.vg); | |||
| nvgRestore(vg); | |||
| nvgRestore(args.vg); | |||
| } | |||
| } | |||
| @@ -212,33 +228,36 @@ void CableWidget::draw(const DrawArgs& args) { | |||
| math::Vec outputPos = getOutputPos(); | |||
| math::Vec inputPos = getInputPos(); | |||
| drawCable(args.vg, outputPos, inputPos, color, thickness, tension, opacity); | |||
| CableWidget_drawCable(this, args, outputPos, inputPos, color, thickness, tension, opacity); | |||
| } | |||
| void CableWidget::drawPlugs(const DrawArgs& args) { | |||
| // Draw output plug | |||
| 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->getPlugLight()->draw(args); | |||
| nvgRestore(args.vg); | |||
| } | |||
| 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); | |||
| } | |||
| 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->getPlugLight()->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); | |||
| } | |||
| } | |||
| @@ -63,36 +63,33 @@ struct PortTooltip : ui::Tooltip { | |||
| }; | |||
| struct PlugLight : MultiLightWidget { | |||
| PlugLight() { | |||
| addBaseColor(componentlibrary::SCHEME_GREEN); | |||
| addBaseColor(componentlibrary::SCHEME_RED); | |||
| addBaseColor(componentlibrary::SCHEME_BLUE); | |||
| box.size = math::Vec(8, 8); | |||
| bgColor = componentlibrary::SCHEME_BLACK_TRANSPARENT; | |||
| } | |||
| }; | |||
| struct PortWidget::Internal { | |||
| ui::Tooltip* tooltip = NULL; | |||
| PlugLight* plugLight; | |||
| app::MultiLightWidget* plugLight; | |||
| }; | |||
| PortWidget::PortWidget() { | |||
| internal = new Internal; | |||
| using namespace componentlibrary; | |||
| struct PlugLight : TRedGreenBlueLight<TGrayModuleLightWidget<MediumLight<app::MultiLightWidget>>> { | |||
| PlugLight() { | |||
| // fb->bypassed = true; | |||
| fb->oversample = 1.0; | |||
| } | |||
| }; | |||
| internal->plugLight = new PlugLight; | |||
| } | |||
| PortWidget::~PortWidget() { | |||
| // The port shouldn't have any cables when destroyed, but just to make sure. | |||
| if (module) | |||
| APP->scene->rack->clearCablesOnPort(this); | |||
| // HACK: In case onDragDrop() is called but not onLeave() afterwards... | |||
| destroyTooltip(); | |||
| // plugLight is not a child but owned by the PortWidget, so we need to delete it here | |||
| delete internal->plugLight; | |||
| // The port shouldn't have any cables when destroyed, but just to make sure. | |||
| if (module) | |||
| APP->scene->rack->clearCablesOnPort(this); | |||
| delete internal; | |||
| } | |||
| @@ -151,6 +148,7 @@ void PortWidget::step() { | |||
| values[i] = module->inputs[portId].plugLights[i].getBrightness(); | |||
| } | |||
| internal->plugLight->setBrightnesses(values); | |||
| internal->plugLight->step(); | |||
| Widget::step(); | |||
| } | |||
| @@ -336,6 +334,11 @@ void PortWidget::onDragLeave(const DragLeaveEvent& e) { | |||
| } | |||
| } | |||
| void PortWidget::onContextDestroy(const ContextDestroyEvent& e) { | |||
| internal->plugLight->onContextDestroy(e); | |||
| Widget::onContextDestroy(e); | |||
| } | |||
| } // namespace app | |||
| } // namespace rack | |||
| @@ -60,7 +60,11 @@ struct CableContainer : widget::TransparentWidget { | |||
| for (widget::Widget* w : children) { | |||
| CableWidget* cw = dynamic_cast<CableWidget*>(w); | |||
| assert(cw); | |||
| cw->drawPlugs(args); | |||
| DrawArgs childArgs = args; | |||
| // TODO Make clip box equal actual viewport | |||
| childArgs.clipBox = math::Rect::inf(); | |||
| cw->drawPlugs(childArgs); | |||
| } | |||
| Widget::draw(args); | |||
| @@ -433,6 +437,7 @@ void RackWidget::removeModule(ModuleWidget* m) { | |||
| bool RackWidget::requestModulePos(ModuleWidget* mw, math::Vec pos) { | |||
| // Check intersection with other modules | |||
| math::Rect mwBox = math::Rect(pos, mw->box.size); | |||
| mwBox.size.x -= 0.01; | |||
| for (widget::Widget* w2 : moduleContainer->children) { | |||
| // Don't intersect with self | |||
| if (mw == w2) | |||
| @@ -441,7 +446,9 @@ bool RackWidget::requestModulePos(ModuleWidget* mw, math::Vec pos) { | |||
| if (!w2->visible) | |||
| continue; | |||
| // Check intersection | |||
| if (mwBox.intersects(w2->box)) | |||
| math::Rect w2Box = w2->box; | |||
| w2Box.size.x -= 0.01; | |||
| if (mwBox.intersects(w2Box)) | |||
| return false; | |||
| } | |||
| @@ -315,16 +315,12 @@ static void Port_step(Port* that, float deltaTime) { | |||
| } | |||
| else if (that->channels == 1) { | |||
| float v = that->getVoltage() / 10.f; | |||
| that->plugLights[0].setSmoothBrightness(v, deltaTime); | |||
| that->plugLights[1].setSmoothBrightness(-v, deltaTime); | |||
| that->plugLights[0].setSmoothBrightness(-v, deltaTime); | |||
| that->plugLights[1].setSmoothBrightness(v, deltaTime); | |||
| that->plugLights[2].setBrightness(0.f); | |||
| } | |||
| else { | |||
| float v2 = 0.f; | |||
| for (int c = 0; c < that->channels; c++) { | |||
| v2 += std::pow(that->getVoltage(c), 2); | |||
| } | |||
| float v = std::sqrt(v2) / 10.f; | |||
| float v = that->getVoltageRMS() / 10.f; | |||
| that->plugLights[0].setBrightness(0.f); | |||
| that->plugLights[1].setBrightness(0.f); | |||
| that->plugLights[2].setSmoothBrightness(v, deltaTime); | |||
| @@ -105,6 +105,7 @@ void FramebufferWidget::step() { | |||
| // Create a framebuffer | |||
| if (internal->fbSize.isFinite() && !internal->fbSize.isZero()) { | |||
| internal->fb = nvgluCreateFramebuffer(vg, internal->fbSize.x, internal->fbSize.y, 0); | |||
| // DEBUG("Created framebuffer of size (%f, %f)", VEC_ARGS(internal->fbSize)); | |||
| } | |||
| } | |||
| if (!internal->fb) { | |||
| @@ -268,15 +268,16 @@ void Widget::draw(const DrawArgs& args) { | |||
| if (!args.clipBox.intersects(child->box)) | |||
| continue; | |||
| DrawArgs childCtx = args; | |||
| DrawArgs childArgs = args; | |||
| // Intersect child clip box with self | |||
| childCtx.clipBox = childCtx.clipBox.intersect(child->box); | |||
| childCtx.clipBox.pos = childCtx.clipBox.pos.minus(child->box.pos); | |||
| childArgs.clipBox = childArgs.clipBox.intersect(child->box); | |||
| // Offset clip box by child pos | |||
| childArgs.clipBox.pos = childArgs.clipBox.pos.minus(child->box.pos); | |||
| nvgSave(args.vg); | |||
| nvgTranslate(args.vg, child->box.pos.x, child->box.pos.y); | |||
| child->draw(childCtx); | |||
| child->draw(childArgs); | |||
| #pragma GCC diagnostic push | |||
| #pragma GCC diagnostic ignored "-Wdeprecated-declarations" | |||