Browse Source

Large architectural changes. Created Quantity class.

tags/v1.0.0
Andrew Belt 5 years ago
parent
commit
f64ea2e181
76 changed files with 605 additions and 335 deletions
  1. +1
    -1
      LICENSE.md
  2. +1
    -1
      include/app.hpp
  3. +1
    -0
      include/app/CircularShadow.hpp
  4. +0
    -17
      include/app/Component.hpp
  5. +30
    -7
      include/app/Knob.hpp
  6. +4
    -1
      include/app/LedDisplay.hpp
  7. +1
    -0
      include/app/LightWidget.hpp
  8. +5
    -0
      include/app/ModuleWidget.hpp
  9. +57
    -0
      include/app/ParamQuantity.hpp
  10. +14
    -11
      include/app/ParamWidget.hpp
  11. +2
    -1
      include/app/PluginManagerWidget.hpp
  12. +6
    -2
      include/app/Port.hpp
  13. +1
    -0
      include/app/RackRail.hpp
  14. +5
    -0
      include/app/RackScene.hpp
  15. +1
    -0
      include/app/RackScrollWidget.hpp
  16. +1
    -1
      include/app/SVGButton.hpp
  17. +1
    -0
      include/app/SVGKnob.hpp
  18. +2
    -0
      include/app/SVGPanel.hpp
  19. +6
    -4
      include/app/Toolbar.hpp
  20. +2
    -8
      include/app/common.hpp
  21. +6
    -4
      include/componentlibrary.hpp
  22. +21
    -1
      include/engine.hpp
  23. +4
    -4
      include/helpers.hpp
  24. +6
    -0
      include/math.hpp
  25. +3
    -0
      include/string.hpp
  26. +2
    -2
      include/system.hpp
  27. +1
    -1
      include/ui.hpp
  28. +13
    -0
      include/ui/Button.hpp
  29. +1
    -0
      include/ui/ChoiceButton.hpp
  30. +1
    -0
      include/ui/IconButton.hpp
  31. +2
    -1
      include/ui/Label.hpp
  32. +1
    -0
      include/ui/List.hpp
  33. +2
    -0
      include/ui/Menu.hpp
  34. +1
    -0
      include/ui/MenuEntry.hpp
  35. +1
    -1
      include/ui/MenuItem.hpp
  36. +1
    -0
      include/ui/MenuLabel.hpp
  37. +1
    -0
      include/ui/MenuOverlay.hpp
  38. +1
    -0
      include/ui/MenuSeparator.hpp
  39. +11
    -4
      include/ui/ProgressBar.hpp
  40. +128
    -0
      include/ui/Quantity.hpp
  41. +0
    -49
      include/ui/QuantityWidget.hpp
  42. +26
    -9
      include/ui/RadioButton.hpp
  43. +1
    -0
      include/ui/Scene.hpp
  44. +1
    -0
      include/ui/ScrollWidget.hpp
  45. +2
    -1
      include/ui/SequentialLayout.hpp
  46. +17
    -6
      include/ui/Slider.hpp
  47. +1
    -0
      include/ui/TextField.hpp
  48. +2
    -1
      include/ui/Tooltip.hpp
  49. +1
    -0
      include/ui/TooltipOverlay.hpp
  50. +1
    -0
      include/ui/WindowOverlay.hpp
  51. +1
    -0
      include/ui/WindowWidget.hpp
  52. +0
    -1
      include/ui/common.hpp
  53. +1
    -1
      include/widgets/FramebufferWidget.hpp
  54. +1
    -1
      include/widgets/OpaqueWidget.hpp
  55. +1
    -1
      include/widgets/SVGWidget.hpp
  56. +1
    -1
      include/widgets/TransformWidget.hpp
  57. +1
    -1
      include/widgets/TransparentWidget.hpp
  58. +7
    -1
      include/widgets/Widget.hpp
  59. +1
    -1
      include/widgets/ZoomWidget.hpp
  60. +1
    -1
      src/app/AudioWidget.cpp
  61. +0
    -52
      src/app/Knob.cpp
  62. +13
    -3
      src/app/ModuleBrowser.cpp
  63. +16
    -27
      src/app/ModuleWidget.cpp
  64. +6
    -2
      src/app/MomentarySwitch.cpp
  65. +16
    -30
      src/app/ParamWidget.cpp
  66. +21
    -8
      src/app/PluginManagerWidget.cpp
  67. +5
    -5
      src/app/SVGKnob.cpp
  68. +5
    -2
      src/app/SVGSlider.cpp
  69. +6
    -4
      src/app/SVGSwitch.cpp
  70. +6
    -4
      src/app/ToggleSwitch.cpp
  71. +58
    -32
      src/app/Toolbar.cpp
  72. +6
    -6
      src/app/WireWidget.cpp
  73. +18
    -1
      src/engine.cpp
  74. +4
    -5
      src/settings.cpp
  75. +8
    -5
      src/string.cpp
  76. +2
    -2
      src/system.cpp

+ 1
- 1
LICENSE.md View File

@@ -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 **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. The **VCV logo and icon** are copyright © 2017 Andrew Belt and may not be used in derivative works.




+ 1
- 1
include/app.hpp View File

@@ -1,8 +1,8 @@
#pragma once #pragma once
#include "ui.hpp"
#include "app/AudioWidget.hpp" #include "app/AudioWidget.hpp"
#include "app/CircularShadow.hpp" #include "app/CircularShadow.hpp"
#include "app/common.hpp" #include "app/common.hpp"
#include "app/Component.hpp"
#include "app/Knob.hpp" #include "app/Knob.hpp"
#include "app/LedDisplay.hpp" #include "app/LedDisplay.hpp"
#include "app/LightWidget.hpp" #include "app/LightWidget.hpp"


+ 1
- 0
include/app/CircularShadow.hpp View File

@@ -1,4 +1,5 @@
#pragma once #pragma once
#include "widgets/TransparentWidget.hpp"
#include "app/common.hpp" #include "app/common.hpp"






+ 0
- 17
include/app/Component.hpp View File

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

+ 30
- 7
include/app/Knob.hpp View File

@@ -6,17 +6,40 @@
namespace rack { namespace rack {




static const float KNOB_SENSITIVITY = 0.0015f;


/** Implements vertical dragging behavior for ParamWidgets */ /** Implements vertical dragging behavior for ParamWidgets */
struct Knob : ParamWidget { struct Knob : ParamWidget {
/** Snap to nearest integer while dragging */
bool snap = false;
/** Multiplier for mouse movement to adjust knob value */ /** Multiplier for mouse movement to adjust knob value */
float speed = 1.0; 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);
}
}
}; };






+ 4
- 1
include/app/LedDisplay.hpp View File

@@ -1,11 +1,14 @@
#pragma once #pragma once
#include "widgets/Widget.hpp"
#include "widgets/TransparentWidget.hpp"
#include "ui/TextField.hpp"
#include "app/common.hpp" #include "app/common.hpp"




namespace rack { namespace rack {




struct LedDisplay : virtual Widget {
struct LedDisplay : VirtualWidget {
void draw(NVGcontext *vg) override; void draw(NVGcontext *vg) override;
}; };




+ 1
- 0
include/app/LightWidget.hpp View File

@@ -1,4 +1,5 @@
#pragma once #pragma once
#include "widgets/TransparentWidget.hpp"
#include "app/common.hpp" #include "app/common.hpp"






+ 5
- 0
include/app/ModuleWidget.hpp View File

@@ -1,5 +1,10 @@
#pragma once #pragma once
#include "widgets/OpaqueWidget.hpp"
#include "ui/Menu.hpp"
#include "app/common.hpp" #include "app/common.hpp"
#include "app/SVGPanel.hpp"
#include "app/Port.hpp"
#include "app/ParamWidget.hpp"
#include "plugin.hpp" #include "plugin.hpp"
#include "engine.hpp" #include "engine.hpp"




+ 57
- 0
include/app/ParamQuantity.hpp View File

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

+ 14
- 11
include/app/ParamWidget.hpp View File

@@ -1,4 +1,6 @@
#pragma once #pragma once
#include "widgets/OpaqueWidget.hpp"
#include "app/ParamQuantity.hpp"
#include "app/common.hpp" #include "app/common.hpp"
#include "engine.hpp" #include "engine.hpp"


@@ -6,22 +8,23 @@
namespace rack { 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); void fromJson(json_t *rootJ);
virtual void reset(); virtual void reset();
virtual void randomize(); virtual void randomize();
void onButton(event::Button &e) override; void onButton(event::Button &e) override;
void onChange(event::Change &e) override;
}; };






+ 2
- 1
include/app/PluginManagerWidget.hpp View File

@@ -1,11 +1,12 @@
#pragma once #pragma once
#include "widgets/Widget.hpp"
#include "app/common.hpp" #include "app/common.hpp"




namespace rack { namespace rack {




struct PluginManagerWidget : virtual Widget {
struct PluginManagerWidget : VirtualWidget {
Widget *loginWidget; Widget *loginWidget;
Widget *manageWidget; Widget *manageWidget;
Widget *downloadWidget; Widget *downloadWidget;


+ 6
- 2
include/app/Port.hpp View File

@@ -1,17 +1,21 @@
#pragma once #pragma once
#include "widgets/OpaqueWidget.hpp"
#include "app/common.hpp" #include "app/common.hpp"
#include "app/MultiLightWidget.hpp"




namespace rack { namespace rack {




struct Port : Component {
struct Port : OpaqueWidget {
Module *module = NULL;
int portId;

enum PortType { enum PortType {
INPUT, INPUT,
OUTPUT OUTPUT
}; };
PortType type = INPUT; PortType type = INPUT;
int portId;
MultiLightWidget *plugLight; MultiLightWidget *plugLight;


Port(); Port();


+ 1
- 0
include/app/RackRail.hpp View File

@@ -1,4 +1,5 @@
#pragma once #pragma once
#include "widgets/TransparentWidget.hpp"
#include "app/common.hpp" #include "app/common.hpp"






+ 5
- 0
include/app/RackScene.hpp View File

@@ -1,10 +1,15 @@
#pragma once #pragma once
#include "ui/Scene.hpp"
#include "app/common.hpp" #include "app/common.hpp"




namespace rack { namespace rack {




struct ScrollWidget;
struct ZoomWidget;


struct RackScene : Scene { struct RackScene : Scene {
ScrollWidget *scrollWidget; ScrollWidget *scrollWidget;
ZoomWidget *zoomWidget; ZoomWidget *zoomWidget;


+ 1
- 0
include/app/RackScrollWidget.hpp View File

@@ -1,4 +1,5 @@
#pragma once #pragma once
#include "ui/ScrollWidget.hpp"
#include "app/common.hpp" #include "app/common.hpp"






+ 1
- 1
include/app/SVGButton.hpp View File

@@ -9,7 +9,7 @@ namespace rack {
/** A Component with a default (up) and active (down) state when clicked. /** A Component with a default (up) and active (down) state when clicked.
Does not modify a Param, simply calls onAction() of a subclass. 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> defaultSVG;
std::shared_ptr<SVG> activeSVG; std::shared_ptr<SVG> activeSVG;
SVGWidget *sw; SVGWidget *sw;


+ 1
- 0
include/app/SVGKnob.hpp View File

@@ -1,6 +1,7 @@
#pragma once #pragma once
#include "app/common.hpp" #include "app/common.hpp"
#include "app/CircularShadow.hpp" #include "app/CircularShadow.hpp"
#include "widgets/TransformWidget.hpp"




namespace rack { namespace rack {


+ 2
- 0
include/app/SVGPanel.hpp View File

@@ -1,5 +1,7 @@
#pragma once #pragma once
#include "app/common.hpp" #include "app/common.hpp"
#include "widgets/FramebufferWidget.hpp"
#include "widgets/SVGWidget.hpp"




namespace rack { namespace rack {


+ 6
- 4
include/app/Toolbar.hpp View File

@@ -1,4 +1,7 @@
#pragma once #pragma once
#include "widgets/OpaqueWidget.hpp"
#include "ui/Slider.hpp"
#include "ui/RadioButton.hpp"
#include "app/common.hpp" #include "app/common.hpp"




@@ -6,10 +9,9 @@ namespace rack {




struct Toolbar : OpaqueWidget { 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(); Toolbar();
void draw(NVGcontext *vg) override; void draw(NVGcontext *vg) override;


+ 2
- 8
include/app/common.hpp View File

@@ -1,5 +1,6 @@
#pragma once #pragma once
#include "ui.hpp"
#include "math.hpp"
#include <string>
#include <jansson.h> #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"; 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 } // namespace rack

+ 6
- 4
include/componentlibrary.hpp View File

@@ -59,8 +59,9 @@ struct RoundHugeBlackKnob : RoundKnob {


struct RoundBlackSnapKnob : RoundBlackKnob { struct RoundBlackSnapKnob : RoundBlackKnob {
RoundBlackSnapKnob() { RoundBlackSnapKnob() {
snap = true;
smooth = false;
// TODO
// quantity.snap = true;
// quantity.smooth = false;
} }
}; };


@@ -308,8 +309,9 @@ struct BefacoBigKnob : SVGKnob {


struct BefacoBigSnapKnob : BefacoBigKnob { struct BefacoBigSnapKnob : BefacoBigKnob {
BefacoBigSnapKnob() { BefacoBigSnapKnob() {
snap = true;
smooth = false;
// TODO
// quantity.snap = true;
// quantity.smooth = false;
} }
}; };




+ 21
- 1
include/engine.hpp View File

@@ -8,7 +8,27 @@ namespace rack {




struct Param { 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);
}; };






+ 4
- 4
include/helpers.hpp View File

@@ -46,8 +46,8 @@ template <class TParamWidget>
TParamWidget *createParam(Vec pos, Module *module, int paramId, float minValue, float maxValue, float defaultValue) { TParamWidget *createParam(Vec pos, Module *module, int paramId, float minValue, float maxValue, float defaultValue) {
TParamWidget *o = new TParamWidget; TParamWidget *o = new TParamWidget;
o->box.pos = pos; o->box.pos = pos;
o->module = module;
o->paramId = paramId;
o->quantity->module = module;
o->quantity->paramId = paramId;
o->setLimits(minValue, maxValue); o->setLimits(minValue, maxValue);
o->setDefaultValue(defaultValue); o->setDefaultValue(defaultValue);
return o; return o;
@@ -57,8 +57,8 @@ template <class TParamWidget>
TParamWidget *createParamCentered(Vec pos, Module *module, int paramId, float minValue, float maxValue, float defaultValue) { TParamWidget *createParamCentered(Vec pos, Module *module, int paramId, float minValue, float maxValue, float defaultValue) {
TParamWidget *o = new TParamWidget; TParamWidget *o = new TParamWidget;
o->box.pos = pos.minus(o->box.size.div(2)); 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->setLimits(minValue, maxValue);
o->setDefaultValue(defaultValue); o->setDefaultValue(defaultValue);
return o; return o;


+ 6
- 0
include/math.hpp View File

@@ -244,6 +244,12 @@ struct Rect {
bool isEqual(Rect r) const { bool isEqual(Rect r) const {
return pos.isEqual(r.pos) && size.isEqual(r.size); 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 { Vec getCenter() const {
return pos.plus(size.mult(0.5f)); return pos.plus(size.mult(0.5f));
} }


+ 3
- 0
include/string.hpp View File

@@ -22,6 +22,9 @@ bool endsWith(std::string str, std::string suffix);
/** Extracts portions of a path */ /** Extracts portions of a path */
std::string directory(std::string path); std::string directory(std::string path);
std::string filename(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); std::string extension(std::string path);


struct CaseInsensitiveCompare { struct CaseInsensitiveCompare {


+ 2
- 2
include/system.hpp View File

@@ -1,5 +1,5 @@
#pragma once #pragma once
#include <vector>
#include <list>
#include "common.hpp" #include "common.hpp"




@@ -7,7 +7,7 @@ namespace rack {
namespace system { namespace system {




std::vector<std::string> listEntries(std::string path);
std::list<std::string> listEntries(std::string path);
bool isFile(std::string path); bool isFile(std::string path);
bool isDirectory(std::string path); bool isDirectory(std::string path);
void copyFile(std::string srcPath, std::string destPath); void copyFile(std::string srcPath, std::string destPath);


+ 1
- 1
include/ui.hpp View File

@@ -1,6 +1,6 @@
#pragma once #pragma once
#include "widgets.hpp"
#include "ui/common.hpp" #include "ui/common.hpp"
#include "ui/QuantityWidget.hpp"
#include "ui/SequentialLayout.hpp" #include "ui/SequentialLayout.hpp"
#include "ui/Label.hpp" #include "ui/Label.hpp"
#include "ui/List.hpp" #include "ui/List.hpp"


+ 13
- 0
include/ui/Button.hpp View File

@@ -1,5 +1,7 @@
#pragma once #pragma once
#include "widgets/OpaqueWidget.hpp"
#include "ui/common.hpp" #include "ui/common.hpp"
#include "ui/Quantity.hpp"




namespace rack { namespace rack {
@@ -8,11 +10,18 @@ namespace rack {
struct Button : OpaqueWidget { struct Button : OpaqueWidget {
std::string text; std::string text;
BNDwidgetState state = BND_DEFAULT; BNDwidgetState state = BND_DEFAULT;
/** Optional, owned. Tracks the pressed state of the button.*/
Quantity *quantity = NULL;


Button() { Button() {
box.size.y = BND_WIDGET_HEIGHT; box.size.y = BND_WIDGET_HEIGHT;
} }


~Button() {
if (quantity)
delete quantity;
}

void draw(NVGcontext *vg) override { 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()); bndToolButton(vg, 0.0, 0.0, box.size.x, box.size.y, BND_CORNER_NONE, state, -1, text.c_str());
Widget::draw(vg); Widget::draw(vg);
@@ -28,10 +37,14 @@ struct Button : OpaqueWidget {


void onDragStart(event::DragStart &e) override { void onDragStart(event::DragStart &e) override {
state = BND_ACTIVE; state = BND_ACTIVE;
if (quantity)
quantity->setMax();
} }


void onDragEnd(event::DragEnd &e) override { void onDragEnd(event::DragEnd &e) override {
state = BND_HOVER; state = BND_HOVER;
if (quantity)
quantity->setMin();
} }


void onDragDrop(event::DragDrop &e) override { void onDragDrop(event::DragDrop &e) override {


+ 1
- 0
include/ui/ChoiceButton.hpp View File

@@ -1,4 +1,5 @@
#pragma once #pragma once
#include "ui/common.hpp"
#include "ui/Button.hpp" #include "ui/Button.hpp"






+ 1
- 0
include/ui/IconButton.hpp View File

@@ -1,5 +1,6 @@
#pragma once #pragma once
#include "ui/common.hpp" #include "ui/common.hpp"
#include "ui/Button.hpp"




namespace rack { namespace rack {


+ 2
- 1
include/ui/Label.hpp View File

@@ -1,11 +1,12 @@
#pragma once #pragma once
#include "widgets/Widget.hpp"
#include "ui/common.hpp" #include "ui/common.hpp"




namespace rack { namespace rack {




struct Label : virtual Widget {
struct Label : VirtualWidget {
std::string text; std::string text;
float fontSize; float fontSize;
NVGcolor color; NVGcolor color;


+ 1
- 0
include/ui/List.hpp View File

@@ -1,4 +1,5 @@
#pragma once #pragma once
#include "widgets/OpaqueWidget.hpp"
#include "ui/common.hpp" #include "ui/common.hpp"






+ 2
- 0
include/ui/Menu.hpp View File

@@ -1,4 +1,6 @@
#pragma once #pragma once
#include "widgets/OpaqueWidget.hpp"
#include "ui/common.hpp"
#include "ui/MenuEntry.hpp" #include "ui/MenuEntry.hpp"






+ 1
- 0
include/ui/MenuEntry.hpp View File

@@ -1,4 +1,5 @@
#pragma once #pragma once
#include "widgets/OpaqueWidget.hpp"
#include "ui/common.hpp" #include "ui/common.hpp"






+ 1
- 1
include/ui/MenuItem.hpp View File

@@ -1,6 +1,6 @@
#pragma once #pragma once
#include "ui/common.hpp" #include "ui/common.hpp"
#include "ui/MenuOverlay.hpp"
#include "ui/MenuEntry.hpp"




namespace rack { namespace rack {


+ 1
- 0
include/ui/MenuLabel.hpp View File

@@ -1,5 +1,6 @@
#pragma once #pragma once
#include "ui/common.hpp" #include "ui/common.hpp"
#include "ui/MenuEntry.hpp"




namespace rack { namespace rack {


+ 1
- 0
include/ui/MenuOverlay.hpp View File

@@ -1,4 +1,5 @@
#pragma once #pragma once
#include "widgets/OpaqueWidget.hpp"
#include "ui/common.hpp" #include "ui/common.hpp"






+ 1
- 0
include/ui/MenuSeparator.hpp View File

@@ -1,5 +1,6 @@
#pragma once #pragma once
#include "ui/common.hpp" #include "ui/common.hpp"
#include "ui/MenuEntry.hpp"




namespace rack { namespace rack {


+ 11
- 4
include/ui/ProgressBar.hpp View File

@@ -1,19 +1,26 @@
#pragma once #pragma once
#include "ui/common.hpp" #include "ui/common.hpp"
#include "ui/QuantityWidget.hpp"




namespace rack { namespace rack {




struct ProgressBar : QuantityWidget {
struct ProgressBar : VirtualWidget {
Quantity *quantity = NULL;

ProgressBar() { ProgressBar() {
box.size.y = BND_WIDGET_HEIGHT; box.size.y = BND_WIDGET_HEIGHT;
} }


~ProgressBar() {
if (quantity)
delete quantity;
}

void draw(NVGcontext *vg) override { 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);
} }
}; };




+ 128
- 0
include/ui/Quantity.hpp View File

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

+ 0
- 49
include/ui/QuantityWidget.hpp View File

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

+ 26
- 9
include/ui/RadioButton.hpp View File

@@ -1,36 +1,53 @@
#pragma once #pragma once
#include "widgets/OpaqueWidget.hpp"
#include "ui/common.hpp" #include "ui/common.hpp"
#include "ui/QuantityWidget.hpp"




namespace rack { namespace rack {




struct RadioButton : OpaqueWidget, QuantityWidget {
struct RadioButton : OpaqueWidget {
BNDwidgetState state = BND_DEFAULT; BNDwidgetState state = BND_DEFAULT;
Quantity *quantity = NULL;


RadioButton() { RadioButton() {
box.size.y = BND_WIDGET_HEIGHT; box.size.y = BND_WIDGET_HEIGHT;
} }


~RadioButton() {
if (quantity)
delete quantity;
}

void draw(NVGcontext *vg) override { 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 { void onEnter(event::Enter &e) override {
state = BND_HOVER;
if (state != BND_ACTIVE)
state = BND_HOVER;
} }


void onLeave(event::Leave &e) override { void onLeave(event::Leave &e) override {
state = BND_DEFAULT;
if (state != BND_ACTIVE)
state = BND_DEFAULT;
} }


void onDragDrop(event::DragDrop &e) override { void onDragDrop(event::DragDrop &e) override {
if (e.origin == this) { 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; event::Action eAction;
onAction(eAction); onAction(eAction);


+ 1
- 0
include/ui/Scene.hpp View File

@@ -1,4 +1,5 @@
#pragma once #pragma once
#include "widgets/OpaqueWidget.hpp"
#include "ui/common.hpp" #include "ui/common.hpp"






+ 1
- 0
include/ui/ScrollWidget.hpp View File

@@ -1,4 +1,5 @@
#pragma once #pragma once
#include "widgets/OpaqueWidget.hpp"
#include "ui/common.hpp" #include "ui/common.hpp"
#include "event.hpp" #include "event.hpp"




+ 2
- 1
include/ui/SequentialLayout.hpp View File

@@ -1,4 +1,5 @@
#pragma once #pragma once
#include "widgets/Widget.hpp"
#include "ui/common.hpp" #include "ui/common.hpp"




@@ -6,7 +7,7 @@ namespace rack {




/** Positions children in a row/column based on their widths/heights */ /** Positions children in a row/column based on their widths/heights */
struct SequentialLayout : virtual Widget {
struct SequentialLayout : VirtualWidget {
enum Orientation { enum Orientation {
HORIZONTAL_ORIENTATION, HORIZONTAL_ORIENTATION,
VERTICAL_ORIENTATION, VERTICAL_ORIENTATION,


+ 17
- 6
include/ui/Slider.hpp View File

@@ -1,6 +1,7 @@
#pragma once #pragma once
#include "widgets/OpaqueWidget.hpp"
#include "ui/Quantity.hpp"
#include "ui/common.hpp" #include "ui/common.hpp"
#include "ui/QuantityWidget.hpp"




namespace rack { namespace rack {
@@ -9,16 +10,23 @@ namespace rack {
static const float SLIDER_SENSITIVITY = 0.001f; static const float SLIDER_SENSITIVITY = 0.001f;




struct Slider : OpaqueWidget, QuantityWidget {
struct Slider : OpaqueWidget {
BNDwidgetState state = BND_DEFAULT; BNDwidgetState state = BND_DEFAULT;
Quantity *quantity = NULL;


Slider() { Slider() {
box.size.y = BND_WIDGET_HEIGHT; box.size.y = BND_WIDGET_HEIGHT;
} }


~Slider() {
if (quantity)
delete quantity;
}

void draw(NVGcontext *vg) override { 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 { void onDragStart(event::DragStart &e) override {
@@ -27,7 +35,9 @@ struct Slider : OpaqueWidget, QuantityWidget {
} }


void onDragMove(event::DragMove &e) override { 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 { void onDragEnd(event::DragEnd &e) override {
@@ -37,7 +47,8 @@ struct Slider : OpaqueWidget, QuantityWidget {


void onButton(event::Button &e) override { void onButton(event::Button &e) override {
if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_RIGHT) { if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_RIGHT) {
setValue(defaultValue);
if (quantity)
quantity->reset();
} }
e.target = this; e.target = this;
} }


+ 1
- 0
include/ui/TextField.hpp View File

@@ -1,4 +1,5 @@
#pragma once #pragma once
#include "widgets/OpaqueWidget.hpp"
#include "ui/common.hpp" #include "ui/common.hpp"
#include "event.hpp" #include "event.hpp"




+ 2
- 1
include/ui/Tooltip.hpp View File

@@ -1,11 +1,12 @@
#pragma once #pragma once
#include "widgets/Widget.hpp"
#include "ui/common.hpp" #include "ui/common.hpp"




namespace rack { namespace rack {




struct Tooltip : virtual Widget {
struct Tooltip : VirtualWidget {
std::string text; std::string text;


void draw(NVGcontext *vg) override { void draw(NVGcontext *vg) override {


+ 1
- 0
include/ui/TooltipOverlay.hpp View File

@@ -1,4 +1,5 @@
#pragma once #pragma once
#include "widgets/TransparentWidget.hpp"
#include "ui/common.hpp" #include "ui/common.hpp"






+ 1
- 0
include/ui/WindowOverlay.hpp View File

@@ -1,4 +1,5 @@
#pragma once #pragma once
#include "widgets/OpaqueWidget.hpp"
#include "ui/common.hpp" #include "ui/common.hpp"






+ 1
- 0
include/ui/WindowWidget.hpp View File

@@ -1,4 +1,5 @@
#pragma once #pragma once
#include "widgets/OpaqueWidget.hpp"
#include "ui/common.hpp" #include "ui/common.hpp"






+ 0
- 1
include/ui/common.hpp View File

@@ -1,5 +1,4 @@
#pragma once #pragma once
#include "widgets.hpp"
#include "blendish.h" #include "blendish.h"


#define CHECKMARK_STRING "✔" #define CHECKMARK_STRING "✔"


+ 1
- 1
include/widgets/FramebufferWidget.hpp View File

@@ -9,7 +9,7 @@ namespace rack {
When `dirty` is true, its children will be re-rendered on the next call to step() override. 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. 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 */ /** Set this to true to re-render the children to the framebuffer the next time it is drawn */
bool dirty = true; bool dirty = true;
/** A margin in pixels around the children in the framebuffer /** A margin in pixels around the children in the framebuffer


+ 1
- 1
include/widgets/OpaqueWidget.hpp View File

@@ -9,7 +9,7 @@ namespace rack {
You can of course override the events. You can of course override the events.
You may also call OpaqueWidget::on*() from the overridden method to continue recursing/consuming the event. 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 { void onHover(event::Hover &e) override {
Widget::onHover(e); Widget::onHover(e);
if (!e.target) if (!e.target)


+ 1
- 1
include/widgets/SVGWidget.hpp View File

@@ -6,7 +6,7 @@ namespace rack {




/** Draws an SVG */ /** Draws an SVG */
struct SVGWidget : virtual Widget {
struct SVGWidget : VirtualWidget {
std::shared_ptr<SVG> svg; std::shared_ptr<SVG> svg;


/** Sets the box size to the svg image size */ /** Sets the box size to the svg image size */


+ 1
- 1
include/widgets/TransformWidget.hpp View File

@@ -6,7 +6,7 @@ namespace rack {




/** Transforms appearance only, not positions of events */ /** Transforms appearance only, not positions of events */
struct TransformWidget : virtual Widget {
struct TransformWidget : VirtualWidget {
/** The transformation matrix */ /** The transformation matrix */
float transform[6]; float transform[6];




+ 1
- 1
include/widgets/TransparentWidget.hpp View File

@@ -6,7 +6,7 @@ namespace rack {




/** Widget that does not respond to events and does not pass events to children */ /** 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. */ /** Override behavior to do nothing instead. */
void onHover(event::Hover &e) override {} void onHover(event::Hover &e) override {}
void onButton(event::Button &e) override {} void onButton(event::Button &e) override {}


+ 7
- 1
include/widgets/Widget.hpp View File

@@ -12,7 +12,7 @@ namespace rack {


/** A node in the 2D scene graph /** A node in the 2D scene graph
It is recommended to inherit virtually from Widget instead of directly. It is recommended to inherit virtually from Widget instead of directly.
e.g. `struct MyWidget : virtual Widget {}`
e.g. `struct MyWidget : VirtualWidget {}`
*/ */
struct Widget { struct Widget {
/** Stores position and size */ /** 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 } // namespace rack

+ 1
- 1
include/widgets/ZoomWidget.hpp View File

@@ -5,7 +5,7 @@
namespace rack { namespace rack {




struct ZoomWidget : virtual Widget {
struct ZoomWidget : VirtualWidget {
float zoom = 1.f; float zoom = 1.f;


Vec getRelativeOffset(Vec v, Widget *relative) override { Vec getRelativeOffset(Vec v, Widget *relative) override {


+ 1
- 1
src/app/AudioWidget.cpp View File

@@ -1,4 +1,4 @@
#include "app.hpp"
#include "app/AudioWidget.hpp"
#include "audio.hpp" #include "audio.hpp"
#include "helpers.hpp" #include "helpers.hpp"




+ 0
- 52
src/app/Knob.cpp View File

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

+ 13
- 3
src/app/ModuleBrowser.cpp View File

@@ -44,9 +44,20 @@ static bool isModelMatch(Model *model, std::string search) {
} }




struct FavoriteQuantity : Quantity {
std::string getString() override {
return "★";
}
};


struct FavoriteRadioButton : RadioButton { struct FavoriteRadioButton : RadioButton {
Model *model = NULL; Model *model = NULL;


FavoriteRadioButton() {
quantity = new FavoriteQuantity;
}

void onAction(event::Action &e) override; void onAction(event::Action &e) override;
}; };


@@ -111,13 +122,12 @@ struct ModelItem : BrowserListItem {


FavoriteRadioButton *favoriteButton = createWidget<FavoriteRadioButton>(Vec(8, itemMargin)); FavoriteRadioButton *favoriteButton = createWidget<FavoriteRadioButton>(Vec(8, itemMargin));
favoriteButton->box.size.x = 20; favoriteButton->box.size.x = 20;
favoriteButton->label = "★";
addChild(favoriteButton); addChild(favoriteButton);


// Set favorite button initial state // Set favorite button initial state
auto it = sFavoriteModels.find(model); auto it = sFavoriteModels.find(model);
if (it != sFavoriteModels.end()) if (it != sFavoriteModels.end())
favoriteButton->setValue(1);
favoriteButton->quantity->setValue(1);
favoriteButton->model = model; favoriteButton->model = model;


Label *nameLabel = createWidget<Label>(favoriteButton->box.getTopRight()); Label *nameLabel = createWidget<Label>(favoriteButton->box.getTopRight());
@@ -465,7 +475,7 @@ void ClearFilterItem::onAction(event::Action &e) {
void FavoriteRadioButton::onAction(event::Action &e) { void FavoriteRadioButton::onAction(event::Action &e) {
if (!model) if (!model)
return; return;
if (value) {
if (quantity->isMax()) {
sFavoriteModels.insert(model); sFavoriteModels.insert(model);
} }
else { else {


+ 16
- 27
src/app/ModuleWidget.cpp View File

@@ -61,18 +61,20 @@ json_t *ModuleWidget::toJson() {


// plugin // plugin
json_object_set_new(rootJ, "plugin", json_string(model->plugin->slug.c_str())); json_object_set_new(rootJ, "plugin", json_string(model->plugin->slug.c_str()));
// version (of plugin)
// version of plugin
if (!model->plugin->version.empty()) if (!model->plugin->version.empty())
json_object_set_new(rootJ, "version", json_string(model->plugin->version.c_str())); json_object_set_new(rootJ, "version", json_string(model->plugin->version.c_str()));
// model // model
json_object_set_new(rootJ, "model", json_string(model->slug.c_str())); json_object_set_new(rootJ, "model", json_string(model->slug.c_str()));
// params // 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 &param : 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 // data
if (module) { if (module) {
json_t *dataJ = module->toJson(); json_t *dataJ = module->toJson();
@@ -126,28 +128,15 @@ void ModuleWidget::fromJson(json_t *rootJ) {
size_t i; size_t i;
json_t *paramJ; json_t *paramJ;
json_array_foreach(paramsJ, i, 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);
} }
} }




+ 6
- 2
src/app/MomentarySwitch.cpp View File

@@ -5,11 +5,15 @@ namespace rack {




void MomentarySwitch::onDragStart(event::DragStart &e) { void MomentarySwitch::onDragStart(event::DragStart &e) {
setValue(maxValue);
if (quantity) {
quantity->setMax();
}
} }


void MomentarySwitch::onDragEnd(event::DragEnd &e) { void MomentarySwitch::onDragEnd(event::DragEnd &e) {
setValue(minValue);
if (quantity) {
quantity->setMin();
}
} }






+ 16
- 30
src/app/ParamWidget.cpp View File

@@ -1,4 +1,4 @@
#include "app.hpp"
#include "app/ParamWidget.hpp"
#include "engine.hpp" #include "engine.hpp"
#include "random.hpp" #include "random.hpp"


@@ -6,33 +6,28 @@
namespace rack { 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) { void ParamWidget::fromJson(json_t *rootJ) {
json_t *valueJ = json_object_get(rootJ, "value"); 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() { 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() { 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); OpaqueWidget::onButton(e);
if (e.target == this) { if (e.target == this) {
if (e.action == GLFW_PRESS && e.button == GLFW_MOUSE_BUTTON_RIGHT) { 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 } // namespace rack

+ 21
- 8
src/app/PluginManagerWidget.cpp View File

@@ -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 { 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; ProgressBar *downloadProgress = new DownloadProgressBar;
downloadProgress->box.size.x = 300; downloadProgress->box.size.x = 300;
downloadProgress->setLimits(0, 100);
downloadProgress->unit = "%";
downloadWidget->addChild(downloadProgress); downloadWidget->addChild(downloadProgress);


// Button *cancelButton = new CancelButton; // Button *cancelButton = new CancelButton;


+ 5
- 5
src/app/SVGKnob.cpp View File

@@ -27,14 +27,14 @@ void SVGKnob::setSVG(std::shared_ptr<SVG> svg) {


void SVGKnob::step() { void SVGKnob::step() {
// Re-transform TransformWidget if dirty // Re-transform TransformWidget if dirty
if (dirty) {
if (dirty && quantity) {
float angle; 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 { 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(); tw->identity();
// Rotate SVG // Rotate SVG


+ 5
- 2
src/app/SVGSlider.cpp View File

@@ -23,9 +23,12 @@ void SVGSlider::setSVGs(std::shared_ptr<SVG> backgroundSVG, std::shared_ptr<SVG>
} }


void SVGSlider::step() { void SVGSlider::step() {
if (dirty) {
if (dirty && quantity) {
// Interpolate handle position // 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(); FramebufferWidget::step();
} }


+ 6
- 4
src/app/SVGSwitch.cpp View File

@@ -20,10 +20,12 @@ void SVGSwitch::addFrame(std::shared_ptr<SVG> svg) {


void SVGSwitch::onChange(event::Change &e) { void SVGSwitch::onChange(event::Change &e) {
assert(frames.size() > 0); 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); ParamWidget::onChange(e);
} }




+ 6
- 4
src/app/ToggleSwitch.cpp View File

@@ -7,10 +7,12 @@ namespace rack {
void ToggleSwitch::onDragStart(event::DragStart &e) { void ToggleSwitch::onDragStart(event::DragStart &e) {
// Cycle through values // Cycle through values
// e.g. a range of [0.0, 3.0] would have modes 0, 1, 2, and 3. // 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);
}
} }






+ 58
- 32
src/app/Toolbar.cpp View File

@@ -10,12 +10,11 @@ namespace rack {


struct TooltipIconButton : IconButton { struct TooltipIconButton : IconButton {
Tooltip *tooltip = NULL; Tooltip *tooltip = NULL;
std::string tooltipText;
void onEnter(event::Enter &e) override { void onEnter(event::Enter &e) override {
if (!tooltip) { if (!tooltip) {
tooltip = new Tooltip; tooltip = new Tooltip;
tooltip->box.pos = getAbsoluteOffset(Vec(0, BND_WIDGET_HEIGHT)); tooltip->box.pos = getAbsoluteOffset(Vec(0, BND_WIDGET_HEIGHT));
tooltip->text = tooltipText;
tooltip->text = getTooltipText();
gRackScene->addChild(tooltip); gRackScene->addChild(tooltip);
} }
IconButton::onEnter(e); IconButton::onEnter(e);
@@ -28,13 +27,14 @@ struct TooltipIconButton : IconButton {
} }
IconButton::onLeave(e); IconButton::onLeave(e);
} }
virtual std::string getTooltipText() {return "";}
}; };


struct NewButton : TooltipIconButton { struct NewButton : TooltipIconButton {
NewButton() { NewButton() {
setSVG(SVG::load(asset::global("res/icons/noun_146097_cc.svg"))); 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 { void onAction(event::Action &e) override {
gRackWidget->reset(); gRackWidget->reset();
} }
@@ -43,8 +43,8 @@ struct NewButton : TooltipIconButton {
struct OpenButton : TooltipIconButton { struct OpenButton : TooltipIconButton {
OpenButton() { OpenButton() {
setSVG(SVG::load(asset::global("res/icons/noun_31859_cc.svg"))); 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 { void onAction(event::Action &e) override {
gRackWidget->loadDialog(); gRackWidget->loadDialog();
} }
@@ -53,8 +53,8 @@ struct OpenButton : TooltipIconButton {
struct SaveButton : TooltipIconButton { struct SaveButton : TooltipIconButton {
SaveButton() { SaveButton() {
setSVG(SVG::load(asset::global("res/icons/noun_1343816_cc.svg"))); 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 { void onAction(event::Action &e) override {
gRackWidget->saveDialog(); gRackWidget->saveDialog();
} }
@@ -63,8 +63,8 @@ struct SaveButton : TooltipIconButton {
struct SaveAsButton : TooltipIconButton { struct SaveAsButton : TooltipIconButton {
SaveAsButton() { SaveAsButton() {
setSVG(SVG::load(asset::global("res/icons/noun_1343811_cc.svg"))); 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 { void onAction(event::Action &e) override {
gRackWidget->saveAsDialog(); gRackWidget->saveAsDialog();
} }
@@ -73,8 +73,8 @@ struct SaveAsButton : TooltipIconButton {
struct RevertButton : TooltipIconButton { struct RevertButton : TooltipIconButton {
RevertButton() { RevertButton() {
setSVG(SVG::load(asset::global("res/icons/noun_1084369_cc.svg"))); 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 { void onAction(event::Action &e) override {
gRackWidget->revert(); gRackWidget->revert();
} }
@@ -83,8 +83,8 @@ struct RevertButton : TooltipIconButton {
struct DisconnectCablesButton : TooltipIconButton { struct DisconnectCablesButton : TooltipIconButton {
DisconnectCablesButton() { DisconnectCablesButton() {
setSVG(SVG::load(asset::global("res/icons/noun_1745061_cc.svg"))); 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 { void onAction(event::Action &e) override {
gRackWidget->disconnect(); gRackWidget->disconnect();
} }
@@ -93,8 +93,8 @@ struct DisconnectCablesButton : TooltipIconButton {
struct PowerMeterButton : TooltipIconButton { struct PowerMeterButton : TooltipIconButton {
PowerMeterButton() { PowerMeterButton() {
setSVG(SVG::load(asset::global("res/icons/noun_305536_cc.svg"))); 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 { void onAction(event::Action &e) override {
gPowerMeter ^= true; gPowerMeter ^= true;
} }
@@ -117,8 +117,8 @@ struct SampleRateItem : MenuItem {
struct SampleRateButton : TooltipIconButton { struct SampleRateButton : TooltipIconButton {
SampleRateButton() { SampleRateButton() {
setSVG(SVG::load(asset::global("res/icons/noun_1240789_cc.svg"))); 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 { void onAction(event::Action &e) override {
Menu *menu = createMenu(); Menu *menu = createMenu();
menu->box.pos = getAbsoluteOffset(Vec(0, box.size.y)); menu->box.pos = getAbsoluteOffset(Vec(0, box.size.y));
@@ -144,18 +144,52 @@ struct SampleRateButton : TooltipIconButton {
struct RackLockButton : TooltipIconButton { struct RackLockButton : TooltipIconButton {
RackLockButton() { RackLockButton() {
setSVG(SVG::load(asset::global("res/icons/noun_468341_cc.svg"))); 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 { void onAction(event::Action &e) override {
gRackWidget->lockModules ^= true; 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 PowerMeterButton);
layout->addChild(new RackLockButton); layout->addChild(new RackLockButton);


wireOpacitySlider = new Slider;
Slider *wireOpacitySlider = new Slider;
WireOpacityQuantity *wireOpacityQuantity = new WireOpacityQuantity;
wireOpacitySlider->quantity = wireOpacityQuantity;
wireOpacitySlider->box.size.x = 150; 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); layout->addChild(wireOpacitySlider);


wireTensionSlider = new Slider;
Slider *wireTensionSlider = new Slider;
WireTensionQuantity *wireTensionQuantity = new WireTensionQuantity;
wireTensionSlider->quantity = wireTensionQuantity;
wireTensionSlider->box.size.x = 150; wireTensionSlider->box.size.x = 150;
wireTensionSlider->label = "Cable tension";
wireTensionSlider->unit = "";
wireTensionSlider->setLimits(0.0, 1.0);
wireTensionSlider->setDefaultValue(0.5);
layout->addChild(wireTensionSlider); layout->addChild(wireTensionSlider);


zoomSlider = new ZoomSlider;
Slider *zoomSlider = new Slider;
ZoomQuantity *zoomQuantity = new ZoomQuantity;
zoomSlider->quantity = zoomQuantity;
zoomSlider->box.size.x = 150; 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); layout->addChild(zoomSlider);


// Kind of hacky, but display the PluginManagerWidget only if the local directory is not the development directory // Kind of hacky, but display the PluginManagerWidget only if the local directory is not the development directory


+ 6
- 6
src/app/WireWidget.cpp View File

@@ -153,17 +153,17 @@ json_t *WireWidget::toJson() {
void WireWidget::fromJson(json_t *rootJ) { void WireWidget::fromJson(json_t *rootJ) {
json_t *colorJ = json_object_get(rootJ, "color"); json_t *colorJ = json_object_get(rootJ, "color");
if (colorJ) { 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)); color = color::fromHexString(json_string_value(colorJ));
} }
} }


void WireWidget::draw(NVGcontext *vg) { 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; WireWidget *activeWire = gRackWidget->wireContainer->activeWire;
if (activeWire) { if (activeWire) {


+ 18
- 1
src/engine.cpp View File

@@ -69,10 +69,27 @@ static int smoothParamId;
static float smoothValue; 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() { float Light::getBrightness() {
// LEDs are diodes, so don't allow reverse current. // LEDs are diodes, so don't allow reverse current.
// For some reason, instead of the RMS, the sqrt of RMS looks better // 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) { void Light::setBrightnessSmooth(float brightness, float frames) {


+ 4
- 5
src/settings.cpp View File

@@ -31,12 +31,12 @@ static json_t *settingsToJson() {
} }


// opacity // opacity
float opacity = gToolbar->wireOpacitySlider->value;
float opacity = gToolbar->wireOpacity;
json_t *opacityJ = json_real(opacity); json_t *opacityJ = json_real(opacity);
json_object_set_new(rootJ, "wireOpacity", opacityJ); json_object_set_new(rootJ, "wireOpacity", opacityJ);


// tension // tension
float tension = gToolbar->wireTensionSlider->value;
float tension = gToolbar->wireTension;
json_t *tensionJ = json_real(tension); json_t *tensionJ = json_real(tension);
json_object_set_new(rootJ, "wireTension", tensionJ); json_object_set_new(rootJ, "wireTension", tensionJ);


@@ -99,18 +99,17 @@ static void settingsFromJson(json_t *rootJ) {
// opacity // opacity
json_t *opacityJ = json_object_get(rootJ, "wireOpacity"); json_t *opacityJ = json_object_get(rootJ, "wireOpacity");
if (opacityJ) if (opacityJ)
gToolbar->wireOpacitySlider->value = json_number_value(opacityJ);
gToolbar->wireOpacity = json_number_value(opacityJ);


// tension // tension
json_t *tensionJ = json_object_get(rootJ, "wireTension"); json_t *tensionJ = json_object_get(rootJ, "wireTension");
if (tensionJ) if (tensionJ)
gToolbar->wireTensionSlider->value = json_number_value(tensionJ);
gToolbar->wireTension = json_number_value(tensionJ);


// zoom // zoom
json_t *zoomJ = json_object_get(rootJ, "zoom"); json_t *zoomJ = json_object_get(rootJ, "zoom");
if (zoomJ) { if (zoomJ) {
gRackScene->zoomWidget->setZoom(clamp((float) json_number_value(zoomJ), 0.25f, 4.0f)); gRackScene->zoomWidget->setZoom(clamp((float) json_number_value(zoomJ), 0.25f, 4.0f));
gToolbar->zoomSlider->setValue(json_number_value(zoomJ) * 100.0);
} }


// allowCursorLock // allowCursorLock


+ 8
- 5
src/string.cpp View File

@@ -63,13 +63,16 @@ std::string filename(std::string path) {
return filename; 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) { 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 } // namespace rack

+ 2
- 2
src/system.cpp View File

@@ -13,8 +13,8 @@ namespace rack {
namespace system { 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()); DIR *dir = opendir(path.c_str());
if (dir) { if (dir) {
struct dirent *d; struct dirent *d;


Loading…
Cancel
Save