mirror of
https://github.com/GTNewHorizons/Angelica.git
synced 2026-10-25 20:22:22 +00:00
feat: add built-in menu blur based on Blur by tterrag
This commit is contained in:
11 files changed
+332
-1
No files matched your search
@@ -17,7 +17,7 @@
|
||||
* HUD caching
|
||||
* Custom fonts and text effects
|
||||
* MCPatcher - connected textures, custom item textures, custom colors, random mobs, better skies
|
||||
* Bundled NotFine, zoom, FPS reducer
|
||||
* Bundled NotFine, zoom, FPS reducer, menu blur
|
||||
|
||||
## Status
|
||||
* OpenGL + Celeritas terrain - Default
|
||||
@@ -129,5 +129,7 @@ Targets GL 3.3+ core profile
|
||||
* FFP vertex shader generation inspired by ffvertex_prog.c (Copyright 2007 VMware, Inc.)
|
||||
* FFP fragment shader generation inspired by ff_fragment_shader.c (Copyright 2007, 2009 VMware, Inc. / 2010-2011 Intel Corporation)
|
||||
* Fog parameter optimization inspired by st_nir_lower_fog.c (Copyright 2023 Google LLC) and prog_statevars.c (Copyright 1999-2007 Brian Paul)
|
||||
* Blur
|
||||
* tterrag for [Blur](https://github.com/tterrag1098/Blur) - Menu blur based on Blur under the MIT license
|
||||
* RGSS Terrain Texture Filtering
|
||||
* Indiesoft for [defold-sharp-sprite](https://github.com/indiesoftby/defold-sharp-sprite), used as the reference implementation under the MIT license
|
||||
@@ -0,0 +1,25 @@
|
||||
The menu blur in this package is based on [Blur](https://github.com/tterrag1098/Blur), used under the following license:
|
||||
|
||||
```
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 tterrag
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
```
|
||||
@@ -0,0 +1,195 @@
|
||||
package com.gtnewhorizons.angelica.blur;
|
||||
|
||||
import com.gtnewhorizons.angelica.client.rendering.GlUniformFloat2v;
|
||||
import com.gtnewhorizons.angelica.config.AngelicaConfig;
|
||||
import com.gtnewhorizons.angelica.glsm.GLStateManager;
|
||||
import com.gtnewhorizons.angelica.glsm.StateSet;
|
||||
import com.gtnewhorizons.angelica.render.PanoramaRenderer;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.shader.Framebuffer;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import org.embeddedt.embeddium.impl.gl.shader.GlProgram;
|
||||
import org.embeddedt.embeddium.impl.gl.shader.ShaderBindingContext;
|
||||
import org.embeddedt.embeddium.impl.gl.shader.uniform.GlUniformFloat;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.GL12;
|
||||
import org.lwjgl.opengl.GL13;
|
||||
import org.lwjgl.opengl.GL30;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* Blurs the world behind open GUIs and fades in a configurable background gradient.
|
||||
* Based on Blur by tterrag (https://github.com/tterrag1098/Blur) under the MIT license, see LICENSE-Blur.md.
|
||||
* Reimplemented on top of Angelica's own blur shader instead of a vanilla post-processing shader.
|
||||
*/
|
||||
public class MenuBlur {
|
||||
|
||||
private static final Minecraft mc = Minecraft.getMinecraft();
|
||||
|
||||
private static GlProgram<BlurUniforms> blurProgram;
|
||||
private static StateSet allState;
|
||||
private static int emptyVao;
|
||||
// The blur runs on half resolution copies of the screen, which is much cheaper and looks the same once blurred
|
||||
private static int horizontalFbo, horizontalTexture, verticalFbo, verticalTexture;
|
||||
private static int sourceWidth, sourceHeight, halfWidth, halfHeight;
|
||||
|
||||
private static boolean blurring;
|
||||
private static long fadeStartMillis;
|
||||
|
||||
/** Called right after the world has been drawn, before the HUD and the open GUI. */
|
||||
public static void renderBlur() {
|
||||
final boolean shouldBlur = shouldBlur();
|
||||
if (shouldBlur && !blurring) {
|
||||
fadeStartMillis = System.currentTimeMillis();
|
||||
}
|
||||
blurring = shouldBlur;
|
||||
if (!blurring) return;
|
||||
|
||||
// The radius grows with the fade, so the blur gets stronger over the first moments
|
||||
final float radius = (float) Math.floor(AngelicaConfig.menuBlurRadius * getProgress());
|
||||
if (radius < 1.0F) return;
|
||||
|
||||
final Framebuffer mainFramebuffer = mc.getFramebuffer();
|
||||
if (blurProgram == null) init();
|
||||
|
||||
final int previousProgram = GLStateManager.getActiveProgram();
|
||||
final int previousVao = GLStateManager.glGetInteger(GL30.GL_VERTEX_ARRAY_BINDING);
|
||||
final int previousFilter = mainFramebuffer.framebufferFilter;
|
||||
final int stateDepth = GLStateManager.pushState(allState);
|
||||
try {
|
||||
ensureBlurTargets(mainFramebuffer.framebufferWidth, mainFramebuffer.framebufferHeight);
|
||||
|
||||
GLStateManager.disableBlend();
|
||||
GLStateManager.disableDepthTest();
|
||||
GLStateManager.glDepthMask(false);
|
||||
GLStateManager.glColorMask(true, true, true, true);
|
||||
GLStateManager.disableCull();
|
||||
GLStateManager.disableScissorTest();
|
||||
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
|
||||
|
||||
blurProgram.bind();
|
||||
GLStateManager.glBindVertexArray(emptyVao);
|
||||
final BlurUniforms uniforms = blurProgram.getInterface();
|
||||
// Each half resolution pixel covers two screen pixels, so half the radius gives the same strength
|
||||
uniforms.radius.setFloat(radius * 0.5F);
|
||||
|
||||
// Horizontal pass, shrinking the screen to half size. Linear filtering makes every
|
||||
// half size pixel average the four screen pixels it covers instead of picking one.
|
||||
mainFramebuffer.setFramebufferFilter(GL11.GL_LINEAR);
|
||||
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, horizontalFbo);
|
||||
GLStateManager.glViewport(0, 0, halfWidth, halfHeight);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, mainFramebuffer.framebufferTexture);
|
||||
uniforms.direction.set(1.0F / halfWidth, 0.0F);
|
||||
GLStateManager.glDrawArrays(GL11.GL_TRIANGLES, 0, 3);
|
||||
|
||||
// Vertical pass, still at half size
|
||||
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, verticalFbo);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, horizontalTexture);
|
||||
uniforms.direction.set(0.0F, 1.0F / halfHeight);
|
||||
GLStateManager.glDrawArrays(GL11.GL_TRIANGLES, 0, 3);
|
||||
|
||||
// Stretch the result back over the screen. A radius of 0 makes the blur shader a plain copy.
|
||||
mainFramebuffer.bindFramebuffer(true);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, verticalTexture);
|
||||
uniforms.radius.setFloat(0.0F);
|
||||
GLStateManager.glDrawArrays(GL11.GL_TRIANGLES, 0, 3);
|
||||
} finally {
|
||||
mainFramebuffer.setFramebufferFilter(previousFilter);
|
||||
GLStateManager.glBindVertexArray(previousVao);
|
||||
GLStateManager.glUseProgram(previousProgram);
|
||||
GLStateManager.popStateTo(stateDepth);
|
||||
mainFramebuffer.bindFramebuffer(true);
|
||||
}
|
||||
}
|
||||
|
||||
/** Replaces one of the two vanilla GUI background gradient colors while the blur is showing. */
|
||||
public static int getBackgroundColor(int vanillaColor, String configuredColor) {
|
||||
if (!blurring) return vanillaColor;
|
||||
|
||||
final int color;
|
||||
try {
|
||||
color = Integer.parseUnsignedInt(configuredColor, 16);
|
||||
} catch (NumberFormatException e) {
|
||||
return vanillaColor;
|
||||
}
|
||||
|
||||
final float progress = getProgress();
|
||||
final int alpha = (int) ((color >>> 24) * progress);
|
||||
final int red = (int) ((color >> 16 & 0xFF) * progress);
|
||||
final int green = (int) ((color >> 8 & 0xFF) * progress);
|
||||
final int blue = (int) ((color & 0xFF) * progress);
|
||||
return alpha << 24 | red << 16 | green << 8 | blue;
|
||||
}
|
||||
|
||||
private static boolean shouldBlur() {
|
||||
return AngelicaConfig.enableMenuBlur
|
||||
&& mc.theWorld != null
|
||||
&& mc.currentScreen != null
|
||||
&& !ArrayUtils.contains(AngelicaConfig.menuBlurExcludedGuis, mc.currentScreen.getClass().getName());
|
||||
}
|
||||
|
||||
private static float getProgress() {
|
||||
if (AngelicaConfig.menuBlurFadeTime <= 0) return 1.0F;
|
||||
return Math.min((System.currentTimeMillis() - fadeStartMillis) / (float) AngelicaConfig.menuBlurFadeTime, 1.0F);
|
||||
}
|
||||
|
||||
private static void init() {
|
||||
blurProgram = PanoramaRenderer.loadProgram("angelica:menu_blur", "angelica:panorama_blur", "angelica:panorama_blur", BlurUniforms::new);
|
||||
allState = StateSet.forMask(GL11.GL_ALL_ATTRIB_BITS);
|
||||
emptyVao = GLStateManager.glGenVertexArrays();
|
||||
}
|
||||
|
||||
private static void ensureBlurTargets(int width, int height) {
|
||||
if (horizontalFbo != 0 && width == sourceWidth && height == sourceHeight) return;
|
||||
|
||||
if (horizontalFbo != 0) {
|
||||
GLStateManager.glDeleteFramebuffers(horizontalFbo);
|
||||
GLStateManager.glDeleteFramebuffers(verticalFbo);
|
||||
GLStateManager.glDeleteTextures(horizontalTexture);
|
||||
GLStateManager.glDeleteTextures(verticalTexture);
|
||||
}
|
||||
sourceWidth = width;
|
||||
sourceHeight = height;
|
||||
halfWidth = Math.max((width + 1) / 2, 1);
|
||||
halfHeight = Math.max((height + 1) / 2, 1);
|
||||
|
||||
horizontalTexture = createTexture(halfWidth, halfHeight);
|
||||
horizontalFbo = createFramebuffer(horizontalTexture);
|
||||
verticalTexture = createTexture(halfWidth, halfHeight);
|
||||
verticalFbo = createFramebuffer(verticalTexture);
|
||||
}
|
||||
|
||||
private static int createTexture(int width, int height) {
|
||||
final int texture = GLStateManager.glGenTextures();
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
|
||||
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, width, height, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, (ByteBuffer) null);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL12.GL_CLAMP_TO_EDGE);
|
||||
GLStateManager.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL12.GL_CLAMP_TO_EDGE);
|
||||
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
|
||||
return texture;
|
||||
}
|
||||
|
||||
private static int createFramebuffer(int colorTexture) {
|
||||
final int fbo = GLStateManager.glGenFramebuffers();
|
||||
GLStateManager.glBindFramebuffer(GL30.GL_FRAMEBUFFER, fbo);
|
||||
GLStateManager.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL11.GL_TEXTURE_2D, colorTexture, 0);
|
||||
final int status = GLStateManager.glCheckFramebufferStatus(GL30.GL_FRAMEBUFFER);
|
||||
if (status != GL30.GL_FRAMEBUFFER_COMPLETE) {
|
||||
throw new RuntimeException("Menu blur FBO incomplete: 0x" + Integer.toHexString(status));
|
||||
}
|
||||
return fbo;
|
||||
}
|
||||
|
||||
private static class BlurUniforms {
|
||||
final GlUniformFloat2v direction;
|
||||
final GlUniformFloat radius;
|
||||
|
||||
BlurUniforms(ShaderBindingContext ctx) {
|
||||
direction = ctx.bindUniform("u_Direction", GlUniformFloat2v::new);
|
||||
radius = ctx.bindUniform("u_Radius", GlUniformFloat::new);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -276,6 +276,32 @@ public class AngelicaConfig {
|
||||
@Config.RequiresMcRestart
|
||||
public static boolean enableZoom;
|
||||
|
||||
@Config.Comment("Blur the world behind open GUIs.")
|
||||
@Config.DefaultBoolean(false)
|
||||
public static boolean enableMenuBlur;
|
||||
|
||||
@Config.Comment("How strong the menu blur is, in pixels.")
|
||||
@Config.DefaultInt(12)
|
||||
@Config.RangeInt(min = 1, max = 100)
|
||||
public static int menuBlurRadius;
|
||||
|
||||
@Config.Comment("How long the menu blur takes to fade in, in milliseconds.")
|
||||
@Config.DefaultInt(200)
|
||||
@Config.RangeInt(min = 0, max = 2000)
|
||||
public static int menuBlurFadeTime;
|
||||
|
||||
@Config.Comment("GUI classes that never get the menu blur.")
|
||||
@Config.DefaultStringList({"net.minecraft.client.gui.GuiChat"})
|
||||
public static String[] menuBlurExcludedGuis;
|
||||
|
||||
@Config.Comment("Top color of the GUI background gradient while the menu blur is showing, as ARGB hex.")
|
||||
@Config.DefaultString("75000000")
|
||||
public static String menuBlurGradientTopColor;
|
||||
|
||||
@Config.Comment("Bottom color of the GUI background gradient while the menu blur is showing, as ARGB hex.")
|
||||
@Config.DefaultString("75000000")
|
||||
public static String menuBlurGradientBottomColor;
|
||||
|
||||
@Config.Comment("Optimizes in-world item rendering")
|
||||
@Config.DefaultBoolean(true)
|
||||
@Config.RequiresMcRestart
|
||||
|
||||
@@ -617,6 +617,14 @@ public enum Mixins implements IMixins {
|
||||
"angelica.zoom.MixinMouseFilter"
|
||||
)),
|
||||
|
||||
ANGELICA_MENU_BLUR(new MixinBuilder("Blur the world behind open GUIs")
|
||||
.setPhase(Phase.EARLY)
|
||||
.addExcludedMod(TargetedMod.BLUR)
|
||||
.addClientMixins(
|
||||
"angelica.blur.MixinEntityRenderer_MenuBlur",
|
||||
"angelica.blur.MixinGuiScreen_MenuBlur"
|
||||
)),
|
||||
|
||||
ANGELICA_FPS_REDUCER(new MixinBuilder("Reduce frame rate and volume while unfocused, minimized, or idle")
|
||||
.setPhase(Phase.EARLY)
|
||||
.addClientMixins(
|
||||
|
||||
@@ -9,6 +9,7 @@ public enum TargetedMod implements ITargetMod {
|
||||
|
||||
ARCHAICFIX("org.embeddedt.archaicfix.ArchaicCore", "archaicfix"),
|
||||
BACKHAND("xonin.backhand.coremod.BackhandLoadingPlugin", "backhand"),
|
||||
BLUR("com.tterrag.blur.BlurPlugin", "blur"),
|
||||
CAMPFIRE_BACKPORT(null, "campfirebackport"),
|
||||
CHISEL(null, "chisel"),
|
||||
COFHCORE( "cofh.asm.LoadingPlugin", "CoFHCore"),
|
||||
|
||||
@@ -847,6 +847,29 @@ public class SodiumGameOptionPages {
|
||||
.build());
|
||||
}
|
||||
|
||||
groups.add(OptionGroup.createBuilder()
|
||||
.add(OptionImpl.createBuilder(boolean.class, angelicaOpts)
|
||||
.setName(I18n.format("options.angelica.menuBlur"))
|
||||
.setTooltip(I18n.format("options.angelica.menuBlur.tooltip"))
|
||||
.setControl(TickBoxControl::new)
|
||||
.setImpact(OptionImpact.LOW)
|
||||
.setBinding((opts, value) -> AngelicaConfig.enableMenuBlur = value, opts -> AngelicaConfig.enableMenuBlur)
|
||||
.build())
|
||||
.add(OptionImpl.createBuilder(int.class, angelicaOpts)
|
||||
.setName(I18n.format("options.angelica.menuBlurRadius"))
|
||||
.setTooltip(I18n.format("options.angelica.menuBlurRadius.tooltip"))
|
||||
.setControl(option -> new SliderControl(option, 1, 100, 1, ControlValueFormatter.number()))
|
||||
.setImpact(OptionImpact.LOW)
|
||||
.setBinding((opts, value) -> AngelicaConfig.menuBlurRadius = value, opts -> AngelicaConfig.menuBlurRadius)
|
||||
.build())
|
||||
.add(OptionImpl.createBuilder(int.class, angelicaOpts)
|
||||
.setName(I18n.format("options.angelica.menuBlurFadeTime"))
|
||||
.setTooltip(I18n.format("options.angelica.menuBlurFadeTime.tooltip"))
|
||||
.setControl(option -> new SliderControl(option, 0, 2000, 50, ControlValueFormatter.milliseconds()))
|
||||
.setBinding((opts, value) -> AngelicaConfig.menuBlurFadeTime = value, opts -> AngelicaConfig.menuBlurFadeTime)
|
||||
.build())
|
||||
.build());
|
||||
|
||||
return new OptionPage(I18n.format("sodium.options.pages.appearance"), ImmutableList.copyOf(groups));
|
||||
}
|
||||
|
||||
|
||||
+4
@@ -43,6 +43,10 @@ public interface ControlValueFormatter {
|
||||
return (v) -> v + "s";
|
||||
}
|
||||
|
||||
static ControlValueFormatter milliseconds() {
|
||||
return (v) -> v + "ms";
|
||||
}
|
||||
|
||||
static ControlValueFormatter quantity(String name) {
|
||||
return (v) -> I18n.format(name, v);
|
||||
}
|
||||
|
||||
@@ -391,6 +391,12 @@ options.angelica.disablef3=Disable F3 Additions
|
||||
options.angelica.disablef3.tooltip=Disables Angelica's additions to the F3 debug screen.
|
||||
options.angelica.verbosef3=Verbose F3
|
||||
options.angelica.verbosef3.tooltip=Shows developer counters (fixed-function shaders, streaming, TESR batching, GPU transfer) on the F3 screen. Toggle in game with F3+V.
|
||||
options.angelica.menuBlur=Menu Blur
|
||||
options.angelica.menuBlur.tooltip=Blurs the world behind open menus like the inventory or the pause menu. Chat is never blurred. Excluded menus and background colors can be changed in the Angelica config.
|
||||
options.angelica.menuBlurRadius=Menu Blur Strength
|
||||
options.angelica.menuBlurRadius.tooltip=How strong the menu blur is. Higher values blur more.
|
||||
options.angelica.menuBlurFadeTime=Menu Blur Fade Time
|
||||
options.angelica.menuBlurFadeTime.tooltip=How long the menu blur takes to fade in after a menu opens. 0 shows it instantly.
|
||||
angelica.debug.verbose.on=Verbose F3: shown
|
||||
angelica.debug.verbose.off=Verbose F3: hidden
|
||||
options.angelica.texteffects.rgb_colors=RGB Colors
|
||||
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
package com.gtnewhorizons.angelica.mixins.early.angelica.blur;
|
||||
|
||||
import com.gtnewhorizons.angelica.blur.MenuBlur;
|
||||
import net.minecraft.client.renderer.EntityRenderer;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(EntityRenderer.class)
|
||||
public class MixinEntityRenderer_MenuBlur {
|
||||
|
||||
@Inject(
|
||||
method = "updateCameraAndRender",
|
||||
at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/EntityRenderer;renderWorld(FJ)V", shift = At.Shift.AFTER))
|
||||
private void angelica$blurWorldBehindGui(float partialTicks, CallbackInfo ci) {
|
||||
MenuBlur.renderBlur();
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package com.gtnewhorizons.angelica.mixins.early.angelica.blur;
|
||||
|
||||
import com.gtnewhorizons.angelica.blur.MenuBlur;
|
||||
import com.gtnewhorizons.angelica.config.AngelicaConfig;
|
||||
import net.minecraft.client.gui.GuiScreen;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.Constant;
|
||||
import org.spongepowered.asm.mixin.injection.ModifyConstant;
|
||||
|
||||
@Mixin(GuiScreen.class)
|
||||
public class MixinGuiScreen_MenuBlur {
|
||||
|
||||
@ModifyConstant(method = "drawWorldBackground", constant = @Constant(intValue = 0xC0101010))
|
||||
private int angelica$blurGradientTop(int original) {
|
||||
return MenuBlur.getBackgroundColor(original, AngelicaConfig.menuBlurGradientTopColor);
|
||||
}
|
||||
|
||||
@ModifyConstant(method = "drawWorldBackground", constant = @Constant(intValue = 0xD0101010))
|
||||
private int angelica$blurGradientBottom(int original) {
|
||||
return MenuBlur.getBackgroundColor(original, AngelicaConfig.menuBlurGradientBottomColor);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user