From 06326a899a51da74c3386b971d8f188f2bee5db8 Mon Sep 17 00:00:00 2001 From: Andrew Belt Date: Mon, 19 Dec 2016 21:24:25 -0500 Subject: [PATCH] Changed how events work, added ModulePanel, added cable tension --- Makefile | 4 +- README.md | 22 +++++ README.txt | 9 -- include/Rack.hpp | 6 ++ include/util.hpp | 42 +++++++-- include/widgets.hpp | 165 ++++++++++++++++++++++----------- src/core/AudioInterface.cpp | 2 +- src/core/MidiInterface.cpp | 21 +++-- src/gui.cpp | 101 +++++++++++--------- src/main.cpp | 11 ++- src/plugin.cpp | 29 +++--- src/util.cpp | 72 +------------- src/widgets/InputPort.cpp | 3 +- src/widgets/MenuItem.cpp | 15 ++- src/widgets/MenuOverlay.cpp | 16 +++- src/widgets/ModulePanel.cpp | 29 ++++++ src/widgets/ModuleWidget.cpp | 8 +- src/widgets/OutputPort.cpp | 2 +- src/widgets/ParamWidget.cpp | 6 +- src/widgets/Port.cpp | 22 ++--- src/widgets/QuantityWidget.cpp | 21 +++++ src/widgets/RackWidget.cpp | 62 ++++--------- src/widgets/Scene.cpp | 21 ++++- src/widgets/Screw.cpp | 4 +- src/widgets/ScrollWidget.cpp | 26 ++++-- src/widgets/Slider.cpp | 4 +- src/widgets/SpriteWidget.cpp | 2 +- src/widgets/Toolbar.cpp | 40 +++++--- src/widgets/Tooltip.cpp | 2 + src/widgets/Widget.cpp | 52 +++++++++-- src/widgets/WireWidget.cpp | 9 +- 31 files changed, 495 insertions(+), 333 deletions(-) create mode 100644 README.md delete mode 100644 README.txt create mode 100644 src/widgets/ModulePanel.cpp diff --git a/Makefile b/Makefile index 62640721..be1a4907 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ ARCH ?= linux 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 LDFLAGS = @@ -15,7 +15,7 @@ CXX = g++ SOURCES += lib/noc/noc_file_dialog.c CFLAGS += -DNOC_FILE_DIALOG_GTK $(shell pkg-config --cflags gtk+-2.0) 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) TARGET = Rack endif diff --git a/README.md b/README.md new file mode 100644 index 00000000..4de9035f --- /dev/null +++ b/README.md @@ -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. diff --git a/README.txt b/README.txt deleted file mode 100644 index e33e3b8a..00000000 --- a/README.txt +++ /dev/null @@ -1,9 +0,0 @@ -░█▀▄░█▀█░█▀▀░█░█ -░█▀▄░█▀█░█░░░█▀▄ -░▀░▀░▀░▀░▀▀▀░▀░▀ - -Virtual modular synthesizer engine - -## Building - -`make ARCH=linux` or `make ARCH=windows` or `make ARCH=apple` diff --git a/include/Rack.hpp b/include/Rack.hpp index 5393bbd8..1f651bb4 100644 --- a/include/Rack.hpp +++ b/include/Rack.hpp @@ -9,6 +9,9 @@ namespace rack { +extern std::string gApplicationName; +extern std::string gApplicationVersion; + extern Scene *gScene; extern RackWidget *gRackWidget; @@ -53,12 +56,15 @@ void pluginDestroy(); extern Vec gMousePos; extern Widget *gHoveredWidget; extern Widget *gDraggedWidget; +extern Widget *gSelectedWidget; void guiInit(); void guiDestroy(); void guiRun(); void guiCursorLock(); 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 loadImage(std::string filename); diff --git a/include/util.hpp b/include/util.hpp index 9533c606..aa14bee6 100644 --- a/include/util.hpp +++ b/include/util.hpp @@ -2,6 +2,7 @@ #include #include +#include namespace rack { @@ -10,8 +11,15 @@ namespace rack { // 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) { - 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) { @@ -30,6 +38,17 @@ inline int maxi(int a, int 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 // Assumes this architecture's division is non-Euclidean inline int eucMod(int a, int base) { @@ -46,7 +65,9 @@ inline void setf(float *p, float 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) { int i = x; x -= i; @@ -57,12 +78,7 @@ inline float interpf(float *p, float x) { // 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 @@ -114,10 +130,12 @@ struct Rect { Rect() {} 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) { return pos.x <= v.x && v.x < pos.x + size.x && pos.y <= v.y && v.y < pos.y + size.y; } + /** Returns whether this Rect overlaps with another Rect */ bool intersects(Rect r) { 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); @@ -134,6 +152,14 @@ struct Rect { Vec getBottomRight() { 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; + } }; diff --git a/include/widgets.hpp b/include/widgets.hpp index c3af657f..db19e61f 100644 --- a/include/widgets.hpp +++ b/include/widgets.hpp @@ -7,8 +7,6 @@ #include #include -#include -#include #include #include "../lib/nanovg/src/nanovg.h" @@ -31,10 +29,10 @@ struct OutputPort; // base class and traits //////////////////// -// A node in the 2D scene graph +/** A node in the 2D scene graph */ 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; std::list children; @@ -42,55 +40,92 @@ struct Widget { Vec getAbsolutePos(); Rect getChildrenBoundingBox(); + template + T *getAncestorOfType() { + if (!parent) return NULL; + T *p = dynamic_cast(parent); + if (p) return p; + return parent->getAncestorOfType(); + } - // 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); // Does not delete widget but transfers ownership to caller // Silenty fails if widget is not a child void removeChild(Widget *widget); void clearChildren(); - // Advances the module by one frame + /** Advances the module by one frame */ virtual void step(); - // Draws to NanoVG context + /** Draws to NanoVG context */ 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 - 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() {} + /** Called when another widget begins responding to `onMouseMove` events */ 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 onDragDrop(Widget *origin) {} - virtual void onDragHover(Widget *origin) {} + /** Called when a widget responds to `onMouseMove` and is being dragged */ 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 onResize() {} + virtual void onAction() {} virtual void onChange() {} }; -// Widget that does not respond to events +/** Widget that does not respond to events */ 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 { @@ -107,38 +142,49 @@ struct QuantityWidget : virtual Widget { float maxValue = 1.0; float defaultValue = 0.0; 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; + /** 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 setLimits(float minValue, float maxValue); void setDefaultValue(float defaultValue); + /** Generates the display value */ + std::string getText(); }; //////////////////// // gui elements //////////////////// -struct Label : TransparentWidget { +struct Label : Widget { std::string text; void draw(NVGcontext *vg); }; // 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() { box.size = Vec(0, 0); } - // Transfers ownership, like addChild() + // Resizes menu and calls addChild() void pushChild(Widget *child); void draw(NVGcontext *vg); }; -struct MenuEntry : Widget { +struct MenuEntry : OpaqueWidget { std::string text; MenuEntry() { box.size = Vec(0, BND_WIDGET_HEIGHT); @@ -155,12 +201,12 @@ struct MenuItem : MenuEntry { void draw(NVGcontext *vg); - void onMouseUp(int button); void onMouseEnter(); void onMouseLeave() ; + void onDragDrop(Widget *origin); }; -struct Button : Widget { +struct Button : OpaqueWidget { std::string text; BNDwidgetState state = BND_DEFAULT; @@ -175,7 +221,7 @@ struct ChoiceButton : Button { void draw(NVGcontext *vg); }; -struct Slider : QuantityWidget { +struct Slider : OpaqueWidget, QuantityWidget { BNDwidgetState state = BND_DEFAULT; Slider(); @@ -185,7 +231,7 @@ struct Slider : QuantityWidget { void onDragEnd(); }; -struct ScrollBar : Widget { +struct ScrollBar : OpaqueWidget { enum { VERTICAL, HORIZONTAL } orientation; float containerOffset = 0.0; float containerSize = 0.0; @@ -200,18 +246,18 @@ struct ScrollBar : Widget { }; // Handles a container with scrollbars -struct ScrollWidget : Widget { +struct ScrollWidget : OpaqueWidget { Widget *container; ScrollBar *hScrollBar; ScrollBar *vScrollBar; ScrollWidget(); + void step(); 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 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. struct Model; -struct ModuleWidget : Widget { +struct ModuleWidget : OpaqueWidget { 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. Module *module = NULL; @@ -256,7 +302,7 @@ struct ModuleWidget : Widget { void onMouseDown(int button); }; -struct WireWidget : Widget { +struct WireWidget : OpaqueWidget { OutputPort *outputPort = NULL; InputPort *inputPort = NULL; Wire *wire = NULL; @@ -270,12 +316,11 @@ struct WireWidget : Widget { void drawInputPlug(NVGcontext *vg); }; -struct RackWidget : Widget { +struct RackWidget : OpaqueWidget { // Only put ModuleWidgets in here Widget *moduleContainer; // Only put WireWidgets in here Widget *wireContainer; - // An unowned reference to the currently dragged wire WireWidget *activeWire = NULL; RackWidget(); @@ -294,6 +339,12 @@ struct RackWidget : Widget { void onMouseDown(int button); }; +struct ModulePanel : TransparentWidget { + std::string imageFilename; + NVGcolor backgroundColor; + void draw(NVGcontext *vg); +}; + //////////////////// // params //////////////////// @@ -308,7 +359,7 @@ struct Screw : TransparentWidget, SpriteWidget { Screw(); }; -struct ParamWidget : QuantityWidget { +struct ParamWidget : OpaqueWidget, QuantityWidget { Module *module = NULL; int paramId; @@ -362,7 +413,7 @@ struct MomentarySwitch : virtual Switch { // ports //////////////////// -struct Port : Widget { +struct Port : OpaqueWidget, SpriteWidget { Module *module = NULL; WireWidget *connectedWire = NULL; @@ -371,7 +422,6 @@ struct Port : Widget { void disconnect(); int type; - void draw(NVGcontext *vg); void drawGlow(NVGcontext *vg); void onMouseDown(int button); void onDragEnd(); @@ -397,17 +447,20 @@ struct OutputPort : Port { // scene //////////////////// -struct Toolbar : Widget { +struct Toolbar : OpaqueWidget { Slider *wireOpacitySlider; + Slider *wireTensionSlider; Toolbar(); void draw(NVGcontext *vg); }; -struct Scene : Widget { +struct Scene : OpaqueWidget { Toolbar *toolbar; ScrollWidget *scrollWidget; + Widget *overlay = NULL; Scene(); - void onResize(); + void setOverlay(Widget *w); + void step(); }; diff --git a/src/core/AudioInterface.cpp b/src/core/AudioInterface.cpp index ce78bca9..03f4efa1 100644 --- a/src/core/AudioInterface.cpp +++ b/src/core/AudioInterface.cpp @@ -169,7 +169,7 @@ struct AudioChoice : ChoiceButton { menu->pushChild(audioItem); } overlay->addChild(menu); - gScene->addChild(overlay); + gScene->setOverlay(overlay); } }; diff --git a/src/core/MidiInterface.cpp b/src/core/MidiInterface.cpp index 91433eb7..89164317 100644 --- a/src/core/MidiInterface.cpp +++ b/src/core/MidiInterface.cpp @@ -26,9 +26,10 @@ struct MidiInterface : Module { PortMidiStream *stream = NULL; std::list notes; bool pedal = false; - bool gate = false; int note = 64; // C4 int pitchWheel = 64; + bool retrigger = true; + bool retriggered = false; MidiInterface(); ~MidiInterface(); @@ -74,6 +75,11 @@ void MidiInterface::step() { } if (outputs[GATE_OUTPUT]) { + bool gate = pedal || !notes.empty(); + if (retrigger && retriggered) { + gate = false; + retriggered = false; + } *outputs[GATE_OUTPUT] = gate ? 5.0 : 0.0; } if (outputs[PITCH_OUTPUT]) { @@ -113,12 +119,14 @@ void MidiInterface::openPort(int portId) { } void MidiInterface::pressNote(int note) { + // Remove existing similar note auto it = std::find(notes.begin(), notes.end(), note); if (it != notes.end()) notes.erase(it); + // Push note notes.push_back(note); - this->gate = true; this->note = note; + retriggered = true; } void MidiInterface::releaseNote(int note) { @@ -135,10 +143,7 @@ void MidiInterface::releaseNote(int note) { auto it2 = notes.end(); 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); } break; case 0x9: // note on - if (data2) { + if (data2 > 0) { pressNote(data1); } else { @@ -210,7 +215,7 @@ struct MidiChoice : ChoiceButton { menu->pushChild(midiItem); } overlay->addChild(menu); - gScene->addChild(overlay); + gScene->setOverlay(overlay); } }; diff --git a/src/gui.cpp b/src/gui.cpp index c1c0e472..b1ca5e62 100644 --- a/src/gui.cpp +++ b/src/gui.cpp @@ -1,6 +1,9 @@ #include #include "Rack.hpp" +#include +#include + // #define NANOVG_GLEW #define NANOVG_IMPLEMENTATION #include "../lib/nanovg/src/nanovg.h" @@ -9,6 +12,10 @@ #define BLENDISH_IMPLEMENTATION #include "../lib/oui/blendish.h" +extern "C" { + #include "../lib/noc/noc_file_dialog.h" +} + namespace rack { @@ -18,44 +25,45 @@ RackWidget *gRackWidget = NULL; Vec gMousePos; Widget *gHoveredWidget = NULL; Widget *gDraggedWidget = NULL; +Widget *gSelectedWidget = NULL; -static GLFWwindow *window; +static GLFWwindow *window = NULL; static NVGcontext *vg = NULL; void windowSizeCallback(GLFWwindow* window, int width, int height) { gScene->box.size = Vec(width, height); - gScene->onResize(); } 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(); } } - 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) { - 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 = NULL; } + gDraggedWidget = NULL; } } } @@ -70,40 +78,33 @@ void cursorPosCallback(GLFWwindow* window, double xpos, double ypos) { } // 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) { // onDragMove // 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)); - // onDragHover - if (hovered) { - hovered->onDragHover(gDraggedWidget); - } } else { - // onMouseEnter and onMouseLeave + // onMouseMove + Widget *hovered = gScene->onMouseMove(gMousePos, mouseRel); + if (hovered != gHoveredWidget) { if (gHoveredWidget) { + // onMouseLeave gHoveredWidget->onMouseLeave(); } if (hovered) { + // onMouseEnter hovered->onMouseEnter(); } } 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) { Vec scrollRel = Vec(x, y); // onScroll - gScene->scrollWidget->onScroll(scrollRel.mult(-95)); + gScene->onScroll(gMousePos, scrollRel.mult(-95)); } 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() { 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); glfwGetWindowSize(window, &width, &height); @@ -176,10 +177,12 @@ void guiInit() { glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); - window = glfwCreateWindow(1020, 700, "Rack", NULL, NULL); + window = glfwCreateWindow(1020, 700, gApplicationName.c_str(), NULL, NULL); assert(window); glfwMakeContextCurrent(window); + glfwSwapInterval(1); + glfwSetWindowSizeCallback(window, windowSizeCallback); glfwSetMouseButtonCallback(window, mouseButtonCallback); // glfwSetCursorPosCallback(window, cursorPosCallback); @@ -203,7 +206,7 @@ void guiInit() { // Set up Blendish bndSetFont(loadFont("res/DejaVuSans.ttf")); - // bndSetIconImage(loadImage("res/blender_icons16.png")); + // bndSetIconImage(loadImage("res/icons.png")); gScene = new Scene(); } @@ -249,6 +252,16 @@ void guiCursorUnlock() { 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 images; diff --git a/src/main.cpp b/src/main.cpp index 44261d32..50cb7a7d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -7,6 +7,14 @@ #include "Rack.hpp" +namespace rack { + +std::string gApplicationName = "Virtuoso Rack"; +std::string gApplicationVersion = "v0.0.0"; + +} // namespace rack + + using namespace rack; int main() { @@ -20,7 +28,7 @@ int main() { assert(success); CFRelease(bundleURL); - // chdir(dirname(path)); + chdir(dirname(path)); } #endif @@ -40,3 +48,4 @@ int main() { pluginDestroy(); return 0; } + diff --git a/src/plugin.cpp b/src/plugin.cpp index 30442f77..a41211ce 100644 --- a/src/plugin.cpp +++ b/src/plugin.cpp @@ -15,6 +15,7 @@ namespace rack { std::list gPlugins; +static int loadPlugin(const char *path) { // Load dynamic/shared library #if defined(WINDOWS) @@ -24,8 +25,8 @@ int loadPlugin(const char *path) { return -1; } #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); if (!handle) { fprintf(stderr, "Failed to load library %s: %s\n", path, dlerror()); @@ -61,24 +62,24 @@ void pluginInit() { // Load core Plugin *corePlugin = coreInit(); gPlugins.push_back(corePlugin); - // Search for plugin libraries #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) #if defined(LINUX) const char *globPath = "plugins/*/plugin.so"; + #elif defined(WINDOWS) + const char *globPath = "plugins/*/plugin.dll"; #elif defined(APPLE) const char *globPath = "plugins/*/plugin.dylib"; #endif diff --git a/src/util.cpp b/src/util.cpp index ea06996b..56531514 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -1,77 +1,9 @@ #include "util.hpp" -#include 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 . */ - -/* 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 diff --git a/src/widgets/InputPort.cpp b/src/widgets/InputPort.cpp index ab3f0bb3..5f77a5ab 100644 --- a/src/widgets/InputPort.cpp +++ b/src/widgets/InputPort.cpp @@ -4,7 +4,7 @@ namespace rack { void InputPort::draw(NVGcontext *vg) { - Port::draw(vg); + SpriteWidget::draw(vg); if (gRackWidget->activeWire && gRackWidget->activeWire->inputPort) { Port::drawGlow(vg); } @@ -27,6 +27,7 @@ void InputPort::onDragStart() { } void InputPort::onDragDrop(Widget *origin) { + printf("%p\n", origin); if (connectedWire) return; if (gRackWidget->activeWire) { if (gRackWidget->activeWire->inputPort) return; diff --git a/src/widgets/MenuItem.cpp b/src/widgets/MenuItem.cpp index c2fd8107..23538ac5 100644 --- a/src/widgets/MenuItem.cpp +++ b/src/widgets/MenuItem.cpp @@ -15,16 +15,13 @@ void MenuItem::onMouseLeave() { state = BND_DEFAULT; } -void MenuItem::onMouseUp(int button) { +void MenuItem::onDragDrop(Widget *origin) { + if (origin != this) + return; + 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); } diff --git a/src/widgets/MenuOverlay.cpp b/src/widgets/MenuOverlay.cpp index 8bba288f..fc67c7d8 100644 --- a/src/widgets/MenuOverlay.cpp +++ b/src/widgets/MenuOverlay.cpp @@ -3,12 +3,18 @@ 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; } diff --git a/src/widgets/ModulePanel.cpp b/src/widgets/ModulePanel.cpp new file mode 100644 index 00000000..ca208e96 --- /dev/null +++ b/src/widgets/ModulePanel.cpp @@ -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 diff --git a/src/widgets/ModuleWidget.cpp b/src/widgets/ModuleWidget.cpp index db665bec..bfb5f790 100644 --- a/src/widgets/ModuleWidget.cpp +++ b/src/widgets/ModuleWidget.cpp @@ -145,7 +145,7 @@ struct DeleteModuleMenuItem : MenuItem { }; void ModuleWidget::onMouseDown(int button) { - if (button == GLFW_MOUSE_BUTTON_RIGHT) { + if (button == 1) { MenuOverlay *overlay = new MenuOverlay(); Menu *menu = new Menu(); menu->box.pos = gMousePos; @@ -155,12 +155,12 @@ void ModuleWidget::onMouseDown(int button) { menu->pushChild(menuLabel); ResetParamsMenuItem *resetItem = new ResetParamsMenuItem(); - resetItem->text = "Initialize parameters"; + resetItem->text = "Reset parameters"; resetItem->moduleWidget = this; menu->pushChild(resetItem); DisconnectPortsMenuItem *disconnectItem = new DisconnectPortsMenuItem(); - disconnectItem->text = "Disconnect wires"; + disconnectItem->text = "Disconnect cables"; disconnectItem->moduleWidget = this; menu->pushChild(disconnectItem); @@ -175,7 +175,7 @@ void ModuleWidget::onMouseDown(int button) { menu->pushChild(deleteItem); } overlay->addChild(menu); - gScene->addChild(overlay); + gScene->setOverlay(overlay); } } diff --git a/src/widgets/OutputPort.cpp b/src/widgets/OutputPort.cpp index 8e9dd1af..7cc74cfa 100644 --- a/src/widgets/OutputPort.cpp +++ b/src/widgets/OutputPort.cpp @@ -4,7 +4,7 @@ namespace rack { void OutputPort::draw(NVGcontext *vg) { - Port::draw(vg); + SpriteWidget::draw(vg); if (gRackWidget->activeWire && gRackWidget->activeWire->outputPort) { Port::drawGlow(vg); } diff --git a/src/widgets/ParamWidget.cpp b/src/widgets/ParamWidget.cpp index 3b494583..902ec278 100644 --- a/src/widgets/ParamWidget.cpp +++ b/src/widgets/ParamWidget.cpp @@ -13,13 +13,15 @@ void ParamWidget::fromJson(json_t *root) { } void ParamWidget::onMouseDown(int button) { - if (button == GLFW_MOUSE_BUTTON_RIGHT) { + if (button == 1) { setValue(defaultValue); } } void ParamWidget::onChange() { - assert(module); + if (!module) + return; + // moduleWidget->module->params[paramId] = value; rackSetParamSmooth(module, paramId, value); } diff --git a/src/widgets/Port.cpp b/src/widgets/Port.cpp index 5f6edb5a..335cf459 100644 --- a/src/widgets/Port.cpp +++ b/src/widgets/Port.cpp @@ -5,7 +5,12 @@ namespace rack { Port::Port() { 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() { @@ -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) { Vec c = box.getCenter(); NVGcolor icol = nvgRGBAf(1, 1, 1, 0.5); @@ -45,7 +37,7 @@ void Port::drawGlow(NVGcontext *vg) { } void Port::onMouseDown(int button) { - if (button == GLFW_MOUSE_BUTTON_RIGHT) { + if (button == 1) { disconnect(); } } diff --git a/src/widgets/QuantityWidget.cpp b/src/widgets/QuantityWidget.cpp index 07504c0a..483b4ff4 100644 --- a/src/widgets/QuantityWidget.cpp +++ b/src/widgets/QuantityWidget.cpp @@ -3,6 +3,10 @@ namespace rack { +QuantityWidget::QuantityWidget() { + onChange(); +} + void QuantityWidget::setValue(float value) { this->value = clampf(value, minValue, maxValue); onChange(); @@ -18,5 +22,22 @@ void QuantityWidget::setDefaultValue(float 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 diff --git a/src/widgets/RackWidget.cpp b/src/widgets/RackWidget.cpp index 3956bebf..be110b34 100644 --- a/src/widgets/RackWidget.cpp +++ b/src/widgets/RackWidget.cpp @@ -5,7 +5,7 @@ namespace rack { RackWidget::RackWidget() { - moduleContainer = new TranslucentWidget(); + moduleContainer = new Widget(); addChild(moduleContainer); wireContainer = new TransparentWidget(); @@ -56,26 +56,10 @@ void RackWidget::loadPatch(std::string filename) { 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(w); - if (m) return m; - return getAncestorModuleWidget(w->parent); -} - json_t *RackWidget::toJson() { // root 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 json_t *modulesJ = json_array(); std::map moduleIds; @@ -102,9 +86,9 @@ json_t *RackWidget::toJson() { // wire json_t *wire = json_object(); { - ModuleWidget *outputModuleWidget = getAncestorModuleWidget(wireWidget->outputPort); + ModuleWidget *outputModuleWidget = wireWidget->outputPort->getAncestorOfType(); assert(outputModuleWidget); - ModuleWidget *inputModuleWidget = getAncestorModuleWidget(wireWidget->inputPort); + ModuleWidget *inputModuleWidget = wireWidget->inputPort->getAncestorOfType(); assert(inputModuleWidget); int outputModuleId = moduleIds[outputModuleWidget]; int inputModuleId = moduleIds[inputModuleWidget]; @@ -121,26 +105,16 @@ json_t *RackWidget::toJson() { } 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 std::map moduleWidgets; json_t *modulesJ = json_object_get(root, "modules"); + if (!modulesJ) return; size_t moduleId; json_t *moduleJ; json_array_foreach(modulesJ, moduleId, moduleJ) { // Get plugin json_t *pluginSlugJ = json_object_get(moduleJ, "plugin"); + if (!pluginSlugJ) continue; const char *pluginSlug = json_string_value(pluginSlugJ); Plugin *plugin = NULL; for (Plugin *p : gPlugins) { @@ -149,7 +123,7 @@ void RackWidget::fromJson(json_t *root) { break; } } - assert(plugin); + if (!plugin) continue; // Get model json_t *modelSlug = json_object_get(moduleJ, "model"); @@ -160,10 +134,11 @@ void RackWidget::fromJson(json_t *root) { break; } } - assert(model); + if (!model) continue; // Create ModuleWidget ModuleWidget *moduleWidget = model->createModuleWidget(); + assert(moduleWidget); moduleWidget->fromJson(moduleJ); moduleContainer->addChild(moduleWidget); moduleWidgets[moduleId] = moduleWidget; @@ -171,23 +146,25 @@ void RackWidget::fromJson(json_t *root) { // wires json_t *wiresJ = json_object_get(root, "wires"); + if (!wiresJ) return; size_t wireId; json_t *wireJ; json_array_foreach(wiresJ, wireId, wireJ) { int outputModuleId, outputId; 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, "inputModuleId", &inputModuleId, "inputId", &inputId); + if (err) continue; // Get ports ModuleWidget *outputModuleWidget = moduleWidgets[outputModuleId]; - assert(outputModuleWidget); + if (!outputModuleWidget) continue; OutputPort *outputPort = outputModuleWidget->outputs[outputId]; - assert(outputPort); + if (!outputPort) continue; ModuleWidget *inputModuleWidget = moduleWidgets[inputModuleId]; - assert(inputModuleWidget); + if (!inputModuleWidget) continue; InputPort *inputPort = inputModuleWidget->inputs[inputId]; - assert(inputPort); + if (!inputPort) continue; // Create WireWidget WireWidget *wireWidget = new WireWidget(); 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; savePatch("autosave.json"); } @@ -299,7 +277,7 @@ struct AddModuleMenuItem : MenuItem { }; void RackWidget::onMouseDown(int button) { - if (button == GLFW_MOUSE_BUTTON_RIGHT) { + if (button == 1) { // Get relative position of the click Vec modulePos = gMousePos.minus(getAbsolutePos()); @@ -320,7 +298,7 @@ void RackWidget::onMouseDown(int button) { } } overlay->addChild(menu); - gScene->addChild(overlay); + gScene->setOverlay(overlay); } } diff --git a/src/widgets/Scene.cpp b/src/widgets/Scene.cpp index 8214ce02..8f2dfa43 100644 --- a/src/widgets/Scene.cpp +++ b/src/widgets/Scene.cpp @@ -17,10 +17,27 @@ Scene::Scene() { 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; scrollWidget->box.size = box.size.minus(scrollWidget->box.pos); - scrollWidget->onResize(); + if (overlay) { + overlay->box.size = box.size; + } + + Widget::step(); } diff --git a/src/widgets/Screw.cpp b/src/widgets/Screw.cpp index 66812f5b..402056f9 100644 --- a/src/widgets/Screw.cpp +++ b/src/widgets/Screw.cpp @@ -8,7 +8,9 @@ Screw::Screw() { spriteOffset = Vec(-7, -7); spriteSize = Vec(29, 29); spriteFilename = "res/screw.png"; - index = randomi64() % 5; + + std::uniform_int_distribution<> dist(0, 4); + index = dist(rng); } diff --git a/src/widgets/ScrollWidget.cpp b/src/widgets/ScrollWidget.cpp index 7a26a257..78c6fc17 100644 --- a/src/widgets/ScrollWidget.cpp +++ b/src/widgets/ScrollWidget.cpp @@ -16,6 +16,18 @@ ScrollWidget::ScrollWidget() { 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) { // Update the scrollbar sizes Vec c = container->getChildrenBoundingBox().getBottomRight(); @@ -28,19 +40,13 @@ void ScrollWidget::draw(NVGcontext *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); vScrollBar->move(scrollRel.y); + return this; } diff --git a/src/widgets/Slider.cpp b/src/widgets/Slider.cpp index 68a43212..c9f68a31 100644 --- a/src/widgets/Slider.cpp +++ b/src/widgets/Slider.cpp @@ -11,9 +11,7 @@ Slider::Slider() { void Slider::draw(NVGcontext *vg) { 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() { diff --git a/src/widgets/SpriteWidget.cpp b/src/widgets/SpriteWidget.cpp index 7abfcbab..dc1d6f52 100644 --- a/src/widgets/SpriteWidget.cpp +++ b/src/widgets/SpriteWidget.cpp @@ -16,7 +16,7 @@ void SpriteWidget::draw(NVGcontext *vg) { nvgImageSize(vg, imageId, &width, &height); int stride = width / spriteSize.x; 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; } Vec offset = Vec((index % stride) * spriteSize.x, (index / stride) * spriteSize.y); diff --git a/src/widgets/Toolbar.cpp b/src/widgets/Toolbar.cpp index 8e97ec95..f04239f6 100644 --- a/src/widgets/Toolbar.cpp +++ b/src/widgets/Toolbar.cpp @@ -1,9 +1,5 @@ #include "Rack.hpp" -extern "C" { - #include "../lib/noc/noc_file_dialog.h" -} - namespace rack { @@ -18,7 +14,7 @@ struct NewItem : MenuItem { struct SaveItem : MenuItem { 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) { gRackWidget->savePatch(path); } @@ -27,7 +23,7 @@ struct SaveItem : MenuItem { struct OpenItem : MenuItem { void onAction() { - const char *path = noc_file_dialog_open(NOC_FILE_DIALOG_OPEN, filters, NULL, NULL); + const char *path = guiOpenDialog(filters, NULL); if (path) { gRackWidget->loadPatch(path); } @@ -58,7 +54,7 @@ struct FileChoice : ChoiceButton { menu->pushChild(saveAsItem); } 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.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}; for (int i = 0; i < 6; i++) { SampleRateItem *item = new SampleRateItem(); @@ -88,20 +90,20 @@ struct SampleRateChoice : ChoiceButton { } overlay->addChild(menu); - gScene->addChild(overlay); + gScene->setOverlay(overlay); } }; Toolbar::Toolbar() { float margin = 5; - box.size = Vec(1020, BND_WIDGET_HEIGHT + 2*margin); + box.size.y = BND_WIDGET_HEIGHT + 2*margin; float xPos = margin; { Label *label = new Label(); label->box.pos = Vec(xPos, margin); - label->text = "Rack v0.0.0 alpha"; + label->text = gApplicationName + " " + gApplicationVersion; addChild(label); xPos += 150; } @@ -132,13 +134,27 @@ Toolbar::Toolbar() { wireOpacitySlider = new Slider(); wireOpacitySlider->box.pos = Vec(xPos, margin); wireOpacitySlider->box.size.x = 150; - wireOpacitySlider->label = "Wire opacity"; + wireOpacitySlider->label = "Cable opacity"; + wireOpacitySlider->precision = 0; wireOpacitySlider->unit = "%"; wireOpacitySlider->setLimits(0.0, 100.0); - wireOpacitySlider->setDefaultValue(100.0); + wireOpacitySlider->setDefaultValue(50.0); addChild(wireOpacitySlider); 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) { diff --git a/src/widgets/Tooltip.cpp b/src/widgets/Tooltip.cpp index 5266e122..baea5703 100644 --- a/src/widgets/Tooltip.cpp +++ b/src/widgets/Tooltip.cpp @@ -9,6 +9,8 @@ void Tooltip::step() { // Wrap size to contents // box.size = getChildrenBoundingBox().getBottomRight(); + + Widget::step(); } diff --git a/src/widgets/Widget.cpp b/src/widgets/Widget.cpp index 195cc91f..1f8d9569 100644 --- a/src/widgets/Widget.cpp +++ b/src/widgets/Widget.cpp @@ -12,6 +12,8 @@ Widget::~Widget() { gHoveredWidget = NULL; if (gDraggedWidget == this) gDraggedWidget = NULL; + if (gSelectedWidget == this) + gSelectedWidget = NULL; clearChildren(); } @@ -77,18 +79,52 @@ void Widget::draw(NVGcontext *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++) { 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 diff --git a/src/widgets/WireWidget.cpp b/src/widgets/WireWidget.cpp index adcedf1c..556ca9d2 100644 --- a/src/widgets/WireWidget.cpp +++ b/src/widgets/WireWidget.cpp @@ -3,10 +3,10 @@ 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(); - 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); @@ -108,7 +108,8 @@ void WireWidget::draw(NVGcontext *vg) { float wireOpacity = gScene->toolbar->wireOpacitySlider->value / 100.0; if (wireOpacity > 0.0) { nvgGlobalAlpha(vg, wireOpacity); - drawWire(vg, outputPos, inputPos, color); + float tension = gScene->toolbar->wireTensionSlider->value; + drawWire(vg, outputPos, inputPos, tension, color); } nvgRestore(vg); }