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