| @@ -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 | |||
| @@ -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")); | |||
| } | |||
| }; | |||
| @@ -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 { | |||
| @@ -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> svg); | |||
| void step(); | |||
| void onChange(); | |||
| }; | |||
| struct Switch : ParamWidget, SpriteWidget { | |||
| }; | |||
| @@ -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> 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); | |||
| @@ -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(); | |||
| @@ -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); | |||
| } | |||
| @@ -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; | |||
| @@ -23,7 +23,7 @@ void ParamWidget::onChange() { | |||
| if (!module) | |||
| return; | |||
| // moduleWidget->module->params[paramId] = value; | |||
| // module->params[paramId] = value; | |||
| engineSetParamSmooth(module, paramId, value); | |||
| } | |||
| @@ -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 | |||
| @@ -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> 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 | |||
| @@ -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 | |||
| @@ -0,0 +1,95 @@ | |||
| #include "widgets.hpp" | |||
| // for gVg | |||
| #include "gui.hpp" | |||
| // for key codes | |||
| #include <GLFW/glfw3.h> | |||
| 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 | |||
| @@ -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]; | |||
| @@ -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); | |||
| } | |||
| @@ -5,10 +5,10 @@ namespace rack { | |||
| TransformWidget::TransformWidget() { | |||
| reset(); | |||
| identity(); | |||
| } | |||
| void TransformWidget::reset() { | |||
| void TransformWidget::identity() { | |||
| nvgTransformIdentity(transform); | |||
| } | |||
| @@ -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() { | |||