@@ -1 +1 @@ | |||||
Subproject commit 1f9c8864fc556a1be4d4bf1d6bfe20cde25734b4 | |||||
Subproject commit d201a1cf69940450960db87c8e30b883087c9d01 |
@@ -44,6 +44,8 @@ extern bool invertZoom; | |||||
extern float cableOpacity; | extern float cableOpacity; | ||||
/** Straightness of cables in the range [0, 1]. Unitless and arbitrary. */ | /** Straightness of cables in the range [0, 1]. Unitless and arbitrary. */ | ||||
extern float cableTension; | extern float cableTension; | ||||
extern float rackBrightness; | |||||
extern float haloBrightness; | |||||
/** Allows rack to hide and lock the cursor position when dragging knobs etc. */ | /** Allows rack to hide and lock the cursor position when dragging knobs etc. */ | ||||
extern bool allowCursorLock; | extern bool allowCursorLock; | ||||
enum KnobMode { | enum KnobMode { | ||||
@@ -1,5 +1,6 @@ | |||||
#include <app/LightWidget.hpp> | #include <app/LightWidget.hpp> | ||||
#include <color.hpp> | #include <color.hpp> | ||||
#include <settings.hpp> | |||||
namespace rack { | namespace rack { | ||||
@@ -7,26 +8,15 @@ namespace app { | |||||
void LightWidget::draw(const DrawArgs& args) { | void LightWidget::draw(const DrawArgs& args) { | ||||
drawLight(args); | |||||
TransparentWidget::draw(args); | |||||
drawHalo(args); | |||||
} | |||||
void LightWidget::drawLight(const DrawArgs& args) { | |||||
float radius = std::min(box.size.x, box.size.y) / 2.0; | float radius = std::min(box.size.x, box.size.y) / 2.0; | ||||
nvgBeginPath(args.vg); | nvgBeginPath(args.vg); | ||||
nvgCircle(args.vg, radius, radius, radius); | nvgCircle(args.vg, radius, radius, radius); | ||||
// Background | // Background | ||||
if (bgColor.a > 0.0) { | if (bgColor.a > 0.0) { | ||||
nvgFillColor(args.vg, bgColor); | |||||
nvgFill(args.vg); | |||||
} | |||||
// Foreground | |||||
if (color.a > 0.0) { | |||||
nvgFillColor(args.vg, color); | |||||
// TODO Set color in TGrayModuleLightWidget instead. | |||||
nvgFillColor(args.vg, color::mult(bgColor, 0.5)); | |||||
// nvgFillColor(args.vg, bgColor); | |||||
nvgFill(args.vg); | nvgFill(args.vg); | ||||
} | } | ||||
@@ -36,21 +26,47 @@ void LightWidget::drawLight(const DrawArgs& args) { | |||||
nvgStrokeColor(args.vg, borderColor); | nvgStrokeColor(args.vg, borderColor); | ||||
nvgStroke(args.vg); | nvgStroke(args.vg); | ||||
} | } | ||||
// Child widgets | |||||
// TODO Upload new graphics instead of use this hack. | |||||
// nvgGlobalAlpha(args.vg, 0.5); | |||||
TransparentWidget::draw(args); | |||||
// Dynamic light and halo | |||||
nvgGlobalAlpha(args.vg, 1.0); | |||||
// Use the formula `lightColor * (1 - dest) + dest` for blending | |||||
nvgGlobalCompositeBlendFunc(args.vg, NVG_ONE_MINUS_DST_COLOR, NVG_ONE); | |||||
drawLight(args); | |||||
drawHalo(args); | |||||
} | |||||
void LightWidget::drawLight(const DrawArgs& args) { | |||||
// Foreground | |||||
if (color.a > 0.0) { | |||||
float radius = std::min(box.size.x, box.size.y) / 2.0; | |||||
nvgBeginPath(args.vg); | |||||
nvgCircle(args.vg, radius, radius, radius); | |||||
nvgFillColor(args.vg, color); | |||||
nvgFill(args.vg); | |||||
} | |||||
} | } | ||||
void LightWidget::drawHalo(const DrawArgs& args) { | void LightWidget::drawHalo(const DrawArgs& args) { | ||||
const float halo = settings::haloBrightness; | |||||
if (halo == 0.f) | |||||
return; | |||||
float radius = std::min(box.size.x, box.size.y) / 2.0; | float radius = std::min(box.size.x, box.size.y) / 2.0; | ||||
float oradius = radius + 20.0; | |||||
float oradius = std::min(radius * 5.f, 30.f); | |||||
nvgBeginPath(args.vg); | nvgBeginPath(args.vg); | ||||
nvgRect(args.vg, radius - oradius, radius - oradius, 2 * oradius, 2 * oradius); | nvgRect(args.vg, radius - oradius, radius - oradius, 2 * oradius, 2 * oradius); | ||||
NVGpaint paint; | |||||
NVGcolor icol = color::mult(color, 0.2); | |||||
NVGcolor ocol = nvgRGB(0, 0, 0); | |||||
paint = nvgRadialGradient(args.vg, radius, radius, radius, oradius, icol, ocol); | |||||
NVGcolor icol = color::mult(color, halo); | |||||
NVGcolor ocol = nvgRGBA(0, 0, 0, 0); | |||||
NVGpaint paint = nvgRadialGradient(args.vg, radius, radius, radius, oradius, icol, ocol); | |||||
nvgFillPaint(args.vg, paint); | nvgFillPaint(args.vg, paint); | ||||
nvgGlobalCompositeOperation(args.vg, NVG_LIGHTER); | |||||
nvgFill(args.vg); | nvgFill(args.vg); | ||||
} | } | ||||
@@ -189,6 +189,8 @@ struct FileButton : MenuButton { | |||||
revertItem->disabled = (APP->patch->path == ""); | revertItem->disabled = (APP->patch->path == ""); | ||||
menu->addChild(revertItem); | menu->addChild(revertItem); | ||||
menu->addChild(new ui::MenuSeparator); | |||||
QuitItem* quitItem = new QuitItem; | QuitItem* quitItem = new QuitItem; | ||||
quitItem->text = "Quit"; | quitItem->text = "Quit"; | ||||
quitItem->rightText = RACK_MOD_CTRL_NAME "+Q"; | quitItem->rightText = RACK_MOD_CTRL_NAME "+Q"; | ||||
@@ -248,7 +250,7 @@ struct EditButton : MenuButton { | |||||
struct ZoomQuantity : Quantity { | struct ZoomQuantity : Quantity { | ||||
void setValue(float value) override { | void setValue(float value) override { | ||||
settings::zoom = value; | |||||
settings::zoom = math::clamp(value, getMinValue(), getMaxValue()); | |||||
} | } | ||||
float getValue() override { | float getValue() override { | ||||
return settings::zoom; | return settings::zoom; | ||||
@@ -275,7 +277,6 @@ struct ZoomQuantity : Quantity { | |||||
return "%"; | return "%"; | ||||
} | } | ||||
}; | }; | ||||
struct ZoomSlider : ui::Slider { | struct ZoomSlider : ui::Slider { | ||||
ZoomSlider() { | ZoomSlider() { | ||||
quantity = new ZoomQuantity; | quantity = new ZoomQuantity; | ||||
@@ -308,7 +309,6 @@ struct CableOpacityQuantity : Quantity { | |||||
return "%"; | return "%"; | ||||
} | } | ||||
}; | }; | ||||
struct CableOpacitySlider : ui::Slider { | struct CableOpacitySlider : ui::Slider { | ||||
CableOpacitySlider() { | CableOpacitySlider() { | ||||
quantity = new CableOpacityQuantity; | quantity = new CableOpacityQuantity; | ||||
@@ -335,7 +335,6 @@ struct CableTensionQuantity : Quantity { | |||||
return 2; | return 2; | ||||
} | } | ||||
}; | }; | ||||
struct CableTensionSlider : ui::Slider { | struct CableTensionSlider : ui::Slider { | ||||
CableTensionSlider() { | CableTensionSlider() { | ||||
quantity = new CableTensionQuantity; | quantity = new CableTensionQuantity; | ||||
@@ -345,6 +344,76 @@ struct CableTensionSlider : ui::Slider { | |||||
} | } | ||||
}; | }; | ||||
struct RackBrightnessQuantity : Quantity { | |||||
void setValue(float value) override { | |||||
settings::rackBrightness = math::clamp(value, getMinValue(), getMaxValue()); | |||||
} | |||||
float getValue() override { | |||||
return settings::rackBrightness; | |||||
} | |||||
float getDefaultValue() override { | |||||
return 1.0; | |||||
} | |||||
float getDisplayValue() override { | |||||
return getValue() * 100; | |||||
} | |||||
void setDisplayValue(float displayValue) override { | |||||
setValue(displayValue / 100); | |||||
} | |||||
std::string getUnit() override { | |||||
return "%"; | |||||
} | |||||
std::string getLabel() override { | |||||
return "Room brightness"; | |||||
} | |||||
int getDisplayPrecision() override { | |||||
return 3; | |||||
} | |||||
}; | |||||
struct RackBrightnessSlider : ui::Slider { | |||||
RackBrightnessSlider() { | |||||
quantity = new RackBrightnessQuantity; | |||||
} | |||||
~RackBrightnessSlider() { | |||||
delete quantity; | |||||
} | |||||
}; | |||||
struct HaloBrightnessQuantity : Quantity { | |||||
void setValue(float value) override { | |||||
settings::haloBrightness = math::clamp(value, getMinValue(), getMaxValue()); | |||||
} | |||||
float getValue() override { | |||||
return settings::haloBrightness; | |||||
} | |||||
float getDefaultValue() override { | |||||
return 0.0; | |||||
} | |||||
float getDisplayValue() override { | |||||
return getValue() * 100; | |||||
} | |||||
void setDisplayValue(float displayValue) override { | |||||
setValue(displayValue / 100); | |||||
} | |||||
std::string getUnit() override { | |||||
return "%"; | |||||
} | |||||
std::string getLabel() override { | |||||
return "Light bloom"; | |||||
} | |||||
int getDisplayPrecision() override { | |||||
return 3; | |||||
} | |||||
}; | |||||
struct HaloBrightnessSlider : ui::Slider { | |||||
HaloBrightnessSlider() { | |||||
quantity = new HaloBrightnessQuantity; | |||||
} | |||||
~HaloBrightnessSlider() { | |||||
delete quantity; | |||||
} | |||||
}; | |||||
struct TooltipsItem : ui::MenuItem { | struct TooltipsItem : ui::MenuItem { | ||||
void onAction(const ActionEvent& e) override { | void onAction(const ActionEvent& e) override { | ||||
settings::tooltips ^= true; | settings::tooltips ^= true; | ||||
@@ -470,6 +539,14 @@ struct ViewButton : MenuButton { | |||||
cableTensionSlider->box.size.x = 200.0; | cableTensionSlider->box.size.x = 200.0; | ||||
menu->addChild(cableTensionSlider); | menu->addChild(cableTensionSlider); | ||||
RackBrightnessSlider* rackBrightnessSlider = new RackBrightnessSlider; | |||||
rackBrightnessSlider->box.size.x = 200.0; | |||||
menu->addChild(rackBrightnessSlider); | |||||
HaloBrightnessSlider* haloBrightnessSlider = new HaloBrightnessSlider; | |||||
haloBrightnessSlider->box.size.x = 200.0; | |||||
menu->addChild(haloBrightnessSlider); | |||||
FrameRateItem* frameRateItem = new FrameRateItem; | FrameRateItem* frameRateItem = new FrameRateItem; | ||||
frameRateItem->text = "Frame rate"; | frameRateItem->text = "Frame rate"; | ||||
frameRateItem->rightText = RIGHT_ARROW; | frameRateItem->rightText = RIGHT_ARROW; | ||||
@@ -97,6 +97,10 @@ void RackWidget::step() { | |||||
} | } | ||||
void RackWidget::draw(const DrawArgs& args) { | void RackWidget::draw(const DrawArgs& args) { | ||||
// Darken all children by user setting | |||||
float b = std::pow(settings::rackBrightness, 1.f); | |||||
nvgGlobalTint(args.vg, nvgRGBAf(b, b, b, 1)); | |||||
// Resize and reposition the RackRail to align on the grid. | // Resize and reposition the RackRail to align on the grid. | ||||
math::Rect railBox; | math::Rect railBox; | ||||
railBox.pos = args.clipBox.pos.div(BUS_BOARD_GRID_SIZE).floor().mult(BUS_BOARD_GRID_SIZE); | railBox.pos = args.clipBox.pos.div(BUS_BOARD_GRID_SIZE).floor().mult(BUS_BOARD_GRID_SIZE); | ||||
@@ -27,6 +27,8 @@ float zoom = 0.25; | |||||
bool invertZoom = false; | bool invertZoom = false; | ||||
float cableOpacity = 0.5; | float cableOpacity = 0.5; | ||||
float cableTension = 0.5; | float cableTension = 0.5; | ||||
float rackBrightness = 1.0; | |||||
float haloBrightness = 0.0; | |||||
bool allowCursorLock = true; | bool allowCursorLock = true; | ||||
KnobMode knobMode = KNOB_MODE_LINEAR; | KnobMode knobMode = KNOB_MODE_LINEAR; | ||||
bool knobScroll = false; | bool knobScroll = false; | ||||
@@ -103,6 +105,10 @@ json_t* toJson() { | |||||
json_object_set_new(rootJ, "cableTension", json_real(cableTension)); | json_object_set_new(rootJ, "cableTension", json_real(cableTension)); | ||||
json_object_set_new(rootJ, "rackBrightness", json_real(rackBrightness)); | |||||
json_object_set_new(rootJ, "haloBrightness", json_real(haloBrightness)); | |||||
json_object_set_new(rootJ, "allowCursorLock", json_boolean(allowCursorLock)); | json_object_set_new(rootJ, "allowCursorLock", json_boolean(allowCursorLock)); | ||||
json_object_set_new(rootJ, "knobMode", json_integer((int) knobMode)); | json_object_set_new(rootJ, "knobMode", json_integer((int) knobMode)); | ||||
@@ -219,6 +225,14 @@ void fromJson(json_t* rootJ) { | |||||
if (cableTensionJ) | if (cableTensionJ) | ||||
cableTension = json_number_value(cableTensionJ); | cableTension = json_number_value(cableTensionJ); | ||||
json_t* rackBrightnessJ = json_object_get(rootJ, "rackBrightness"); | |||||
if (rackBrightnessJ) | |||||
rackBrightness = json_number_value(rackBrightnessJ); | |||||
json_t* haloBrightnessJ = json_object_get(rootJ, "haloBrightness"); | |||||
if (haloBrightnessJ) | |||||
haloBrightness = json_number_value(haloBrightnessJ); | |||||
json_t* allowCursorLockJ = json_object_get(rootJ, "allowCursorLock"); | json_t* allowCursorLockJ = json_object_get(rootJ, "allowCursorLock"); | ||||
if (allowCursorLockJ) | if (allowCursorLockJ) | ||||
allowCursorLock = json_boolean_value(allowCursorLockJ); | allowCursorLock = json_boolean_value(allowCursorLockJ); | ||||
@@ -365,8 +379,8 @@ void save(std::string path) { | |||||
return; | return; | ||||
DEFER({std::fclose(file);}); | DEFER({std::fclose(file);}); | ||||
// Because settings include doubles, it should use 17 decimal digits of precision instead of just 9 for float32. | |||||
json_dumpf(rootJ, file, JSON_INDENT(2) | JSON_REAL_PRECISION(17)); | |||||
// 11 is enough precision to handle double UNIX time values to 0.1 seconds. | |||||
json_dumpf(rootJ, file, JSON_INDENT(2) | JSON_REAL_PRECISION(11)); | |||||
json_decref(rootJ); | json_decref(rootJ); | ||||
} | } | ||||