| @@ -8,7 +8,7 @@ Licenses of **third-party libraries** are listed in [LICENSE-dist.txt](LICENSE-d | |||
| The **Component Library graphics** in the `res/ComponentLibrary` directory are copyright © 2018 [Grayscale](http://grayscale.info/) and licensed under [CC BY-NC 4.0](https://creativecommons.org/licenses/by-nc/4.0/). Commercial plugins must request a commercial license to use Component Library graphics by emailing contact@vcvrack.com. | |||
| The **Core panel graphics** in the `res/Core` directory are copyright © 2018 [Grayscale](http://grayscale.info/) and licensed under [CC BY-NC-ND 4.0](https://creativecommons.org/licenses/by-nc-nd/4.0/). You may not distribute modified adaptations. | |||
| The **Core panel graphics** in the `res/Core` directory are copyright © 2018 [Grayscale](http://grayscale.info/) and licensed under [CC BY-NC-ND 4.0](https://creativecommons.org/licenses/by-nc-nd/4.0/). You may not create derivative works. | |||
| The **VCV logo and icon** are copyright © 2017 Andrew Belt and may not be used in derivative works. | |||
| @@ -1,8 +1,8 @@ | |||
| #pragma once | |||
| #include "ui.hpp" | |||
| #include "app/AudioWidget.hpp" | |||
| #include "app/CircularShadow.hpp" | |||
| #include "app/common.hpp" | |||
| #include "app/Component.hpp" | |||
| #include "app/Knob.hpp" | |||
| #include "app/LedDisplay.hpp" | |||
| #include "app/LightWidget.hpp" | |||
| @@ -1,4 +1,5 @@ | |||
| #pragma once | |||
| #include "widgets/TransparentWidget.hpp" | |||
| #include "app/common.hpp" | |||
| @@ -1,17 +0,0 @@ | |||
| #pragma once | |||
| #include "app/common.hpp" | |||
| namespace rack { | |||
| struct Module; | |||
| /** A Widget that exists on a Panel and interacts with a Module */ | |||
| struct Component : OpaqueWidget { | |||
| Module *module = NULL; | |||
| }; | |||
| } // namespace rack | |||
| @@ -6,17 +6,40 @@ | |||
| namespace rack { | |||
| static const float KNOB_SENSITIVITY = 0.0015f; | |||
| /** Implements vertical dragging behavior for ParamWidgets */ | |||
| struct Knob : ParamWidget { | |||
| /** Snap to nearest integer while dragging */ | |||
| bool snap = false; | |||
| /** Multiplier for mouse movement to adjust knob value */ | |||
| float speed = 1.0; | |||
| float dragValue; | |||
| Knob(); | |||
| void onDragStart(event::DragStart &e) override; | |||
| void onDragMove(event::DragMove &e) override; | |||
| void onDragEnd(event::DragEnd &e) override; | |||
| void onDragStart(event::DragStart &e) override { | |||
| windowCursorLock(); | |||
| } | |||
| void onDragEnd(event::DragEnd &e) override { | |||
| windowCursorUnlock(); | |||
| } | |||
| void onDragMove(event::DragMove &e) override { | |||
| if (quantity) { | |||
| float range; | |||
| if (quantity->isBounded()) { | |||
| range = quantity->getRange(); | |||
| } | |||
| else { | |||
| // Continuous encoders scale as if their limits are +/-1 | |||
| range = 2.f; | |||
| } | |||
| float delta = KNOB_SENSITIVITY * -e.mouseDelta.y * speed * range; | |||
| // Drag slower if Mod is held | |||
| if (windowIsModPressed()) | |||
| delta /= 16.f; | |||
| quantity->moveValue(delta); | |||
| } | |||
| } | |||
| }; | |||
| @@ -1,11 +1,14 @@ | |||
| #pragma once | |||
| #include "widgets/Widget.hpp" | |||
| #include "widgets/TransparentWidget.hpp" | |||
| #include "ui/TextField.hpp" | |||
| #include "app/common.hpp" | |||
| namespace rack { | |||
| struct LedDisplay : virtual Widget { | |||
| struct LedDisplay : VirtualWidget { | |||
| void draw(NVGcontext *vg) override; | |||
| }; | |||
| @@ -1,4 +1,5 @@ | |||
| #pragma once | |||
| #include "widgets/TransparentWidget.hpp" | |||
| #include "app/common.hpp" | |||
| @@ -1,5 +1,10 @@ | |||
| #pragma once | |||
| #include "widgets/OpaqueWidget.hpp" | |||
| #include "ui/Menu.hpp" | |||
| #include "app/common.hpp" | |||
| #include "app/SVGPanel.hpp" | |||
| #include "app/Port.hpp" | |||
| #include "app/ParamWidget.hpp" | |||
| #include "plugin.hpp" | |||
| #include "engine.hpp" | |||
| @@ -0,0 +1,57 @@ | |||
| #pragma once | |||
| #include "ui/Quantity.hpp" | |||
| #include "engine.hpp" | |||
| namespace rack { | |||
| /** A Quantity that wraps an engine Param */ | |||
| struct ParamQuantity : Quantity { | |||
| Module *module = NULL; | |||
| int paramId = 0; | |||
| /** Use engine smoothing of Param values */ | |||
| bool smooth = false; | |||
| /** Snap to the nearest integer */ | |||
| bool snap = false; | |||
| float snapValue = 0.f; | |||
| Param *getParam() { | |||
| assert(module); | |||
| return &module->params[paramId]; | |||
| } | |||
| void commitSnap() { | |||
| // TODO | |||
| } | |||
| void setValue(float value) override { | |||
| // TODO Smooth | |||
| // TODO Snap | |||
| getParam()->value = value; | |||
| } | |||
| float getValue() override { | |||
| return getParam()->value; | |||
| } | |||
| float getMinValue() override { | |||
| return getParam()->minValue; | |||
| } | |||
| float getMaxValue() override { | |||
| return getParam()->maxValue; | |||
| } | |||
| float getDefaultValue() override { | |||
| return getParam()->defaultValue; | |||
| } | |||
| std::string getLabel() override { | |||
| return getParam()->label; | |||
| } | |||
| std::string getUnit() override { | |||
| return getParam()->unit; | |||
| } | |||
| int getPrecision() override { | |||
| return getParam()->precision; | |||
| } | |||
| }; | |||
| } // namespace rack | |||
| @@ -1,4 +1,6 @@ | |||
| #pragma once | |||
| #include "widgets/OpaqueWidget.hpp" | |||
| #include "app/ParamQuantity.hpp" | |||
| #include "app/common.hpp" | |||
| #include "engine.hpp" | |||
| @@ -6,22 +8,23 @@ | |||
| namespace rack { | |||
| /** A Component which has control over a Param */ | |||
| struct ParamWidget : Component, QuantityWidget { | |||
| int paramId; | |||
| /** Used to momentarily disable value randomization | |||
| To permanently disable or change randomization behavior, override the randomize() method instead of changing this. | |||
| */ | |||
| bool randomizable = true; | |||
| /** Apply per-sample smoothing in the engine */ | |||
| bool smooth = false; | |||
| /** Controls a ParamQuantity */ | |||
| struct ParamWidget : OpaqueWidget { | |||
| ParamQuantity *quantity; | |||
| json_t *toJson(); | |||
| ParamWidget() { | |||
| quantity = new ParamQuantity; | |||
| } | |||
| ~ParamWidget() { | |||
| delete quantity; | |||
| } | |||
| /** For legacy patch loading */ | |||
| void fromJson(json_t *rootJ); | |||
| virtual void reset(); | |||
| virtual void randomize(); | |||
| void onButton(event::Button &e) override; | |||
| void onChange(event::Change &e) override; | |||
| }; | |||
| @@ -1,11 +1,12 @@ | |||
| #pragma once | |||
| #include "widgets/Widget.hpp" | |||
| #include "app/common.hpp" | |||
| namespace rack { | |||
| struct PluginManagerWidget : virtual Widget { | |||
| struct PluginManagerWidget : VirtualWidget { | |||
| Widget *loginWidget; | |||
| Widget *manageWidget; | |||
| Widget *downloadWidget; | |||
| @@ -1,17 +1,21 @@ | |||
| #pragma once | |||
| #include "widgets/OpaqueWidget.hpp" | |||
| #include "app/common.hpp" | |||
| #include "app/MultiLightWidget.hpp" | |||
| namespace rack { | |||
| struct Port : Component { | |||
| struct Port : OpaqueWidget { | |||
| Module *module = NULL; | |||
| int portId; | |||
| enum PortType { | |||
| INPUT, | |||
| OUTPUT | |||
| }; | |||
| PortType type = INPUT; | |||
| int portId; | |||
| MultiLightWidget *plugLight; | |||
| Port(); | |||
| @@ -1,4 +1,5 @@ | |||
| #pragma once | |||
| #include "widgets/TransparentWidget.hpp" | |||
| #include "app/common.hpp" | |||
| @@ -1,10 +1,15 @@ | |||
| #pragma once | |||
| #include "ui/Scene.hpp" | |||
| #include "app/common.hpp" | |||
| namespace rack { | |||
| struct ScrollWidget; | |||
| struct ZoomWidget; | |||
| struct RackScene : Scene { | |||
| ScrollWidget *scrollWidget; | |||
| ZoomWidget *zoomWidget; | |||
| @@ -1,4 +1,5 @@ | |||
| #pragma once | |||
| #include "ui/ScrollWidget.hpp" | |||
| #include "app/common.hpp" | |||
| @@ -9,7 +9,7 @@ namespace rack { | |||
| /** A Component with a default (up) and active (down) state when clicked. | |||
| Does not modify a Param, simply calls onAction() of a subclass. | |||
| */ | |||
| struct SVGButton : Component, FramebufferWidget { | |||
| struct SVGButton : FramebufferWidget { | |||
| std::shared_ptr<SVG> defaultSVG; | |||
| std::shared_ptr<SVG> activeSVG; | |||
| SVGWidget *sw; | |||
| @@ -1,6 +1,7 @@ | |||
| #pragma once | |||
| #include "app/common.hpp" | |||
| #include "app/CircularShadow.hpp" | |||
| #include "widgets/TransformWidget.hpp" | |||
| namespace rack { | |||
| @@ -1,5 +1,7 @@ | |||
| #pragma once | |||
| #include "app/common.hpp" | |||
| #include "widgets/FramebufferWidget.hpp" | |||
| #include "widgets/SVGWidget.hpp" | |||
| namespace rack { | |||
| @@ -1,4 +1,7 @@ | |||
| #pragma once | |||
| #include "widgets/OpaqueWidget.hpp" | |||
| #include "ui/Slider.hpp" | |||
| #include "ui/RadioButton.hpp" | |||
| #include "app/common.hpp" | |||
| @@ -6,10 +9,9 @@ namespace rack { | |||
| struct Toolbar : OpaqueWidget { | |||
| Slider *wireOpacitySlider; | |||
| Slider *wireTensionSlider; | |||
| Slider *zoomSlider; | |||
| RadioButton *cpuUsageButton; | |||
| // TODO Move these to future Rack app state | |||
| float wireOpacity = 0.5; | |||
| float wireTension = 0.5; | |||
| Toolbar(); | |||
| void draw(NVGcontext *vg) override; | |||
| @@ -1,5 +1,6 @@ | |||
| #pragma once | |||
| #include "ui.hpp" | |||
| #include "math.hpp" | |||
| #include <string> | |||
| #include <jansson.h> | |||
| @@ -37,11 +38,4 @@ static const std::string PRESET_FILTERS = "VCV Rack module preset (.vcvm):vcvm"; | |||
| static const std::string PATCH_FILTERS = "VCV Rack patch (.vcv):vcv"; | |||
| /** Deprecated. Will be removed in v1 */ | |||
| json_t *colorToJson(NVGcolor color); | |||
| /** Deprecated. Will be removed in v1 */ | |||
| NVGcolor jsonToColor(json_t *colorJ); | |||
| } // namespace rack | |||
| @@ -59,8 +59,9 @@ struct RoundHugeBlackKnob : RoundKnob { | |||
| struct RoundBlackSnapKnob : RoundBlackKnob { | |||
| RoundBlackSnapKnob() { | |||
| snap = true; | |||
| smooth = false; | |||
| // TODO | |||
| // quantity.snap = true; | |||
| // quantity.smooth = false; | |||
| } | |||
| }; | |||
| @@ -308,8 +309,9 @@ struct BefacoBigKnob : SVGKnob { | |||
| struct BefacoBigSnapKnob : BefacoBigKnob { | |||
| BefacoBigSnapKnob() { | |||
| snap = true; | |||
| smooth = false; | |||
| // TODO | |||
| // quantity.snap = true; | |||
| // quantity.smooth = false; | |||
| } | |||
| }; | |||
| @@ -8,7 +8,27 @@ namespace rack { | |||
| struct Param { | |||
| float value = 0.0; | |||
| float value = 0.f; | |||
| float minValue = 0.f; | |||
| float maxValue = 1.f; | |||
| float defaultValue = 0.f; | |||
| std::string label; | |||
| std::string unit; | |||
| int precision = 2; | |||
| // TODO Change this horrible method name | |||
| void setup(float minValue, float maxValue, float defaultValue, std::string label = "", std::string unit = "", int precision = 2) { | |||
| this->value = defaultValue; | |||
| this->minValue = minValue; | |||
| this->maxValue = maxValue; | |||
| this->defaultValue = defaultValue; | |||
| this->label = label; | |||
| this->unit = unit; | |||
| this->precision = precision; | |||
| } | |||
| json_t *toJson(); | |||
| void fromJson(json_t *rootJ); | |||
| }; | |||
| @@ -46,8 +46,8 @@ template <class TParamWidget> | |||
| TParamWidget *createParam(Vec pos, Module *module, int paramId, float minValue, float maxValue, float defaultValue) { | |||
| TParamWidget *o = new TParamWidget; | |||
| o->box.pos = pos; | |||
| o->module = module; | |||
| o->paramId = paramId; | |||
| o->quantity->module = module; | |||
| o->quantity->paramId = paramId; | |||
| o->setLimits(minValue, maxValue); | |||
| o->setDefaultValue(defaultValue); | |||
| return o; | |||
| @@ -57,8 +57,8 @@ template <class TParamWidget> | |||
| TParamWidget *createParamCentered(Vec pos, Module *module, int paramId, float minValue, float maxValue, float defaultValue) { | |||
| TParamWidget *o = new TParamWidget; | |||
| o->box.pos = pos.minus(o->box.size.div(2)); | |||
| o->module = module; | |||
| o->paramId = paramId; | |||
| o->quantity->module = module; | |||
| o->quantity->paramId = paramId; | |||
| o->setLimits(minValue, maxValue); | |||
| o->setDefaultValue(defaultValue); | |||
| return o; | |||
| @@ -244,6 +244,12 @@ struct Rect { | |||
| bool isEqual(Rect r) const { | |||
| return pos.isEqual(r.pos) && size.isEqual(r.size); | |||
| } | |||
| float getLeft() const { | |||
| return pos.x + size.x; | |||
| } | |||
| float getBottom() const { | |||
| return pos.y + size.y; | |||
| } | |||
| Vec getCenter() const { | |||
| return pos.plus(size.mult(0.5f)); | |||
| } | |||
| @@ -22,6 +22,9 @@ bool endsWith(std::string str, std::string suffix); | |||
| /** Extracts portions of a path */ | |||
| std::string directory(std::string path); | |||
| std::string filename(std::string path); | |||
| /** Extracts the portion of a path without the extension */ | |||
| std::string basename(std::string path); | |||
| /** Extracts the extension of a path */ | |||
| std::string extension(std::string path); | |||
| struct CaseInsensitiveCompare { | |||
| @@ -1,5 +1,5 @@ | |||
| #pragma once | |||
| #include <vector> | |||
| #include <list> | |||
| #include "common.hpp" | |||
| @@ -7,7 +7,7 @@ namespace rack { | |||
| namespace system { | |||
| std::vector<std::string> listEntries(std::string path); | |||
| std::list<std::string> listEntries(std::string path); | |||
| bool isFile(std::string path); | |||
| bool isDirectory(std::string path); | |||
| void copyFile(std::string srcPath, std::string destPath); | |||
| @@ -1,6 +1,6 @@ | |||
| #pragma once | |||
| #include "widgets.hpp" | |||
| #include "ui/common.hpp" | |||
| #include "ui/QuantityWidget.hpp" | |||
| #include "ui/SequentialLayout.hpp" | |||
| #include "ui/Label.hpp" | |||
| #include "ui/List.hpp" | |||
| @@ -1,5 +1,7 @@ | |||
| #pragma once | |||
| #include "widgets/OpaqueWidget.hpp" | |||
| #include "ui/common.hpp" | |||
| #include "ui/Quantity.hpp" | |||
| namespace rack { | |||
| @@ -8,11 +10,18 @@ namespace rack { | |||
| struct Button : OpaqueWidget { | |||
| std::string text; | |||
| BNDwidgetState state = BND_DEFAULT; | |||
| /** Optional, owned. Tracks the pressed state of the button.*/ | |||
| Quantity *quantity = NULL; | |||
| Button() { | |||
| box.size.y = BND_WIDGET_HEIGHT; | |||
| } | |||
| ~Button() { | |||
| if (quantity) | |||
| delete quantity; | |||
| } | |||
| void draw(NVGcontext *vg) override { | |||
| bndToolButton(vg, 0.0, 0.0, box.size.x, box.size.y, BND_CORNER_NONE, state, -1, text.c_str()); | |||
| Widget::draw(vg); | |||
| @@ -28,10 +37,14 @@ struct Button : OpaqueWidget { | |||
| void onDragStart(event::DragStart &e) override { | |||
| state = BND_ACTIVE; | |||
| if (quantity) | |||
| quantity->setMax(); | |||
| } | |||
| void onDragEnd(event::DragEnd &e) override { | |||
| state = BND_HOVER; | |||
| if (quantity) | |||
| quantity->setMin(); | |||
| } | |||
| void onDragDrop(event::DragDrop &e) override { | |||
| @@ -1,4 +1,5 @@ | |||
| #pragma once | |||
| #include "ui/common.hpp" | |||
| #include "ui/Button.hpp" | |||
| @@ -1,5 +1,6 @@ | |||
| #pragma once | |||
| #include "ui/common.hpp" | |||
| #include "ui/Button.hpp" | |||
| namespace rack { | |||
| @@ -1,11 +1,12 @@ | |||
| #pragma once | |||
| #include "widgets/Widget.hpp" | |||
| #include "ui/common.hpp" | |||
| namespace rack { | |||
| struct Label : virtual Widget { | |||
| struct Label : VirtualWidget { | |||
| std::string text; | |||
| float fontSize; | |||
| NVGcolor color; | |||
| @@ -1,4 +1,5 @@ | |||
| #pragma once | |||
| #include "widgets/OpaqueWidget.hpp" | |||
| #include "ui/common.hpp" | |||
| @@ -1,4 +1,6 @@ | |||
| #pragma once | |||
| #include "widgets/OpaqueWidget.hpp" | |||
| #include "ui/common.hpp" | |||
| #include "ui/MenuEntry.hpp" | |||
| @@ -1,4 +1,5 @@ | |||
| #pragma once | |||
| #include "widgets/OpaqueWidget.hpp" | |||
| #include "ui/common.hpp" | |||
| @@ -1,6 +1,6 @@ | |||
| #pragma once | |||
| #include "ui/common.hpp" | |||
| #include "ui/MenuOverlay.hpp" | |||
| #include "ui/MenuEntry.hpp" | |||
| namespace rack { | |||
| @@ -1,5 +1,6 @@ | |||
| #pragma once | |||
| #include "ui/common.hpp" | |||
| #include "ui/MenuEntry.hpp" | |||
| namespace rack { | |||
| @@ -1,4 +1,5 @@ | |||
| #pragma once | |||
| #include "widgets/OpaqueWidget.hpp" | |||
| #include "ui/common.hpp" | |||
| @@ -1,5 +1,6 @@ | |||
| #pragma once | |||
| #include "ui/common.hpp" | |||
| #include "ui/MenuEntry.hpp" | |||
| namespace rack { | |||
| @@ -1,19 +1,26 @@ | |||
| #pragma once | |||
| #include "ui/common.hpp" | |||
| #include "ui/QuantityWidget.hpp" | |||
| namespace rack { | |||
| struct ProgressBar : QuantityWidget { | |||
| struct ProgressBar : VirtualWidget { | |||
| Quantity *quantity = NULL; | |||
| ProgressBar() { | |||
| box.size.y = BND_WIDGET_HEIGHT; | |||
| } | |||
| ~ProgressBar() { | |||
| if (quantity) | |||
| delete quantity; | |||
| } | |||
| void draw(NVGcontext *vg) override { | |||
| float progress = rescale(value, minValue, maxValue, 0.0, 1.0); | |||
| bndSlider(vg, 0.0, 0.0, box.size.x, box.size.y, BND_CORNER_ALL, BND_DEFAULT, progress, getText().c_str(), NULL); | |||
| float progress = quantity ? quantity->getScaledValue() : 0.f; | |||
| std::string text = quantity ? quantity->getString() : ""; | |||
| bndSlider(vg, 0.0, 0.0, box.size.x, box.size.y, BND_CORNER_ALL, BND_DEFAULT, progress, text.c_str(), NULL); | |||
| } | |||
| }; | |||
| @@ -0,0 +1,128 @@ | |||
| #pragma once | |||
| #include "string.hpp" | |||
| namespace rack { | |||
| /** A controller for manipulating a float value (which subclasses must store somehow) with limits and labels | |||
| Often used as a decorator component for Widgets that read or write a quantity. | |||
| */ | |||
| struct Quantity { | |||
| virtual ~Quantity() {} | |||
| /** Sets the value directly | |||
| Override this to change the state of your subclass to represent the new value. | |||
| */ | |||
| virtual void setValue(float value) {} | |||
| /** Returns the value | |||
| Override this to return the state of your subclass. | |||
| */ | |||
| virtual float getValue() {return 0.f;} | |||
| /** Returns the minimum allowed value */ | |||
| virtual float getMinValue() {return 0.f;} | |||
| /** Returns the maximum allowed value */ | |||
| virtual float getMaxValue() {return 1.f;} | |||
| /** Returns the default value, for resetting */ | |||
| virtual float getDefaultValue() {return 0.f;} | |||
| /** Returns the value, possibly transformed | |||
| Useful for logarithmic scaling, multiplying by 100 for percentages, etc. | |||
| */ | |||
| virtual float getDisplayValue() {return getValue();} | |||
| /** Sets the value by the transformed display value */ | |||
| virtual void setDisplayValue(float displayValue) {setValue(displayValue);} | |||
| /** The number of decimal places for display | |||
| A precision of 2 will display as "1.00" for example. | |||
| */ | |||
| virtual int getPrecision() {return 2;} | |||
| /** Returns a string representation of the display value */ | |||
| virtual std::string getDisplayValueString() { | |||
| return string::f("%.*f", getPrecision(), getDisplayValue()); | |||
| } | |||
| /** The name of the quantity */ | |||
| virtual std::string getLabel() {return "";} | |||
| /** The unit abbreviation of the quantity | |||
| Include an initial space character if you want a space after the number, e.g. "440 Hz". This allows space-less units, like "100%". | |||
| */ | |||
| virtual std::string getUnit() {return "";} | |||
| /** Returns a string representation of the quantity */ | |||
| virtual std::string getString() { | |||
| std::string s; | |||
| std::string label = getLabel(); | |||
| if (!label.empty()) | |||
| s += label + ": "; | |||
| s += getDisplayValueString() + getUnit(); | |||
| return s; | |||
| } | |||
| // Helper methods | |||
| /** Resets the value to the default value */ | |||
| void reset() { | |||
| setValue(getDefaultValue()); | |||
| } | |||
| /** Checks whether the value is at the min value */ | |||
| bool isMin() { | |||
| return getValue() <= getMinValue(); | |||
| } | |||
| /** Checks whether the value is at the max value */ | |||
| bool isMax() { | |||
| return getValue() >= getMaxValue(); | |||
| } | |||
| /** Sets the value to the min value */ | |||
| void setMin() { | |||
| setValue(getMinValue()); | |||
| } | |||
| /** Sets the value to the max value */ | |||
| void setMax() { | |||
| setValue(getMaxValue()); | |||
| } | |||
| /** Sets value from the range 0 to 1 */ | |||
| void setScaledValue(float scaledValue) { | |||
| setValue(rescale(scaledValue, 0.f, 1.f, getMinValue(), getMaxValue())); | |||
| } | |||
| /** Returns the value rescaled to the range 0 to 1 */ | |||
| float getScaledValue() { | |||
| return rescale(getValue(), getMinValue(), getMaxValue(), 0.f, 1.f); | |||
| } | |||
| /** The difference between the max and min values */ | |||
| float getRange() { | |||
| return getMaxValue() - getMinValue(); | |||
| } | |||
| /** Checks whether the bounds are finite */ | |||
| bool isBounded() { | |||
| return std::isfinite(getMinValue()) && std::isfinite(getMaxValue()); | |||
| } | |||
| /** Adds an amount to the value */ | |||
| void moveValue(float deltaValue) { | |||
| setValue(getValue() + deltaValue); | |||
| } | |||
| /** Adds an amount to the value scaled to the range 0 to 1 */ | |||
| void moveScaledValue(float deltaScaledValue) { | |||
| moveValue(deltaScaledValue * getRange()); | |||
| } | |||
| }; | |||
| } // namespace rack | |||
| @@ -1,49 +0,0 @@ | |||
| #pragma once | |||
| #include "ui/common.hpp" | |||
| namespace rack { | |||
| /** A Widget representing a float value */ | |||
| 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 decimal place to round for displaying values. | |||
| A precision of 2 will display as "1.00" for example. | |||
| */ | |||
| int precision = 2; | |||
| void reset() { | |||
| setValue(defaultValue); | |||
| } | |||
| void setValue(float value) { | |||
| this->value = clampBetween(value, minValue, maxValue); | |||
| event::Change e; | |||
| onChange(e); | |||
| } | |||
| void setLimits(float minValue, float maxValue) { | |||
| this->minValue = minValue; | |||
| this->maxValue = maxValue; | |||
| setValue(value); | |||
| } | |||
| void setDefaultValue(float defaultValue) { | |||
| this->defaultValue = defaultValue; | |||
| } | |||
| /** Generates the display value */ | |||
| std::string getText() { | |||
| return string::f("%s: %.*f%s", label.c_str(), precision, value, unit.c_str()); | |||
| } | |||
| }; | |||
| } // namespace rack | |||
| @@ -1,36 +1,53 @@ | |||
| #pragma once | |||
| #include "widgets/OpaqueWidget.hpp" | |||
| #include "ui/common.hpp" | |||
| #include "ui/QuantityWidget.hpp" | |||
| namespace rack { | |||
| struct RadioButton : OpaqueWidget, QuantityWidget { | |||
| struct RadioButton : OpaqueWidget { | |||
| BNDwidgetState state = BND_DEFAULT; | |||
| Quantity *quantity = NULL; | |||
| RadioButton() { | |||
| box.size.y = BND_WIDGET_HEIGHT; | |||
| } | |||
| ~RadioButton() { | |||
| if (quantity) | |||
| delete quantity; | |||
| } | |||
| void draw(NVGcontext *vg) override { | |||
| bndRadioButton(vg, 0.0, 0.0, box.size.x, box.size.y, BND_CORNER_NONE, value == 0.0 ? state : BND_ACTIVE, -1, label.c_str()); | |||
| std::string label; | |||
| if (quantity) | |||
| label = quantity->getLabel(); | |||
| bndRadioButton(vg, 0.0, 0.0, box.size.x, box.size.y, BND_CORNER_NONE, state, -1, label.c_str()); | |||
| } | |||
| void onEnter(event::Enter &e) override { | |||
| state = BND_HOVER; | |||
| if (state != BND_ACTIVE) | |||
| state = BND_HOVER; | |||
| } | |||
| void onLeave(event::Leave &e) override { | |||
| state = BND_DEFAULT; | |||
| if (state != BND_ACTIVE) | |||
| state = BND_DEFAULT; | |||
| } | |||
| void onDragDrop(event::DragDrop &e) override { | |||
| if (e.origin == this) { | |||
| if (value) | |||
| setValue(0.0); | |||
| else | |||
| setValue(1.0); | |||
| if (state == BND_ACTIVE) { | |||
| state = BND_HOVER; | |||
| if (quantity) | |||
| quantity->setMin(); | |||
| } | |||
| else { | |||
| state = BND_ACTIVE; | |||
| if (quantity) | |||
| quantity->setMax(); | |||
| } | |||
| event::Action eAction; | |||
| onAction(eAction); | |||
| @@ -1,4 +1,5 @@ | |||
| #pragma once | |||
| #include "widgets/OpaqueWidget.hpp" | |||
| #include "ui/common.hpp" | |||
| @@ -1,4 +1,5 @@ | |||
| #pragma once | |||
| #include "widgets/OpaqueWidget.hpp" | |||
| #include "ui/common.hpp" | |||
| #include "event.hpp" | |||
| @@ -1,4 +1,5 @@ | |||
| #pragma once | |||
| #include "widgets/Widget.hpp" | |||
| #include "ui/common.hpp" | |||
| @@ -6,7 +7,7 @@ namespace rack { | |||
| /** Positions children in a row/column based on their widths/heights */ | |||
| struct SequentialLayout : virtual Widget { | |||
| struct SequentialLayout : VirtualWidget { | |||
| enum Orientation { | |||
| HORIZONTAL_ORIENTATION, | |||
| VERTICAL_ORIENTATION, | |||
| @@ -1,6 +1,7 @@ | |||
| #pragma once | |||
| #include "widgets/OpaqueWidget.hpp" | |||
| #include "ui/Quantity.hpp" | |||
| #include "ui/common.hpp" | |||
| #include "ui/QuantityWidget.hpp" | |||
| namespace rack { | |||
| @@ -9,16 +10,23 @@ namespace rack { | |||
| static const float SLIDER_SENSITIVITY = 0.001f; | |||
| struct Slider : OpaqueWidget, QuantityWidget { | |||
| struct Slider : OpaqueWidget { | |||
| BNDwidgetState state = BND_DEFAULT; | |||
| Quantity *quantity = NULL; | |||
| Slider() { | |||
| box.size.y = BND_WIDGET_HEIGHT; | |||
| } | |||
| ~Slider() { | |||
| if (quantity) | |||
| delete quantity; | |||
| } | |||
| void draw(NVGcontext *vg) override { | |||
| float progress = rescale(value, minValue, maxValue, 0.0, 1.0); | |||
| bndSlider(vg, 0.0, 0.0, box.size.x, box.size.y, BND_CORNER_NONE, state, progress, getText().c_str(), NULL); | |||
| float progress = quantity ? quantity->getScaledValue() : 0.f; | |||
| std::string text = quantity ? quantity->getString() : ""; | |||
| bndSlider(vg, 0.0, 0.0, box.size.x, box.size.y, BND_CORNER_NONE, state, progress, text.c_str(), NULL); | |||
| } | |||
| void onDragStart(event::DragStart &e) override { | |||
| @@ -27,7 +35,9 @@ struct Slider : OpaqueWidget, QuantityWidget { | |||
| } | |||
| void onDragMove(event::DragMove &e) override { | |||
| setValue(value + SLIDER_SENSITIVITY * (maxValue - minValue) * e.mouseDelta.x); | |||
| if (quantity) { | |||
| quantity->moveScaledValue(SLIDER_SENSITIVITY * e.mouseDelta.x); | |||
| } | |||
| } | |||
| void onDragEnd(event::DragEnd &e) override { | |||
| @@ -37,7 +47,8 @@ struct Slider : OpaqueWidget, QuantityWidget { | |||
| void onButton(event::Button &e) override { | |||
| if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_RIGHT) { | |||
| setValue(defaultValue); | |||
| if (quantity) | |||
| quantity->reset(); | |||
| } | |||
| e.target = this; | |||
| } | |||
| @@ -1,4 +1,5 @@ | |||
| #pragma once | |||
| #include "widgets/OpaqueWidget.hpp" | |||
| #include "ui/common.hpp" | |||
| #include "event.hpp" | |||
| @@ -1,11 +1,12 @@ | |||
| #pragma once | |||
| #include "widgets/Widget.hpp" | |||
| #include "ui/common.hpp" | |||
| namespace rack { | |||
| struct Tooltip : virtual Widget { | |||
| struct Tooltip : VirtualWidget { | |||
| std::string text; | |||
| void draw(NVGcontext *vg) override { | |||
| @@ -1,4 +1,5 @@ | |||
| #pragma once | |||
| #include "widgets/TransparentWidget.hpp" | |||
| #include "ui/common.hpp" | |||
| @@ -1,4 +1,5 @@ | |||
| #pragma once | |||
| #include "widgets/OpaqueWidget.hpp" | |||
| #include "ui/common.hpp" | |||
| @@ -1,4 +1,5 @@ | |||
| #pragma once | |||
| #include "widgets/OpaqueWidget.hpp" | |||
| #include "ui/common.hpp" | |||
| @@ -1,5 +1,4 @@ | |||
| #pragma once | |||
| #include "widgets.hpp" | |||
| #include "blendish.h" | |||
| #define CHECKMARK_STRING "✔" | |||
| @@ -9,7 +9,7 @@ namespace rack { | |||
| When `dirty` is true, its children will be re-rendered on the next call to step() override. | |||
| Events are not passed to the underlying scene. | |||
| */ | |||
| struct FramebufferWidget : virtual Widget { | |||
| struct FramebufferWidget : VirtualWidget { | |||
| /** Set this to true to re-render the children to the framebuffer the next time it is drawn */ | |||
| bool dirty = true; | |||
| /** A margin in pixels around the children in the framebuffer | |||
| @@ -9,7 +9,7 @@ namespace rack { | |||
| You can of course override the events. | |||
| You may also call OpaqueWidget::on*() from the overridden method to continue recursing/consuming the event. | |||
| */ | |||
| struct OpaqueWidget : virtual Widget { | |||
| struct OpaqueWidget : VirtualWidget { | |||
| void onHover(event::Hover &e) override { | |||
| Widget::onHover(e); | |||
| if (!e.target) | |||
| @@ -6,7 +6,7 @@ namespace rack { | |||
| /** Draws an SVG */ | |||
| struct SVGWidget : virtual Widget { | |||
| struct SVGWidget : VirtualWidget { | |||
| std::shared_ptr<SVG> svg; | |||
| /** Sets the box size to the svg image size */ | |||
| @@ -6,7 +6,7 @@ namespace rack { | |||
| /** Transforms appearance only, not positions of events */ | |||
| struct TransformWidget : virtual Widget { | |||
| struct TransformWidget : VirtualWidget { | |||
| /** The transformation matrix */ | |||
| float transform[6]; | |||
| @@ -6,7 +6,7 @@ namespace rack { | |||
| /** Widget that does not respond to events and does not pass events to children */ | |||
| struct TransparentWidget : virtual Widget { | |||
| struct TransparentWidget : VirtualWidget { | |||
| /** Override behavior to do nothing instead. */ | |||
| void onHover(event::Hover &e) override {} | |||
| void onButton(event::Button &e) override {} | |||
| @@ -12,7 +12,7 @@ namespace rack { | |||
| /** A node in the 2D scene graph | |||
| It is recommended to inherit virtually from Widget instead of directly. | |||
| e.g. `struct MyWidget : virtual Widget {}` | |||
| e.g. `struct MyWidget : VirtualWidget {}` | |||
| */ | |||
| struct Widget { | |||
| /** Stores position and size */ | |||
| @@ -125,4 +125,10 @@ struct Widget { | |||
| }; | |||
| /** Inherit from this class instead of inheriting from Widget directly. | |||
| Allows multiple inheritance in the class hierarchy. | |||
| */ | |||
| struct VirtualWidget : virtual Widget {}; | |||
| } // namespace rack | |||
| @@ -5,7 +5,7 @@ | |||
| namespace rack { | |||
| struct ZoomWidget : virtual Widget { | |||
| struct ZoomWidget : VirtualWidget { | |||
| float zoom = 1.f; | |||
| Vec getRelativeOffset(Vec v, Widget *relative) override { | |||
| @@ -1,4 +1,4 @@ | |||
| #include "app.hpp" | |||
| #include "app/AudioWidget.hpp" | |||
| #include "audio.hpp" | |||
| #include "helpers.hpp" | |||
| @@ -1,52 +0,0 @@ | |||
| #include "app.hpp" | |||
| #include "window.hpp" | |||
| #include "engine.hpp" | |||
| // For GLFW_KEY_LEFT_CONTROL, etc. | |||
| #include <GLFW/glfw3.h> | |||
| namespace rack { | |||
| static const float KNOB_SENSITIVITY = 0.0015f; | |||
| Knob::Knob() { | |||
| smooth = true; | |||
| } | |||
| void Knob::onDragStart(event::DragStart &e) { | |||
| windowCursorLock(); | |||
| dragValue = value; | |||
| randomizable = false; | |||
| } | |||
| void Knob::onDragMove(event::DragMove &e) { | |||
| float range; | |||
| if (std::isfinite(minValue) && std::isfinite(maxValue)) { | |||
| range = maxValue - minValue; | |||
| } | |||
| else { | |||
| // Continuous encoders scale as if their limits are +/-1 | |||
| range = 1.f - (-1.f); | |||
| } | |||
| float delta = KNOB_SENSITIVITY * -e.mouseDelta.y * speed * range; | |||
| // Drag slower if Mod is held | |||
| if (windowIsModPressed()) | |||
| delta /= 16.f; | |||
| dragValue += delta; | |||
| dragValue = clampBetween(dragValue, minValue, maxValue); | |||
| if (snap) | |||
| setValue(std::round(dragValue)); | |||
| else | |||
| setValue(dragValue); | |||
| } | |||
| void Knob::onDragEnd(event::DragEnd &e) { | |||
| windowCursorUnlock(); | |||
| randomizable = true; | |||
| } | |||
| } // namespace rack | |||
| @@ -44,9 +44,20 @@ static bool isModelMatch(Model *model, std::string search) { | |||
| } | |||
| struct FavoriteQuantity : Quantity { | |||
| std::string getString() override { | |||
| return "★"; | |||
| } | |||
| }; | |||
| struct FavoriteRadioButton : RadioButton { | |||
| Model *model = NULL; | |||
| FavoriteRadioButton() { | |||
| quantity = new FavoriteQuantity; | |||
| } | |||
| void onAction(event::Action &e) override; | |||
| }; | |||
| @@ -111,13 +122,12 @@ struct ModelItem : BrowserListItem { | |||
| FavoriteRadioButton *favoriteButton = createWidget<FavoriteRadioButton>(Vec(8, itemMargin)); | |||
| favoriteButton->box.size.x = 20; | |||
| favoriteButton->label = "★"; | |||
| addChild(favoriteButton); | |||
| // Set favorite button initial state | |||
| auto it = sFavoriteModels.find(model); | |||
| if (it != sFavoriteModels.end()) | |||
| favoriteButton->setValue(1); | |||
| favoriteButton->quantity->setValue(1); | |||
| favoriteButton->model = model; | |||
| Label *nameLabel = createWidget<Label>(favoriteButton->box.getTopRight()); | |||
| @@ -465,7 +475,7 @@ void ClearFilterItem::onAction(event::Action &e) { | |||
| void FavoriteRadioButton::onAction(event::Action &e) { | |||
| if (!model) | |||
| return; | |||
| if (value) { | |||
| if (quantity->isMax()) { | |||
| sFavoriteModels.insert(model); | |||
| } | |||
| else { | |||
| @@ -61,18 +61,20 @@ json_t *ModuleWidget::toJson() { | |||
| // plugin | |||
| json_object_set_new(rootJ, "plugin", json_string(model->plugin->slug.c_str())); | |||
| // version (of plugin) | |||
| // version of plugin | |||
| if (!model->plugin->version.empty()) | |||
| json_object_set_new(rootJ, "version", json_string(model->plugin->version.c_str())); | |||
| // model | |||
| json_object_set_new(rootJ, "model", json_string(model->slug.c_str())); | |||
| // params | |||
| json_t *paramsJ = json_array(); | |||
| for (ParamWidget *paramWidget : params) { | |||
| json_t *paramJ = paramWidget->toJson(); | |||
| json_array_append_new(paramsJ, paramJ); | |||
| if (module) { | |||
| json_t *paramsJ = json_array(); | |||
| for (Param ¶m : module->params) { | |||
| json_t *paramJ = param.toJson(); | |||
| json_array_append_new(paramsJ, paramJ); | |||
| } | |||
| json_object_set_new(rootJ, "params", paramsJ); | |||
| } | |||
| json_object_set_new(rootJ, "params", paramsJ); | |||
| // data | |||
| if (module) { | |||
| json_t *dataJ = module->toJson(); | |||
| @@ -126,28 +128,15 @@ void ModuleWidget::fromJson(json_t *rootJ) { | |||
| size_t i; | |||
| json_t *paramJ; | |||
| json_array_foreach(paramsJ, i, paramJ) { | |||
| if (legacy && legacy <= 1) { | |||
| // Legacy 1 mode | |||
| // The index in the array we're iterating is the index of the ParamWidget in the params vector. | |||
| if (i < params.size()) { | |||
| // Create upgraded version of param JSON object | |||
| json_t *newParamJ = json_object(); | |||
| json_object_set(newParamJ, "value", paramJ); | |||
| params[i]->fromJson(newParamJ); | |||
| json_decref(newParamJ); | |||
| } | |||
| uint32_t paramId = i; | |||
| // Get paramId | |||
| json_t *paramIdJ = json_object_get(paramJ, "paramId"); | |||
| if (paramIdJ) { | |||
| // Legacy v0.6.0 to <v1.0 | |||
| paramId = json_integer_value(paramIdJ); | |||
| } | |||
| else { | |||
| // Get paramId | |||
| json_t *paramIdJ = json_object_get(paramJ, "paramId"); | |||
| if (!paramIdJ) | |||
| continue; | |||
| int paramId = json_integer_value(paramIdJ); | |||
| // Find ParamWidget(s) with paramId | |||
| for (ParamWidget *paramWidget : params) { | |||
| if (paramWidget->paramId == paramId) | |||
| paramWidget->fromJson(paramJ); | |||
| } | |||
| if (paramId < module->params.size()) { | |||
| module->params[paramId].fromJson(paramJ); | |||
| } | |||
| } | |||
| @@ -5,11 +5,15 @@ namespace rack { | |||
| void MomentarySwitch::onDragStart(event::DragStart &e) { | |||
| setValue(maxValue); | |||
| if (quantity) { | |||
| quantity->setMax(); | |||
| } | |||
| } | |||
| void MomentarySwitch::onDragEnd(event::DragEnd &e) { | |||
| setValue(minValue); | |||
| if (quantity) { | |||
| quantity->setMin(); | |||
| } | |||
| } | |||
| @@ -1,4 +1,4 @@ | |||
| #include "app.hpp" | |||
| #include "app/ParamWidget.hpp" | |||
| #include "engine.hpp" | |||
| #include "random.hpp" | |||
| @@ -6,33 +6,28 @@ | |||
| namespace rack { | |||
| json_t *ParamWidget::toJson() { | |||
| json_t *rootJ = json_object(); | |||
| json_object_set_new(rootJ, "paramId", json_integer(paramId)); | |||
| // Infinite params should serialize to 0 | |||
| float v = (std::isfinite(minValue) && std::isfinite(maxValue)) ? value : 0.f; | |||
| json_object_set_new(rootJ, "value", json_real(v)); | |||
| return rootJ; | |||
| } | |||
| void ParamWidget::fromJson(json_t *rootJ) { | |||
| json_t *valueJ = json_object_get(rootJ, "value"); | |||
| if (valueJ) | |||
| setValue(json_number_value(valueJ)); | |||
| if (valueJ) { | |||
| if (quantity) | |||
| quantity->setValue(json_number_value(valueJ)); | |||
| } | |||
| } | |||
| void ParamWidget::reset() { | |||
| // Infinite params should not be reset | |||
| if (std::isfinite(minValue) && std::isfinite(maxValue)) { | |||
| setValue(defaultValue); | |||
| if (quantity) { | |||
| // Infinite params should not be reset | |||
| if (quantity->isBounded()) | |||
| quantity->reset(); | |||
| } | |||
| } | |||
| void ParamWidget::randomize() { | |||
| // Infinite params should not be randomized | |||
| if (randomizable && std::isfinite(minValue) && std::isfinite(maxValue)) { | |||
| setValue(rescale(random::uniform(), 0.f, 1.f, minValue, maxValue)); | |||
| if (quantity) { | |||
| // Infinite params should not be randomized | |||
| if (quantity->isBounded()) { | |||
| quantity->setScaledValue(random::uniform()); | |||
| } | |||
| } | |||
| } | |||
| @@ -40,20 +35,11 @@ void ParamWidget::onButton(event::Button &e) { | |||
| OpaqueWidget::onButton(e); | |||
| if (e.target == this) { | |||
| if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_RIGHT) { | |||
| reset(); | |||
| if (quantity) | |||
| quantity->reset(); | |||
| } | |||
| } | |||
| } | |||
| void ParamWidget::onChange(event::Change &e) { | |||
| if (!module) | |||
| return; | |||
| if (smooth) | |||
| engineSetParamSmooth(module, paramId, value); | |||
| else | |||
| engineSetParam(module, paramId, value); | |||
| } | |||
| } // namespace rack | |||
| @@ -103,13 +103,28 @@ struct LogOutButton : Button { | |||
| }; | |||
| struct DownloadQuantity : Quantity { | |||
| float getValue() override { | |||
| return pluginGetDownloadProgress(); | |||
| } | |||
| float getDisplayValue() override { | |||
| return getValue() * 100.f; | |||
| } | |||
| int getPrecision() override {return 0;} | |||
| std::string getLabel() override { | |||
| return "Downloading " + pluginGetDownloadName(); | |||
| } | |||
| std::string getUnit() override {return "%";} | |||
| }; | |||
| struct DownloadProgressBar : ProgressBar { | |||
| void step() override { | |||
| label = "Downloading"; | |||
| std::string name = pluginGetDownloadName(); | |||
| if (name != "") | |||
| label += " " + name; | |||
| setValue(100.0 * pluginGetDownloadProgress()); | |||
| DownloadProgressBar() { | |||
| quantity = new DownloadQuantity; | |||
| } | |||
| }; | |||
| @@ -187,8 +202,6 @@ PluginManagerWidget::PluginManagerWidget() { | |||
| ProgressBar *downloadProgress = new DownloadProgressBar; | |||
| downloadProgress->box.size.x = 300; | |||
| downloadProgress->setLimits(0, 100); | |||
| downloadProgress->unit = "%"; | |||
| downloadWidget->addChild(downloadProgress); | |||
| // Button *cancelButton = new CancelButton; | |||
| @@ -27,14 +27,14 @@ void SVGKnob::setSVG(std::shared_ptr<SVG> svg) { | |||
| void SVGKnob::step() { | |||
| // Re-transform TransformWidget if dirty | |||
| if (dirty) { | |||
| if (dirty && quantity) { | |||
| float angle; | |||
| if (std::isfinite(minValue) && std::isfinite(maxValue)) { | |||
| angle = rescale(value, minValue, maxValue, minAngle, maxAngle); | |||
| if (quantity->isBounded()) { | |||
| angle = rescale(quantity->getValue(), -1.f, 1.f, minAngle, maxAngle); | |||
| angle = std::fmod(angle, 2*M_PI); | |||
| } | |||
| else { | |||
| angle = rescale(value, -1.0, 1.0, minAngle, maxAngle); | |||
| angle = std::fmod(angle, 2*M_PI); | |||
| angle = rescale(quantity->getScaledValue(), 0.f, 1.f, minAngle, maxAngle); | |||
| } | |||
| tw->identity(); | |||
| // Rotate SVG | |||
| @@ -23,9 +23,12 @@ void SVGSlider::setSVGs(std::shared_ptr<SVG> backgroundSVG, std::shared_ptr<SVG> | |||
| } | |||
| void SVGSlider::step() { | |||
| if (dirty) { | |||
| if (dirty && quantity) { | |||
| // Interpolate handle position | |||
| handle->box.pos = Vec(rescale(value, minValue, maxValue, minHandlePos.x, maxHandlePos.x), rescale(value, minValue, maxValue, minHandlePos.y, maxHandlePos.y)); | |||
| float v = quantity->getScaledValue(); | |||
| handle->box.pos = Vec( | |||
| rescale(v, 0.f, 1.f, minHandlePos.x, maxHandlePos.x), | |||
| rescale(v, 0.f, 1.f, minHandlePos.y, maxHandlePos.y)); | |||
| } | |||
| FramebufferWidget::step(); | |||
| } | |||
| @@ -20,10 +20,12 @@ void SVGSwitch::addFrame(std::shared_ptr<SVG> svg) { | |||
| void SVGSwitch::onChange(event::Change &e) { | |||
| assert(frames.size() > 0); | |||
| float valueScaled = rescale(value, minValue, maxValue, 0, frames.size() - 1); | |||
| int index = clamp((int) roundf(valueScaled), 0, (int) frames.size() - 1); | |||
| sw->setSVG(frames[index]); | |||
| dirty = true; | |||
| if (quantity) { | |||
| int index = quantity->getScaledValue() * (frames.size() - 1); | |||
| index = clamp(index, 0, (int) frames.size() - 1); | |||
| sw->setSVG(frames[index]); | |||
| dirty = true; | |||
| } | |||
| ParamWidget::onChange(e); | |||
| } | |||
| @@ -7,10 +7,12 @@ namespace rack { | |||
| void ToggleSwitch::onDragStart(event::DragStart &e) { | |||
| // Cycle through values | |||
| // e.g. a range of [0.0, 3.0] would have modes 0, 1, 2, and 3. | |||
| if (value >= maxValue) | |||
| setValue(minValue); | |||
| else | |||
| setValue(value + 1.0); | |||
| if (quantity) { | |||
| if (quantity->isMax()) | |||
| quantity->setMin(); | |||
| else | |||
| quantity->moveValue(1.f); | |||
| } | |||
| } | |||
| @@ -10,12 +10,11 @@ namespace rack { | |||
| struct TooltipIconButton : IconButton { | |||
| Tooltip *tooltip = NULL; | |||
| std::string tooltipText; | |||
| void onEnter(event::Enter &e) override { | |||
| if (!tooltip) { | |||
| tooltip = new Tooltip; | |||
| tooltip->box.pos = getAbsoluteOffset(Vec(0, BND_WIDGET_HEIGHT)); | |||
| tooltip->text = tooltipText; | |||
| tooltip->text = getTooltipText(); | |||
| gRackScene->addChild(tooltip); | |||
| } | |||
| IconButton::onEnter(e); | |||
| @@ -28,13 +27,14 @@ struct TooltipIconButton : IconButton { | |||
| } | |||
| IconButton::onLeave(e); | |||
| } | |||
| virtual std::string getTooltipText() {return "";} | |||
| }; | |||
| struct NewButton : TooltipIconButton { | |||
| NewButton() { | |||
| setSVG(SVG::load(asset::global("res/icons/noun_146097_cc.svg"))); | |||
| tooltipText = "New patch (" WINDOW_MOD_KEY_NAME "+N)"; | |||
| } | |||
| std::string getTooltipText() override {return "New patch (" WINDOW_MOD_KEY_NAME "+N)";} | |||
| void onAction(event::Action &e) override { | |||
| gRackWidget->reset(); | |||
| } | |||
| @@ -43,8 +43,8 @@ struct NewButton : TooltipIconButton { | |||
| struct OpenButton : TooltipIconButton { | |||
| OpenButton() { | |||
| setSVG(SVG::load(asset::global("res/icons/noun_31859_cc.svg"))); | |||
| tooltipText = "Open patch (" WINDOW_MOD_KEY_NAME "+O)"; | |||
| } | |||
| std::string getTooltipText() override {return "Open patch (" WINDOW_MOD_KEY_NAME "+O)";} | |||
| void onAction(event::Action &e) override { | |||
| gRackWidget->loadDialog(); | |||
| } | |||
| @@ -53,8 +53,8 @@ struct OpenButton : TooltipIconButton { | |||
| struct SaveButton : TooltipIconButton { | |||
| SaveButton() { | |||
| setSVG(SVG::load(asset::global("res/icons/noun_1343816_cc.svg"))); | |||
| tooltipText = "Save patch (" WINDOW_MOD_KEY_NAME "+S)"; | |||
| } | |||
| std::string getTooltipText() override {return "Save patch (" WINDOW_MOD_KEY_NAME "+S)";} | |||
| void onAction(event::Action &e) override { | |||
| gRackWidget->saveDialog(); | |||
| } | |||
| @@ -63,8 +63,8 @@ struct SaveButton : TooltipIconButton { | |||
| struct SaveAsButton : TooltipIconButton { | |||
| SaveAsButton() { | |||
| setSVG(SVG::load(asset::global("res/icons/noun_1343811_cc.svg"))); | |||
| tooltipText = "Save patch as (" WINDOW_MOD_KEY_NAME "+Shift+S)"; | |||
| } | |||
| std::string getTooltipText() override {return "Save patch as (" WINDOW_MOD_KEY_NAME "+Shift+S)";} | |||
| void onAction(event::Action &e) override { | |||
| gRackWidget->saveAsDialog(); | |||
| } | |||
| @@ -73,8 +73,8 @@ struct SaveAsButton : TooltipIconButton { | |||
| struct RevertButton : TooltipIconButton { | |||
| RevertButton() { | |||
| setSVG(SVG::load(asset::global("res/icons/noun_1084369_cc.svg"))); | |||
| tooltipText = "Revert patch"; | |||
| } | |||
| std::string getTooltipText() override {return "Revert patch";} | |||
| void onAction(event::Action &e) override { | |||
| gRackWidget->revert(); | |||
| } | |||
| @@ -83,8 +83,8 @@ struct RevertButton : TooltipIconButton { | |||
| struct DisconnectCablesButton : TooltipIconButton { | |||
| DisconnectCablesButton() { | |||
| setSVG(SVG::load(asset::global("res/icons/noun_1745061_cc.svg"))); | |||
| tooltipText = "Disconnect cables"; | |||
| } | |||
| std::string getTooltipText() override {return "Disconnect cables";} | |||
| void onAction(event::Action &e) override { | |||
| gRackWidget->disconnect(); | |||
| } | |||
| @@ -93,8 +93,8 @@ struct DisconnectCablesButton : TooltipIconButton { | |||
| struct PowerMeterButton : TooltipIconButton { | |||
| PowerMeterButton() { | |||
| setSVG(SVG::load(asset::global("res/icons/noun_305536_cc.svg"))); | |||
| tooltipText = "Toggle power meter (see manual for explanation)"; | |||
| } | |||
| std::string getTooltipText() override {return "Toggle power meter (see manual for explanation)";} | |||
| void onAction(event::Action &e) override { | |||
| gPowerMeter ^= true; | |||
| } | |||
| @@ -117,8 +117,8 @@ struct SampleRateItem : MenuItem { | |||
| struct SampleRateButton : TooltipIconButton { | |||
| SampleRateButton() { | |||
| setSVG(SVG::load(asset::global("res/icons/noun_1240789_cc.svg"))); | |||
| tooltipText = "Engine sample rate"; | |||
| } | |||
| std::string getTooltipText() override {return "Engine sample rate";} | |||
| void onAction(event::Action &e) override { | |||
| Menu *menu = createMenu(); | |||
| menu->box.pos = getAbsoluteOffset(Vec(0, box.size.y)); | |||
| @@ -144,18 +144,52 @@ struct SampleRateButton : TooltipIconButton { | |||
| struct RackLockButton : TooltipIconButton { | |||
| RackLockButton() { | |||
| setSVG(SVG::load(asset::global("res/icons/noun_468341_cc.svg"))); | |||
| tooltipText = "Lock modules"; | |||
| } | |||
| std::string getTooltipText() override {return "Lock modules";} | |||
| void onAction(event::Action &e) override { | |||
| gRackWidget->lockModules ^= true; | |||
| } | |||
| }; | |||
| struct ZoomSlider : Slider { | |||
| void onAction(event::Action &e) override { | |||
| Slider::onAction(e); | |||
| gRackScene->zoomWidget->setZoom(std::round(value) / 100.0); | |||
| struct WireOpacityQuantity : Quantity { | |||
| void setValue(float value) override { | |||
| // TODO | |||
| } | |||
| float getValue() override { | |||
| return 0; | |||
| } | |||
| float getDefaultValue() override {return 0.5;} | |||
| std::string getLabel() override {return "Cable opacity";} | |||
| int getPrecision() override {return 0;} | |||
| }; | |||
| struct WireTensionQuantity : Quantity { | |||
| void setValue(float value) override { | |||
| // TODO | |||
| } | |||
| float getValue() override { | |||
| return 0; | |||
| } | |||
| float getDefaultValue() override {return 0.5;} | |||
| std::string getLabel() override {return "Cable tension";} | |||
| int getPrecision() override {return 0;} | |||
| }; | |||
| struct ZoomQuantity : Quantity { | |||
| void setValue(float value) override { | |||
| gRackScene->zoomWidget->setZoom(std::round(value) / 100); | |||
| } | |||
| float getValue() override { | |||
| return gRackScene->zoomWidget->zoom * 100; | |||
| } | |||
| float getMinValue() override {return 25;} | |||
| float getMaxValue() override {return 200;} | |||
| float getDefaultValue() override {return 100;} | |||
| std::string getLabel() override {return "Zoom";} | |||
| std::string getUnit() override {return "%";} | |||
| int getPrecision() override {return 0;} | |||
| }; | |||
| @@ -178,30 +212,22 @@ Toolbar::Toolbar() { | |||
| layout->addChild(new PowerMeterButton); | |||
| layout->addChild(new RackLockButton); | |||
| wireOpacitySlider = new Slider; | |||
| Slider *wireOpacitySlider = new Slider; | |||
| WireOpacityQuantity *wireOpacityQuantity = new WireOpacityQuantity; | |||
| wireOpacitySlider->quantity = wireOpacityQuantity; | |||
| wireOpacitySlider->box.size.x = 150; | |||
| wireOpacitySlider->label = "Cable opacity"; | |||
| wireOpacitySlider->precision = 0; | |||
| wireOpacitySlider->unit = "%"; | |||
| wireOpacitySlider->setLimits(0.0, 100.0); | |||
| wireOpacitySlider->setDefaultValue(50.0); | |||
| layout->addChild(wireOpacitySlider); | |||
| wireTensionSlider = new Slider; | |||
| Slider *wireTensionSlider = new Slider; | |||
| WireTensionQuantity *wireTensionQuantity = new WireTensionQuantity; | |||
| wireTensionSlider->quantity = wireTensionQuantity; | |||
| wireTensionSlider->box.size.x = 150; | |||
| wireTensionSlider->label = "Cable tension"; | |||
| wireTensionSlider->unit = ""; | |||
| wireTensionSlider->setLimits(0.0, 1.0); | |||
| wireTensionSlider->setDefaultValue(0.5); | |||
| layout->addChild(wireTensionSlider); | |||
| zoomSlider = new ZoomSlider; | |||
| Slider *zoomSlider = new Slider; | |||
| ZoomQuantity *zoomQuantity = new ZoomQuantity; | |||
| zoomSlider->quantity = zoomQuantity; | |||
| zoomSlider->box.size.x = 150; | |||
| zoomSlider->precision = 0; | |||
| zoomSlider->label = "Zoom"; | |||
| zoomSlider->unit = "%"; | |||
| zoomSlider->setLimits(25.0, 200.0); | |||
| zoomSlider->setDefaultValue(100.0); | |||
| layout->addChild(zoomSlider); | |||
| // Kind of hacky, but display the PluginManagerWidget only if the local directory is not the development directory | |||
| @@ -153,17 +153,17 @@ json_t *WireWidget::toJson() { | |||
| void WireWidget::fromJson(json_t *rootJ) { | |||
| json_t *colorJ = json_object_get(rootJ, "color"); | |||
| if (colorJ) { | |||
| // Legacy v0.6.0 and earlier | |||
| if (json_is_object(colorJ)) | |||
| color = jsonToColor(colorJ); | |||
| else | |||
| // v0.6.0 and earlier patches use JSON objects. Just ignore them if so and use the existing wire color. | |||
| if (json_is_string(colorJ)) | |||
| color = color::fromHexString(json_string_value(colorJ)); | |||
| } | |||
| } | |||
| void WireWidget::draw(NVGcontext *vg) { | |||
| float opacity = gToolbar->wireOpacitySlider->value / 100.0; | |||
| float tension = gToolbar->wireTensionSlider->value; | |||
| // float opacity = gToolbar->wireOpacitySlider->value / 100.0; | |||
| // float tension = gToolbar->wireTensionSlider->value; | |||
| float opacity = 0.5; | |||
| float tension = 0.5; | |||
| WireWidget *activeWire = gRackWidget->wireContainer->activeWire; | |||
| if (activeWire) { | |||
| @@ -69,10 +69,27 @@ static int smoothParamId; | |||
| static float smoothValue; | |||
| json_t *Param::toJson() { | |||
| json_t *rootJ = json_object(); | |||
| // Infinite params should serialize to 0 | |||
| float v = (std::isfinite(minValue) && std::isfinite(maxValue)) ? value : 0.f; | |||
| json_object_set_new(rootJ, "value", json_real(v)); | |||
| return rootJ; | |||
| } | |||
| void Param::fromJson(json_t *rootJ) { | |||
| json_t *valueJ = json_object_get(rootJ, "value"); | |||
| if (valueJ) | |||
| value = json_number_value(valueJ); | |||
| } | |||
| float Light::getBrightness() { | |||
| // LEDs are diodes, so don't allow reverse current. | |||
| // For some reason, instead of the RMS, the sqrt of RMS looks better | |||
| return powf(fmaxf(0.f, value), 0.25f); | |||
| return std::pow(std::fmaxf(0.f, value), 0.25f); | |||
| } | |||
| void Light::setBrightnessSmooth(float brightness, float frames) { | |||
| @@ -31,12 +31,12 @@ static json_t *settingsToJson() { | |||
| } | |||
| // opacity | |||
| float opacity = gToolbar->wireOpacitySlider->value; | |||
| float opacity = gToolbar->wireOpacity; | |||
| json_t *opacityJ = json_real(opacity); | |||
| json_object_set_new(rootJ, "wireOpacity", opacityJ); | |||
| // tension | |||
| float tension = gToolbar->wireTensionSlider->value; | |||
| float tension = gToolbar->wireTension; | |||
| json_t *tensionJ = json_real(tension); | |||
| json_object_set_new(rootJ, "wireTension", tensionJ); | |||
| @@ -99,18 +99,17 @@ static void settingsFromJson(json_t *rootJ) { | |||
| // opacity | |||
| json_t *opacityJ = json_object_get(rootJ, "wireOpacity"); | |||
| if (opacityJ) | |||
| gToolbar->wireOpacitySlider->value = json_number_value(opacityJ); | |||
| gToolbar->wireOpacity = json_number_value(opacityJ); | |||
| // tension | |||
| json_t *tensionJ = json_object_get(rootJ, "wireTension"); | |||
| if (tensionJ) | |||
| gToolbar->wireTensionSlider->value = json_number_value(tensionJ); | |||
| gToolbar->wireTension = json_number_value(tensionJ); | |||
| // zoom | |||
| json_t *zoomJ = json_object_get(rootJ, "zoom"); | |||
| if (zoomJ) { | |||
| gRackScene->zoomWidget->setZoom(clamp((float) json_number_value(zoomJ), 0.25f, 4.0f)); | |||
| gToolbar->zoomSlider->setValue(json_number_value(zoomJ) * 100.0); | |||
| } | |||
| // allowCursorLock | |||
| @@ -63,13 +63,16 @@ std::string filename(std::string path) { | |||
| return filename; | |||
| } | |||
| std::string basename(std::string path) { | |||
| size_t pos = path.rfind('.'); | |||
| return std::string(path, 0, pos); | |||
| } | |||
| std::string extension(std::string path) { | |||
| const char *ext = strrchr(filename(path).c_str(), '.'); | |||
| if (!ext) | |||
| return ""; | |||
| return ext + 1; | |||
| size_t pos = path.rfind('.'); | |||
| return std::string(path, pos); | |||
| } | |||
| } // namespace network | |||
| } // namespace string | |||
| } // namespace rack | |||
| @@ -13,8 +13,8 @@ namespace rack { | |||
| namespace system { | |||
| std::vector<std::string> listEntries(std::string path) { | |||
| std::vector<std::string> filenames; | |||
| std::list<std::string> listEntries(std::string path) { | |||
| std::list<std::string> filenames; | |||
| DIR *dir = opendir(path.c_str()); | |||
| if (dir) { | |||
| struct dirent *d; | |||