Browse Source

Add SVGSlider, SVGSwitch, attempt SVG gradients

tags/v0.3.0
Andrew Belt 7 years ago
parent
commit
6e4cddc4e1
14 changed files with 233 additions and 109 deletions
  1. +55
    -21
      include/app.hpp
  2. +46
    -51
      include/components.hpp
  3. +3
    -0
      include/math.hpp
  4. +2
    -2
      include/rack.hpp
  5. +1
    -1
      include/widgets.hpp
  6. +1
    -1
      src/app/Knob.cpp
  7. +1
    -0
      src/app/Port.cpp
  8. +3
    -2
      src/app/SVGKnob.cpp
  9. +3
    -6
      src/app/SVGPort.cpp
  10. +15
    -0
      src/app/SVGScrew.cpp
  11. +30
    -0
      src/app/SVGSlider.cpp
  12. +33
    -0
      src/app/SVGSwitch.cpp
  13. +8
    -15
      src/widgets/FramebufferWidget.cpp
  14. +32
    -10
      src/widgets/SVGWidget.cpp

+ 55
- 21
include/app.hpp View File

@@ -124,6 +124,7 @@ struct ParamWidget : OpaqueWidget, QuantityWidget {
void onChange();
};

/** Implements vertical dragging behavior for ParamWidgets */
struct Knob : ParamWidget {
void onDragStart();
void onDragMove(Vec mouseRel);
@@ -150,42 +151,76 @@ struct SVGKnob : Knob, FramebufferWidget {
void onChange();
};

struct Switch : ParamWidget, SpriteWidget {
struct SVGSlider : Knob, FramebufferWidget {
/** Intermediate positions will be interpolated between these positions */
Vec minHandlePos, maxHandlePos;
/** Not owned */
SVGWidget *background;
SVGWidget *handle;

SVGSlider();
void step();
void onChange();
};

struct SVGSwitch : ParamWidget, FramebufferWidget {
struct Switch : ParamWidget {
virtual void setIndex(int index) {}
};

struct SVGSwitch : virtual Switch, FramebufferWidget {
std::vector<std::shared_ptr<SVG>> frames;
/** Not owned */
TransformWidget *tw;
SVGWidget *swPressed;
SVGWidget *swReleased;
SVGWidget *sw;

SVGSwitch();
/** Adds an SVG file to represent the next switch position */
void addFrame(std::shared_ptr<SVG> svg);
void setIndex(int index);
void step();
};

/** A switch that cycles through each mechanical position */
struct ToggleSwitch : virtual Switch {
void onDragStart() {
index = 1;
// Cycle through values
// e.g. a range of [0.0, 3.0] would have modes 0, 1, 2, and 3.
float v = value + 1.0;
setValue(v <= maxValue ? v : minValue);
}
void onChange() {
int index = (int)roundf(value);
setIndex(index);
ParamWidget::onChange();
}
};

/** FIXME I don't think this should exist. The audio engine should read from a MomentarySwitch and increment its internal state, instead of relying on the knob to do that logic. */
struct ModeSwitch : virtual Switch {
void onDragStart() {
setIndex(1);
}
void onDragEnd() {
index = 0;
setIndex(0);
}
void onDragDrop(Widget *origin) {
if (origin != this)
return;

// Cycle through modes
// Cycle through values
// e.g. a range of [0.0, 3.0] would have modes 0, 1, 2, and 3.
float v = value + 1.0;
setValue(v > maxValue ? minValue : v);
setValue(v <= maxValue ? v : minValue);
}
};

/** A switch that is turned on when held */
struct MomentarySwitch : virtual Switch {
void onDragStart() {
setValue(maxValue);
index = 1;
setIndex(1);
}
void onDragEnd() {
setValue(minValue);
index = 0;
setIndex(0);
}
};

@@ -218,21 +253,20 @@ struct Port : OpaqueWidget {
void onDragLeave(Widget *origin);
};

struct SpritePort : Port, SpriteWidget {
void draw(NVGcontext *vg) {
Port::draw(vg);
SpriteWidget::draw(vg);
}
};

struct SVGPort : Port, FramebufferWidget {
SVGWidget *sw;
SVGWidget *background;

SVGPort();
void setSVG(std::shared_ptr<SVG> svg);
void draw(NVGcontext *vg);
};

/** If you don't add these to your ModuleWidget, they will fall out of the rack... */
struct SVGScrew : FramebufferWidget {
SVGWidget *sw;

SVGScrew();
};

////////////////////
// scene
////////////////////


+ 46
- 51
include/components.hpp View File

@@ -313,15 +313,17 @@ struct BefacoTinyKnob : SVGKnob {
}
};

struct BefacoSlidePot : SpriteKnob {
struct BefacoSlidePot : SVGSlider {
BefacoSlidePot() {
box.size = Vec(12, 122);
spriteOffset = Vec(-2, -6);
spriteSize = Vec(18, 134);
minIndex = 97;
maxIndex = 0;
spriteCount = 98;
spriteImage = Image::load("res/ComponentLibrary/BefacoSlidePot.png");
Vec margin = Vec(3.5, 3.5);
maxHandlePos = Vec(-1, -2).plus(margin);
minHandlePos = Vec(-1, 87).plus(margin);
background->svg = SVG::load("res/ComponentLibrary/BefacoSlidePot.svg");
background->wrap();
background->box.pos = margin;
box.size = background->box.size.plus(margin.mult(2));
handle->svg = SVG::load("res/ComponentLibrary/BefacoSlidePotHandle.svg");
handle->wrap();
}
};

@@ -329,33 +331,30 @@ struct BefacoSlidePot : SpriteKnob {
// Jacks
////////////////////

struct PJ301MPort : SpritePort {
struct PJ301MPort : SVGPort {
PJ301MPort() {
box.size = Vec(24, 24);
spriteOffset = Vec(-2, -2);
spriteSize = Vec(30, 30);
spriteImage = Image::load("res/ComponentLibrary/PJ301M.png");
// setSVG(SVG::load("res/ComponentLibrary/PJ301M.svg"));
padding = Vec(1, 1);
background->svg = SVG::load("res/ComponentLibrary/PJ301M.svg");
background->wrap();
box.size = background->box.size;
}
};

struct PJ3410Port : SpritePort {
struct PJ3410Port : SVGPort {
PJ3410Port() {
box.size = Vec(32, 31);
spriteOffset = Vec(-1, -1);
spriteSize = Vec(36, 36);
spriteImage = Image::load("res/ComponentLibrary/PJ3410.png");
// setSVG(SVG::load("res/ComponentLibrary/PJ3410.svg"));
padding = Vec(1, 1);
background->svg = SVG::load("res/ComponentLibrary/PJ3410.svg");
background->wrap();
box.size = background->box.size;
}
};

struct CL1362Port : SpritePort {
struct CL1362Port : SVGPort {
CL1362Port() {
box.size = Vec(33, 29);
spriteOffset = Vec(-2, -2);
spriteSize = Vec(39, 36);
spriteImage = Image::load("res/ComponentLibrary/CL1362.png");
// setSVG(SVG::load("res/ComponentLibrary/CL1362.svg"));
padding = Vec(1, 1);
background->svg = SVG::load("res/ComponentLibrary/CL1362.svg");
background->wrap();
box.size = background->box.size;
}
};

@@ -417,45 +416,41 @@ struct SmallLight : BASE {
// Switches
////////////////////

// struct BefacoBigKnob : {
// BefacoBigKnob() {
// box.size = Vec(75, 75);
// minAngle = -0.75*M_PI;
// maxAngle = 0.75*M_PI;
// setSVG(SVG::load("res/ComponentLibrary/BefacoBigKnob.svg"));
// }
// };
struct NKK : SVGSwitch, ToggleSwitch {
NKK() {
addFrame(SVG::load("res/ComponentLibrary/NKK0.svg"));
addFrame(SVG::load("res/ComponentLibrary/NKK1.svg"));
addFrame(SVG::load("res/ComponentLibrary/NKK2.svg"));
sw->wrap();
box.size = sw->box.size;
}
};


////////////////////
// Misc
////////////////////

/** If you don't add these to your ModuleWidget, it will fall out of the rack... */
struct Screw : SpriteWidget {
Screw() {
box.size = Vec(15, 14);
spriteOffset = Vec(0, 0);
spriteSize = Vec(15, 14);
}
};

struct BlackScrew : Screw {
BlackScrew() {
spriteImage = Image::load("res/ComponentLibrary/ScrewBlack.png");
struct ScrewSilver : SVGScrew {
ScrewSilver() {
sw->svg = SVG::load("res/ComponentLibrary/ScrewSilver.svg");
sw->wrap();
box.size = sw->box.size;
}
};

struct SilverScrew : Screw {
SilverScrew() {
spriteImage = Image::load("res/ComponentLibrary/ScrewSilver.png");
struct ScrewBlack : SVGScrew {
ScrewBlack() {
sw->svg = SVG::load("res/ComponentLibrary/ScrewBlack.svg");
sw->wrap();
box.size = sw->box.size;
}
};

struct LightPanel : Panel {
LightPanel() {
backgroundColor = nvgRGB(0xf2, 0xf2, 0xf2);
borderColor = nvgRGB(0xb8, 0xb8, 0xb8);
backgroundColor = nvgRGB(0xe8, 0xe8, 0xe8);
borderColor = nvgRGB(0xac, 0xac, 0xac);
}
};



+ 3
- 0
include/math.hpp View File

@@ -191,6 +191,9 @@ struct Rect {

Rect() {}
Rect(Vec pos, Vec size) : pos(pos), size(size) {}
static Rect fromMinMax(Vec min, Vec max) {
return Rect(min, max.minus(min));
}

/** Returns whether this Rect contains another Rect, inclusive on the top/left, non-inclusive on the bottom/right */
bool contains(Vec v) {


+ 2
- 2
include/rack.hpp View File

@@ -69,8 +69,8 @@ Port *createOutput(Vec pos, Module *module, int outputId) {
}

template <class TScrew>
Screw *createScrew(Vec pos) {
Screw *screw = new TScrew();
Widget *createScrew(Vec pos) {
Widget *screw = new TScrew();
screw->box.pos = pos;
return screw;
}


+ 1
- 1
include/widgets.hpp View File

@@ -195,7 +195,7 @@ struct FramebufferWidget : virtual Widget {
/** A margin in pixels around the children in the framebuffer
This prevents cutting the rendered SVG off on the box edges.
*/
int margin = 0;
Vec padding;
/** The root object in the framebuffer scene
The FramebufferWidget owns the pointer
*/


+ 1
- 1
src/app/Knob.cpp View File

@@ -6,7 +6,7 @@

namespace rack {

#define KNOB_SENSITIVITY 0.001
#define KNOB_SENSITIVITY 0.0015


void Knob::onDragStart() {


+ 1
- 0
src/app/Port.cpp View File

@@ -21,6 +21,7 @@ void Port::disconnect() {

void Port::draw(NVGcontext *vg) {
if (gRackWidget->activeWire) {
// Dim the Port if the active wire cannot plug into this Port
if (type == INPUT ? gRackWidget->activeWire->inputPort : gRackWidget->activeWire->outputPort)
nvgGlobalAlpha(vg, 0.5);
}


+ 3
- 2
src/app/SVGKnob.cpp View File

@@ -5,12 +5,13 @@ namespace rack {


SVGKnob::SVGKnob() {
margin = 4;
padding = Vec(1, 1);

shadow = new CircularShadow();
shadow->blur = 5.0;
shadow->box.pos = Vec(0, 1);
addChild(shadow);
// TODO Remove shadow entirely
// addChild(shadow);

tw = new TransformWidget();
addChild(tw);


+ 3
- 6
src/app/SVGPort.cpp View File

@@ -5,13 +5,10 @@ namespace rack {


SVGPort::SVGPort() {
sw = new SVGWidget();
addChild(sw);
}
padding = Vec(1, 1);

void SVGPort::setSVG(std::shared_ptr<SVG> svg) {
sw->svg = svg;
sw->wrap();
background = new SVGWidget();
addChild(background);
}

void SVGPort::draw(NVGcontext *vg) {


+ 15
- 0
src/app/SVGScrew.cpp View File

@@ -0,0 +1,15 @@
#include "app.hpp"


namespace rack {


SVGScrew::SVGScrew() {
padding = Vec(1, 1);

sw = new SVGWidget();
addChild(sw);
}


} // namespace rack

+ 30
- 0
src/app/SVGSlider.cpp View File

@@ -0,0 +1,30 @@
#include "app.hpp"


namespace rack {


SVGSlider::SVGSlider() {
background = new SVGWidget();
addChild(background);

handle = new SVGWidget();
addChild(handle);
}

void SVGSlider::step() {
if (dirty) {
// Update handle position
Vec handlePos = Vec(mapf(value, minValue, maxValue, minHandlePos.x, maxHandlePos.x), mapf(value, minValue, maxValue, minHandlePos.y, maxHandlePos.y));
handle->box.pos = handlePos;
}
FramebufferWidget::step();
}

void SVGSlider::onChange() {
dirty = true;
ParamWidget::onChange();
}


} // namespace rack

+ 33
- 0
src/app/SVGSwitch.cpp View File

@@ -0,0 +1,33 @@
#include "app.hpp"


namespace rack {


SVGSwitch::SVGSwitch() {
padding = Vec(1, 1);

sw = new SVGWidget();
addChild(sw);
}

void SVGSwitch::addFrame(std::shared_ptr<SVG> svg) {
frames.push_back(svg);
// Automatically set the frame as this SVG file.
// This allows us to wrap() the widget after calling
if (!sw->svg)
sw->svg = svg;
}

void SVGSwitch::setIndex(int index) {
if (0 <= index && index < (int)frames.size())
sw->svg = frames[index];
dirty = true;
}

void SVGSwitch::step() {
FramebufferWidget::step();
}


} // namespace rack

+ 8
- 15
src/widgets/FramebufferWidget.cpp View File

@@ -10,7 +10,6 @@ namespace rack {

struct FramebufferWidget::Internal {
NVGLUframebuffer *fb = NULL;
Vec offset;

~Internal() {
setFramebuffer(NULL);
@@ -31,22 +30,16 @@ FramebufferWidget::~FramebufferWidget() {
}

void FramebufferWidget::step() {
if (children.empty())
return;

// Step children before rendering
Widget::step();

// Render the scene to the framebuffer if dirty
if (dirty) {
Rect childrenBox = getChildrenBoundingBox();
assert(childrenBox.size.isFinite());
int width = ceilf(childrenBox.size.x) + 2*margin;
int height = ceilf(childrenBox.size.y) + 2*margin;
internal->offset = childrenBox.pos.minus(Vec(margin, margin));
Vec fbSize = box.size.plus(padding.mult(2));
assert(fbSize.isFinite());

internal->setFramebuffer(NULL);
NVGLUframebuffer *fb = nvgluCreateFramebuffer(gVg, width, height, NVG_IMAGE_REPEATX | NVG_IMAGE_REPEATY);
NVGLUframebuffer *fb = nvgluCreateFramebuffer(gVg, fbSize.x, fbSize.y, NVG_IMAGE_REPEATX | NVG_IMAGE_REPEATY);
if (!fb)
return;
internal->setFramebuffer(fb);
@@ -54,12 +47,12 @@ void FramebufferWidget::step() {
// TODO Support screens with pixelRatio != 1.0 (e.g. Retina) by using the actual size of the framebuffer, etc.
const float pixelRatio = 1.0;
nvgluBindFramebuffer(fb);
glViewport(0.0, 0.0, width, height);
glViewport(0.0, 0.0, fbSize.x, fbSize.y);
glClearColor(0.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
nvgBeginFrame(gVg, width, height, pixelRatio);
nvgBeginFrame(gVg, fbSize.x, fbSize.y, pixelRatio);

nvgTranslate(gVg, -internal->offset.x, -internal->offset.y);
nvgTranslate(gVg, padding.x, padding.y);
Widget::draw(gVg);

nvgEndFrame(gVg);
@@ -77,8 +70,8 @@ void FramebufferWidget::draw(NVGcontext *vg) {
int width, height;
nvgImageSize(vg, internal->fb->image, &width, &height);
nvgBeginPath(vg);
nvgRect(vg, internal->offset.x, internal->offset.y, width, height);
NVGpaint paint = nvgImagePattern(vg, internal->offset.x, internal->offset.y, width, height, 0.0, internal->fb->image, 1.0);
nvgRect(vg, -padding.x, -padding.y, width, height);
NVGpaint paint = nvgImagePattern(vg, -padding.x, -padding.y, width, height, 0.0, internal->fb->image, 1.0);
nvgFillPaint(vg, paint);
nvgFill(vg);



+ 32
- 10
src/widgets/SVGWidget.cpp View File

@@ -19,7 +19,7 @@ static void drawSVG(NVGcontext *vg, NSVGimage *svg) {
DEBUG_ONLY(printf("new image: %g x %g px\n", svg->width, svg->height);)
int shapeIndex = 0;
for (NSVGshape *shape = svg->shapes; shape; shape = shape->next, shapeIndex++) {
DEBUG_ONLY(printf(" new shape: %d id \"%s\", fillrule %d\n", shapeIndex, shape->id, shape->fillRule);)
DEBUG_ONLY(printf(" new shape: %d id \"%s\", fillrule %d, from (%f, %f) to (%f, %f)\n", shapeIndex, shape->id, shape->fillRule, shape->bounds[0], shape->bounds[1], shape->bounds[2], shape->bounds[3]);)

if (!(shape->flags & NSVG_FLAGS_VISIBLE))
continue;
@@ -29,14 +29,10 @@ static void drawSVG(NVGcontext *vg, NSVGimage *svg) {
if (shape->opacity < 1.0)
nvgGlobalAlpha(vg, shape->opacity);

nvgStrokeWidth(vg, shape->strokeWidth);
// strokeDashOffset, strokeDashArray, strokeDashCount not yet supported
// strokeLineJoin, strokeLineCap not yet supported

// Build path
nvgBeginPath(vg);
for (NSVGpath *path = shape->paths; path; path = path->next) {
DEBUG_ONLY(printf(" new path: %d points, %s\n", path->npts, path->closed ? "closed" : "open");)
DEBUG_ONLY(printf(" new path: %d points, %s, from (%f, %f) to (%f, %f)\n", path->npts, path->closed ? "closed" : "open", path->bounds[0], path->bounds[1], path->bounds[2], path->bounds[3]);)

nvgMoveTo(vg, path->pts[0], path->pts[1]);
for (int i = 1; i < path->npts; i += 3) {
@@ -61,14 +57,38 @@ static void drawSVG(NVGcontext *vg, NSVGimage *svg) {
DEBUG_ONLY(printf(" fill color (%g, %g, %g, %g)\n", color.r, color.g, color.b, color.a);)
} break;
case NSVG_PAINT_LINEAR_GRADIENT: {
// NSVGgradient *g = shape->fill.gradient;
// printf(" lin grad: %f\t%f\n", g->fx, g->fy);
NSVGgradient *g = shape->fill.gradient;
DEBUG_ONLY(printf(" linear gradient: %f\t%f\n", g->fx, g->fy);)
} break;
case NSVG_PAINT_RADIAL_GRADIENT: {
NSVGgradient *g = shape->fill.gradient;
DEBUG_ONLY(printf(" radial gradient: %f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\n", g->fx, g->fy, g->xform[0], g->xform[1], g->xform[2], g->xform[3], g->xform[4], g->xform[5]);)
for (int i = 0; i < g->nstops; i++) {
DEBUG_ONLY(printf(" stop: #%08x\t%f\n", g->stops[i].color, g->stops[i].offset);)
}
assert(g->nstops >= 1);
NVGcolor color0 = getNVGColor(g->stops[0].color);
NVGcolor color1 = getNVGColor(g->stops[g->nstops - 1].color);

float inverse[6];
Rect shapeBox = Rect::fromMinMax(Vec(shape->bounds[0], shape->bounds[1]), Vec(shape->bounds[2], shape->bounds[3]));
nvgTransformInverse(inverse, g->xform);
Vec c;
nvgTransformPoint(&c.x, &c.y, inverse, 5, 5);
printf("%f %f\n", c.x, c.y);

NVGpaint paint = nvgRadialGradient(vg, c.x, c.y, 0.0, 160, color0, color1);
nvgFillPaint(vg, paint);
} break;
}
nvgFill(vg);
}

// Stroke shape
nvgStrokeWidth(vg, shape->strokeWidth);
// strokeDashOffset, strokeDashArray, strokeDashCount not yet supported
// strokeLineJoin, strokeLineCap not yet supported

if (shape->stroke.type) {
switch (shape->stroke.type) {
case NSVG_PAINT_COLOR: {
@@ -92,10 +112,12 @@ static void drawSVG(NVGcontext *vg, NSVGimage *svg) {


void SVGWidget::wrap() {
if (svg)
if (svg) {
box.size = Vec(svg->handle->width, svg->handle->height);
else
}
else {
box.size = Vec();
}
}

void SVGWidget::draw(NVGcontext *vg) {


Loading…
Cancel
Save