| @@ -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) { | |||