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