@@ -1,6 +1,6 @@ | |||||
ARCH ?= linux | ARCH ?= linux | ||||
CFLAGS = -MMD -g -Wall -O2 | CFLAGS = -MMD -g -Wall -O2 | ||||
CXXFLAGS = -MMD -g -Wall -std=c++11 -O2 -ffast-math \ | |||||
CXXFLAGS = -MMD -g -Wall -std=c++11 -O2 -ffast-math -fno-exceptions \ | |||||
-I./lib -I./include | -I./lib -I./include | ||||
LDFLAGS = | LDFLAGS = | ||||
@@ -15,7 +15,7 @@ CXX = g++ | |||||
SOURCES += lib/noc/noc_file_dialog.c | SOURCES += lib/noc/noc_file_dialog.c | ||||
CFLAGS += -DNOC_FILE_DIALOG_GTK $(shell pkg-config --cflags gtk+-2.0) | CFLAGS += -DNOC_FILE_DIALOG_GTK $(shell pkg-config --cflags gtk+-2.0) | ||||
CXXFLAGS += -DLINUX | CXXFLAGS += -DLINUX | ||||
LDFLAGS += -lpthread -lGL -lGLEW -lglfw -ldl -lprofiler -ljansson -lportaudio -lportmidi \ | |||||
LDFLAGS += -lpthread -lGL -lGLEW -lglfw -ldl -ljansson -lportaudio -lportmidi \ | |||||
$(shell pkg-config --libs gtk+-2.0) | $(shell pkg-config --libs gtk+-2.0) | ||||
TARGET = Rack | TARGET = Rack | ||||
endif | endif | ||||
@@ -0,0 +1,22 @@ | |||||
*Note: This software is in semi-public alpha. If you have stumbled upon this project, feel free to try it out and report bugs to the GitHub issue tracker. However, with more users it becomes difficult to make breaking changes, so please don't spread this to your friends and the Internet just yet, until the official announcement has been made.* | |||||
░█▀▄░█▀█░█▀▀░█░█ | |||||
░█▀▄░█▀█░█░░░█▀▄ | |||||
░▀░▀░▀░▀░▀▀▀░▀░▀ | |||||
Eurorack-style modular DAW | |||||
## Building | |||||
Install dependencies | |||||
- [GLEW](http://www.glfw.org/) | |||||
- [GLFW](http://glew.sourceforge.net/) | |||||
- [jansson](http://www.digip.org/jansson/) | |||||
- [portaudio](http://www.portaudio.com/) | |||||
- [portmidi](http://portmedia.sourceforge.net/portmidi/) | |||||
- GTK+-2.0 if Linux (for file open/save dialog) | |||||
Run `make ARCH=linux` or `make ARCH=windows` or `make ARCH=apple` | |||||
If the build breaks because you think I've missed a step, feel free to post an issue. |
@@ -1,9 +0,0 @@ | |||||
░█▀▄░█▀█░█▀▀░█░█ | |||||
░█▀▄░█▀█░█░░░█▀▄ | |||||
░▀░▀░▀░▀░▀▀▀░▀░▀ | |||||
Virtual modular synthesizer engine | |||||
## Building | |||||
`make ARCH=linux` or `make ARCH=windows` or `make ARCH=apple` |
@@ -9,6 +9,9 @@ | |||||
namespace rack { | namespace rack { | ||||
extern std::string gApplicationName; | |||||
extern std::string gApplicationVersion; | |||||
extern Scene *gScene; | extern Scene *gScene; | ||||
extern RackWidget *gRackWidget; | extern RackWidget *gRackWidget; | ||||
@@ -53,12 +56,15 @@ void pluginDestroy(); | |||||
extern Vec gMousePos; | extern Vec gMousePos; | ||||
extern Widget *gHoveredWidget; | extern Widget *gHoveredWidget; | ||||
extern Widget *gDraggedWidget; | extern Widget *gDraggedWidget; | ||||
extern Widget *gSelectedWidget; | |||||
void guiInit(); | void guiInit(); | ||||
void guiDestroy(); | void guiDestroy(); | ||||
void guiRun(); | void guiRun(); | ||||
void guiCursorLock(); | void guiCursorLock(); | ||||
void guiCursorUnlock(); | void guiCursorUnlock(); | ||||
const char *guiSaveDialog(const char *filters, const char *filename); | |||||
const char *guiOpenDialog(const char *filters, const char *filename); | |||||
int loadFont(std::string filename); | int loadFont(std::string filename); | ||||
int loadImage(std::string filename); | int loadImage(std::string filename); | ||||
@@ -2,6 +2,7 @@ | |||||
#include <stdint.h> | #include <stdint.h> | ||||
#include <math.h> | #include <math.h> | ||||
#include <random> | |||||
namespace rack { | namespace rack { | ||||
@@ -10,8 +11,15 @@ namespace rack { | |||||
// Math | // Math | ||||
//////////////////// | //////////////////// | ||||
/** Limits a value between a minimum and maximum | |||||
If min > max for some reason, returns min; | |||||
*/ | |||||
inline float clampf(float x, float min, float max) { | inline float clampf(float x, float min, float max) { | ||||
return fmaxf(min, fminf(max, x)); | |||||
if (x > max) | |||||
x = max; | |||||
if (x < min) | |||||
x = min; | |||||
return x; | |||||
} | } | ||||
inline float mapf(float x, float xMin, float xMax, float yMin, float yMax) { | inline float mapf(float x, float xMin, float xMax, float yMin, float yMax) { | ||||
@@ -30,6 +38,17 @@ inline int maxi(int a, int b) { | |||||
return a > b ? a : b; | return a > b ? a : b; | ||||
} | } | ||||
inline float quadraticBipolar(float x) { | |||||
float x2 = x*x; | |||||
return x >= 0.0 ? x2 : -x2; | |||||
} | |||||
inline float quarticBipolar(float x) { | |||||
float x2 = x*x; | |||||
float x4 = x2*x2; | |||||
return x >= 0.0 ? x4 : -x4; | |||||
} | |||||
// Euclidean modulus, always returns 0 <= mod < base for positive base | // Euclidean modulus, always returns 0 <= mod < base for positive base | ||||
// Assumes this architecture's division is non-Euclidean | // Assumes this architecture's division is non-Euclidean | ||||
inline int eucMod(int a, int base) { | inline int eucMod(int a, int base) { | ||||
@@ -46,7 +65,9 @@ inline void setf(float *p, float v) { | |||||
*p = v; | *p = v; | ||||
} | } | ||||
// Linearly interpolate an array `p` with index `x` | |||||
/** Linearly interpolate an array `p` with index `x` | |||||
Assumes that the array at `p` is of length at least ceil(x)+1. | |||||
*/ | |||||
inline float interpf(float *p, float x) { | inline float interpf(float *p, float x) { | ||||
int i = x; | int i = x; | ||||
x -= i; | x -= i; | ||||
@@ -57,12 +78,7 @@ inline float interpf(float *p, float x) { | |||||
// RNG | // RNG | ||||
//////////////////// | //////////////////// | ||||
uint64_t randomi64(void); | |||||
// Return a uniform random number on [0.0, 1.0) | |||||
inline float randomf(void) { | |||||
return (float)randomi64() / UINT64_MAX; | |||||
} | |||||
extern std::mt19937 rng; | |||||
//////////////////// | //////////////////// | ||||
// 2D float vector | // 2D float vector | ||||
@@ -114,10 +130,12 @@ struct Rect { | |||||
Rect() {} | Rect() {} | ||||
Rect(Vec pos, Vec size) : pos(pos), size(size) {} | Rect(Vec pos, Vec size) : pos(pos), size(size) {} | ||||
/** Returns whether this Rect contains another Rect, inclusive on the top/left, non-inclusive on the bottom/right */ | |||||
bool contains(Vec v) { | bool contains(Vec v) { | ||||
return pos.x <= v.x && v.x < pos.x + size.x | return pos.x <= v.x && v.x < pos.x + size.x | ||||
&& pos.y <= v.y && v.y < pos.y + size.y; | && pos.y <= v.y && v.y < pos.y + size.y; | ||||
} | } | ||||
/** Returns whether this Rect overlaps with another Rect */ | |||||
bool intersects(Rect r) { | bool intersects(Rect r) { | ||||
return (pos.x + size.x > r.pos.x && r.pos.x + r.size.x > pos.x) | return (pos.x + size.x > r.pos.x && r.pos.x + r.size.x > pos.x) | ||||
&& (pos.y + size.y > r.pos.y && r.pos.y + r.size.y > pos.y); | && (pos.y + size.y > r.pos.y && r.pos.y + r.size.y > pos.y); | ||||
@@ -134,6 +152,14 @@ struct Rect { | |||||
Vec getBottomRight() { | Vec getBottomRight() { | ||||
return pos.plus(size); | return pos.plus(size); | ||||
} | } | ||||
/** Clamps the position to fix inside a bounding box */ | |||||
Rect clamp(Rect bound) { | |||||
Rect r; | |||||
r.size = size; | |||||
r.pos.x = clampf(pos.x, bound.pos.x, bound.pos.x + bound.size.x - size.x); | |||||
r.pos.y = clampf(pos.y, bound.pos.y, bound.pos.y + bound.size.y - size.y); | |||||
return r; | |||||
} | |||||
}; | }; | ||||
@@ -7,8 +7,6 @@ | |||||
#include <list> | #include <list> | ||||
#include <map> | #include <map> | ||||
#include <GL/glew.h> | |||||
#include <GLFW/glfw3.h> | |||||
#include <jansson.h> | #include <jansson.h> | ||||
#include "../lib/nanovg/src/nanovg.h" | #include "../lib/nanovg/src/nanovg.h" | ||||
@@ -31,10 +29,10 @@ struct OutputPort; | |||||
// base class and traits | // base class and traits | ||||
//////////////////// | //////////////////// | ||||
// A node in the 2D scene graph | |||||
/** A node in the 2D scene graph */ | |||||
struct Widget { | struct Widget { | ||||
// Stores position and size | |||||
Rect box = Rect(Vec(0, 0), Vec(INFINITY, INFINITY)); | |||||
/** Stores position and size */ | |||||
Rect box = Rect(Vec(), Vec(INFINITY, INFINITY)); | |||||
Widget *parent = NULL; | Widget *parent = NULL; | ||||
std::list<Widget*> children; | std::list<Widget*> children; | ||||
@@ -42,55 +40,92 @@ struct Widget { | |||||
Vec getAbsolutePos(); | Vec getAbsolutePos(); | ||||
Rect getChildrenBoundingBox(); | Rect getChildrenBoundingBox(); | ||||
template <class T> | |||||
T *getAncestorOfType() { | |||||
if (!parent) return NULL; | |||||
T *p = dynamic_cast<T*>(parent); | |||||
if (p) return p; | |||||
return parent->getAncestorOfType<T>(); | |||||
} | |||||
// Gives ownership of widget to this widget instance | |||||
/** Adds widget to list of children. | |||||
Gives ownership of widget to this widget instance. | |||||
*/ | |||||
void addChild(Widget *widget); | void addChild(Widget *widget); | ||||
// Does not delete widget but transfers ownership to caller | // Does not delete widget but transfers ownership to caller | ||||
// Silenty fails if widget is not a child | // Silenty fails if widget is not a child | ||||
void removeChild(Widget *widget); | void removeChild(Widget *widget); | ||||
void clearChildren(); | void clearChildren(); | ||||
// Advances the module by one frame | |||||
/** Advances the module by one frame */ | |||||
virtual void step(); | virtual void step(); | ||||
// Draws to NanoVG context | |||||
/** Draws to NanoVG context */ | |||||
virtual void draw(NVGcontext *vg); | virtual void draw(NVGcontext *vg); | ||||
// Override this to return NULL if the widget is to be "invisible" to mouse events. | |||||
// Override this to return `this` if the widget is to override events of its children. | |||||
virtual Widget *pick(Vec pos); | |||||
// Events | // Events | ||||
virtual void onMouseDown(int button) {} | |||||
virtual void onMouseUp(int button) {} | |||||
virtual void onMouseMove(Vec mouseRel) {} | |||||
/** Called when a mouse button is pressed over this widget | |||||
0 for left, 1 for right, 2 for middle. | |||||
Return `this` to accept the event. | |||||
Return NULL to reject the event and pass it to the widget behind this one. | |||||
*/ | |||||
virtual Widget *onMouseDown(Vec pos, int button); | |||||
virtual Widget *onMouseUp(Vec pos, int button); | |||||
virtual Widget *onMouseMove(Vec pos, Vec mouseRel); | |||||
/** Called when this widget begins responding to `onMouseMove` events */ | |||||
virtual void onMouseEnter() {} | virtual void onMouseEnter() {} | ||||
/** Called when another widget begins responding to `onMouseMove` events */ | |||||
virtual void onMouseLeave() {} | virtual void onMouseLeave() {} | ||||
virtual void onScroll(Vec scrollRel) {} | |||||
virtual Widget *onScroll(Vec pos, Vec scrollRel); | |||||
/** Called when a widget responds to `onMouseDown` for a left button press */ | |||||
virtual void onDragStart() {} | virtual void onDragStart() {} | ||||
virtual void onDragDrop(Widget *origin) {} | |||||
virtual void onDragHover(Widget *origin) {} | |||||
/** Called when a widget responds to `onMouseMove` and is being dragged */ | |||||
virtual void onDragMove(Vec mouseRel) {} | virtual void onDragMove(Vec mouseRel) {} | ||||
/** Called when a widget responds to `onMouseUp` for a left button release and a widget is being dragged */ | |||||
virtual void onDragDrop(Widget *origin) {} | |||||
/** Called when the left button is released and this widget is being dragged */ | |||||
virtual void onDragEnd() {} | virtual void onDragEnd() {} | ||||
virtual void onResize() {} | |||||
virtual void onAction() {} | virtual void onAction() {} | ||||
virtual void onChange() {} | virtual void onChange() {} | ||||
}; | }; | ||||
// Widget that does not respond to events | |||||
/** Widget that does not respond to events */ | |||||
struct TransparentWidget : virtual Widget { | struct TransparentWidget : virtual Widget { | ||||
Widget *pick(Vec pos) { return NULL; } | |||||
}; | |||||
// Widget that does not respond to events, but allows its children to | |||||
struct TranslucentWidget : virtual Widget { | |||||
Widget *pick(Vec pos) { | |||||
Widget *picked = Widget::pick(pos); | |||||
if (picked == this) { | |||||
return NULL; | |||||
} | |||||
return picked; | |||||
Widget *onMouseDown(Vec pos, int button) {return NULL;} | |||||
Widget *onMouseUp(Vec pos, int button) {return NULL;} | |||||
Widget *onMouseMove(Vec pos, Vec mouseRel) {return NULL;} | |||||
Widget *onScroll(Vec pos, Vec scrollRel) {return NULL;} | |||||
}; | |||||
/** Widget that itself responds to mouse events */ | |||||
struct OpaqueWidget : virtual Widget { | |||||
Widget *onMouseDown(Vec pos, int button) { | |||||
Widget *w = Widget::onMouseDown(pos, button); | |||||
if (w) return w; | |||||
onMouseDown(button); | |||||
return this; | |||||
} | } | ||||
Widget *onMouseUp(Vec pos, int button) { | |||||
Widget *w = Widget::onMouseUp(pos, button); | |||||
if (w) return w; | |||||
onMouseUp(button); | |||||
return this; | |||||
} | |||||
Widget *onMouseMove(Vec pos, Vec mouseRel) { | |||||
Widget *w = Widget::onMouseMove(pos, mouseRel); | |||||
if (w) return w; | |||||
onMouseMove(mouseRel); | |||||
return this; | |||||
} | |||||
/** "High level" events called by the above lower level events. | |||||
Use these if you don't care about the clicked position. | |||||
*/ | |||||
virtual void onMouseDown(int button) {} | |||||
virtual void onMouseUp(int button) {} | |||||
virtual void onMouseMove(Vec mouseRel) {} | |||||
}; | }; | ||||
struct SpriteWidget : virtual Widget { | struct SpriteWidget : virtual Widget { | ||||
@@ -107,38 +142,49 @@ struct QuantityWidget : virtual Widget { | |||||
float maxValue = 1.0; | float maxValue = 1.0; | ||||
float defaultValue = 0.0; | float defaultValue = 0.0; | ||||
std::string label; | std::string label; | ||||
// Include a space character if you want a space after the number, e.g. " Hz" | |||||
/** Include a space character if you want a space after the number, e.g. " Hz" */ | |||||
std::string unit; | std::string unit; | ||||
/** The digit place to round for displaying values. | |||||
A precision of -2 will display as "1.00" for example. | |||||
*/ | |||||
int precision = -2; | |||||
QuantityWidget(); | |||||
void setValue(float value); | void setValue(float value); | ||||
void setLimits(float minValue, float maxValue); | void setLimits(float minValue, float maxValue); | ||||
void setDefaultValue(float defaultValue); | void setDefaultValue(float defaultValue); | ||||
/** Generates the display value */ | |||||
std::string getText(); | |||||
}; | }; | ||||
//////////////////// | //////////////////// | ||||
// gui elements | // gui elements | ||||
//////////////////// | //////////////////// | ||||
struct Label : TransparentWidget { | |||||
struct Label : Widget { | |||||
std::string text; | std::string text; | ||||
void draw(NVGcontext *vg); | void draw(NVGcontext *vg); | ||||
}; | }; | ||||
// Deletes itself from parent when clicked | // Deletes itself from parent when clicked | ||||
struct MenuOverlay : Widget { | |||||
void onMouseDown(int button); | |||||
struct MenuOverlay : OpaqueWidget { | |||||
void step(); | |||||
Widget *onScroll(Vec pos, Vec scrollRel) { | |||||
return this; | |||||
} | |||||
void onDragDrop(Widget *origin); | |||||
}; | }; | ||||
struct Menu : Widget { | |||||
struct Menu : OpaqueWidget { | |||||
Menu() { | Menu() { | ||||
box.size = Vec(0, 0); | box.size = Vec(0, 0); | ||||
} | } | ||||
// Transfers ownership, like addChild() | |||||
// Resizes menu and calls addChild() | |||||
void pushChild(Widget *child); | void pushChild(Widget *child); | ||||
void draw(NVGcontext *vg); | void draw(NVGcontext *vg); | ||||
}; | }; | ||||
struct MenuEntry : Widget { | |||||
struct MenuEntry : OpaqueWidget { | |||||
std::string text; | std::string text; | ||||
MenuEntry() { | MenuEntry() { | ||||
box.size = Vec(0, BND_WIDGET_HEIGHT); | box.size = Vec(0, BND_WIDGET_HEIGHT); | ||||
@@ -155,12 +201,12 @@ struct MenuItem : MenuEntry { | |||||
void draw(NVGcontext *vg); | void draw(NVGcontext *vg); | ||||
void onMouseUp(int button); | |||||
void onMouseEnter(); | void onMouseEnter(); | ||||
void onMouseLeave() ; | void onMouseLeave() ; | ||||
void onDragDrop(Widget *origin); | |||||
}; | }; | ||||
struct Button : Widget { | |||||
struct Button : OpaqueWidget { | |||||
std::string text; | std::string text; | ||||
BNDwidgetState state = BND_DEFAULT; | BNDwidgetState state = BND_DEFAULT; | ||||
@@ -175,7 +221,7 @@ struct ChoiceButton : Button { | |||||
void draw(NVGcontext *vg); | void draw(NVGcontext *vg); | ||||
}; | }; | ||||
struct Slider : QuantityWidget { | |||||
struct Slider : OpaqueWidget, QuantityWidget { | |||||
BNDwidgetState state = BND_DEFAULT; | BNDwidgetState state = BND_DEFAULT; | ||||
Slider(); | Slider(); | ||||
@@ -185,7 +231,7 @@ struct Slider : QuantityWidget { | |||||
void onDragEnd(); | void onDragEnd(); | ||||
}; | }; | ||||
struct ScrollBar : Widget { | |||||
struct ScrollBar : OpaqueWidget { | |||||
enum { VERTICAL, HORIZONTAL } orientation; | enum { VERTICAL, HORIZONTAL } orientation; | ||||
float containerOffset = 0.0; | float containerOffset = 0.0; | ||||
float containerSize = 0.0; | float containerSize = 0.0; | ||||
@@ -200,18 +246,18 @@ struct ScrollBar : Widget { | |||||
}; | }; | ||||
// Handles a container with scrollbars | // Handles a container with scrollbars | ||||
struct ScrollWidget : Widget { | |||||
struct ScrollWidget : OpaqueWidget { | |||||
Widget *container; | Widget *container; | ||||
ScrollBar *hScrollBar; | ScrollBar *hScrollBar; | ||||
ScrollBar *vScrollBar; | ScrollBar *vScrollBar; | ||||
ScrollWidget(); | ScrollWidget(); | ||||
void step(); | |||||
void draw(NVGcontext *vg); | void draw(NVGcontext *vg); | ||||
void onResize(); | |||||
void onScroll(Vec scrollRel); | |||||
Widget *onScroll(Vec pos, Vec scrollRel); | |||||
}; | }; | ||||
struct Tooltip : TransparentWidget { | |||||
struct Tooltip : Widget { | |||||
void step(); | void step(); | ||||
void draw(NVGcontext *vg); | void draw(NVGcontext *vg); | ||||
}; | }; | ||||
@@ -222,7 +268,7 @@ struct Tooltip : TransparentWidget { | |||||
// A 1U module should be 15x380. Thus the width of a module should be a factor of 15. | // A 1U module should be 15x380. Thus the width of a module should be a factor of 15. | ||||
struct Model; | struct Model; | ||||
struct ModuleWidget : Widget { | |||||
struct ModuleWidget : OpaqueWidget { | |||||
Model *model = NULL; | Model *model = NULL; | ||||
// Eventually this should be replaced with a `moduleId` which will be used for inter-process communication between the gui world and the audio world. | // Eventually this should be replaced with a `moduleId` which will be used for inter-process communication between the gui world and the audio world. | ||||
Module *module = NULL; | Module *module = NULL; | ||||
@@ -256,7 +302,7 @@ struct ModuleWidget : Widget { | |||||
void onMouseDown(int button); | void onMouseDown(int button); | ||||
}; | }; | ||||
struct WireWidget : Widget { | |||||
struct WireWidget : OpaqueWidget { | |||||
OutputPort *outputPort = NULL; | OutputPort *outputPort = NULL; | ||||
InputPort *inputPort = NULL; | InputPort *inputPort = NULL; | ||||
Wire *wire = NULL; | Wire *wire = NULL; | ||||
@@ -270,12 +316,11 @@ struct WireWidget : Widget { | |||||
void drawInputPlug(NVGcontext *vg); | void drawInputPlug(NVGcontext *vg); | ||||
}; | }; | ||||
struct RackWidget : Widget { | |||||
struct RackWidget : OpaqueWidget { | |||||
// Only put ModuleWidgets in here | // Only put ModuleWidgets in here | ||||
Widget *moduleContainer; | Widget *moduleContainer; | ||||
// Only put WireWidgets in here | // Only put WireWidgets in here | ||||
Widget *wireContainer; | Widget *wireContainer; | ||||
// An unowned reference to the currently dragged wire | |||||
WireWidget *activeWire = NULL; | WireWidget *activeWire = NULL; | ||||
RackWidget(); | RackWidget(); | ||||
@@ -294,6 +339,12 @@ struct RackWidget : Widget { | |||||
void onMouseDown(int button); | void onMouseDown(int button); | ||||
}; | }; | ||||
struct ModulePanel : TransparentWidget { | |||||
std::string imageFilename; | |||||
NVGcolor backgroundColor; | |||||
void draw(NVGcontext *vg); | |||||
}; | |||||
//////////////////// | //////////////////// | ||||
// params | // params | ||||
//////////////////// | //////////////////// | ||||
@@ -308,7 +359,7 @@ struct Screw : TransparentWidget, SpriteWidget { | |||||
Screw(); | Screw(); | ||||
}; | }; | ||||
struct ParamWidget : QuantityWidget { | |||||
struct ParamWidget : OpaqueWidget, QuantityWidget { | |||||
Module *module = NULL; | Module *module = NULL; | ||||
int paramId; | int paramId; | ||||
@@ -362,7 +413,7 @@ struct MomentarySwitch : virtual Switch { | |||||
// ports | // ports | ||||
//////////////////// | //////////////////// | ||||
struct Port : Widget { | |||||
struct Port : OpaqueWidget, SpriteWidget { | |||||
Module *module = NULL; | Module *module = NULL; | ||||
WireWidget *connectedWire = NULL; | WireWidget *connectedWire = NULL; | ||||
@@ -371,7 +422,6 @@ struct Port : Widget { | |||||
void disconnect(); | void disconnect(); | ||||
int type; | int type; | ||||
void draw(NVGcontext *vg); | |||||
void drawGlow(NVGcontext *vg); | void drawGlow(NVGcontext *vg); | ||||
void onMouseDown(int button); | void onMouseDown(int button); | ||||
void onDragEnd(); | void onDragEnd(); | ||||
@@ -397,17 +447,20 @@ struct OutputPort : Port { | |||||
// scene | // scene | ||||
//////////////////// | //////////////////// | ||||
struct Toolbar : Widget { | |||||
struct Toolbar : OpaqueWidget { | |||||
Slider *wireOpacitySlider; | Slider *wireOpacitySlider; | ||||
Slider *wireTensionSlider; | |||||
Toolbar(); | Toolbar(); | ||||
void draw(NVGcontext *vg); | void draw(NVGcontext *vg); | ||||
}; | }; | ||||
struct Scene : Widget { | |||||
struct Scene : OpaqueWidget { | |||||
Toolbar *toolbar; | Toolbar *toolbar; | ||||
ScrollWidget *scrollWidget; | ScrollWidget *scrollWidget; | ||||
Widget *overlay = NULL; | |||||
Scene(); | Scene(); | ||||
void onResize(); | |||||
void setOverlay(Widget *w); | |||||
void step(); | |||||
}; | }; | ||||
@@ -169,7 +169,7 @@ struct AudioChoice : ChoiceButton { | |||||
menu->pushChild(audioItem); | menu->pushChild(audioItem); | ||||
} | } | ||||
overlay->addChild(menu); | overlay->addChild(menu); | ||||
gScene->addChild(overlay); | |||||
gScene->setOverlay(overlay); | |||||
} | } | ||||
}; | }; | ||||
@@ -26,9 +26,10 @@ struct MidiInterface : Module { | |||||
PortMidiStream *stream = NULL; | PortMidiStream *stream = NULL; | ||||
std::list<int> notes; | std::list<int> notes; | ||||
bool pedal = false; | bool pedal = false; | ||||
bool gate = false; | |||||
int note = 64; // C4 | int note = 64; // C4 | ||||
int pitchWheel = 64; | int pitchWheel = 64; | ||||
bool retrigger = true; | |||||
bool retriggered = false; | |||||
MidiInterface(); | MidiInterface(); | ||||
~MidiInterface(); | ~MidiInterface(); | ||||
@@ -74,6 +75,11 @@ void MidiInterface::step() { | |||||
} | } | ||||
if (outputs[GATE_OUTPUT]) { | if (outputs[GATE_OUTPUT]) { | ||||
bool gate = pedal || !notes.empty(); | |||||
if (retrigger && retriggered) { | |||||
gate = false; | |||||
retriggered = false; | |||||
} | |||||
*outputs[GATE_OUTPUT] = gate ? 5.0 : 0.0; | *outputs[GATE_OUTPUT] = gate ? 5.0 : 0.0; | ||||
} | } | ||||
if (outputs[PITCH_OUTPUT]) { | if (outputs[PITCH_OUTPUT]) { | ||||
@@ -113,12 +119,14 @@ void MidiInterface::openPort(int portId) { | |||||
} | } | ||||
void MidiInterface::pressNote(int note) { | void MidiInterface::pressNote(int note) { | ||||
// Remove existing similar note | |||||
auto it = std::find(notes.begin(), notes.end(), note); | auto it = std::find(notes.begin(), notes.end(), note); | ||||
if (it != notes.end()) | if (it != notes.end()) | ||||
notes.erase(it); | notes.erase(it); | ||||
// Push note | |||||
notes.push_back(note); | notes.push_back(note); | ||||
this->gate = true; | |||||
this->note = note; | this->note = note; | ||||
retriggered = true; | |||||
} | } | ||||
void MidiInterface::releaseNote(int note) { | void MidiInterface::releaseNote(int note) { | ||||
@@ -135,10 +143,7 @@ void MidiInterface::releaseNote(int note) { | |||||
auto it2 = notes.end(); | auto it2 = notes.end(); | ||||
it2--; | it2--; | ||||
this->note = *it2; | this->note = *it2; | ||||
} | |||||
else { | |||||
// No notes are held, turn the gate off | |||||
this->gate = false; | |||||
retriggered = true; | |||||
} | } | ||||
} | } | ||||
@@ -158,7 +163,7 @@ void MidiInterface::processMidi(long msg) { | |||||
releaseNote(data1); | releaseNote(data1); | ||||
} break; | } break; | ||||
case 0x9: // note on | case 0x9: // note on | ||||
if (data2) { | |||||
if (data2 > 0) { | |||||
pressNote(data1); | pressNote(data1); | ||||
} | } | ||||
else { | else { | ||||
@@ -210,7 +215,7 @@ struct MidiChoice : ChoiceButton { | |||||
menu->pushChild(midiItem); | menu->pushChild(midiItem); | ||||
} | } | ||||
overlay->addChild(menu); | overlay->addChild(menu); | ||||
gScene->addChild(overlay); | |||||
gScene->setOverlay(overlay); | |||||
} | } | ||||
}; | }; | ||||
@@ -1,6 +1,9 @@ | |||||
#include <unistd.h> | #include <unistd.h> | ||||
#include "Rack.hpp" | #include "Rack.hpp" | ||||
#include <GL/glew.h> | |||||
#include <GLFW/glfw3.h> | |||||
// #define NANOVG_GLEW | // #define NANOVG_GLEW | ||||
#define NANOVG_IMPLEMENTATION | #define NANOVG_IMPLEMENTATION | ||||
#include "../lib/nanovg/src/nanovg.h" | #include "../lib/nanovg/src/nanovg.h" | ||||
@@ -9,6 +12,10 @@ | |||||
#define BLENDISH_IMPLEMENTATION | #define BLENDISH_IMPLEMENTATION | ||||
#include "../lib/oui/blendish.h" | #include "../lib/oui/blendish.h" | ||||
extern "C" { | |||||
#include "../lib/noc/noc_file_dialog.h" | |||||
} | |||||
namespace rack { | namespace rack { | ||||
@@ -18,44 +25,45 @@ RackWidget *gRackWidget = NULL; | |||||
Vec gMousePos; | Vec gMousePos; | ||||
Widget *gHoveredWidget = NULL; | Widget *gHoveredWidget = NULL; | ||||
Widget *gDraggedWidget = NULL; | Widget *gDraggedWidget = NULL; | ||||
Widget *gSelectedWidget = NULL; | |||||
static GLFWwindow *window; | |||||
static GLFWwindow *window = NULL; | |||||
static NVGcontext *vg = NULL; | static NVGcontext *vg = NULL; | ||||
void windowSizeCallback(GLFWwindow* window, int width, int height) { | void windowSizeCallback(GLFWwindow* window, int width, int height) { | ||||
gScene->box.size = Vec(width, height); | gScene->box.size = Vec(width, height); | ||||
gScene->onResize(); | |||||
} | } | ||||
void mouseButtonCallback(GLFWwindow *window, int button, int action, int mods) { | void mouseButtonCallback(GLFWwindow *window, int button, int action, int mods) { | ||||
if (gHoveredWidget) { | |||||
// onMouseDown and onMouseUp | |||||
if (action == GLFW_PRESS) { | |||||
gHoveredWidget->onMouseDown(button); | |||||
} | |||||
else if (action == GLFW_RELEASE) { | |||||
gHoveredWidget->onMouseUp(button); | |||||
} | |||||
} | |||||
if (action == GLFW_PRESS) { | |||||
// onMouseDown | |||||
Widget *w = gScene->onMouseDown(gMousePos, button); | |||||
gSelectedWidget = w; | |||||
// onDragStart, onDragEnd, and onDragDrop | |||||
if (button == GLFW_MOUSE_BUTTON_LEFT) { | |||||
if (action == GLFW_PRESS) { | |||||
if (gHoveredWidget) { | |||||
gDraggedWidget = gHoveredWidget; | |||||
if (button == GLFW_MOUSE_BUTTON_LEFT) { | |||||
gDraggedWidget = w; | |||||
if (gDraggedWidget) { | |||||
// onDragStart | |||||
gDraggedWidget->onDragStart(); | gDraggedWidget->onDragStart(); | ||||
} | } | ||||
} | } | ||||
else if (action == GLFW_RELEASE) { | |||||
} | |||||
else if (action == GLFW_RELEASE) { | |||||
// onMouseUp | |||||
Widget *w = gScene->onMouseUp(gMousePos, button); | |||||
if (button == GLFW_MOUSE_BUTTON_LEFT) { | |||||
if (gDraggedWidget) { | if (gDraggedWidget) { | ||||
Widget *dropped = gScene->pick(gMousePos); | |||||
if (dropped) { | |||||
dropped->onDragDrop(gDraggedWidget); | |||||
} | |||||
// onDragDrop | |||||
w->onDragDrop(gDraggedWidget); | |||||
} | |||||
// gDraggedWidget might have been set to null in the last event, recheck here | |||||
if (gDraggedWidget) { | |||||
// onDragEnd | |||||
gDraggedWidget->onDragEnd(); | gDraggedWidget->onDragEnd(); | ||||
gDraggedWidget = NULL; | |||||
} | } | ||||
gDraggedWidget = NULL; | |||||
} | } | ||||
} | } | ||||
} | } | ||||
@@ -70,40 +78,33 @@ void cursorPosCallback(GLFWwindow* window, double xpos, double ypos) { | |||||
} | } | ||||
// onScroll | // onScroll | ||||
int middleButton = glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_MIDDLE); | |||||
if (middleButton == GLFW_PRESS) { | |||||
gScene->scrollWidget->onScroll(mouseRel.neg()); | |||||
} | |||||
Widget *hovered = gScene->pick(mousePos); | |||||
// int middleButton = glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_MIDDLE); | |||||
// if (middleButton == GLFW_PRESS) { | |||||
// gScene->scrollWidget->onScroll(mouseRel.neg()); | |||||
// } | |||||
if (gDraggedWidget) { | if (gDraggedWidget) { | ||||
// onDragMove | // onDragMove | ||||
// Drag slower if Ctrl is held | // Drag slower if Ctrl is held | ||||
bool fine = glfwGetKey(window, GLFW_KEY_LEFT_CONTROL ) == GLFW_PRESS || glfwGetKey(window, GLFW_KEY_RIGHT_CONTROL) == GLFW_PRESS; | |||||
float factor = fine ? 0.1 : 1.0; | |||||
bool fine = glfwGetKey(window, GLFW_KEY_LEFT_CONTROL) == GLFW_PRESS || glfwGetKey(window, GLFW_KEY_RIGHT_CONTROL) == GLFW_PRESS; | |||||
float factor = fine ? 1.0/8.0 : 1.0; | |||||
gDraggedWidget->onDragMove(mouseRel.mult(factor)); | gDraggedWidget->onDragMove(mouseRel.mult(factor)); | ||||
// onDragHover | |||||
if (hovered) { | |||||
hovered->onDragHover(gDraggedWidget); | |||||
} | |||||
} | } | ||||
else { | else { | ||||
// onMouseEnter and onMouseLeave | |||||
// onMouseMove | |||||
Widget *hovered = gScene->onMouseMove(gMousePos, mouseRel); | |||||
if (hovered != gHoveredWidget) { | if (hovered != gHoveredWidget) { | ||||
if (gHoveredWidget) { | if (gHoveredWidget) { | ||||
// onMouseLeave | |||||
gHoveredWidget->onMouseLeave(); | gHoveredWidget->onMouseLeave(); | ||||
} | } | ||||
if (hovered) { | if (hovered) { | ||||
// onMouseEnter | |||||
hovered->onMouseEnter(); | hovered->onMouseEnter(); | ||||
} | } | ||||
} | } | ||||
gHoveredWidget = hovered; | gHoveredWidget = hovered; | ||||
// onMouseMove | |||||
if (hovered) { | |||||
hovered->onMouseMove(mouseRel); | |||||
} | |||||
} | } | ||||
} | } | ||||
@@ -119,7 +120,7 @@ void cursorEnterCallback(GLFWwindow* window, int entered) { | |||||
void scrollCallback(GLFWwindow *window, double x, double y) { | void scrollCallback(GLFWwindow *window, double x, double y) { | ||||
Vec scrollRel = Vec(x, y); | Vec scrollRel = Vec(x, y); | ||||
// onScroll | // onScroll | ||||
gScene->scrollWidget->onScroll(scrollRel.mult(-95)); | |||||
gScene->onScroll(gMousePos, scrollRel.mult(-95)); | |||||
} | } | ||||
void charCallback(GLFWwindow *window, unsigned int value) { | void charCallback(GLFWwindow *window, unsigned int value) { | ||||
@@ -151,7 +152,7 @@ void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods | |||||
void renderGui() { | void renderGui() { | ||||
int width, height; | int width, height; | ||||
// The docs say to use the framebuffer size to get pixel-perfect matching for high-DPI displays, but I actually don't want this. A "screen" pixel | |||||
// The docs say to use the framebuffer size to get pixel-perfect matching for high-DPI displays, but I actually don't want this. On 2x displays, one gui pixel should be 2x2 monitor pixels. | |||||
// glfwGetFramebufferSize(window, &width, &height); | // glfwGetFramebufferSize(window, &width, &height); | ||||
glfwGetWindowSize(window, &width, &height); | glfwGetWindowSize(window, &width, &height); | ||||
@@ -176,10 +177,12 @@ void guiInit() { | |||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2); | glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2); | ||||
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); | glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); | ||||
window = glfwCreateWindow(1020, 700, "Rack", NULL, NULL); | |||||
window = glfwCreateWindow(1020, 700, gApplicationName.c_str(), NULL, NULL); | |||||
assert(window); | assert(window); | ||||
glfwMakeContextCurrent(window); | glfwMakeContextCurrent(window); | ||||
glfwSwapInterval(1); | |||||
glfwSetWindowSizeCallback(window, windowSizeCallback); | glfwSetWindowSizeCallback(window, windowSizeCallback); | ||||
glfwSetMouseButtonCallback(window, mouseButtonCallback); | glfwSetMouseButtonCallback(window, mouseButtonCallback); | ||||
// glfwSetCursorPosCallback(window, cursorPosCallback); | // glfwSetCursorPosCallback(window, cursorPosCallback); | ||||
@@ -203,7 +206,7 @@ void guiInit() { | |||||
// Set up Blendish | // Set up Blendish | ||||
bndSetFont(loadFont("res/DejaVuSans.ttf")); | bndSetFont(loadFont("res/DejaVuSans.ttf")); | ||||
// bndSetIconImage(loadImage("res/blender_icons16.png")); | |||||
// bndSetIconImage(loadImage("res/icons.png")); | |||||
gScene = new Scene(); | gScene = new Scene(); | ||||
} | } | ||||
@@ -249,6 +252,16 @@ void guiCursorUnlock() { | |||||
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL); | glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_NORMAL); | ||||
} | } | ||||
const char *guiSaveDialog(const char *filters, const char *filename) { | |||||
return noc_file_dialog_open(NOC_FILE_DIALOG_SAVE, filters, NULL, filename); | |||||
} | |||||
const char *guiOpenDialog(const char *filters, const char *filename) { | |||||
return noc_file_dialog_open(NOC_FILE_DIALOG_OPEN, filters, NULL, filename); | |||||
} | |||||
std::map<std::string, int> images; | std::map<std::string, int> images; | ||||
@@ -7,6 +7,14 @@ | |||||
#include "Rack.hpp" | #include "Rack.hpp" | ||||
namespace rack { | |||||
std::string gApplicationName = "Virtuoso Rack"; | |||||
std::string gApplicationVersion = "v0.0.0"; | |||||
} // namespace rack | |||||
using namespace rack; | using namespace rack; | ||||
int main() { | int main() { | ||||
@@ -20,7 +28,7 @@ int main() { | |||||
assert(success); | assert(success); | ||||
CFRelease(bundleURL); | CFRelease(bundleURL); | ||||
// chdir(dirname(path)); | |||||
chdir(dirname(path)); | |||||
} | } | ||||
#endif | #endif | ||||
@@ -40,3 +48,4 @@ int main() { | |||||
pluginDestroy(); | pluginDestroy(); | ||||
return 0; | return 0; | ||||
} | } | ||||
@@ -15,6 +15,7 @@ namespace rack { | |||||
std::list<Plugin*> gPlugins; | std::list<Plugin*> gPlugins; | ||||
static | |||||
int loadPlugin(const char *path) { | int loadPlugin(const char *path) { | ||||
// Load dynamic/shared library | // Load dynamic/shared library | ||||
#if defined(WINDOWS) | #if defined(WINDOWS) | ||||
@@ -24,8 +25,8 @@ int loadPlugin(const char *path) { | |||||
return -1; | return -1; | ||||
} | } | ||||
#elif defined(LINUX) || defined(APPLE) | #elif defined(LINUX) || defined(APPLE) | ||||
char ppath[512]; | |||||
snprintf(ppath, 512, "./%s", path); | |||||
char ppath[1024]; | |||||
snprintf(ppath, sizeof(ppath), "./%s", path); | |||||
void *handle = dlopen(ppath, RTLD_NOW | RTLD_GLOBAL); | void *handle = dlopen(ppath, RTLD_NOW | RTLD_GLOBAL); | ||||
if (!handle) { | if (!handle) { | ||||
fprintf(stderr, "Failed to load library %s: %s\n", path, dlerror()); | fprintf(stderr, "Failed to load library %s: %s\n", path, dlerror()); | ||||
@@ -61,24 +62,24 @@ void pluginInit() { | |||||
// Load core | // Load core | ||||
Plugin *corePlugin = coreInit(); | Plugin *corePlugin = coreInit(); | ||||
gPlugins.push_back(corePlugin); | gPlugins.push_back(corePlugin); | ||||
// Search for plugin libraries | // Search for plugin libraries | ||||
#if defined(WINDOWS) | #if defined(WINDOWS) | ||||
// TODO | |||||
// WIN32_FIND_DATA ffd; | |||||
// HANDLE hFind = FindFirstFile("plugins/*/plugin.dll", &ffd); | |||||
// if (hFind != INVALID_HANDLE_VALUE) { | |||||
// do { | |||||
// loadPlugin(ffd.cFileName); | |||||
// } while (FindNextFile(hFind, &ffd)); | |||||
// } | |||||
// FindClose(hFind); | |||||
loadPlugin("plugins/Simple/plugin.dll"); | |||||
loadPlugin("plugins/AudibleInstruments/plugin.dll"); | |||||
WIN32_FIND_DATA ffd; | |||||
HANDLE hFind = FindFirstFile("plugins/*", &ffd); | |||||
if (hFind != INVALID_HANDLE_VALUE) { | |||||
do { | |||||
char pluginFilename[MAX_PATH]; | |||||
snprintf(pluginFilename, sizeof(pluginFilename), "plugins/%s/plugin.dll", ffd.cFileName); | |||||
loadPlugin(pluginFilename); | |||||
} while (FindNextFile(hFind, &ffd)); | |||||
} | |||||
FindClose(hFind); | |||||
#elif defined(LINUX) || defined(APPLE) | #elif defined(LINUX) || defined(APPLE) | ||||
#if defined(LINUX) | #if defined(LINUX) | ||||
const char *globPath = "plugins/*/plugin.so"; | const char *globPath = "plugins/*/plugin.so"; | ||||
#elif defined(WINDOWS) | |||||
const char *globPath = "plugins/*/plugin.dll"; | |||||
#elif defined(APPLE) | #elif defined(APPLE) | ||||
const char *globPath = "plugins/*/plugin.dylib"; | const char *globPath = "plugins/*/plugin.dylib"; | ||||
#endif | #endif | ||||
@@ -1,77 +1,9 @@ | |||||
#include "util.hpp" | #include "util.hpp" | ||||
#include <stdint.h> | |||||
namespace rack { | namespace rack { | ||||
/* Written in 2016 by David Blackman and Sebastiano Vigna (vigna@acm.org) | |||||
To the extent possible under law, the author has dedicated all copyright | |||||
and related and neighboring rights to this software to the public domain | |||||
worldwide. This software is distributed without any warranty. | |||||
See <http://creativecommons.org/publicdomain/zero/1.0/>. */ | |||||
/* This is the successor to xorshift128+. It is the fastest full-period | |||||
generator passing BigCrush without systematic failures, but due to the | |||||
relatively short period it is acceptable only for applications with a | |||||
mild amount of parallelism; otherwise, use a xorshift1024* generator. | |||||
Beside passing BigCrush, this generator passes the PractRand test suite | |||||
up to (and included) 16TB, with the exception of binary rank tests, | |||||
which fail due to the lowest bit being an LFSR; all other bits pass all | |||||
tests. We suggest to use a sign test to extract a random Boolean value. | |||||
Note that the generator uses a simulated rotate operation, which most C | |||||
compilers will turn into a single instruction. In Java, you can use | |||||
Long.rotateLeft(). In languages that do not make low-level rotation | |||||
instructions accessible xorshift128+ could be faster. | |||||
The state must be seeded so that it is not everywhere zero. If you have | |||||
a 64-bit seed, we suggest to seed a splitmix64 generator and use its | |||||
output to fill s. */ | |||||
static uint64_t s[2] = {15879747621982183911ULL, 4486682751732329651ULL}; | |||||
static inline uint64_t rotl(const uint64_t x, int k) { | |||||
return (x << k) | (x >> (64 - k)); | |||||
} | |||||
uint64_t randomi64(void) { | |||||
const uint64_t s0 = s[0]; | |||||
uint64_t s1 = s[1]; | |||||
const uint64_t result = s0 + s1; | |||||
s1 ^= s0; | |||||
s[0] = rotl(s0, 55) ^ s1 ^ (s1 << 14); // a, b | |||||
s[1] = rotl(s1, 36); // c | |||||
return result; | |||||
} | |||||
/* This is the jump function for the generator. It is equivalent | |||||
to 2^64 calls to next(); it can be used to generate 2^64 | |||||
non-overlapping subsequences for parallel computations. */ | |||||
void randomjump(void) { | |||||
static const uint64_t JUMP[] = { 0xbeac0467eba5facb, 0xd86b048b86aa9922 }; | |||||
uint64_t s0 = 0; | |||||
uint64_t s1 = 0; | |||||
for(unsigned int i = 0; i < sizeof JUMP / sizeof *JUMP; i++) | |||||
for(int b = 0; b < 64; b++) { | |||||
if (JUMP[i] & 1ULL << b) { | |||||
s0 ^= s[0]; | |||||
s1 ^= s[1]; | |||||
} | |||||
randomi64(); | |||||
} | |||||
s[0] = s0; | |||||
s[1] = s1; | |||||
} | |||||
static std::random_device rd; | |||||
std::mt19937 rng(rd()); | |||||
} // namespace rack | } // namespace rack |
@@ -4,7 +4,7 @@ | |||||
namespace rack { | namespace rack { | ||||
void InputPort::draw(NVGcontext *vg) { | void InputPort::draw(NVGcontext *vg) { | ||||
Port::draw(vg); | |||||
SpriteWidget::draw(vg); | |||||
if (gRackWidget->activeWire && gRackWidget->activeWire->inputPort) { | if (gRackWidget->activeWire && gRackWidget->activeWire->inputPort) { | ||||
Port::drawGlow(vg); | Port::drawGlow(vg); | ||||
} | } | ||||
@@ -27,6 +27,7 @@ void InputPort::onDragStart() { | |||||
} | } | ||||
void InputPort::onDragDrop(Widget *origin) { | void InputPort::onDragDrop(Widget *origin) { | ||||
printf("%p\n", origin); | |||||
if (connectedWire) return; | if (connectedWire) return; | ||||
if (gRackWidget->activeWire) { | if (gRackWidget->activeWire) { | ||||
if (gRackWidget->activeWire->inputPort) return; | if (gRackWidget->activeWire->inputPort) return; | ||||
@@ -15,16 +15,13 @@ void MenuItem::onMouseLeave() { | |||||
state = BND_DEFAULT; | state = BND_DEFAULT; | ||||
} | } | ||||
void MenuItem::onMouseUp(int button) { | |||||
void MenuItem::onDragDrop(Widget *origin) { | |||||
if (origin != this) | |||||
return; | |||||
onAction(); | onAction(); | ||||
// Remove overlay from scene | |||||
// HACK | |||||
Widget *overlay = parent->parent; | |||||
assert(overlay); | |||||
if (overlay->parent) { | |||||
overlay->parent->removeChild(overlay); | |||||
} | |||||
delete overlay; | |||||
// deletes `this` | |||||
gScene->setOverlay(NULL); | |||||
} | } | ||||
@@ -3,12 +3,18 @@ | |||||
namespace rack { | namespace rack { | ||||
void MenuOverlay::onMouseDown(int button) { | |||||
if (parent) { | |||||
parent->removeChild(this); | |||||
void MenuOverlay::step() { | |||||
// Try to fit all children into the overlay's box | |||||
for (Widget *child : children) { | |||||
child->box = child->box.clamp(box); | |||||
} | |||||
} | |||||
void MenuOverlay::onDragDrop(Widget *origin) { | |||||
if (origin == this) { | |||||
// deletes `this` | |||||
gScene->setOverlay(NULL); | |||||
} | } | ||||
// Commit sudoku | |||||
delete this; | |||||
} | } | ||||
@@ -0,0 +1,29 @@ | |||||
#include "Rack.hpp" | |||||
namespace rack { | |||||
void ModulePanel::draw(NVGcontext *vg) { | |||||
nvgBeginPath(vg); | |||||
nvgRect(vg, box.pos.x, box.pos.y, box.size.x, box.size.y); | |||||
NVGpaint paint; | |||||
// Background gradient | |||||
Vec c = box.getTopRight(); | |||||
float length = box.size.norm(); | |||||
NVGcolor color1 = nvgLerpRGBA(backgroundColor, nvgRGBf(1.0, 1.0, 1.0), 0.5); | |||||
NVGcolor color2 = backgroundColor; | |||||
paint = nvgRadialGradient(vg, c.x, c.y, 0.0, length, color1, color2); | |||||
nvgFillPaint(vg, paint); | |||||
nvgFill(vg); | |||||
// Background image | |||||
if (!imageFilename.empty()) { | |||||
int imageId = loadImage(imageFilename); | |||||
int width, height; | |||||
nvgImageSize(vg, imageId, &width, &height); | |||||
paint = nvgImagePattern(vg, box.pos.x, box.pos.y, width, height, 0.0, imageId, 1.0); | |||||
nvgFillPaint(vg, paint); | |||||
nvgFill(vg); | |||||
} | |||||
} | |||||
} // namespace rack |
@@ -145,7 +145,7 @@ struct DeleteModuleMenuItem : MenuItem { | |||||
}; | }; | ||||
void ModuleWidget::onMouseDown(int button) { | void ModuleWidget::onMouseDown(int button) { | ||||
if (button == GLFW_MOUSE_BUTTON_RIGHT) { | |||||
if (button == 1) { | |||||
MenuOverlay *overlay = new MenuOverlay(); | MenuOverlay *overlay = new MenuOverlay(); | ||||
Menu *menu = new Menu(); | Menu *menu = new Menu(); | ||||
menu->box.pos = gMousePos; | menu->box.pos = gMousePos; | ||||
@@ -155,12 +155,12 @@ void ModuleWidget::onMouseDown(int button) { | |||||
menu->pushChild(menuLabel); | menu->pushChild(menuLabel); | ||||
ResetParamsMenuItem *resetItem = new ResetParamsMenuItem(); | ResetParamsMenuItem *resetItem = new ResetParamsMenuItem(); | ||||
resetItem->text = "Initialize parameters"; | |||||
resetItem->text = "Reset parameters"; | |||||
resetItem->moduleWidget = this; | resetItem->moduleWidget = this; | ||||
menu->pushChild(resetItem); | menu->pushChild(resetItem); | ||||
DisconnectPortsMenuItem *disconnectItem = new DisconnectPortsMenuItem(); | DisconnectPortsMenuItem *disconnectItem = new DisconnectPortsMenuItem(); | ||||
disconnectItem->text = "Disconnect wires"; | |||||
disconnectItem->text = "Disconnect cables"; | |||||
disconnectItem->moduleWidget = this; | disconnectItem->moduleWidget = this; | ||||
menu->pushChild(disconnectItem); | menu->pushChild(disconnectItem); | ||||
@@ -175,7 +175,7 @@ void ModuleWidget::onMouseDown(int button) { | |||||
menu->pushChild(deleteItem); | menu->pushChild(deleteItem); | ||||
} | } | ||||
overlay->addChild(menu); | overlay->addChild(menu); | ||||
gScene->addChild(overlay); | |||||
gScene->setOverlay(overlay); | |||||
} | } | ||||
} | } | ||||
@@ -4,7 +4,7 @@ | |||||
namespace rack { | namespace rack { | ||||
void OutputPort::draw(NVGcontext *vg) { | void OutputPort::draw(NVGcontext *vg) { | ||||
Port::draw(vg); | |||||
SpriteWidget::draw(vg); | |||||
if (gRackWidget->activeWire && gRackWidget->activeWire->outputPort) { | if (gRackWidget->activeWire && gRackWidget->activeWire->outputPort) { | ||||
Port::drawGlow(vg); | Port::drawGlow(vg); | ||||
} | } | ||||
@@ -13,13 +13,15 @@ void ParamWidget::fromJson(json_t *root) { | |||||
} | } | ||||
void ParamWidget::onMouseDown(int button) { | void ParamWidget::onMouseDown(int button) { | ||||
if (button == GLFW_MOUSE_BUTTON_RIGHT) { | |||||
if (button == 1) { | |||||
setValue(defaultValue); | setValue(defaultValue); | ||||
} | } | ||||
} | } | ||||
void ParamWidget::onChange() { | void ParamWidget::onChange() { | ||||
assert(module); | |||||
if (!module) | |||||
return; | |||||
// moduleWidget->module->params[paramId] = value; | // moduleWidget->module->params[paramId] = value; | ||||
rackSetParamSmooth(module, paramId, value); | rackSetParamSmooth(module, paramId, value); | ||||
} | } | ||||
@@ -5,7 +5,12 @@ namespace rack { | |||||
Port::Port() { | Port::Port() { | ||||
box.size = Vec(20, 20); | box.size = Vec(20, 20); | ||||
type = randomi64() % 5; | |||||
spriteOffset = Vec(-18, -18); | |||||
spriteSize = Vec(56, 56); | |||||
spriteFilename = "res/port.png"; | |||||
std::uniform_int_distribution<> dist(0, 4); | |||||
index = dist(rng); | |||||
} | } | ||||
Port::~Port() { | Port::~Port() { | ||||
@@ -20,19 +25,6 @@ void Port::disconnect() { | |||||
} | } | ||||
} | } | ||||
void Port::draw(NVGcontext *vg) { | |||||
Vec pos = box.pos.plus(Vec(-18, -18)); | |||||
int width, height; | |||||
int imageId = loadImage("res/port.png"); | |||||
nvgImageSize(vg, imageId, &width, &height); | |||||
float offsetY = type * width; | |||||
NVGpaint paint = nvgImagePattern(vg, pos.x, pos.y - offsetY, width, height, 0.0, imageId, 1.0); | |||||
nvgFillPaint(vg, paint); | |||||
nvgBeginPath(vg); | |||||
nvgRect(vg, pos.x, pos.y, width, width); | |||||
nvgFill(vg); | |||||
} | |||||
void Port::drawGlow(NVGcontext *vg) { | void Port::drawGlow(NVGcontext *vg) { | ||||
Vec c = box.getCenter(); | Vec c = box.getCenter(); | ||||
NVGcolor icol = nvgRGBAf(1, 1, 1, 0.5); | NVGcolor icol = nvgRGBAf(1, 1, 1, 0.5); | ||||
@@ -45,7 +37,7 @@ void Port::drawGlow(NVGcontext *vg) { | |||||
} | } | ||||
void Port::onMouseDown(int button) { | void Port::onMouseDown(int button) { | ||||
if (button == GLFW_MOUSE_BUTTON_RIGHT) { | |||||
if (button == 1) { | |||||
disconnect(); | disconnect(); | ||||
} | } | ||||
} | } | ||||
@@ -3,6 +3,10 @@ | |||||
namespace rack { | namespace rack { | ||||
QuantityWidget::QuantityWidget() { | |||||
onChange(); | |||||
} | |||||
void QuantityWidget::setValue(float value) { | void QuantityWidget::setValue(float value) { | ||||
this->value = clampf(value, minValue, maxValue); | this->value = clampf(value, minValue, maxValue); | ||||
onChange(); | onChange(); | ||||
@@ -18,5 +22,22 @@ void QuantityWidget::setDefaultValue(float defaultValue) { | |||||
setValue(defaultValue); | setValue(defaultValue); | ||||
} | } | ||||
std::string QuantityWidget::getText() { | |||||
std::string text = label; | |||||
text += ": "; | |||||
char valueStr[128]; | |||||
if (precision >= 0) { | |||||
float factor = powf(10.0, precision); | |||||
float v = roundf(value / factor) * factor; | |||||
snprintf(valueStr, sizeof(valueStr), "%.0f", v); | |||||
} | |||||
else { | |||||
snprintf(valueStr, sizeof(valueStr), "%.*f", -precision, value); | |||||
} | |||||
text += valueStr; | |||||
text += unit; | |||||
return text; | |||||
} | |||||
} // namespace rack | } // namespace rack |
@@ -5,7 +5,7 @@ | |||||
namespace rack { | namespace rack { | ||||
RackWidget::RackWidget() { | RackWidget::RackWidget() { | ||||
moduleContainer = new TranslucentWidget(); | |||||
moduleContainer = new Widget(); | |||||
addChild(moduleContainer); | addChild(moduleContainer); | ||||
wireContainer = new TransparentWidget(); | wireContainer = new TransparentWidget(); | ||||
@@ -56,26 +56,10 @@ void RackWidget::loadPatch(std::string filename) { | |||||
fclose(file); | fclose(file); | ||||
} | } | ||||
// Recursive function to get the ModuleWidget eventually containing a widget | |||||
static ModuleWidget *getAncestorModuleWidget(Widget *w) { | |||||
if (!w) return NULL; | |||||
ModuleWidget *m = dynamic_cast<ModuleWidget*>(w); | |||||
if (m) return m; | |||||
return getAncestorModuleWidget(w->parent); | |||||
} | |||||
json_t *RackWidget::toJson() { | json_t *RackWidget::toJson() { | ||||
// root | // root | ||||
json_t *root = json_object(); | json_t *root = json_object(); | ||||
// rack | |||||
json_t *rack = json_object(); | |||||
{ | |||||
json_t *size = json_pack("[f, f]", (double) box.size.x, (double) box.size.y); | |||||
json_object_set_new(rack, "size", size); | |||||
} | |||||
json_object_set_new(root, "rack", rack); | |||||
// modules | // modules | ||||
json_t *modulesJ = json_array(); | json_t *modulesJ = json_array(); | ||||
std::map<ModuleWidget*, int> moduleIds; | std::map<ModuleWidget*, int> moduleIds; | ||||
@@ -102,9 +86,9 @@ json_t *RackWidget::toJson() { | |||||
// wire | // wire | ||||
json_t *wire = json_object(); | json_t *wire = json_object(); | ||||
{ | { | ||||
ModuleWidget *outputModuleWidget = getAncestorModuleWidget(wireWidget->outputPort); | |||||
ModuleWidget *outputModuleWidget = wireWidget->outputPort->getAncestorOfType<ModuleWidget>(); | |||||
assert(outputModuleWidget); | assert(outputModuleWidget); | ||||
ModuleWidget *inputModuleWidget = getAncestorModuleWidget(wireWidget->inputPort); | |||||
ModuleWidget *inputModuleWidget = wireWidget->inputPort->getAncestorOfType<ModuleWidget>(); | |||||
assert(inputModuleWidget); | assert(inputModuleWidget); | ||||
int outputModuleId = moduleIds[outputModuleWidget]; | int outputModuleId = moduleIds[outputModuleWidget]; | ||||
int inputModuleId = moduleIds[inputModuleWidget]; | int inputModuleId = moduleIds[inputModuleWidget]; | ||||
@@ -121,26 +105,16 @@ json_t *RackWidget::toJson() { | |||||
} | } | ||||
void RackWidget::fromJson(json_t *root) { | void RackWidget::fromJson(json_t *root) { | ||||
// TODO There's virtually no validation in here. Bad input will result in a crash. | |||||
// rack | |||||
json_t *rack = json_object_get(root, "rack"); | |||||
assert(rack); | |||||
{ | |||||
// size | |||||
json_t *size = json_object_get(rack, "size"); | |||||
double width, height; | |||||
json_unpack(size, "[F, F]", &width, &height); | |||||
box.size = Vec(width, height); | |||||
} | |||||
// modules | // modules | ||||
std::map<int, ModuleWidget*> moduleWidgets; | std::map<int, ModuleWidget*> moduleWidgets; | ||||
json_t *modulesJ = json_object_get(root, "modules"); | json_t *modulesJ = json_object_get(root, "modules"); | ||||
if (!modulesJ) return; | |||||
size_t moduleId; | size_t moduleId; | ||||
json_t *moduleJ; | json_t *moduleJ; | ||||
json_array_foreach(modulesJ, moduleId, moduleJ) { | json_array_foreach(modulesJ, moduleId, moduleJ) { | ||||
// Get plugin | // Get plugin | ||||
json_t *pluginSlugJ = json_object_get(moduleJ, "plugin"); | json_t *pluginSlugJ = json_object_get(moduleJ, "plugin"); | ||||
if (!pluginSlugJ) continue; | |||||
const char *pluginSlug = json_string_value(pluginSlugJ); | const char *pluginSlug = json_string_value(pluginSlugJ); | ||||
Plugin *plugin = NULL; | Plugin *plugin = NULL; | ||||
for (Plugin *p : gPlugins) { | for (Plugin *p : gPlugins) { | ||||
@@ -149,7 +123,7 @@ void RackWidget::fromJson(json_t *root) { | |||||
break; | break; | ||||
} | } | ||||
} | } | ||||
assert(plugin); | |||||
if (!plugin) continue; | |||||
// Get model | // Get model | ||||
json_t *modelSlug = json_object_get(moduleJ, "model"); | json_t *modelSlug = json_object_get(moduleJ, "model"); | ||||
@@ -160,10 +134,11 @@ void RackWidget::fromJson(json_t *root) { | |||||
break; | break; | ||||
} | } | ||||
} | } | ||||
assert(model); | |||||
if (!model) continue; | |||||
// Create ModuleWidget | // Create ModuleWidget | ||||
ModuleWidget *moduleWidget = model->createModuleWidget(); | ModuleWidget *moduleWidget = model->createModuleWidget(); | ||||
assert(moduleWidget); | |||||
moduleWidget->fromJson(moduleJ); | moduleWidget->fromJson(moduleJ); | ||||
moduleContainer->addChild(moduleWidget); | moduleContainer->addChild(moduleWidget); | ||||
moduleWidgets[moduleId] = moduleWidget; | moduleWidgets[moduleId] = moduleWidget; | ||||
@@ -171,23 +146,25 @@ void RackWidget::fromJson(json_t *root) { | |||||
// wires | // wires | ||||
json_t *wiresJ = json_object_get(root, "wires"); | json_t *wiresJ = json_object_get(root, "wires"); | ||||
if (!wiresJ) return; | |||||
size_t wireId; | size_t wireId; | ||||
json_t *wireJ; | json_t *wireJ; | ||||
json_array_foreach(wiresJ, wireId, wireJ) { | json_array_foreach(wiresJ, wireId, wireJ) { | ||||
int outputModuleId, outputId; | int outputModuleId, outputId; | ||||
int inputModuleId, inputId; | int inputModuleId, inputId; | ||||
json_unpack(wireJ, "{s:i, s:i, s:i, s:i}", | |||||
int err = json_unpack(wireJ, "{s:i, s:i, s:i, s:i}", | |||||
"outputModuleId", &outputModuleId, "outputId", &outputId, | "outputModuleId", &outputModuleId, "outputId", &outputId, | ||||
"inputModuleId", &inputModuleId, "inputId", &inputId); | "inputModuleId", &inputModuleId, "inputId", &inputId); | ||||
if (err) continue; | |||||
// Get ports | // Get ports | ||||
ModuleWidget *outputModuleWidget = moduleWidgets[outputModuleId]; | ModuleWidget *outputModuleWidget = moduleWidgets[outputModuleId]; | ||||
assert(outputModuleWidget); | |||||
if (!outputModuleWidget) continue; | |||||
OutputPort *outputPort = outputModuleWidget->outputs[outputId]; | OutputPort *outputPort = outputModuleWidget->outputs[outputId]; | ||||
assert(outputPort); | |||||
if (!outputPort) continue; | |||||
ModuleWidget *inputModuleWidget = moduleWidgets[inputModuleId]; | ModuleWidget *inputModuleWidget = moduleWidgets[inputModuleId]; | ||||
assert(inputModuleWidget); | |||||
if (!inputModuleWidget) continue; | |||||
InputPort *inputPort = inputModuleWidget->inputs[inputId]; | InputPort *inputPort = inputModuleWidget->inputs[inputId]; | ||||
assert(inputPort); | |||||
if (!inputPort) continue; | |||||
// Create WireWidget | // Create WireWidget | ||||
WireWidget *wireWidget = new WireWidget(); | WireWidget *wireWidget = new WireWidget(); | ||||
wireWidget->outputPort = outputPort; | wireWidget->outputPort = outputPort; | ||||
@@ -253,8 +230,9 @@ void RackWidget::step() { | |||||
} | } | ||||
} | } | ||||
// Autosave every 2 minutes | |||||
if (++frame >= 60*60*2) { | |||||
// Autosave every 15 seconds | |||||
// (This is alpha software, expect crashes!) | |||||
if (++frame >= 60*15) { | |||||
frame = 0; | frame = 0; | ||||
savePatch("autosave.json"); | savePatch("autosave.json"); | ||||
} | } | ||||
@@ -299,7 +277,7 @@ struct AddModuleMenuItem : MenuItem { | |||||
}; | }; | ||||
void RackWidget::onMouseDown(int button) { | void RackWidget::onMouseDown(int button) { | ||||
if (button == GLFW_MOUSE_BUTTON_RIGHT) { | |||||
if (button == 1) { | |||||
// Get relative position of the click | // Get relative position of the click | ||||
Vec modulePos = gMousePos.minus(getAbsolutePos()); | Vec modulePos = gMousePos.minus(getAbsolutePos()); | ||||
@@ -320,7 +298,7 @@ void RackWidget::onMouseDown(int button) { | |||||
} | } | ||||
} | } | ||||
overlay->addChild(menu); | overlay->addChild(menu); | ||||
gScene->addChild(overlay); | |||||
gScene->setOverlay(overlay); | |||||
} | } | ||||
} | } | ||||
@@ -17,10 +17,27 @@ Scene::Scene() { | |||||
scrollWidget->box.pos.y = toolbar->box.size.y; | scrollWidget->box.pos.y = toolbar->box.size.y; | ||||
} | } | ||||
void Scene::onResize() { | |||||
void Scene::setOverlay(Widget *w) { | |||||
if (overlay) { | |||||
removeChild(overlay); | |||||
delete overlay; | |||||
overlay = NULL; | |||||
} | |||||
if (w) { | |||||
addChild(w); | |||||
overlay = w; | |||||
overlay->box.pos = Vec(); | |||||
} | |||||
} | |||||
void Scene::step() { | |||||
toolbar->box.size.x = box.size.x; | toolbar->box.size.x = box.size.x; | ||||
scrollWidget->box.size = box.size.minus(scrollWidget->box.pos); | scrollWidget->box.size = box.size.minus(scrollWidget->box.pos); | ||||
scrollWidget->onResize(); | |||||
if (overlay) { | |||||
overlay->box.size = box.size; | |||||
} | |||||
Widget::step(); | |||||
} | } | ||||
@@ -8,7 +8,9 @@ Screw::Screw() { | |||||
spriteOffset = Vec(-7, -7); | spriteOffset = Vec(-7, -7); | ||||
spriteSize = Vec(29, 29); | spriteSize = Vec(29, 29); | ||||
spriteFilename = "res/screw.png"; | spriteFilename = "res/screw.png"; | ||||
index = randomi64() % 5; | |||||
std::uniform_int_distribution<> dist(0, 4); | |||||
index = dist(rng); | |||||
} | } | ||||
@@ -16,6 +16,18 @@ ScrollWidget::ScrollWidget() { | |||||
addChild(vScrollBar); | addChild(vScrollBar); | ||||
} | } | ||||
void ScrollWidget::step() { | |||||
Vec b = Vec(box.size.x - vScrollBar->box.size.x, box.size.y - hScrollBar->box.size.y); | |||||
hScrollBar->box.pos.y = b.y; | |||||
hScrollBar->box.size.x = b.x; | |||||
vScrollBar->box.pos.x = b.x; | |||||
vScrollBar->box.size.y = b.y; | |||||
Widget::step(); | |||||
} | |||||
void ScrollWidget::draw(NVGcontext *vg) { | void ScrollWidget::draw(NVGcontext *vg) { | ||||
// Update the scrollbar sizes | // Update the scrollbar sizes | ||||
Vec c = container->getChildrenBoundingBox().getBottomRight(); | Vec c = container->getChildrenBoundingBox().getBottomRight(); | ||||
@@ -28,19 +40,13 @@ void ScrollWidget::draw(NVGcontext *vg) { | |||||
Widget::draw(vg); | Widget::draw(vg); | ||||
} | } | ||||
void ScrollWidget::onResize() { | |||||
Vec b = Vec(box.size.x - vScrollBar->box.size.x, box.size.y - hScrollBar->box.size.y); | |||||
hScrollBar->box.pos.y = b.y; | |||||
hScrollBar->box.size.x = b.x; | |||||
vScrollBar->box.pos.x = b.x; | |||||
vScrollBar->box.size.y = b.y; | |||||
} | |||||
Widget *ScrollWidget::onScroll(Vec pos, Vec scrollRel) { | |||||
Widget *w = Widget::onScroll(pos, scrollRel); | |||||
if (w) return w; | |||||
void ScrollWidget::onScroll(Vec scrollRel) { | |||||
hScrollBar->move(scrollRel.x); | hScrollBar->move(scrollRel.x); | ||||
vScrollBar->move(scrollRel.y); | vScrollBar->move(scrollRel.y); | ||||
return this; | |||||
} | } | ||||
@@ -11,9 +11,7 @@ Slider::Slider() { | |||||
void Slider::draw(NVGcontext *vg) { | void Slider::draw(NVGcontext *vg) { | ||||
float progress = mapf(value, minValue, maxValue, 0.0, 1.0); | float progress = mapf(value, minValue, maxValue, 0.0, 1.0); | ||||
char quantity[100]; | |||||
snprintf(quantity, 100, "%.3g%s", value, unit.c_str()); | |||||
bndSlider(vg, box.pos.x, box.pos.y, box.size.x, box.size.y, BND_CORNER_NONE, state, progress, label.c_str(), quantity); | |||||
bndSlider(vg, box.pos.x, box.pos.y, box.size.x, box.size.y, BND_CORNER_NONE, state, progress, getText().c_str(), NULL); | |||||
} | } | ||||
void Slider::onDragStart() { | void Slider::onDragStart() { | ||||
@@ -16,7 +16,7 @@ void SpriteWidget::draw(NVGcontext *vg) { | |||||
nvgImageSize(vg, imageId, &width, &height); | nvgImageSize(vg, imageId, &width, &height); | ||||
int stride = width / spriteSize.x; | int stride = width / spriteSize.x; | ||||
if (stride == 0) { | if (stride == 0) { | ||||
printf("Width of SpriteWidget is %d but spriteSize is %f\n", width, spriteSize.x); | |||||
printf("Size of SpriteWidget is %d, %d but spriteSize is %f, %f\n", width, height, spriteSize.x, spriteSize.y); | |||||
return; | return; | ||||
} | } | ||||
Vec offset = Vec((index % stride) * spriteSize.x, (index / stride) * spriteSize.y); | Vec offset = Vec((index % stride) * spriteSize.x, (index / stride) * spriteSize.y); | ||||
@@ -1,9 +1,5 @@ | |||||
#include "Rack.hpp" | #include "Rack.hpp" | ||||
extern "C" { | |||||
#include "../lib/noc/noc_file_dialog.h" | |||||
} | |||||
namespace rack { | namespace rack { | ||||
@@ -18,7 +14,7 @@ struct NewItem : MenuItem { | |||||
struct SaveItem : MenuItem { | struct SaveItem : MenuItem { | ||||
void onAction() { | void onAction() { | ||||
const char *path = noc_file_dialog_open(NOC_FILE_DIALOG_SAVE, filters, NULL, "Untitled.json"); | |||||
const char *path = guiSaveDialog(filters, "Untitled.json"); | |||||
if (path) { | if (path) { | ||||
gRackWidget->savePatch(path); | gRackWidget->savePatch(path); | ||||
} | } | ||||
@@ -27,7 +23,7 @@ struct SaveItem : MenuItem { | |||||
struct OpenItem : MenuItem { | struct OpenItem : MenuItem { | ||||
void onAction() { | void onAction() { | ||||
const char *path = noc_file_dialog_open(NOC_FILE_DIALOG_OPEN, filters, NULL, NULL); | |||||
const char *path = guiOpenDialog(filters, NULL); | |||||
if (path) { | if (path) { | ||||
gRackWidget->loadPatch(path); | gRackWidget->loadPatch(path); | ||||
} | } | ||||
@@ -58,7 +54,7 @@ struct FileChoice : ChoiceButton { | |||||
menu->pushChild(saveAsItem); | menu->pushChild(saveAsItem); | ||||
} | } | ||||
overlay->addChild(menu); | overlay->addChild(menu); | ||||
gScene->addChild(overlay); | |||||
gScene->setOverlay(overlay); | |||||
} | } | ||||
}; | }; | ||||
@@ -77,6 +73,12 @@ struct SampleRateChoice : ChoiceButton { | |||||
menu->box.pos = getAbsolutePos().plus(Vec(0, box.size.y)); | menu->box.pos = getAbsolutePos().plus(Vec(0, box.size.y)); | ||||
menu->box.size.x = box.size.x; | menu->box.size.x = box.size.x; | ||||
{ | |||||
MenuLabel *item = new MenuLabel(); | |||||
item->text = "(sample rate switching not yet implemented)"; | |||||
menu->pushChild(item); | |||||
} | |||||
float sampleRates[6] = {44100, 48000, 88200, 96000, 176400, 192000}; | float sampleRates[6] = {44100, 48000, 88200, 96000, 176400, 192000}; | ||||
for (int i = 0; i < 6; i++) { | for (int i = 0; i < 6; i++) { | ||||
SampleRateItem *item = new SampleRateItem(); | SampleRateItem *item = new SampleRateItem(); | ||||
@@ -88,20 +90,20 @@ struct SampleRateChoice : ChoiceButton { | |||||
} | } | ||||
overlay->addChild(menu); | overlay->addChild(menu); | ||||
gScene->addChild(overlay); | |||||
gScene->setOverlay(overlay); | |||||
} | } | ||||
}; | }; | ||||
Toolbar::Toolbar() { | Toolbar::Toolbar() { | ||||
float margin = 5; | float margin = 5; | ||||
box.size = Vec(1020, BND_WIDGET_HEIGHT + 2*margin); | |||||
box.size.y = BND_WIDGET_HEIGHT + 2*margin; | |||||
float xPos = margin; | float xPos = margin; | ||||
{ | { | ||||
Label *label = new Label(); | Label *label = new Label(); | ||||
label->box.pos = Vec(xPos, margin); | label->box.pos = Vec(xPos, margin); | ||||
label->text = "Rack v0.0.0 alpha"; | |||||
label->text = gApplicationName + " " + gApplicationVersion; | |||||
addChild(label); | addChild(label); | ||||
xPos += 150; | xPos += 150; | ||||
} | } | ||||
@@ -132,13 +134,27 @@ Toolbar::Toolbar() { | |||||
wireOpacitySlider = new Slider(); | wireOpacitySlider = new Slider(); | ||||
wireOpacitySlider->box.pos = Vec(xPos, margin); | wireOpacitySlider->box.pos = Vec(xPos, margin); | ||||
wireOpacitySlider->box.size.x = 150; | wireOpacitySlider->box.size.x = 150; | ||||
wireOpacitySlider->label = "Wire opacity"; | |||||
wireOpacitySlider->label = "Cable opacity"; | |||||
wireOpacitySlider->precision = 0; | |||||
wireOpacitySlider->unit = "%"; | wireOpacitySlider->unit = "%"; | ||||
wireOpacitySlider->setLimits(0.0, 100.0); | wireOpacitySlider->setLimits(0.0, 100.0); | ||||
wireOpacitySlider->setDefaultValue(100.0); | |||||
wireOpacitySlider->setDefaultValue(50.0); | |||||
addChild(wireOpacitySlider); | addChild(wireOpacitySlider); | ||||
xPos += wireOpacitySlider->box.size.x; | xPos += wireOpacitySlider->box.size.x; | ||||
} | } | ||||
xPos += margin; | |||||
{ | |||||
wireTensionSlider = new Slider(); | |||||
wireTensionSlider->box.pos = Vec(xPos, margin); | |||||
wireTensionSlider->box.size.x = 150; | |||||
wireTensionSlider->label = "Cable tension"; | |||||
// wireTensionSlider->unit = ""; | |||||
wireTensionSlider->setLimits(0.0, 1.0); | |||||
wireTensionSlider->setDefaultValue(0.5); | |||||
addChild(wireTensionSlider); | |||||
xPos += wireTensionSlider->box.size.x; | |||||
} | |||||
} | } | ||||
void Toolbar::draw(NVGcontext *vg) { | void Toolbar::draw(NVGcontext *vg) { | ||||
@@ -9,6 +9,8 @@ void Tooltip::step() { | |||||
// Wrap size to contents | // Wrap size to contents | ||||
// box.size = getChildrenBoundingBox().getBottomRight(); | // box.size = getChildrenBoundingBox().getBottomRight(); | ||||
Widget::step(); | |||||
} | } | ||||
@@ -12,6 +12,8 @@ Widget::~Widget() { | |||||
gHoveredWidget = NULL; | gHoveredWidget = NULL; | ||||
if (gDraggedWidget == this) | if (gDraggedWidget == this) | ||||
gDraggedWidget = NULL; | gDraggedWidget = NULL; | ||||
if (gSelectedWidget == this) | |||||
gSelectedWidget = NULL; | |||||
clearChildren(); | clearChildren(); | ||||
} | } | ||||
@@ -77,18 +79,52 @@ void Widget::draw(NVGcontext *vg) { | |||||
nvgRestore(vg); | nvgRestore(vg); | ||||
} | } | ||||
Widget *Widget::pick(Vec pos) { | |||||
if (!box.contains(pos)) | |||||
return NULL; | |||||
pos = pos.minus(box.pos); | |||||
Widget *Widget::onMouseDown(Vec pos, int button) { | |||||
for (auto it = children.rbegin(); it != children.rend(); it++) { | for (auto it = children.rbegin(); it != children.rend(); it++) { | ||||
Widget *child = *it; | Widget *child = *it; | ||||
Widget *picked = child->pick(pos); | |||||
if (picked) | |||||
return picked; | |||||
if (child->box.contains(pos)) { | |||||
Widget *w = child->onMouseDown(pos.minus(child->box.pos), button); | |||||
if (w) | |||||
return w; | |||||
} | |||||
} | } | ||||
return this; | |||||
return NULL; | |||||
} | } | ||||
Widget *Widget::onMouseUp(Vec pos, int button) { | |||||
for (auto it = children.rbegin(); it != children.rend(); it++) { | |||||
Widget *child = *it; | |||||
if (child->box.contains(pos)) { | |||||
Widget *w = child->onMouseUp(pos.minus(child->box.pos), button); | |||||
if (w) | |||||
return w; | |||||
} | |||||
} | |||||
return NULL; | |||||
} | |||||
Widget *Widget::onMouseMove(Vec pos, Vec mouseRel) { | |||||
for (auto it = children.rbegin(); it != children.rend(); it++) { | |||||
Widget *child = *it; | |||||
if (child->box.contains(pos)) { | |||||
Widget *w = child->onMouseMove(pos.minus(child->box.pos), mouseRel); | |||||
if (w) | |||||
return w; | |||||
} | |||||
} | |||||
return NULL; | |||||
} | |||||
Widget *Widget::onScroll(Vec pos, Vec scrollRel) { | |||||
for (auto it = children.rbegin(); it != children.rend(); it++) { | |||||
Widget *child = *it; | |||||
if (child->box.contains(pos)) { | |||||
Widget *w = child->onScroll(pos.minus(child->box.pos), scrollRel); | |||||
if (w) | |||||
return w; | |||||
} | |||||
} | |||||
return NULL; | |||||
} | |||||
} // namespace rack | } // namespace rack |
@@ -3,10 +3,10 @@ | |||||
namespace rack { | namespace rack { | ||||
void drawWire(NVGcontext *vg, Vec pos1, Vec pos2, NVGcolor color) { | |||||
void drawWire(NVGcontext *vg, Vec pos1, Vec pos2, float tension, NVGcolor color) { | |||||
float dist = pos1.minus(pos2).norm(); | float dist = pos1.minus(pos2).norm(); | ||||
Vec slump = Vec(0, 100.0 + 0.5*dist); | |||||
Vec pos3 = pos1.plus(pos2).div(2).plus(slump); | |||||
float slump = (1.0 - tension) * (150.0 + 1.0*dist); | |||||
Vec pos3 = pos1.plus(pos2).div(2).plus(Vec(0, slump)); | |||||
NVGcolor colorOutline = nvgRGBf(0, 0, 0); | NVGcolor colorOutline = nvgRGBf(0, 0, 0); | ||||
@@ -108,7 +108,8 @@ void WireWidget::draw(NVGcontext *vg) { | |||||
float wireOpacity = gScene->toolbar->wireOpacitySlider->value / 100.0; | float wireOpacity = gScene->toolbar->wireOpacitySlider->value / 100.0; | ||||
if (wireOpacity > 0.0) { | if (wireOpacity > 0.0) { | ||||
nvgGlobalAlpha(vg, wireOpacity); | nvgGlobalAlpha(vg, wireOpacity); | ||||
drawWire(vg, outputPos, inputPos, color); | |||||
float tension = gScene->toolbar->wireTensionSlider->value; | |||||
drawWire(vg, outputPos, inputPos, tension, color); | |||||
} | } | ||||
nvgRestore(vg); | nvgRestore(vg); | ||||
} | } | ||||