@@ -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" | |||