diff --git a/LICENSE.md b/LICENSE.md index 83528969..962213dc 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -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. diff --git a/include/app.hpp b/include/app.hpp index 69c4be07..a0fce24b 100644 --- a/include/app.hpp +++ b/include/app.hpp @@ -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" diff --git a/include/app/CircularShadow.hpp b/include/app/CircularShadow.hpp index c3d189c3..e78b17a1 100644 --- a/include/app/CircularShadow.hpp +++ b/include/app/CircularShadow.hpp @@ -1,4 +1,5 @@ #pragma once +#include "widgets/TransparentWidget.hpp" #include "app/common.hpp" diff --git a/include/app/Component.hpp b/include/app/Component.hpp deleted file mode 100644 index b499dd4e..00000000 --- a/include/app/Component.hpp +++ /dev/null @@ -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 diff --git a/include/app/Knob.hpp b/include/app/Knob.hpp index 8ea9c796..269ca980 100644 --- a/include/app/Knob.hpp +++ b/include/app/Knob.hpp @@ -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); + } + } }; diff --git a/include/app/LedDisplay.hpp b/include/app/LedDisplay.hpp index 30e5ebb0..915b0c1e 100644 --- a/include/app/LedDisplay.hpp +++ b/include/app/LedDisplay.hpp @@ -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; }; diff --git a/include/app/LightWidget.hpp b/include/app/LightWidget.hpp index 536a6439..e48c03bb 100644 --- a/include/app/LightWidget.hpp +++ b/include/app/LightWidget.hpp @@ -1,4 +1,5 @@ #pragma once +#include "widgets/TransparentWidget.hpp" #include "app/common.hpp" diff --git a/include/app/ModuleWidget.hpp b/include/app/ModuleWidget.hpp index de272153..eafb10fb 100644 --- a/include/app/ModuleWidget.hpp +++ b/include/app/ModuleWidget.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" diff --git a/include/app/ParamQuantity.hpp b/include/app/ParamQuantity.hpp new file mode 100644 index 00000000..d0fda26d --- /dev/null +++ b/include/app/ParamQuantity.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 diff --git a/include/app/ParamWidget.hpp b/include/app/ParamWidget.hpp index 03ebad64..00546bc2 100644 --- a/include/app/ParamWidget.hpp +++ b/include/app/ParamWidget.hpp @@ -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; }; diff --git a/include/app/PluginManagerWidget.hpp b/include/app/PluginManagerWidget.hpp index 72a1d313..95fb5483 100644 --- a/include/app/PluginManagerWidget.hpp +++ b/include/app/PluginManagerWidget.hpp @@ -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; diff --git a/include/app/Port.hpp b/include/app/Port.hpp index 4390cdaa..4d4945b3 100644 --- a/include/app/Port.hpp +++ b/include/app/Port.hpp @@ -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(); diff --git a/include/app/RackRail.hpp b/include/app/RackRail.hpp index eb689c28..d4268062 100644 --- a/include/app/RackRail.hpp +++ b/include/app/RackRail.hpp @@ -1,4 +1,5 @@ #pragma once +#include "widgets/TransparentWidget.hpp" #include "app/common.hpp" diff --git a/include/app/RackScene.hpp b/include/app/RackScene.hpp index b02bb44d..6e83467e 100644 --- a/include/app/RackScene.hpp +++ b/include/app/RackScene.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; diff --git a/include/app/RackScrollWidget.hpp b/include/app/RackScrollWidget.hpp index 60aa1876..f695f1d6 100644 --- a/include/app/RackScrollWidget.hpp +++ b/include/app/RackScrollWidget.hpp @@ -1,4 +1,5 @@ #pragma once +#include "ui/ScrollWidget.hpp" #include "app/common.hpp" diff --git a/include/app/SVGButton.hpp b/include/app/SVGButton.hpp index 267fbd25..28439e61 100644 --- a/include/app/SVGButton.hpp +++ b/include/app/SVGButton.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 defaultSVG; std::shared_ptr activeSVG; SVGWidget *sw; diff --git a/include/app/SVGKnob.hpp b/include/app/SVGKnob.hpp index d73ef852..850b3cc4 100644 --- a/include/app/SVGKnob.hpp +++ b/include/app/SVGKnob.hpp @@ -1,6 +1,7 @@ #pragma once #include "app/common.hpp" #include "app/CircularShadow.hpp" +#include "widgets/TransformWidget.hpp" namespace rack { diff --git a/include/app/SVGPanel.hpp b/include/app/SVGPanel.hpp index 59bcf260..60e2df79 100644 --- a/include/app/SVGPanel.hpp +++ b/include/app/SVGPanel.hpp @@ -1,5 +1,7 @@ #pragma once #include "app/common.hpp" +#include "widgets/FramebufferWidget.hpp" +#include "widgets/SVGWidget.hpp" namespace rack { diff --git a/include/app/Toolbar.hpp b/include/app/Toolbar.hpp index c1662aca..687fcda1 100644 --- a/include/app/Toolbar.hpp +++ b/include/app/Toolbar.hpp @@ -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; diff --git a/include/app/common.hpp b/include/app/common.hpp index 096afd0b..4d91b6b2 100644 --- a/include/app/common.hpp +++ b/include/app/common.hpp @@ -1,5 +1,6 @@ #pragma once -#include "ui.hpp" +#include "math.hpp" +#include #include @@ -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 diff --git a/include/componentlibrary.hpp b/include/componentlibrary.hpp index 8523fdfb..5ce36162 100644 --- a/include/componentlibrary.hpp +++ b/include/componentlibrary.hpp @@ -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; } }; diff --git a/include/engine.hpp b/include/engine.hpp index 0b28eba4..30982ce4 100644 --- a/include/engine.hpp +++ b/include/engine.hpp @@ -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); }; diff --git a/include/helpers.hpp b/include/helpers.hpp index 5132dad4..12a86b42 100644 --- a/include/helpers.hpp +++ b/include/helpers.hpp @@ -46,8 +46,8 @@ template 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 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; diff --git a/include/math.hpp b/include/math.hpp index ac43fe61..2af5190b 100644 --- a/include/math.hpp +++ b/include/math.hpp @@ -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)); } diff --git a/include/string.hpp b/include/string.hpp index 4524775c..f52ab531 100644 --- a/include/string.hpp +++ b/include/string.hpp @@ -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 { diff --git a/include/system.hpp b/include/system.hpp index 6ba1b5ab..3e29c953 100644 --- a/include/system.hpp +++ b/include/system.hpp @@ -1,5 +1,5 @@ #pragma once -#include +#include #include "common.hpp" @@ -7,7 +7,7 @@ namespace rack { namespace system { -std::vector listEntries(std::string path); +std::list listEntries(std::string path); bool isFile(std::string path); bool isDirectory(std::string path); void copyFile(std::string srcPath, std::string destPath); diff --git a/include/ui.hpp b/include/ui.hpp index 00020030..0077a022 100644 --- a/include/ui.hpp +++ b/include/ui.hpp @@ -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" diff --git a/include/ui/Button.hpp b/include/ui/Button.hpp index 04d3c007..6829c63a 100644 --- a/include/ui/Button.hpp +++ b/include/ui/Button.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 { diff --git a/include/ui/ChoiceButton.hpp b/include/ui/ChoiceButton.hpp index 8012c6e2..76d6209a 100644 --- a/include/ui/ChoiceButton.hpp +++ b/include/ui/ChoiceButton.hpp @@ -1,4 +1,5 @@ #pragma once +#include "ui/common.hpp" #include "ui/Button.hpp" diff --git a/include/ui/IconButton.hpp b/include/ui/IconButton.hpp index f8f42d50..b53d4739 100644 --- a/include/ui/IconButton.hpp +++ b/include/ui/IconButton.hpp @@ -1,5 +1,6 @@ #pragma once #include "ui/common.hpp" +#include "ui/Button.hpp" namespace rack { diff --git a/include/ui/Label.hpp b/include/ui/Label.hpp index f427c587..4c23ef6f 100644 --- a/include/ui/Label.hpp +++ b/include/ui/Label.hpp @@ -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; diff --git a/include/ui/List.hpp b/include/ui/List.hpp index 76ffee03..43bc409b 100644 --- a/include/ui/List.hpp +++ b/include/ui/List.hpp @@ -1,4 +1,5 @@ #pragma once +#include "widgets/OpaqueWidget.hpp" #include "ui/common.hpp" diff --git a/include/ui/Menu.hpp b/include/ui/Menu.hpp index 581abbf6..fadd852e 100644 --- a/include/ui/Menu.hpp +++ b/include/ui/Menu.hpp @@ -1,4 +1,6 @@ #pragma once +#include "widgets/OpaqueWidget.hpp" +#include "ui/common.hpp" #include "ui/MenuEntry.hpp" diff --git a/include/ui/MenuEntry.hpp b/include/ui/MenuEntry.hpp index 911da47a..7d38c2ef 100644 --- a/include/ui/MenuEntry.hpp +++ b/include/ui/MenuEntry.hpp @@ -1,4 +1,5 @@ #pragma once +#include "widgets/OpaqueWidget.hpp" #include "ui/common.hpp" diff --git a/include/ui/MenuItem.hpp b/include/ui/MenuItem.hpp index 75f84c2e..41c2f306 100644 --- a/include/ui/MenuItem.hpp +++ b/include/ui/MenuItem.hpp @@ -1,6 +1,6 @@ #pragma once #include "ui/common.hpp" -#include "ui/MenuOverlay.hpp" +#include "ui/MenuEntry.hpp" namespace rack { diff --git a/include/ui/MenuLabel.hpp b/include/ui/MenuLabel.hpp index f5c0fc58..b63ba2dc 100644 --- a/include/ui/MenuLabel.hpp +++ b/include/ui/MenuLabel.hpp @@ -1,5 +1,6 @@ #pragma once #include "ui/common.hpp" +#include "ui/MenuEntry.hpp" namespace rack { diff --git a/include/ui/MenuOverlay.hpp b/include/ui/MenuOverlay.hpp index 295fcca1..dfbd4030 100644 --- a/include/ui/MenuOverlay.hpp +++ b/include/ui/MenuOverlay.hpp @@ -1,4 +1,5 @@ #pragma once +#include "widgets/OpaqueWidget.hpp" #include "ui/common.hpp" diff --git a/include/ui/MenuSeparator.hpp b/include/ui/MenuSeparator.hpp index 4406ba42..3b471690 100644 --- a/include/ui/MenuSeparator.hpp +++ b/include/ui/MenuSeparator.hpp @@ -1,5 +1,6 @@ #pragma once #include "ui/common.hpp" +#include "ui/MenuEntry.hpp" namespace rack { diff --git a/include/ui/ProgressBar.hpp b/include/ui/ProgressBar.hpp index 7e976ea6..bf68a8b4 100644 --- a/include/ui/ProgressBar.hpp +++ b/include/ui/ProgressBar.hpp @@ -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); } }; diff --git a/include/ui/Quantity.hpp b/include/ui/Quantity.hpp new file mode 100644 index 00000000..ec3dcd0b --- /dev/null +++ b/include/ui/Quantity.hpp @@ -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 diff --git a/include/ui/QuantityWidget.hpp b/include/ui/QuantityWidget.hpp deleted file mode 100644 index 67d5f334..00000000 --- a/include/ui/QuantityWidget.hpp +++ /dev/null @@ -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 diff --git a/include/ui/RadioButton.hpp b/include/ui/RadioButton.hpp index d1ec8edb..cf5c6ebd 100644 --- a/include/ui/RadioButton.hpp +++ b/include/ui/RadioButton.hpp @@ -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); diff --git a/include/ui/Scene.hpp b/include/ui/Scene.hpp index 0779144a..7eeb4874 100644 --- a/include/ui/Scene.hpp +++ b/include/ui/Scene.hpp @@ -1,4 +1,5 @@ #pragma once +#include "widgets/OpaqueWidget.hpp" #include "ui/common.hpp" diff --git a/include/ui/ScrollWidget.hpp b/include/ui/ScrollWidget.hpp index 5e45a4aa..2ec82fff 100644 --- a/include/ui/ScrollWidget.hpp +++ b/include/ui/ScrollWidget.hpp @@ -1,4 +1,5 @@ #pragma once +#include "widgets/OpaqueWidget.hpp" #include "ui/common.hpp" #include "event.hpp" diff --git a/include/ui/SequentialLayout.hpp b/include/ui/SequentialLayout.hpp index 5b0103fc..0322f0bf 100644 --- a/include/ui/SequentialLayout.hpp +++ b/include/ui/SequentialLayout.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, diff --git a/include/ui/Slider.hpp b/include/ui/Slider.hpp index 1be9ceb5..6fec576b 100644 --- a/include/ui/Slider.hpp +++ b/include/ui/Slider.hpp @@ -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; } diff --git a/include/ui/TextField.hpp b/include/ui/TextField.hpp index ba9c7107..9c814b59 100644 --- a/include/ui/TextField.hpp +++ b/include/ui/TextField.hpp @@ -1,4 +1,5 @@ #pragma once +#include "widgets/OpaqueWidget.hpp" #include "ui/common.hpp" #include "event.hpp" diff --git a/include/ui/Tooltip.hpp b/include/ui/Tooltip.hpp index 0162c56f..4584d57d 100644 --- a/include/ui/Tooltip.hpp +++ b/include/ui/Tooltip.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 { diff --git a/include/ui/TooltipOverlay.hpp b/include/ui/TooltipOverlay.hpp index 6ace069d..02f80a8f 100644 --- a/include/ui/TooltipOverlay.hpp +++ b/include/ui/TooltipOverlay.hpp @@ -1,4 +1,5 @@ #pragma once +#include "widgets/TransparentWidget.hpp" #include "ui/common.hpp" diff --git a/include/ui/WindowOverlay.hpp b/include/ui/WindowOverlay.hpp index ee20cf55..c294b71c 100644 --- a/include/ui/WindowOverlay.hpp +++ b/include/ui/WindowOverlay.hpp @@ -1,4 +1,5 @@ #pragma once +#include "widgets/OpaqueWidget.hpp" #include "ui/common.hpp" diff --git a/include/ui/WindowWidget.hpp b/include/ui/WindowWidget.hpp index 991a6be4..e8b6befd 100644 --- a/include/ui/WindowWidget.hpp +++ b/include/ui/WindowWidget.hpp @@ -1,4 +1,5 @@ #pragma once +#include "widgets/OpaqueWidget.hpp" #include "ui/common.hpp" diff --git a/include/ui/common.hpp b/include/ui/common.hpp index 18a3691a..46183b58 100644 --- a/include/ui/common.hpp +++ b/include/ui/common.hpp @@ -1,5 +1,4 @@ #pragma once -#include "widgets.hpp" #include "blendish.h" #define CHECKMARK_STRING "✔" diff --git a/include/widgets/FramebufferWidget.hpp b/include/widgets/FramebufferWidget.hpp index 4e32c0ed..e83875e1 100644 --- a/include/widgets/FramebufferWidget.hpp +++ b/include/widgets/FramebufferWidget.hpp @@ -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 diff --git a/include/widgets/OpaqueWidget.hpp b/include/widgets/OpaqueWidget.hpp index c5e2783d..49415eec 100644 --- a/include/widgets/OpaqueWidget.hpp +++ b/include/widgets/OpaqueWidget.hpp @@ -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) diff --git a/include/widgets/SVGWidget.hpp b/include/widgets/SVGWidget.hpp index f1168201..8cbae659 100644 --- a/include/widgets/SVGWidget.hpp +++ b/include/widgets/SVGWidget.hpp @@ -6,7 +6,7 @@ namespace rack { /** Draws an SVG */ -struct SVGWidget : virtual Widget { +struct SVGWidget : VirtualWidget { std::shared_ptr svg; /** Sets the box size to the svg image size */ diff --git a/include/widgets/TransformWidget.hpp b/include/widgets/TransformWidget.hpp index fe7bc60f..deb2ac8d 100644 --- a/include/widgets/TransformWidget.hpp +++ b/include/widgets/TransformWidget.hpp @@ -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]; diff --git a/include/widgets/TransparentWidget.hpp b/include/widgets/TransparentWidget.hpp index f53910a5..2476268b 100644 --- a/include/widgets/TransparentWidget.hpp +++ b/include/widgets/TransparentWidget.hpp @@ -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 {} diff --git a/include/widgets/Widget.hpp b/include/widgets/Widget.hpp index 388c8277..cc50ea95 100644 --- a/include/widgets/Widget.hpp +++ b/include/widgets/Widget.hpp @@ -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 diff --git a/include/widgets/ZoomWidget.hpp b/include/widgets/ZoomWidget.hpp index c6919d92..71beb68e 100644 --- a/include/widgets/ZoomWidget.hpp +++ b/include/widgets/ZoomWidget.hpp @@ -5,7 +5,7 @@ namespace rack { -struct ZoomWidget : virtual Widget { +struct ZoomWidget : VirtualWidget { float zoom = 1.f; Vec getRelativeOffset(Vec v, Widget *relative) override { diff --git a/src/app/AudioWidget.cpp b/src/app/AudioWidget.cpp index 6e5082be..f6313a7b 100644 --- a/src/app/AudioWidget.cpp +++ b/src/app/AudioWidget.cpp @@ -1,4 +1,4 @@ -#include "app.hpp" +#include "app/AudioWidget.hpp" #include "audio.hpp" #include "helpers.hpp" diff --git a/src/app/Knob.cpp b/src/app/Knob.cpp deleted file mode 100644 index 71c2f829..00000000 --- a/src/app/Knob.cpp +++ /dev/null @@ -1,52 +0,0 @@ -#include "app.hpp" -#include "window.hpp" -#include "engine.hpp" -// For GLFW_KEY_LEFT_CONTROL, etc. -#include - - -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 diff --git a/src/app/ModuleBrowser.cpp b/src/app/ModuleBrowser.cpp index bd73d591..bcc8e567 100644 --- a/src/app/ModuleBrowser.cpp +++ b/src/app/ModuleBrowser.cpp @@ -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(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