Browse Source

Add PlugWidget. Implement appearance of cables and plugs from Pyer. Add layer variable to Widget::DrawArgs. Add color::isEqual().

tags/v2.0.0
Andrew Belt 3 years ago
parent
commit
99adc5c0fe
9 changed files with 211 additions and 162 deletions
  1. +9
    -4
      include/app/CableWidget.hpp
  2. +0
    -3
      include/app/PortWidget.hpp
  3. +1
    -0
      include/color.hpp
  4. +7
    -1
      include/widget/TransformWidget.hpp
  5. +4
    -1
      include/widget/Widget.hpp
  6. +171
    -107
      src/app/CableWidget.cpp
  7. +0
    -35
      src/app/PortWidget.cpp
  8. +11
    -11
      src/app/RackWidget.cpp
  9. +8
    -0
      src/color.cpp

+ 9
- 4
include/app/CableWidget.hpp View File

@@ -1,9 +1,8 @@
#pragma once
#include <map>
#include <app/common.hpp>
#include <widget/OpaqueWidget.hpp>
#include <widget/Widget.hpp>
#include <app/PortWidget.hpp>
#include <app/ModuleWidget.hpp>
#include <engine/Cable.hpp>


@@ -11,13 +10,19 @@ namespace rack {
namespace app {


struct CableWidget : widget::OpaqueWidget {
struct PlugWidget;


struct CableWidget : widget::Widget {
struct Internal;
Internal* internal;

/** Owned. */
engine::Cable* cable = NULL;
NVGcolor color;
PlugWidget* inputPlug;
PlugWidget* outputPlug;

PortWidget* inputPort = NULL;
PortWidget* outputPort = NULL;
PortWidget* hoveredInputPort = NULL;
@@ -38,8 +43,8 @@ struct CableWidget : widget::OpaqueWidget {
math::Vec getOutputPos();
json_t* toJson();
void fromJson(json_t* rootJ);
void step() override;
void draw(const DrawArgs& args) override;
void drawPlugs(const DrawArgs& args);
engine::Cable* releaseCable();
};



+ 0
- 3
include/app/PortWidget.hpp View File

@@ -2,7 +2,6 @@
#include <app/common.hpp>
#include <widget/OpaqueWidget.hpp>
#include <ui/Tooltip.hpp>
#include <app/MultiLightWidget.hpp>
#include <engine/Module.hpp>
#include <engine/PortInfo.hpp>

@@ -24,7 +23,6 @@ struct PortWidget : widget::OpaqueWidget {
~PortWidget();
engine::Port* getPort();
engine::PortInfo* getPortInfo();
LightWidget* getPlugLight();
void createTooltip();
void destroyTooltip();

@@ -39,7 +37,6 @@ 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;
};




+ 1
- 0
include/color.hpp View File

@@ -26,6 +26,7 @@ static const NVGcolor YELLOW = nvgRGB(0xff, 0xff, 0x00);
static const NVGcolor WHITE = nvgRGB(0xff, 0xff, 0xff);


bool isEqual(NVGcolor a, NVGcolor b);
NVGcolor clamp(NVGcolor a);
NVGcolor minus(NVGcolor a, NVGcolor b);
NVGcolor plus(NVGcolor a, NVGcolor b);


+ 7
- 1
include/widget/TransformWidget.hpp View File

@@ -21,7 +21,7 @@ struct TransformWidget : Widget {

void translate(math::Vec delta) {
float t[6];
nvgTransformTranslate(t, delta.x, delta.y);
nvgTransformTranslate(t, VEC_ARGS(delta));
nvgTransformPremultiply(transform, t);
}

@@ -31,6 +31,12 @@ struct TransformWidget : Widget {
nvgTransformPremultiply(transform, t);
}

void rotate(float angle, math::Vec origin) {
translate(origin);
rotate(angle);
translate(origin.neg());
}

void scale(math::Vec s) {
float t[6];
nvgTransformScale(t, s.x, s.y);


+ 4
- 1
include/widget/Widget.hpp View File

@@ -125,9 +125,12 @@ struct Widget : WeakBase {
virtual void step();

struct DrawArgs {
NVGcontext* vg;
NVGcontext* vg = NULL;
/** Local box representing the visible viewport. */
math::Rect clipBox;
NVGLUframebuffer* fb = NULL;
/** Custom widgets may draw children multiple times, passing a different layer number each time. */
int layer = 0;
};

/** Draws the widget to the NanoVG context */


+ 171
- 107
src/app/CableWidget.cpp View File

@@ -1,30 +1,129 @@
#include <app/CableWidget.hpp>
#include <widget/SvgWidget.hpp>
#include <widget/TransformWidget.hpp>
#include <app/Scene.hpp>
#include <app/RackWidget.hpp>
#include <Window.hpp>
#include <app/ModuleWidget.hpp>
#include <context.hpp>
#include <patch.hpp>
#include <asset.hpp>
#include <settings.hpp>
#include <engine/Engine.hpp>
#include <engine/Port.hpp>
#include <app/MultiLightWidget.hpp>
#include <componentlibrary.hpp>


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<componentlibrary::TGrayModuleLightWidget<componentlibrary::MediumLight<app::MultiLightWidget>>> {
};


struct PlugWidget : widget::Widget {
float angle = 0.f;
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(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(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);
}

void step() override {
std::vector<float> 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 {
std::shared_ptr<Svg> plugSvg;
std::shared_ptr<Svg> plugPortSvg;
};


CableWidget::CableWidget() {
internal = new Internal;
color = color::BLACK_TRANSPARENT;
internal->plugSvg = Svg::load(asset::system("res/ComponentLibrary/Plug.svg"));
internal->plugPortSvg = Svg::load(asset::system("res/ComponentLibrary/PlugPort.svg"));

inputPlug = new PlugWidget;
addChild(inputPlug);

outputPlug = new PlugWidget;
addChild(outputPlug);
}

CableWidget::~CableWidget() {
@@ -126,7 +225,7 @@ void CableWidget::fromJson(json_t* rootJ) {
}
}

static math::Vec getCableSlump(math::Vec pos1, math::Vec pos2) {
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
@@ -134,87 +233,44 @@ static math::Vec getCableSlump(math::Vec pos1, math::Vec pos2) {
return avg;
}

static void CableWidget_drawPlug(CableWidget* that, const widget::Widget::DrawArgs& args, math::Vec pos, math::Vec slump, NVGcolor color, bool top) {
if (!top)
return;

nvgSave(args.vg);
nvgTranslate(args.vg, pos.x, pos.y);

// Plug
nvgSave(args.vg);
nvgTint(args.vg, color);
std::shared_ptr<Svg> plugSvg = that->internal->plugSvg;
math::Vec plugSize = plugSvg->getSize();
float angle = slump.minus(pos).arg() - 0.5f * M_PI;
nvgRotate(args.vg, angle);
nvgTranslate(args.vg, VEC_ARGS(plugSize.div(2).neg()));
plugSvg->draw(args.vg);
nvgRestore(args.vg);

// Port
nvgSave(args.vg);
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);

nvgRestore(args.vg);
}

static void CableWidget_drawCable(CableWidget* that, const widget::Widget::DrawArgs& args, math::Vec pos1, math::Vec pos2, NVGcolor color, bool thick, float opacity) {
if (opacity <= 0.0)
return;

float thickness = thick ? 10.0 : 6.0;

// The endpoints are off-center
math::Vec slump = getCableSlump(pos1, pos2);
pos1 = pos1.plus(slump.minus(pos1).normalize().mult(13.0));
pos2 = pos2.plus(slump.minus(pos2).normalize().mult(13.0));
void CableWidget::step() {
math::Vec outputPos = getOutputPos();
math::Vec inputPos = getInputPos();
math::Vec slump = getSlumpPos(outputPos, inputPos);

nvgSave(args.vg);
nvgAlpha(args.vg, std::pow(opacity, 1.5));
// 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(color);
outputPlug->setPortWidget(outputPort);

nvgLineCap(args.vg, NVG_ROUND);
// Avoids glitches when cable is bent
nvgLineJoin(args.vg, NVG_ROUND);
// 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(color);
inputPlug->setPortWidget(inputPort);

// Shadow
math::Vec shadowSlump = slump.plus(math::Vec(0, 30));
nvgBeginPath(args.vg);
nvgMoveTo(args.vg, VEC_ARGS(pos1));
nvgQuadTo(args.vg, VEC_ARGS(shadowSlump), VEC_ARGS(pos2));
NVGcolor shadowColor = nvgRGBAf(0, 0, 0, 0.10);
nvgStrokeColor(args.vg, shadowColor);
nvgStrokeWidth(args.vg, thickness - 1.0);
nvgStroke(args.vg);

// Cable solid
nvgBeginPath(args.vg);
nvgMoveTo(args.vg, VEC_ARGS(pos1));
nvgQuadTo(args.vg, VEC_ARGS(slump), VEC_ARGS(pos2));
// nvgStrokePaint(args.vg, nvgLinearGradient(args.vg, VEC_ARGS(pos1), VEC_ARGS(pos2), color::mult(color, 0.5), color));
nvgStrokeColor(args.vg, color::mult(color, 0.75));
nvgStrokeWidth(args.vg, thickness);
nvgStroke(args.vg);

nvgStrokeColor(args.vg, color);
nvgStrokeWidth(args.vg, thickness - 1.0);
nvgStroke(args.vg);

nvgRestore(args.vg);
Widget::step();
}

void CableWidget::draw(const DrawArgs& args) {
if (args.layer == 0) {
// Draw PlugWidgets
Widget::draw(args);
return;
}

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->channels > 1) {
if (output->isPolyphonic()) {
thick = true;
}

@@ -224,7 +280,7 @@ void CableWidget::draw(const DrawArgs& args) {
opacity = 1.0;
}
// Draw translucent cable if not active (i.e. 0 channels)
else if (output->channels == 0) {
else if (output->getChannels() == 0) {
opacity *= 0.5;
}
}
@@ -233,44 +289,52 @@ void CableWidget::draw(const DrawArgs& args) {
opacity = 1.0;
}

math::Vec outputPos = getOutputPos();
math::Vec inputPos = getInputPos();
CableWidget_drawCable(this, args, outputPos, inputPos, color, thick, opacity);
}
if (opacity <= 0.0)
return;
nvgAlpha(args.vg, std::pow(opacity, 1.5));

void CableWidget::drawPlugs(const DrawArgs& args) {
math::Vec outputPos = getOutputPos();
math::Vec inputPos = getInputPos();
math::Vec slump = getCableSlump(outputPos, inputPos);

// Draw output plug
bool outputTop = !isComplete() || APP->scene->rack->getTopCable(outputPort) == this;
CableWidget_drawPlug(this, args, outputPos, slump, 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);
}
float thickness = thick ? 10.0 : 6.0;

// Draw input plug
bool inputTop = !isComplete() || APP->scene->rack->getTopCable(inputPort) == this;
CableWidget_drawPlug(this, args, inputPos, slump, 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);
// 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 (args.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 (args.layer == 2) {
// 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.75));
nvgStrokeWidth(args.vg, thickness);
nvgStroke(args.vg);

// Draw cable
nvgStrokeColor(args.vg, color);
nvgStrokeWidth(args.vg, thickness - 1.0);
nvgStroke(args.vg);
}
}


engine::Cable* CableWidget::releaseCable() {
engine::Cable* cable = this->cable;
this->cable = NULL;


+ 0
- 35
src/app/PortWidget.cpp View File

@@ -5,7 +5,6 @@
#include <history.hpp>
#include <engine/Engine.hpp>
#include <settings.hpp>
#include <componentlibrary.hpp>


namespace rack {
@@ -66,21 +65,11 @@ struct PortTooltip : ui::Tooltip {

struct PortWidget::Internal {
ui::Tooltip* tooltip = NULL;
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() {
@@ -89,8 +78,6 @@ PortWidget::~PortWidget() {
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;
delete internal;
}

@@ -112,10 +99,6 @@ engine::PortInfo* PortWidget::getPortInfo() {
return module->outputInfos[portId];
}

LightWidget* PortWidget::getPlugLight() {
return internal->plugLight;
}

void PortWidget::createTooltip() {
if (!settings::tooltips)
return;
@@ -138,19 +121,6 @@ void PortWidget::destroyTooltip() {
}

void PortWidget::step() {
if (!module)
return;

std::vector<float> values(3);
for (int i = 0; i < 3; i++) {
if (type == engine::Port::OUTPUT)
values[i] = module->outputs[portId].plugLights[i].getBrightness();
else
values[i] = module->inputs[portId].plugLights[i].getBrightness();
}
internal->plugLight->setBrightnesses(values);
internal->plugLight->step();

Widget::step();
}

@@ -336,11 +306,6 @@ void PortWidget::onDragLeave(const DragLeaveEvent& e) {
}
}

void PortWidget::onContextDestroy(const ContextDestroyEvent& e) {
internal->plugLight->onContextDestroy(e);
Widget::onContextDestroy(e);
}


} // namespace app
} // namespace rack

+ 11
- 11
src/app/RackWidget.cpp View File

@@ -56,18 +56,18 @@ struct ModuleContainer : widget::Widget {

struct CableContainer : widget::TransparentWidget {
void draw(const DrawArgs& args) override {
// Draw cable plugs
for (widget::Widget* w : children) {
CableWidget* cw = dynamic_cast<CableWidget*>(w);
assert(cw);

DrawArgs childArgs = args;
// TODO Make clip box equal actual viewport
childArgs.clipBox = math::Rect::inf();
cw->drawPlugs(childArgs);
}

// Draw Plugs
Widget::draw(args);

// Draw cable shadows
DrawArgs args1 = args;
args1.layer = 1;
Widget::draw(args1);

// Draw cables
DrawArgs args2 = args;
args2.layer = 2;
Widget::draw(args2);
}
};



+ 8
- 0
src/color.cpp View File

@@ -6,6 +6,14 @@ namespace rack {
namespace color {


bool isEqual(NVGcolor a, NVGcolor b) {
for (int i = 0; i < 4; i++) {
if (a.rgba[i] != b.rgba[i])
return false;
}
return true;
}

NVGcolor clamp(NVGcolor a) {
for (int i = 0; i < 4; i++)
a.rgba[i] = math::clamp(a.rgba[i], 0.f, 1.f);


Loading…
Cancel
Save