@@ -1 +1 @@ | |||
Subproject commit 1f9c8864fc556a1be4d4bf1d6bfe20cde25734b4 | |||
Subproject commit d201a1cf69940450960db87c8e30b883087c9d01 |
@@ -44,6 +44,8 @@ extern bool invertZoom; | |||
extern float cableOpacity; | |||
/** Straightness of cables in the range [0, 1]. Unitless and arbitrary. */ | |||
extern float cableTension; | |||
extern float rackBrightness; | |||
extern float haloBrightness; | |||
/** Allows rack to hide and lock the cursor position when dragging knobs etc. */ | |||
extern bool allowCursorLock; | |||
enum KnobMode { | |||
@@ -1,5 +1,6 @@ | |||
#include <app/LightWidget.hpp> | |||
#include <color.hpp> | |||
#include <settings.hpp> | |||
namespace rack { | |||
@@ -7,26 +8,15 @@ namespace app { | |||
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; | |||
nvgBeginPath(args.vg); | |||
nvgCircle(args.vg, radius, radius, radius); | |||
// Background | |||
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); | |||
} | |||
@@ -36,21 +26,47 @@ void LightWidget::drawLight(const DrawArgs& args) { | |||
nvgStrokeColor(args.vg, borderColor); | |||
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) { | |||
const float halo = settings::haloBrightness; | |||
if (halo == 0.f) | |||
return; | |||
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); | |||
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); | |||
nvgGlobalCompositeOperation(args.vg, NVG_LIGHTER); | |||
nvgFill(args.vg); | |||
} | |||
@@ -189,6 +189,8 @@ struct FileButton : MenuButton { | |||
revertItem->disabled = (APP->patch->path == ""); | |||
menu->addChild(revertItem); | |||
menu->addChild(new ui::MenuSeparator); | |||
QuitItem* quitItem = new QuitItem; | |||
quitItem->text = "Quit"; | |||
quitItem->rightText = RACK_MOD_CTRL_NAME "+Q"; | |||
@@ -248,7 +250,7 @@ struct EditButton : MenuButton { | |||
struct ZoomQuantity : Quantity { | |||
void setValue(float value) override { | |||
settings::zoom = value; | |||
settings::zoom = math::clamp(value, getMinValue(), getMaxValue()); | |||
} | |||
float getValue() override { | |||
return settings::zoom; | |||
@@ -275,7 +277,6 @@ struct ZoomQuantity : Quantity { | |||
return "%"; | |||
} | |||
}; | |||
struct ZoomSlider : ui::Slider { | |||
ZoomSlider() { | |||
quantity = new ZoomQuantity; | |||
@@ -308,7 +309,6 @@ struct CableOpacityQuantity : Quantity { | |||
return "%"; | |||
} | |||
}; | |||
struct CableOpacitySlider : ui::Slider { | |||
CableOpacitySlider() { | |||
quantity = new CableOpacityQuantity; | |||
@@ -335,7 +335,6 @@ struct CableTensionQuantity : Quantity { | |||
return 2; | |||
} | |||
}; | |||
struct CableTensionSlider : ui::Slider { | |||
CableTensionSlider() { | |||
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 { | |||
void onAction(const ActionEvent& e) override { | |||
settings::tooltips ^= true; | |||
@@ -470,6 +539,14 @@ struct ViewButton : MenuButton { | |||
cableTensionSlider->box.size.x = 200.0; | |||
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->text = "Frame rate"; | |||
frameRateItem->rightText = RIGHT_ARROW; | |||
@@ -97,6 +97,10 @@ void RackWidget::step() { | |||
} | |||
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. | |||
math::Rect railBox; | |||
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; | |||
float cableOpacity = 0.5; | |||
float cableTension = 0.5; | |||
float rackBrightness = 1.0; | |||
float haloBrightness = 0.0; | |||
bool allowCursorLock = true; | |||
KnobMode knobMode = KNOB_MODE_LINEAR; | |||
bool knobScroll = false; | |||
@@ -103,6 +105,10 @@ json_t* toJson() { | |||
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, "knobMode", json_integer((int) knobMode)); | |||
@@ -219,6 +225,14 @@ void fromJson(json_t* rootJ) { | |||
if (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"); | |||
if (allowCursorLockJ) | |||
allowCursorLock = json_boolean_value(allowCursorLockJ); | |||
@@ -365,8 +379,8 @@ void save(std::string path) { | |||
return; | |||
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); | |||
} | |||