#pragma once #include #include #include #include #include #include #include "../ext/nanovg/src/nanovg.h" #include "../ext/oui/blendish.h" #include "util.hpp" namespace rack { struct Module; struct Wire; struct RackWidget; struct ParamWidget; struct InputPort; struct OutputPort; //////////////////// // base class and traits //////////////////// /** A node in the 2D scene graph */ struct Widget { /** Stores position and size */ Rect box = Rect(Vec(), Vec(INFINITY, INFINITY)); Widget *parent = NULL; std::list children; virtual ~Widget(); Vec getAbsolutePos(); Rect getChildrenBoundingBox(); template T *getAncestorOfType() { if (!parent) return NULL; T *p = dynamic_cast(parent); if (p) return p; return parent->getAncestorOfType(); } /** 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 */ virtual void step(); /** Draws to NanoVG context */ virtual void draw(NVGcontext *vg); // Events /** 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 Widget *onScroll(Vec pos, Vec scrollRel); /** Called when a widget responds to `onMouseDown` for a left button press */ virtual void onDragStart() {} /** Called when the left button is released and this widget is being dragged */ virtual void onDragEnd() {} /** 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) {} virtual void onAction() {} virtual void onChange() {} }; /** Widget that does not respond to events */ struct TransparentWidget : virtual Widget { 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 { Vec spriteOffset; Vec spriteSize; std::string spriteFilename; int index = 0; void draw(NVGcontext *vg); }; struct QuantityWidget : virtual Widget { float value = 0.0; float minValue = 0.0; 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" */ 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 : Widget { std::string text; Label() { box.size.y = BND_WIDGET_HEIGHT; } void draw(NVGcontext *vg); }; // Deletes itself from parent when clicked struct MenuOverlay : OpaqueWidget { void step(); Widget *onScroll(Vec pos, Vec scrollRel) { return this; } void onDragDrop(Widget *origin); }; struct Menu : OpaqueWidget { Menu() { box.size = Vec(0, 0); } // Resizes menu and calls addChild() void pushChild(Widget *child); void draw(NVGcontext *vg); }; struct MenuEntry : OpaqueWidget { std::string text; MenuEntry() { box.size = Vec(0, BND_WIDGET_HEIGHT); } float computeMinWidth(NVGcontext *vg); }; struct MenuLabel : MenuEntry { void draw(NVGcontext *vg); }; struct MenuItem : MenuEntry { BNDwidgetState state = BND_DEFAULT; void draw(NVGcontext *vg); void onMouseEnter(); void onMouseLeave() ; void onDragDrop(Widget *origin); }; struct Button : OpaqueWidget { std::string text; BNDwidgetState state = BND_DEFAULT; Button() { box.size.y = BND_WIDGET_HEIGHT; } void draw(NVGcontext *vg); void onMouseEnter(); void onMouseLeave(); void onDragStart(); void onDragEnd(); void onDragDrop(Widget *origin); }; struct ChoiceButton : Button { void draw(NVGcontext *vg); }; struct Slider : OpaqueWidget, QuantityWidget { BNDwidgetState state = BND_DEFAULT; Slider() { box.size.y = BND_WIDGET_HEIGHT; } void draw(NVGcontext *vg); void onDragStart(); void onDragMove(Vec mouseRel); void onDragEnd(); }; struct ScrollBar : OpaqueWidget { enum { VERTICAL, HORIZONTAL } orientation; float containerOffset = 0.0; float containerSize = 0.0; BNDwidgetState state = BND_DEFAULT; ScrollBar() { box.size = Vec(BND_SCROLLBAR_WIDTH, BND_SCROLLBAR_HEIGHT); } void draw(NVGcontext *vg); void move(float delta); void onDragStart(); void onDragMove(Vec mouseRel); void onDragEnd(); }; // Handles a container with scrollbars struct ScrollWidget : OpaqueWidget { Widget *container; ScrollBar *hScrollBar; ScrollBar *vScrollBar; ScrollWidget(); void step(); void draw(NVGcontext *vg); Widget *onScroll(Vec pos, Vec scrollRel); }; struct Tooltip : Widget { void step(); void draw(NVGcontext *vg); }; //////////////////// // module //////////////////// // A 1U module should be 15x380. Thus the width of a module should be a factor of 15. struct Model; 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; // int moduleId; std::vector inputs; std::vector outputs; std::vector params; ModuleWidget(Module *module); ~ModuleWidget(); // Convenience functions for adding special widgets (calls addChild()) void addInput(InputPort *input); void addOutput(OutputPort *output); void addParam(ParamWidget *param); json_t *toJson(); void fromJson(json_t *root); void disconnectPorts(); void resetParams(); void cloneParams(ModuleWidget *source); void draw(NVGcontext *vg); bool requested = false; Vec requestedPos; Vec dragPos; void onDragStart(); void onDragMove(Vec mouseRel); void onDragEnd(); void onMouseDown(int button); }; struct WireWidget : OpaqueWidget { OutputPort *outputPort = NULL; InputPort *inputPort = NULL; Wire *wire = NULL; NVGcolor color; WireWidget(); ~WireWidget(); void updateWire(); void draw(NVGcontext *vg); void drawOutputPlug(NVGcontext *vg); void drawInputPlug(NVGcontext *vg); }; struct RackWidget : OpaqueWidget { // Only put ModuleWidgets in here Widget *moduleContainer; // Only put WireWidgets in here Widget *wireContainer; WireWidget *activeWire = NULL; RackWidget(); ~RackWidget(); void clear(); void savePatch(std::string filename); void loadPatch(std::string filename); json_t *toJson(); void fromJson(json_t *root); void repositionModule(ModuleWidget *module); void step(); void draw(NVGcontext *vg); void onMouseDown(int button); }; struct ModulePanel : TransparentWidget { NVGcolor backgroundColor; NVGcolor highlightColor; std::string imageFilename; void draw(NVGcontext *vg); }; //////////////////// // params //////////////////// struct Light : TransparentWidget, SpriteWidget { NVGcolor color; void draw(NVGcontext *vg); }; // If you don't add these to your ModuleWidget, it will fall out of the RackWidget struct Screw : TransparentWidget, SpriteWidget { Screw(); }; struct ParamWidget : OpaqueWidget, QuantityWidget { Module *module = NULL; int paramId; json_t *toJson(); void fromJson(json_t *root); void onMouseDown(int button); void onChange(); }; struct Knob : ParamWidget, SpriteWidget { int minIndex, maxIndex, spriteCount; void step(); void onDragStart(); void onDragMove(Vec mouseRel); void onDragEnd(); }; struct Switch : ParamWidget, SpriteWidget { }; struct ToggleSwitch : virtual Switch { void onDragStart() { index = 1; } void onDragEnd() { index = 0; } void onDragDrop(Widget *origin) { if (origin != this) return; // Cycle through modes // e.g. a range of [0.0, 3.0] would have modes 0, 1, 2, and 3. float v = value + 1.0; setValue(v > maxValue ? minValue : v); } }; struct MomentarySwitch : virtual Switch { void onDragStart() { setValue(maxValue); index = 1; } void onDragEnd() { setValue(minValue); index = 0; } }; //////////////////// // ports //////////////////// struct Port : OpaqueWidget, SpriteWidget { Module *module = NULL; WireWidget *connectedWire = NULL; Port(); ~Port(); void disconnect(); int type; void drawGlow(NVGcontext *vg); void onMouseDown(int button); void onDragEnd(); }; struct InputPort : Port { int inputId; void draw(NVGcontext *vg); void onDragStart(); void onDragDrop(Widget *origin); }; struct OutputPort : Port { int outputId; void draw(NVGcontext *vg); void onDragStart(); void onDragDrop(Widget *origin); }; //////////////////// // scene //////////////////// struct Toolbar : OpaqueWidget { Slider *wireOpacitySlider; Slider *wireTensionSlider; Toolbar(); void draw(NVGcontext *vg); }; struct Scene : OpaqueWidget { Toolbar *toolbar; ScrollWidget *scrollWidget; Widget *overlay = NULL; Scene(); void setOverlay(Widget *w); void step(); }; } // namespace rack