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