| @@ -18,6 +18,6 @@ Install dependencies | |||||
| - [libsamplerate](http://www.mega-nerd.com/SRC/) | - [libsamplerate](http://www.mega-nerd.com/SRC/) | ||||
| - GTK+-2.0 if Linux (for file open/save dialog) | - GTK+-2.0 if Linux (for file open/save dialog) | ||||
| Run `make ARCH=linux` or `make ARCH=windows` or `make ARCH=apple` | |||||
| Run `make ARCH=lin` or `make ARCH=win` or `make ARCH=mac` | |||||
| If the build breaks because you think I've missed a step, feel free to post an issue. | If the build breaks because you think I've missed a step, feel free to post an issue. | ||||
| @@ -1 +1 @@ | |||||
| Subproject commit 8feae63a46fdf8ed83613b6a46da25d44adde07f | |||||
| Subproject commit c629efbfa6ca90035d6625dfa3586048a9c3201f | |||||
| @@ -4,20 +4,38 @@ | |||||
| namespace rack { | namespace rack { | ||||
| #define SCHEME_BLACK nvgRGB(0x00, 0x00, 0x00) | |||||
| #define SCHEME_WHITE nvgRGB(0xff, 0xff, 0xff) | |||||
| #define SCHEME_RED nvgRGB(0xed, 0x2c, 0x24) | |||||
| #define SCHEME_ORANGE nvgRGB(0xf2, 0xb1, 0x20) | |||||
| #define SCHEME_YELLOW nvgRGB(0xf9, 0xdf, 0x1c) | |||||
| #define SCHEME_GREEN nvgRGB(0x90, 0xc7, 0x3e) | |||||
| #define SCHEME_CYAN nvgRGB(0x22, 0xe6, 0xef) | |||||
| #define SCHEME_BLUE nvgRGB(0x29, 0xb2, 0xef) | |||||
| #define SCHEME_PURPLE nvgRGB(0xd5, 0x2b, 0xed) | |||||
| enum ColorNames { | |||||
| COLOR_BLACK, | |||||
| COLOR_WHITE, | |||||
| COLOR_RED, | |||||
| COLOR_ORANGE, | |||||
| COLOR_YELLOW, | |||||
| COLOR_GREEN, | |||||
| COLOR_CYAN, | |||||
| COLOR_BLUE, | |||||
| COLOR_PURPLE, | |||||
| NUM_COLORS | |||||
| }; | |||||
| extern const NVGcolor colors[NUM_COLORS]; | |||||
| //////////////////// | //////////////////// | ||||
| // Knobs | // Knobs | ||||
| //////////////////// | //////////////////// | ||||
| struct SynthTechAlco : SpriteKnob { | |||||
| SynthTechAlco() { | |||||
| box.size = Vec(45, 45); | |||||
| spriteOffset = Vec(-3, -2); | |||||
| spriteSize = Vec(51, 51); | |||||
| minIndex = 49; | |||||
| maxIndex = -51; | |||||
| spriteCount = 120; | |||||
| spriteImage = Image::load("res/ComponentLibrary/SynthTechAlco.png"); | |||||
| } | |||||
| }; | |||||
| struct KnobDavies1900h : SpriteKnob { | struct KnobDavies1900h : SpriteKnob { | ||||
| KnobDavies1900h() { | KnobDavies1900h() { | ||||
| box.size = Vec(36, 36); | box.size = Vec(36, 36); | ||||
| @@ -102,7 +120,7 @@ typedef PJ301M<OutputPort> OutputPortPJ301M; | |||||
| template <typename BASE> | template <typename BASE> | ||||
| struct PJ3410 : BASE { | struct PJ3410 : BASE { | ||||
| PJ3410() { | PJ3410() { | ||||
| this->box.size = Vec(32, 32); | |||||
| this->box.size = Vec(32, 31); | |||||
| this->spriteOffset = Vec(-1, -1); | this->spriteOffset = Vec(-1, -1); | ||||
| this->spriteSize = Vec(36, 36); | this->spriteSize = Vec(36, 36); | ||||
| this->spriteImage = Image::load("res/ComponentLibrary/PJ3410.png"); | this->spriteImage = Image::load("res/ComponentLibrary/PJ3410.png"); | ||||
| @@ -131,23 +149,31 @@ struct ValueLight : Light { | |||||
| float *value; | float *value; | ||||
| }; | }; | ||||
| struct RedValueLight : ValueLight { | |||||
| template <int COLOR> | |||||
| struct ColorValueLight : ValueLight { | |||||
| void step() { | void step() { | ||||
| float v = sqrtBipolar(getf(value)); | float v = sqrtBipolar(getf(value)); | ||||
| color = nvgLerpRGBA(SCHEME_BLACK, SCHEME_RED, v); | |||||
| color = nvgLerpRGBA(colors[COLOR_BLACK], colors[COLOR], v); | |||||
| } | } | ||||
| }; | }; | ||||
| struct GreenRedPolarityLight : ValueLight { | |||||
| typedef ColorValueLight<COLOR_RED> RedValueLight; | |||||
| typedef ColorValueLight<COLOR_YELLOW> YellowValueLight; | |||||
| typedef ColorValueLight<COLOR_GREEN> GreenValueLight; | |||||
| template <int COLOR_POS, int COLOR_NEG> | |||||
| struct PolarityLight : ValueLight { | |||||
| void step() { | void step() { | ||||
| float v = sqrtBipolar(getf(value)); | float v = sqrtBipolar(getf(value)); | ||||
| if (v >= 0.0) | if (v >= 0.0) | ||||
| color = nvgLerpRGBA(SCHEME_BLACK, SCHEME_GREEN, v); | |||||
| color = nvgLerpRGBA(colors[COLOR_BLACK], colors[COLOR_POS], v); | |||||
| else | else | ||||
| color = nvgLerpRGBA(SCHEME_BLACK, SCHEME_RED, -v); | |||||
| color = nvgLerpRGBA(colors[COLOR_BLACK], colors[COLOR_NEG], -v); | |||||
| } | } | ||||
| }; | }; | ||||
| typedef PolarityLight<COLOR_GREEN, COLOR_RED> GreenRedPolarityLight; | |||||
| template <typename BASE> | template <typename BASE> | ||||
| struct LargeLight : BASE { | struct LargeLight : BASE { | ||||
| LargeLight() { | LargeLight() { | ||||
| @@ -197,13 +223,13 @@ struct SilverScrew : Screw { | |||||
| struct LightPanel : Panel { | struct LightPanel : Panel { | ||||
| LightPanel() { | LightPanel() { | ||||
| backgroundColor = nvgRGB(0xe8, 0xe8, 0xe8); | backgroundColor = nvgRGB(0xe8, 0xe8, 0xe8); | ||||
| borderColor = nvgRGB(0xac, 0xac, 0xac); | |||||
| borderColor = nvgRGB(0xa1, 0xa1, 0xa1); | |||||
| } | } | ||||
| }; | }; | ||||
| struct DarkPanel : Panel { | struct DarkPanel : Panel { | ||||
| DarkPanel() { | DarkPanel() { | ||||
| backgroundColor = nvgRGB(0x0f, 0x0f, 0x0f); | |||||
| backgroundColor = nvgRGB(0x17, 0x17, 0x17); | |||||
| borderColor = nvgRGB(0x5e, 0x5e, 0x5e); | borderColor = nvgRGB(0x5e, 0x5e, 0x5e); | ||||
| } | } | ||||
| }; | }; | ||||
| @@ -13,7 +13,6 @@ void guiCursorUnlock(); | |||||
| const char *guiSaveDialog(const char *filters, const char *filename); | const char *guiSaveDialog(const char *filters, const char *filename); | ||||
| const char *guiOpenDialog(const char *filters, const char *filename); | const char *guiOpenDialog(const char *filters, const char *filename); | ||||
| // TODO This should probably go in another file, like resources.hpp? | |||||
| void drawSVG(NVGcontext *vg, NSVGimage *svg); | |||||
| extern NVGcontext *gVg; | |||||
| } // namespace rack | } // namespace rack | ||||
| @@ -61,6 +61,12 @@ inline float sqrtBipolar(float x) { | |||||
| return x >= 0.0 ? sqrtf(x) : -sqrtf(-x); | return x >= 0.0 ? sqrtf(x) : -sqrtf(-x); | ||||
| } | } | ||||
| /** This is pretty much a scaled sinh */ | |||||
| inline float exponentialBipolar(float b, float x) { | |||||
| const float a = b - 1.0 / b; | |||||
| return (powf(b, x) - powf(b, -x)) / a; | |||||
| } | |||||
| inline float sincf(float x) { | inline float sincf(float x) { | ||||
| if (x == 0.0) | if (x == 0.0) | ||||
| return 1.0; | return 1.0; | ||||
| @@ -145,6 +151,12 @@ struct Vec { | |||||
| Vec round() { | Vec round() { | ||||
| return Vec(roundf(x), roundf(y)); | return Vec(roundf(x), roundf(y)); | ||||
| } | } | ||||
| bool isFinite() { | |||||
| return isfinite(x) && isfinite(y); | |||||
| } | |||||
| bool isZero() { | |||||
| return x == 0.0 && y == 0.0; | |||||
| } | |||||
| }; | }; | ||||
| @@ -43,7 +43,7 @@ struct SVG { | |||||
| //////////////////// | //////////////////// | ||||
| // base class and traits | |||||
| // Base widget | |||||
| //////////////////// | //////////////////// | ||||
| /** A node in the 2D scene graph */ | /** A node in the 2D scene graph */ | ||||
| @@ -112,6 +112,22 @@ struct Widget { | |||||
| virtual void onChange() {} | virtual void onChange() {} | ||||
| }; | }; | ||||
| struct TransformWidget : Widget { | |||||
| /** The transformation matrix */ | |||||
| float transform[6]; | |||||
| TransformWidget(); | |||||
| void reset(); | |||||
| void translate(Vec delta); | |||||
| void rotate(float angle); | |||||
| void scale(Vec s); | |||||
| void draw(NVGcontext *vg); | |||||
| }; | |||||
| //////////////////// | |||||
| // Trait widgets | |||||
| //////////////////// | |||||
| /** Widget that does not respond to events */ | /** Widget that does not respond to events */ | ||||
| struct TransparentWidget : virtual Widget { | struct TransparentWidget : virtual Widget { | ||||
| Widget *onMouseDown(Vec pos, int button) {return NULL;} | Widget *onMouseDown(Vec pos, int button) {return NULL;} | ||||
| @@ -157,6 +173,33 @@ struct SpriteWidget : virtual Widget { | |||||
| void draw(NVGcontext *vg); | void draw(NVGcontext *vg); | ||||
| }; | }; | ||||
| struct SVGWidget : virtual Widget { | |||||
| std::shared_ptr<SVG> svg; | |||||
| /** Sets the box size to the svg page */ | |||||
| void step(); | |||||
| void draw(NVGcontext *vg); | |||||
| }; | |||||
| /** Caches a widget's draw() result to a framebuffer so it is called less frequently | |||||
| 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 { | |||||
| 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; | |||||
| struct Internal; | |||||
| Internal *internal; | |||||
| FramebufferWidget(); | |||||
| ~FramebufferWidget(); | |||||
| void step(); | |||||
| void draw(NVGcontext *vg); | |||||
| }; | |||||
| struct QuantityWidget : virtual Widget { | struct QuantityWidget : virtual Widget { | ||||
| float value = 0.0; | float value = 0.0; | ||||
| float minValue = 0.0; | float minValue = 0.0; | ||||
| @@ -179,7 +222,7 @@ struct QuantityWidget : virtual Widget { | |||||
| }; | }; | ||||
| //////////////////// | //////////////////// | ||||
| // gui elements | |||||
| // GUI widgets | |||||
| //////////////////// | //////////////////// | ||||
| struct Label : Widget { | struct Label : Widget { | ||||
| @@ -0,0 +1,19 @@ | |||||
| #include "components.hpp" | |||||
| namespace rack { | |||||
| const NVGcolor colors[NUM_COLORS] = { | |||||
| nvgRGB(0x00, 0x00, 0x00), | |||||
| nvgRGB(0xff, 0xff, 0xff), | |||||
| nvgRGB(0xed, 0x2c, 0x24), | |||||
| nvgRGB(0xf2, 0xb1, 0x20), | |||||
| nvgRGB(0xf9, 0xdf, 0x1c), | |||||
| nvgRGB(0x90, 0xc7, 0x3e), | |||||
| nvgRGB(0x22, 0xe6, 0xef), | |||||
| nvgRGB(0x29, 0xb2, 0xef), | |||||
| nvgRGB(0xd5, 0x2b, 0xed), | |||||
| }; | |||||
| } // namespace rack | |||||
| @@ -7,9 +7,10 @@ | |||||
| #include "scene.hpp" | #include "scene.hpp" | ||||
| // Include implementations here | // Include implementations here | ||||
| // By the way, please stop packaging your libraries like this. It's easiest to use a single source file (e.g. foo.c) and a single header (e.g. foo.h) | |||||
| // 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_GL3_IMPLEMENTATION | #define NANOVG_GL3_IMPLEMENTATION | ||||
| #include "../ext/nanovg/src/nanovg_gl.h" | #include "../ext/nanovg/src/nanovg_gl.h" | ||||
| #include "../ext/nanovg/src/nanovg_gl_utils.h" | |||||
| #define BLENDISH_IMPLEMENTATION | #define BLENDISH_IMPLEMENTATION | ||||
| #include "../ext/oui/blendish.h" | #include "../ext/oui/blendish.h" | ||||
| #define NANOSVG_IMPLEMENTATION | #define NANOSVG_IMPLEMENTATION | ||||
| @@ -23,8 +24,8 @@ extern "C" { | |||||
| namespace rack { | namespace rack { | ||||
| static GLFWwindow *window = NULL; | static GLFWwindow *window = NULL; | ||||
| static NVGcontext *vg = NULL; | |||||
| static std::shared_ptr<Font> defaultFont; | static std::shared_ptr<Font> defaultFont; | ||||
| NVGcontext *gVg = NULL; | |||||
| void windowSizeCallback(GLFWwindow* window, int width, int height) { | void windowSizeCallback(GLFWwindow* window, int width, int height) { | ||||
| @@ -160,16 +161,16 @@ void renderGui() { | |||||
| // Update and render | // Update and render | ||||
| glViewport(0, 0, width, height); | glViewport(0, 0, width, height); | ||||
| glClearColor(1.0, 1.0, 1.0, 1.0); | |||||
| glClearColor(0.0, 0.0, 0.0, 1.0); | |||||
| glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); | ||||
| nvgBeginFrame(vg, width, height, 1.0); | |||||
| nvgBeginFrame(gVg, width, height, 1.0); | |||||
| nvgSave(vg); | |||||
| gScene->draw(vg); | |||||
| nvgRestore(vg); | |||||
| nvgSave(gVg); | |||||
| gScene->draw(gVg); | |||||
| nvgRestore(gVg); | |||||
| nvgEndFrame(vg); | |||||
| nvgEndFrame(gVg); | |||||
| glfwSwapBuffers(window); | glfwSwapBuffers(window); | ||||
| } | } | ||||
| @@ -180,12 +181,10 @@ void guiInit() { | |||||
| err = glfwInit(); | err = glfwInit(); | ||||
| assert(err); | assert(err); | ||||
| // #ifndef WINDOWS | |||||
| glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); | glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); | ||||
| glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2); | glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2); | ||||
| glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); | glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); | ||||
| glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); | glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); | ||||
| // #endif | |||||
| window = glfwCreateWindow(1000, 750, gApplicationName.c_str(), NULL, NULL); | window = glfwCreateWindow(1000, 750, gApplicationName.c_str(), NULL, NULL); | ||||
| assert(window); | assert(window); | ||||
| glfwMakeContextCurrent(window); | glfwMakeContextCurrent(window); | ||||
| @@ -210,8 +209,8 @@ void guiInit() { | |||||
| glfwSetWindowSizeLimits(window, 240, 160, GLFW_DONT_CARE, GLFW_DONT_CARE); | glfwSetWindowSizeLimits(window, 240, 160, GLFW_DONT_CARE, GLFW_DONT_CARE); | ||||
| // Set up NanoVG | // Set up NanoVG | ||||
| vg = nvgCreateGL3(NVG_ANTIALIAS); | |||||
| assert(vg); | |||||
| gVg = nvgCreateGL3(NVG_ANTIALIAS); | |||||
| assert(gVg); | |||||
| // Set up Blendish | // Set up Blendish | ||||
| defaultFont = Font::load("res/DejaVuSans.ttf"); | defaultFont = Font::load("res/DejaVuSans.ttf"); | ||||
| @@ -221,7 +220,7 @@ void guiInit() { | |||||
| void guiDestroy() { | void guiDestroy() { | ||||
| defaultFont.reset(); | defaultFont.reset(); | ||||
| nvgDeleteGL3(vg); | |||||
| nvgDeleteGL3(gVg); | |||||
| glfwDestroyWindow(window); | glfwDestroyWindow(window); | ||||
| glfwTerminate(); | glfwTerminate(); | ||||
| } | } | ||||
| @@ -275,7 +274,7 @@ const char *guiOpenDialog(const char *filters, const char *filename) { | |||||
| //////////////////// | //////////////////// | ||||
| Font::Font(const std::string &filename) { | Font::Font(const std::string &filename) { | ||||
| handle = nvgCreateFont(vg, filename.c_str(), filename.c_str()); | |||||
| handle = nvgCreateFont(gVg, filename.c_str(), filename.c_str()); | |||||
| if (handle >= 0) { | if (handle >= 0) { | ||||
| fprintf(stderr, "Loaded font %s\n", filename.c_str()); | fprintf(stderr, "Loaded font %s\n", filename.c_str()); | ||||
| } | } | ||||
| @@ -285,7 +284,7 @@ Font::Font(const std::string &filename) { | |||||
| } | } | ||||
| Font::~Font() { | Font::~Font() { | ||||
| // There is no NanoVG deleteFont() function, so do nothing | |||||
| // There is no NanoVG deleteFont() function yet, so do nothing | |||||
| } | } | ||||
| std::shared_ptr<Font> Font::load(const std::string &filename) { | std::shared_ptr<Font> Font::load(const std::string &filename) { | ||||
| @@ -301,7 +300,7 @@ std::shared_ptr<Font> Font::load(const std::string &filename) { | |||||
| //////////////////// | //////////////////// | ||||
| Image::Image(const std::string &filename) { | Image::Image(const std::string &filename) { | ||||
| handle = nvgCreateImage(vg, filename.c_str(), NVG_IMAGE_REPEATX | NVG_IMAGE_REPEATY); | |||||
| handle = nvgCreateImage(gVg, filename.c_str(), NVG_IMAGE_REPEATX | NVG_IMAGE_REPEATY); | |||||
| if (handle > 0) { | if (handle > 0) { | ||||
| fprintf(stderr, "Loaded image %s\n", filename.c_str()); | fprintf(stderr, "Loaded image %s\n", filename.c_str()); | ||||
| } | } | ||||
| @@ -312,7 +311,7 @@ Image::Image(const std::string &filename) { | |||||
| Image::~Image() { | Image::~Image() { | ||||
| // TODO What if handle is invalid? | // TODO What if handle is invalid? | ||||
| nvgDeleteImage(vg, handle); | |||||
| nvgDeleteImage(gVg, handle); | |||||
| } | } | ||||
| std::shared_ptr<Image> Image::load(const std::string &filename) { | std::shared_ptr<Image> Image::load(const std::string &filename) { | ||||
| @@ -350,83 +349,4 @@ std::shared_ptr<SVG> SVG::load(const std::string &filename) { | |||||
| } | } | ||||
| //////////////////// | |||||
| // drawSVG | |||||
| //////////////////// | |||||
| NVGcolor getNVGColor(int color) { | |||||
| return nvgRGBA((color >> 0) & 0xff, (color >> 8) & 0xff, (color >> 16) & 0xff, (color >> 24) & 0xff); | |||||
| // return nvgRGBA((color >> 24) & 0xff, (color >> 16) & 0xff, (color >> 8) & 0xff, (color) & 0xff); | |||||
| } | |||||
| void drawSVG(NVGcontext *vg, NSVGimage *svg) { | |||||
| for (NSVGshape *shape = svg->shapes; shape; shape = shape->next) { | |||||
| // printf(" new shape: id \"%s\", fillrule %d\n", shape->id, shape->fillRule); | |||||
| if (!(shape->flags & NSVG_FLAGS_VISIBLE)) | |||||
| continue; | |||||
| nvgSave(vg); | |||||
| nvgGlobalAlpha(vg, shape->opacity); | |||||
| nvgStrokeWidth(vg, shape->strokeWidth); | |||||
| // strokeDashOffset, strokeDashArray, strokeDashCount not supported | |||||
| // strokeLineJoin, strokeLineCap not supported | |||||
| // Build path | |||||
| nvgBeginPath(vg); | |||||
| for (NSVGpath *path = shape->paths; path; path = path->next) { | |||||
| // printf(" new path: %d points, %s\n", path->npts, path->closed ? "closed" : "notclosed"); | |||||
| nvgMoveTo(vg, path->pts[0], path->pts[1]); | |||||
| for (int i = 1; i < path->npts; i += 3) { | |||||
| float *p = &path->pts[2*i]; | |||||
| nvgBezierTo(vg, p[0], p[1], p[2], p[3], p[4], p[5]); | |||||
| // nvgLineTo(vg, p[4], p[5]); | |||||
| } | |||||
| if (path->closed) | |||||
| nvgClosePath(vg); | |||||
| if (path->next) | |||||
| nvgPathWinding(vg, NVG_HOLE); | |||||
| } | |||||
| // Fill shape | |||||
| if (shape->fill.type) { | |||||
| switch (shape->fill.type) { | |||||
| case NSVG_PAINT_COLOR: { | |||||
| NVGcolor color = getNVGColor(shape->fill.color); | |||||
| nvgFillColor(vg, color); | |||||
| // printf(" fill color (%f %f %f %f)\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); | |||||
| } break; | |||||
| } | |||||
| nvgFill(vg); | |||||
| } | |||||
| // Stroke shape | |||||
| if (shape->stroke.type) { | |||||
| switch (shape->stroke.type) { | |||||
| case NSVG_PAINT_COLOR: { | |||||
| NVGcolor color = getNVGColor(shape->stroke.color); | |||||
| nvgFillColor(vg, color); | |||||
| // printf(" stroke color (%f %f %f %f)\n", color.r, color.g, color.b, color.a); | |||||
| } break; | |||||
| case NSVG_PAINT_LINEAR_GRADIENT: { | |||||
| NSVGgradient *g = shape->stroke.gradient; | |||||
| // printf(" lin grad: %f\t%f\n", g->fx, g->fy); | |||||
| } break; | |||||
| } | |||||
| nvgStroke(vg); | |||||
| } | |||||
| nvgRestore(vg); | |||||
| } | |||||
| } | |||||
| } // namespace rack | } // namespace rack | ||||
| @@ -0,0 +1,88 @@ | |||||
| #include "widgets.hpp" | |||||
| #include "gui.hpp" | |||||
| #include <GL/glew.h> | |||||
| #include "../ext/nanovg/src/nanovg_gl.h" | |||||
| #include "../ext/nanovg/src/nanovg_gl_utils.h" | |||||
| namespace rack { | |||||
| struct FramebufferWidget::Internal { | |||||
| NVGLUframebuffer *fb = NULL; | |||||
| ~Internal() { | |||||
| setFramebuffer(NULL); | |||||
| } | |||||
| void setFramebuffer(NVGLUframebuffer *fb) { | |||||
| if (this->fb) | |||||
| nvgluDeleteFramebuffer(this->fb); | |||||
| this->fb = fb; | |||||
| } | |||||
| }; | |||||
| FramebufferWidget::FramebufferWidget() { | |||||
| internal = new Internal(); | |||||
| } | |||||
| FramebufferWidget::~FramebufferWidget() { | |||||
| if (scene) { | |||||
| delete scene; | |||||
| } | |||||
| delete internal; | |||||
| } | |||||
| /** A margin in pixels around the scene in the framebuffer */ | |||||
| static const int margin = 1; | |||||
| void FramebufferWidget::step() { | |||||
| if (!scene) | |||||
| return; | |||||
| scene->step(); | |||||
| // Render the scene to the framebuffer if dirty | |||||
| if (dirty) { | |||||
| assert(scene->box.size.isFinite()); | |||||
| int width = ceilf(scene->box.size.x) + 2*margin; | |||||
| int height = ceilf(scene->box.size.y) + 2*margin; | |||||
| internal->setFramebuffer(NULL); | |||||
| NVGLUframebuffer *fb = nvgluCreateFramebuffer(gVg, width, height, NVG_IMAGE_REPEATX | NVG_IMAGE_REPEATY); | |||||
| assert(fb); | |||||
| internal->setFramebuffer(fb); | |||||
| // 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); | |||||
| 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); | |||||
| nvgTranslate(gVg, margin, margin); | |||||
| scene->draw(gVg); | |||||
| nvgEndFrame(gVg); | |||||
| nvgluBindFramebuffer(NULL); | |||||
| dirty = false; | |||||
| } | |||||
| } | |||||
| void FramebufferWidget::draw(NVGcontext *vg) { | |||||
| if (!internal->fb) | |||||
| 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); | |||||
| nvgFillPaint(vg, paint); | |||||
| nvgFill(vg); | |||||
| } | |||||
| } // namespace rack | |||||
| @@ -17,18 +17,18 @@ void Light::draw(NVGcontext *vg) { | |||||
| nvgStrokeColor(vg, colorOutline); | nvgStrokeColor(vg, colorOutline); | ||||
| nvgStroke(vg); | nvgStroke(vg); | ||||
| // nvgGlobalCompositeOperation(vg, NVG_LIGHTER); | |||||
| // NVGpaint paint; | |||||
| // NVGcolor icol = color; | |||||
| // icol.a = 0.2; | |||||
| // NVGcolor ocol = color; | |||||
| // ocol.a = 0.0; | |||||
| // float oradius = radius + 20.0; | |||||
| // paint = nvgRadialGradient(vg, radius, radius, radius, oradius, icol, ocol); | |||||
| // nvgFillPaint(vg, paint); | |||||
| // nvgBeginPath(vg); | |||||
| // nvgRect(vg, radius - oradius, radius - oradius, 2*oradius, 2*oradius); | |||||
| // nvgFill(vg); | |||||
| nvgGlobalCompositeOperation(vg, NVG_LIGHTER); | |||||
| NVGpaint paint; | |||||
| NVGcolor icol = color; | |||||
| icol.a = 0.1; | |||||
| NVGcolor ocol = color; | |||||
| ocol.a = 0.0; | |||||
| float oradius = radius + 30.0; | |||||
| paint = nvgRadialGradient(vg, radius, radius, radius, oradius, icol, ocol); | |||||
| nvgFillPaint(vg, paint); | |||||
| nvgBeginPath(vg); | |||||
| nvgRect(vg, radius - oradius, radius - oradius, 2*oradius, 2*oradius); | |||||
| nvgFill(vg); | |||||
| } | } | ||||
| @@ -181,7 +181,7 @@ void ModuleWidget::onMouseDown(int button) { | |||||
| menu->pushChild(menuLabel); | menu->pushChild(menuLabel); | ||||
| ResetParamsMenuItem *resetItem = new ResetParamsMenuItem(); | ResetParamsMenuItem *resetItem = new ResetParamsMenuItem(); | ||||
| resetItem->text = "Reset parameters"; | |||||
| resetItem->text = "Initialize parameters"; | |||||
| resetItem->moduleWidget = this; | resetItem->moduleWidget = this; | ||||
| menu->pushChild(resetItem); | menu->pushChild(resetItem); | ||||
| @@ -0,0 +1,95 @@ | |||||
| #include "widgets.hpp" | |||||
| namespace rack { | |||||
| static NVGcolor getNVGColor(int color) { | |||||
| return nvgRGBA((color >> 0) & 0xff, (color >> 8) & 0xff, (color >> 16) & 0xff, (color >> 24) & 0xff); | |||||
| // return nvgRGBA((color >> 24) & 0xff, (color >> 16) & 0xff, (color >> 8) & 0xff, (color) & 0xff); | |||||
| } | |||||
| static void drawSVG(NVGcontext *vg, NSVGimage *svg) { | |||||
| for (NSVGshape *shape = svg->shapes; shape; shape = shape->next) { | |||||
| // printf(" new shape: id \"%s\", fillrule %d\n", shape->id, shape->fillRule); | |||||
| if (!(shape->flags & NSVG_FLAGS_VISIBLE)) | |||||
| continue; | |||||
| nvgSave(vg); | |||||
| nvgGlobalAlpha(vg, shape->opacity); | |||||
| nvgStrokeWidth(vg, shape->strokeWidth); | |||||
| // strokeDashOffset, strokeDashArray, strokeDashCount not supported | |||||
| // strokeLineJoin, strokeLineCap not supported | |||||
| // Build path | |||||
| nvgBeginPath(vg); | |||||
| for (NSVGpath *path = shape->paths; path; path = path->next) { | |||||
| // printf(" new path: %d points, %s\n", path->npts, path->closed ? "closed" : "notclosed"); | |||||
| nvgMoveTo(vg, path->pts[0], path->pts[1]); | |||||
| for (int i = 1; i < path->npts; i += 3) { | |||||
| float *p = &path->pts[2*i]; | |||||
| nvgBezierTo(vg, p[0], p[1], p[2], p[3], p[4], p[5]); | |||||
| // nvgLineTo(vg, p[4], p[5]); | |||||
| } | |||||
| if (path->closed) | |||||
| nvgClosePath(vg); | |||||
| if (path->next) | |||||
| nvgPathWinding(vg, NVG_HOLE); | |||||
| } | |||||
| // Fill shape | |||||
| if (shape->fill.type) { | |||||
| switch (shape->fill.type) { | |||||
| case NSVG_PAINT_COLOR: { | |||||
| NVGcolor color = getNVGColor(shape->fill.color); | |||||
| nvgFillColor(vg, color); | |||||
| // printf(" fill color (%f %f %f %f)\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); | |||||
| } break; | |||||
| } | |||||
| nvgFill(vg); | |||||
| } | |||||
| // Stroke shape | |||||
| if (shape->stroke.type) { | |||||
| switch (shape->stroke.type) { | |||||
| case NSVG_PAINT_COLOR: { | |||||
| NVGcolor color = getNVGColor(shape->stroke.color); | |||||
| nvgFillColor(vg, color); | |||||
| // printf(" stroke color (%f %f %f %f)\n", color.r, color.g, color.b, color.a); | |||||
| } break; | |||||
| case NSVG_PAINT_LINEAR_GRADIENT: { | |||||
| // NSVGgradient *g = shape->stroke.gradient; | |||||
| // printf(" lin grad: %f\t%f\n", g->fx, g->fy); | |||||
| } break; | |||||
| } | |||||
| nvgStroke(vg); | |||||
| } | |||||
| nvgRestore(vg); | |||||
| } | |||||
| } | |||||
| void SVGWidget::step() { | |||||
| // Automatically wrap box size to SVG page size | |||||
| if (svg) | |||||
| box.size = Vec(svg->handle->width, svg->handle->height); | |||||
| else | |||||
| box.size = Vec(); | |||||
| } | |||||
| void SVGWidget::draw(NVGcontext *vg) { | |||||
| drawSVG(vg, svg->handle); | |||||
| } | |||||
| } // namespace rack | |||||
| @@ -0,0 +1,40 @@ | |||||
| #include "scene.hpp" | |||||
| namespace rack { | |||||
| TransformWidget::TransformWidget() { | |||||
| reset(); | |||||
| } | |||||
| void TransformWidget::reset() { | |||||
| nvgTransformIdentity(transform); | |||||
| } | |||||
| void TransformWidget::translate(Vec delta) { | |||||
| float t[6]; | |||||
| nvgTransformTranslate(t, delta.x, delta.y); | |||||
| nvgTransformPremultiply(transform, t); | |||||
| } | |||||
| void TransformWidget::rotate(float angle) { | |||||
| float t[6]; | |||||
| nvgTransformRotate(t, angle); | |||||
| nvgTransformPremultiply(transform, t); | |||||
| } | |||||
| void TransformWidget::scale(Vec s) { | |||||
| float t[6]; | |||||
| nvgTransformScale(t, s.x, s.y); | |||||
| nvgTransformPremultiply(transform, t); | |||||
| } | |||||
| void TransformWidget::draw(NVGcontext *vg) { | |||||
| // No need to save the state because that is done in the parent | |||||
| nvgTransform(vg, transform[0], transform[1], transform[2], transform[3], transform[4], transform[5]); | |||||
| Widget::draw(vg); | |||||
| } | |||||
| } // namespace rack | |||||
| @@ -32,7 +32,7 @@ static void drawWire(NVGcontext *vg, Vec pos1, Vec pos2, NVGcolor color, float t | |||||
| // Wire | // Wire | ||||
| if (opacity > 0.0) { | if (opacity > 0.0) { | ||||
| nvgSave(vg); | nvgSave(vg); | ||||
| nvgGlobalAlpha(vg, opacity); | |||||
| nvgGlobalAlpha(vg, powf(opacity, 1.5)); | |||||
| float dist = pos1.minus(pos2).norm(); | float dist = pos1.minus(pos2).norm(); | ||||
| Vec slump; | Vec slump; | ||||