@@ -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 | |||
//////////////////// | |||
@@ -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); | |||
} | |||
}; | |||
@@ -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) { | |||
@@ -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; | |||
} | |||
@@ -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 | |||
*/ | |||
@@ -6,7 +6,7 @@ | |||
namespace rack { | |||
#define KNOB_SENSITIVITY 0.001 | |||
#define KNOB_SENSITIVITY 0.0015 | |||
void Knob::onDragStart() { | |||
@@ -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); | |||
} | |||
@@ -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); | |||
@@ -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) { | |||
@@ -0,0 +1,15 @@ | |||
#include "app.hpp" | |||
namespace rack { | |||
SVGScrew::SVGScrew() { | |||
padding = Vec(1, 1); | |||
sw = new SVGWidget(); | |||
addChild(sw); | |||
} | |||
} // namespace rack |
@@ -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 |
@@ -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 |
@@ -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); | |||
@@ -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) { | |||