@@ -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; | |||