From 71a1b6a987414009ee2fc8d0643d77731208146f Mon Sep 17 00:00:00 2001 From: Andrew Belt Date: Thu, 16 Feb 2017 04:29:32 -0500 Subject: [PATCH] Added SVGKnob, added TextField, PasswordField, migrated to SVGKnobs --- Makefile | 2 +- include/components.hpp | 51 +++++++---------- include/rack.hpp | 5 ++ include/scene.hpp | 14 +++++ include/widgets.hpp | 34 +++++++++-- src/gui.cpp | 36 +++++++++--- src/widgets/FramebufferWidget.cpp | 9 ++- src/widgets/Light.cpp | 3 + src/widgets/ParamWidget.cpp | 2 +- src/widgets/PasswordField.cpp | 15 +++++ src/widgets/SVGKnob.cpp | 45 +++++++++++++++ src/widgets/SVGWidget.cpp | 3 +- src/widgets/TextField.cpp | 95 +++++++++++++++++++++++++++++++ src/widgets/Toolbar.cpp | 5 +- src/widgets/Tooltip.cpp | 2 +- src/widgets/TransformWidget.cpp | 4 +- src/widgets/WireWidget.cpp | 11 +--- 17 files changed, 275 insertions(+), 61 deletions(-) create mode 100644 src/widgets/PasswordField.cpp create mode 100644 src/widgets/SVGKnob.cpp create mode 100644 src/widgets/TextField.cpp diff --git a/Makefile b/Makefile index 980ce553..594186e4 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ ARCH ?= lin -FLAGS = -g -Wall -O3 -msse -mfpmath=sse -ffast-math \ +FLAGS = -g -Wall -O3 -msse -mfpmath=sse -ffast-math -fno-finite-math-only \ -I./ext -I./include CXXFLAGS = -fno-exceptions diff --git a/include/components.hpp b/include/components.hpp index eb2115e3..c3f042ee 100644 --- a/include/components.hpp +++ b/include/components.hpp @@ -36,56 +36,47 @@ struct SynthTechAlco : SpriteKnob { } }; -struct KnobDavies1900h : SpriteKnob { - KnobDavies1900h() { +struct Davies1900hKnob : SVGKnob { + Davies1900hKnob() { box.size = Vec(36, 36); - spriteOffset = Vec(-2, -2); - spriteSize = Vec(42, 42); - minIndex = 44; - maxIndex = -46; - spriteCount = 120; + minAngle = -0.75*M_PI; + maxAngle = 0.75*M_PI; } }; -struct KnobDavies1900hWhite : KnobDavies1900h { - KnobDavies1900hWhite() { - spriteImage = Image::load("res/ComponentLibrary/Davies1900hWhite.png"); +struct Davies1900hWhiteKnob : Davies1900hKnob { + Davies1900hWhiteKnob() { + setSVG(SVG::load("res/ComponentLibrary/Davies1900hWhite.svg")); } }; -struct KnobDavies1900hBlack : KnobDavies1900h { - KnobDavies1900hBlack() { - spriteImage = Image::load("res/ComponentLibrary/Davies1900hBlack.png"); +struct Davies1900hBlackKnob : Davies1900hKnob { + Davies1900hBlackKnob() { + setSVG(SVG::load("res/ComponentLibrary/Davies1900hBlack.svg")); } }; -struct KnobDavies1900hRed : KnobDavies1900h { - KnobDavies1900hRed() { - spriteImage = Image::load("res/ComponentLibrary/Davies1900hRed.png"); +struct Davies1900hRedKnob : Davies1900hKnob { + Davies1900hRedKnob() { + setSVG(SVG::load("res/ComponentLibrary/Davies1900hRed.svg")); } }; -struct BefacoBigKnob : SpriteKnob { +struct BefacoBigKnob : SVGKnob { BefacoBigKnob() { box.size = Vec(75, 75); - spriteOffset = Vec(-2, -2); - spriteSize = Vec(81, 81); - minIndex = 44; - maxIndex = -46; - spriteCount = 120; - spriteImage = Image::load("res/ComponentLibrary/BefacoBigKnob.png"); + minAngle = -0.75*M_PI; + maxAngle = 0.75*M_PI; + setSVG(SVG::load("res/ComponentLibrary/BefacoBigKnob.svg")); } }; -struct BefacoTinyKnob : SpriteKnob { +struct BefacoTinyKnob : SVGKnob { BefacoTinyKnob() { box.size = Vec(26, 26); - spriteOffset = Vec(-2, -2); - spriteSize = Vec(32, 32); - minIndex = 44; - maxIndex = -46; - spriteCount = 120; - spriteImage = Image::load("res/ComponentLibrary/BefacoTinyKnob.png"); + minAngle = -0.75*M_PI; + maxAngle = 0.75*M_PI; + setSVG(SVG::load("res/ComponentLibrary/BefacoTinyKnob.svg")); } }; diff --git a/include/rack.hpp b/include/rack.hpp index 3984e667..8b2b3dee 100644 --- a/include/rack.hpp +++ b/include/rack.hpp @@ -1,8 +1,13 @@ #pragma once + +#include "math.hpp" +#include "util.hpp" #include "plugin.hpp" #include "engine.hpp" #include "gui.hpp" +#include "scene.hpp" #include "components.hpp" +#include "dsp.hpp" namespace rack { diff --git a/include/scene.hpp b/include/scene.hpp index 03531272..20fe8b2c 100644 --- a/include/scene.hpp +++ b/include/scene.hpp @@ -131,6 +131,20 @@ struct SpriteKnob : Knob, SpriteWidget { void step(); }; +/** A knob which rotates an SVG and caches it in a framebuffer */ +struct SVGKnob : Knob, FramebufferWidget { + /** Angles in radians */ + float minAngle, maxAngle; + /** Not owned */ + TransformWidget *tw; + SVGWidget *sw; + + SVGKnob(); + void setSVG(std::shared_ptr svg); + void step(); + void onChange(); +}; + struct Switch : ParamWidget, SpriteWidget { }; diff --git a/include/widgets.hpp b/include/widgets.hpp index 47468be7..e6ed277d 100644 --- a/include/widgets.hpp +++ b/include/widgets.hpp @@ -95,6 +95,10 @@ struct Widget { virtual void onMouseEnter() {} /** Called when another widget begins responding to `onMouseMove` events */ virtual void onMouseLeave() {} + virtual void onSelect() {} + virtual void onDeselect() {} + virtual void onText(int codepoint) {} + virtual void onKey(int key) {} virtual Widget *onScroll(Vec pos, Vec scrollRel); /** Called when a widget responds to `onMouseDown` for a left button press */ @@ -116,7 +120,7 @@ struct TransformWidget : Widget { /** The transformation matrix */ float transform[6]; TransformWidget(); - void reset(); + void identity(); void translate(Vec delta); void rotate(float angle); void scale(Vec s); @@ -175,8 +179,8 @@ struct SpriteWidget : virtual Widget { struct SVGWidget : virtual Widget { std::shared_ptr svg; - /** Sets the box size to the svg page */ - void step(); + /** Sets the box size to the svg image size */ + void wrap(); void draw(NVGcontext *vg); }; @@ -185,9 +189,9 @@ When `dirty` is true, `scene` will be re-rendered on the next call to step(). Events are not passed to the underlying scene. */ struct FramebufferWidget : virtual Widget { + /** Set this to true to re-render the scene to the framebuffer in the next step() */ bool dirty = true; /** The root object in the framebuffer scene - Its position is ignored for now, fixed at (0, 0) The FramebufferWidget owns the pointer */ Widget *scene = NULL; @@ -196,6 +200,9 @@ struct FramebufferWidget : virtual Widget { FramebufferWidget(); ~FramebufferWidget(); + void setScene(Widget *w) { + scene = w; + } void step(); void draw(NVGcontext *vg); }; @@ -344,6 +351,25 @@ struct ScrollWidget : OpaqueWidget { Widget *onScroll(Vec pos, Vec scrollRel); }; +struct TextField : OpaqueWidget { + std::string text; + int begin = 0; + int end = 0; + + TextField() { + box.size.y = BND_WIDGET_HEIGHT; + } + void draw(NVGcontext *vg); + Widget *onMouseDown(Vec pos, int button); + void onText(int codepoint); + void onKey(int scancode); + void onSelect(); +}; + +struct PasswordField : TextField { + void draw(NVGcontext *vg); +}; + struct Tooltip : Widget { void step(); void draw(NVGcontext *vg); diff --git a/src/gui.cpp b/src/gui.cpp index d25fdbb1..85d68865 100644 --- a/src/gui.cpp +++ b/src/gui.cpp @@ -6,8 +6,7 @@ #include "gui.hpp" #include "scene.hpp" -// Include implementations here -// By the way, please stop packaging your libraries like this. It's best to use a single source file (e.g. foo.c) and a single header (e.g. foo.h) +// #define NANOVG_GL2_IMPLEMENTATION #define NANOVG_GL3_IMPLEMENTATION #include "../ext/nanovg/src/nanovg_gl.h" #include "../ext/nanovg/src/nanovg_gl_utils.h" @@ -36,13 +35,22 @@ void mouseButtonCallback(GLFWwindow *window, int button, int action, int mods) { if (action == GLFW_PRESS) { // onMouseDown Widget *w = gScene->onMouseDown(gMousePos, button); - gSelectedWidget = w; if (button == GLFW_MOUSE_BUTTON_LEFT) { - gDraggedWidget = w; - if (gDraggedWidget) { + if (w) { // onDragStart - gDraggedWidget->onDragStart(); + w->onDragStart(); + } + gDraggedWidget = w; + + if (w != gSelectedWidget) { + if (gSelectedWidget) { + w->onDeselect(); + } + if (w) { + w->onSelect(); + } + gSelectedWidget = w; } } } @@ -127,13 +135,16 @@ void scrollCallback(GLFWwindow *window, double x, double y) { gScene->onScroll(gMousePos, scrollRel.mult(-95)); } -void charCallback(GLFWwindow *window, unsigned int value) { +void charCallback(GLFWwindow *window, unsigned int codepoint) { + if (gSelectedWidget) { + gSelectedWidget->onText(codepoint); + } } static int lastWindowX, lastWindowY, lastWindowWidth, lastWindowHeight; void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods) { - if (action == GLFW_PRESS) { + if (action == GLFW_PRESS || action == GLFW_REPEAT) { if (key == GLFW_KEY_F11 || key == GLFW_KEY_ESCAPE) { // Toggle fullscreen GLFWmonitor *monitor = glfwGetWindowMonitor(window); @@ -151,6 +162,11 @@ void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods glfwSetWindowMonitor(window, monitor, 0, 0, mode->width, mode->height, mode->refreshRate); } } + else { + if (gSelectedWidget) { + gSelectedWidget->onKey(key); + } + } } } @@ -181,6 +197,8 @@ void guiInit() { err = glfwInit(); assert(err); + // glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2); + // glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2); glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); @@ -209,6 +227,7 @@ void guiInit() { glfwSetWindowSizeLimits(window, 240, 160, GLFW_DONT_CARE, GLFW_DONT_CARE); // Set up NanoVG + // gVg = nvgCreateGL2(NVG_ANTIALIAS); gVg = nvgCreateGL3(NVG_ANTIALIAS); assert(gVg); @@ -220,6 +239,7 @@ void guiInit() { void guiDestroy() { defaultFont.reset(); + // nvgDeleteGL2(gVg); nvgDeleteGL3(gVg); glfwDestroyWindow(window); glfwTerminate(); diff --git a/src/widgets/FramebufferWidget.cpp b/src/widgets/FramebufferWidget.cpp index 0fe391ea..1c7691a6 100644 --- a/src/widgets/FramebufferWidget.cpp +++ b/src/widgets/FramebufferWidget.cpp @@ -32,13 +32,16 @@ FramebufferWidget::~FramebufferWidget() { delete internal; } -/** A margin in pixels around the scene in the framebuffer */ +/** A margin in pixels around the scene in the framebuffer +This prevents cutting the rendered SVG off on the box edges. +*/ static const int margin = 1; void FramebufferWidget::step() { if (!scene) return; + // Step scene before rendering scene->step(); // Render the scene to the framebuffer if dirty @@ -73,13 +76,15 @@ void FramebufferWidget::step() { void FramebufferWidget::draw(NVGcontext *vg) { if (!internal->fb) return; + if (!scene) + return; // Draw framebuffer image int width, height; nvgImageSize(vg, internal->fb->image, &width, &height); nvgBeginPath(vg); nvgRect(vg, -margin, -margin, width, height); - NVGpaint paint = nvgImagePattern(vg, -margin, -margin, width, height, 0.0, internal->fb->image, 1.0); + NVGpaint paint = nvgImagePattern(vg, -margin + scene->box.pos.x, -margin + scene->box.pos.y, width, height, 0.0, internal->fb->image, 1.0); nvgFillPaint(vg, paint); nvgFill(vg); } diff --git a/src/widgets/Light.cpp b/src/widgets/Light.cpp index 67534fa4..3d9b6c7d 100644 --- a/src/widgets/Light.cpp +++ b/src/widgets/Light.cpp @@ -8,15 +8,18 @@ void Light::draw(NVGcontext *vg) { NVGcolor colorOutline = nvgLerpRGBA(color, nvgRGBf(0.0, 0.0, 0.0), 0.5); float radius = box.size.x / 2.0; + // Solid nvgBeginPath(vg); nvgCircle(vg, radius, radius, radius); nvgFillColor(vg, color); nvgFill(vg); + // Border nvgStrokeWidth(vg, 1.0); nvgStrokeColor(vg, colorOutline); nvgStroke(vg); + // Glow nvgGlobalCompositeOperation(vg, NVG_LIGHTER); NVGpaint paint; NVGcolor icol = color; diff --git a/src/widgets/ParamWidget.cpp b/src/widgets/ParamWidget.cpp index 086469a9..7a1d35c0 100644 --- a/src/widgets/ParamWidget.cpp +++ b/src/widgets/ParamWidget.cpp @@ -23,7 +23,7 @@ void ParamWidget::onChange() { if (!module) return; - // moduleWidget->module->params[paramId] = value; + // module->params[paramId] = value; engineSetParamSmooth(module, paramId, value); } diff --git a/src/widgets/PasswordField.cpp b/src/widgets/PasswordField.cpp new file mode 100644 index 00000000..738d0239 --- /dev/null +++ b/src/widgets/PasswordField.cpp @@ -0,0 +1,15 @@ +#include "widgets.hpp" + + +namespace rack { + + +void PasswordField::draw(NVGcontext *vg) { + std::string textTmp = text; + text = std::string(textTmp.size(), '*'); + TextField::draw(vg); + text = textTmp; +} + + +} // namespace rack diff --git a/src/widgets/SVGKnob.cpp b/src/widgets/SVGKnob.cpp new file mode 100644 index 00000000..c0238d88 --- /dev/null +++ b/src/widgets/SVGKnob.cpp @@ -0,0 +1,45 @@ +#include "scene.hpp" + + +namespace rack { + + +SVGKnob::SVGKnob() { + tw = new TransformWidget(); + setScene(tw); + + sw = new SVGWidget(); + tw->addChild(sw); +} + +void SVGKnob::setSVG(std::shared_ptr svg) { + sw->svg = svg; + sw->wrap(); +} + +void SVGKnob::step() { + // Re-transform TransformWidget if dirty + if (dirty) { + float angle = mapf(value, minValue, maxValue, minAngle, maxAngle); + tw->box.size = box.size; + tw->identity(); + // Resize SVG + Vec scale = Vec(box.size.x / sw->box.size.x, box.size.y / sw->box.size.y); + tw->scale(scale); + // Rotate SVG + Vec center = sw->box.getCenter(); + tw->translate(center); + tw->rotate(angle); + tw->translate(center.neg()); + } + FramebufferWidget::step(); +} + +void SVGKnob::onChange() { + dirty = true; + ParamWidget::onChange(); +} + + + +} // namespace rack diff --git a/src/widgets/SVGWidget.cpp b/src/widgets/SVGWidget.cpp index 929d5675..7925f45a 100644 --- a/src/widgets/SVGWidget.cpp +++ b/src/widgets/SVGWidget.cpp @@ -79,8 +79,7 @@ static void drawSVG(NVGcontext *vg, NSVGimage *svg) { } -void SVGWidget::step() { - // Automatically wrap box size to SVG page size +void SVGWidget::wrap() { if (svg) box.size = Vec(svg->handle->width, svg->handle->height); else diff --git a/src/widgets/TextField.cpp b/src/widgets/TextField.cpp new file mode 100644 index 00000000..8215a18c --- /dev/null +++ b/src/widgets/TextField.cpp @@ -0,0 +1,95 @@ +#include "widgets.hpp" +// for gVg +#include "gui.hpp" +// for key codes +#include + + +namespace rack { + + +void TextField::draw(NVGcontext *vg) { + BNDwidgetState state; + if (this == gSelectedWidget) + state = BND_ACTIVE; + else if (this == gHoveredWidget) + state = BND_HOVER; + else + state = BND_DEFAULT; + + bndTextField(vg, 0.0, 0.0, box.size.x, box.size.y, BND_CORNER_NONE, state, -1, text.c_str(), begin, end); +} + +Widget *TextField::onMouseDown(Vec pos, int button) { + end = begin = bndTextFieldTextPosition(gVg, 0.0, 0.0, box.size.x, box.size.y, -1, text.c_str(), pos.x, pos.y); + return OpaqueWidget::onMouseDown(pos, button); +} + + +void TextField::onText(int codepoint) { + if (begin < end) + text.erase(begin, end - begin); + char c = codepoint; + text.insert(begin, &c, 1); + begin++; + end = begin; +} + +void TextField::onKey(int key) { + switch (key) { + case GLFW_KEY_BACKSPACE: + if (begin < end) { + text.erase(begin, end - begin); + } + else { + begin--; + if (begin >= 0) + text.erase(begin, 1); + } + end = begin; + break; + case GLFW_KEY_DELETE: + if (begin < end) { + text.erase(begin, end - begin); + } + else { + text.erase(begin, 1); + } + end = begin; + break; + case GLFW_KEY_LEFT: + if (begin < end) { + } + else { + begin--; + } + end = begin; + break; + case GLFW_KEY_RIGHT: + if (begin < end) { + begin = end; + } + else { + begin++; + } + end = begin; + break; + case GLFW_KEY_HOME: + end = begin = 0; + break; + case GLFW_KEY_END: + end = begin = text.size(); + break; + } + + begin = mini(maxi(begin, 0), text.size()); + end = mini(maxi(end, 0), text.size()); +} + +void TextField::onSelect() { + begin = 0; + end = text.size(); +} + + +} // namespace rack diff --git a/src/widgets/Toolbar.cpp b/src/widgets/Toolbar.cpp index 586391f4..5cd0d117 100644 --- a/src/widgets/Toolbar.cpp +++ b/src/widgets/Toolbar.cpp @@ -75,8 +75,9 @@ struct SampleRateChoice : ChoiceButton { menu->box.pos = getAbsolutePos().plus(Vec(0, box.size.y)); menu->box.size.x = box.size.x; - float sampleRates[6] = {44100, 48000, 88200, 96000, 176400, 192000}; - for (int i = 0; i < 6; i++) { + float sampleRates[] = {44100, 48000, 88200, 96000, 176400, 192000}; + int sampleRatesLen = sizeof(sampleRates) / sizeof(sampleRates[0]); + for (int i = 0; i < sampleRatesLen; i++) { SampleRateItem *item = new SampleRateItem(); item->text = stringf("%.0f Hz", sampleRates[i]); item->sampleRate = sampleRates[i]; diff --git a/src/widgets/Tooltip.cpp b/src/widgets/Tooltip.cpp index a804ef18..91f34b4e 100644 --- a/src/widgets/Tooltip.cpp +++ b/src/widgets/Tooltip.cpp @@ -15,7 +15,7 @@ void Tooltip::step() { void Tooltip::draw(NVGcontext *vg) { - bndTooltipBackground(vg, box.pos.x, box.pos.y, box.size.x, box.size.y); + bndTooltipBackground(vg, 0.0, 0.0, box.size.x, box.size.y); Widget::draw(vg); } diff --git a/src/widgets/TransformWidget.cpp b/src/widgets/TransformWidget.cpp index 1206b672..a7092064 100644 --- a/src/widgets/TransformWidget.cpp +++ b/src/widgets/TransformWidget.cpp @@ -5,10 +5,10 @@ namespace rack { TransformWidget::TransformWidget() { - reset(); + identity(); } -void TransformWidget::reset() { +void TransformWidget::identity() { nvgTransformIdentity(transform); } diff --git a/src/widgets/WireWidget.cpp b/src/widgets/WireWidget.cpp index 98eeb1b2..33fd4baa 100644 --- a/src/widgets/WireWidget.cpp +++ b/src/widgets/WireWidget.cpp @@ -68,7 +68,7 @@ static void drawWire(NVGcontext *vg, Vec pos1, Vec pos2, NVGcolor color, float t } -static NVGcolor wireColors[8] = { +static const NVGcolor wireColors[8] = { nvgRGB(0xc9, 0xb7, 0x0e), // yellow nvgRGB(0xc9, 0x18, 0x47), // red nvgRGB(0x0c, 0x8e, 0x15), // green @@ -82,13 +82,8 @@ static int lastWireColorId = -1; WireWidget::WireWidget() { - int wireColorId; - do { - wireColorId = randomu32() % 8; - } while (wireColorId == lastWireColorId); - lastWireColorId = wireColorId; - - color = wireColors[wireColorId]; + lastWireColorId = (lastWireColorId + 1) % 8; + color = wireColors[lastWireColorId]; } WireWidget::~WireWidget() {