diff --git a/README.md b/README.md index 78ab7f1..6ce02d7 100644 --- a/README.md +++ b/README.md @@ -39,4 +39,15 @@ Right-clicking on this screen will bring up a useful context menu with additiona When you're done, just press Escape until you return to the game. -Now, everything is done. Your widgets automatically save for the next time you play. \ No newline at end of file +Now, everything is done. Your widgets automatically save for the next time you play. + +## Color editor + +Newer mod versions (2.3.0+) include a **color editor** which provides full customization for your widgets' background and text colors: + +![Color editor screen](assets/images/color-editor.webp) + +The color list on the left shows the colors which are currently configured; if you add more than one, it becomes a gradient. Clicking on the colors will select them. + +The settings bar on the right of the color list can be used to customize gradient settings, import from other widgets, or use presets. +Below that, the color which is currently selected can be changed or deleted. \ No newline at end of file diff --git a/assets/images/color-editor.webp b/assets/images/color-editor.webp new file mode 100644 index 0000000..48df13c Binary files /dev/null and b/assets/images/color-editor.webp differ diff --git a/gradle.properties b/gradle.properties index 2d83987..160d953 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,7 @@ minecraft_version=1.21.9 yarn_mappings=1.21.9+build.1 loader_version=0.17.2 # Mod Properties -mod_version=2.2.0 +mod_version=2.3.0 maven_group=de.shiewk archives_base_name=Widgets # Dependencies diff --git a/src/main/java/de/shiewk/widgets/ModWidget.java b/src/main/java/de/shiewk/widgets/ModWidget.java index d0164c8..006a46b 100644 --- a/src/main/java/de/shiewk/widgets/ModWidget.java +++ b/src/main/java/de/shiewk/widgets/ModWidget.java @@ -37,6 +37,10 @@ public abstract class ModWidget { public abstract Text getDescription(); public abstract void onSettingsChanged(WidgetSettings settings); + public void onSettingsChanged() { + this.onSettingsChanged(this.getSettings()); + } + public int getX(int scaledScreenWidth){ return settings.anchor.getAlignStartPosX(scaledScreenWidth) + settings.offsetX; } diff --git a/src/main/java/de/shiewk/widgets/client/WidgetRenderer.java b/src/main/java/de/shiewk/widgets/client/WidgetRenderer.java index 8ec8692..16754a7 100644 --- a/src/main/java/de/shiewk/widgets/client/WidgetRenderer.java +++ b/src/main/java/de/shiewk/widgets/client/WidgetRenderer.java @@ -2,7 +2,7 @@ package de.shiewk.widgets.client; import de.shiewk.widgets.ModWidget; import de.shiewk.widgets.WidgetsMod; -import de.shiewk.widgets.client.screen.EditWidgetPositionsScreen; +import de.shiewk.widgets.client.screen.WidgetVisibilityToggle; import de.shiewk.widgets.client.screen.WidgetConfigScreen; import it.unimi.dsi.fastutil.objects.ObjectArrayList; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents; @@ -32,7 +32,7 @@ public class WidgetRenderer implements ClientTickEvents.StartTick, ClientLifecyc public void renderWidgets(DrawContext drawContext, RenderTickCounter tickCounter) { if (client.options.hudHidden) return; - if (client.currentScreen instanceof EditWidgetPositionsScreen) return; + if (client.currentScreen instanceof WidgetVisibilityToggle vt && !vt.shouldRenderWidgets()) return; final Profiler profiler = Profilers.get(); profiler.push("widgets"); final TextRenderer textRenderer = client.textRenderer; @@ -81,7 +81,7 @@ public class WidgetRenderer implements ClientTickEvents.StartTick, ClientLifecyc @Override public void onClientStarted(MinecraftClient client) { for (ModWidget widget : WidgetManager.getAllWidgets()) { - widget.onSettingsChanged(widget.getSettings()); + widget.onSettingsChanged(); } } } diff --git a/src/main/java/de/shiewk/widgets/client/screen/ContextMenuScreen.java b/src/main/java/de/shiewk/widgets/client/screen/ContextMenuScreen.java index d05e4f8..8d7c3f9 100644 --- a/src/main/java/de/shiewk/widgets/client/screen/ContextMenuScreen.java +++ b/src/main/java/de/shiewk/widgets/client/screen/ContextMenuScreen.java @@ -10,7 +10,7 @@ import net.minecraft.text.Text; import java.util.List; -public class ContextMenuScreen extends Screen { +public class ContextMenuScreen extends Screen implements WidgetVisibilityToggle { public record Option(Text title, boolean highlighted, Runnable action){ @@ -133,4 +133,12 @@ public class ContextMenuScreen extends Screen { y += 15; } } + + @Override + public boolean shouldRenderWidgets() { + if (parent instanceof WidgetVisibilityToggle t) { + return t.shouldRenderWidgets(); + } + return true; + } } diff --git a/src/main/java/de/shiewk/widgets/client/screen/EditWidgetPositionsScreen.java b/src/main/java/de/shiewk/widgets/client/screen/EditWidgetPositionsScreen.java index cc1d024..f511a97 100644 --- a/src/main/java/de/shiewk/widgets/client/screen/EditWidgetPositionsScreen.java +++ b/src/main/java/de/shiewk/widgets/client/screen/EditWidgetPositionsScreen.java @@ -23,7 +23,7 @@ import java.util.List; import java.util.Locale; import java.util.function.Consumer; -public class EditWidgetPositionsScreen extends AnimatedScreen { +public class EditWidgetPositionsScreen extends AnimatedScreen implements WidgetVisibilityToggle { private final Screen parent; private final Consumer onEdit; @@ -40,6 +40,11 @@ public class EditWidgetPositionsScreen extends AnimatedScreen { client.setScreen(parent); } + @Override + public boolean shouldRenderWidgets() { + return false; + } + private record AlignResult(double result, boolean isEnd){} private static final int SELECT_COLOR = Color.GREEN.getRGB(), ALIGN_COLOR = Color.ORANGE.getRGB(), ALIGN_DISABLED_COLOR = Color.GRAY.getRGB(); diff --git a/src/main/java/de/shiewk/widgets/client/screen/WidgetConfigScreen.java b/src/main/java/de/shiewk/widgets/client/screen/WidgetConfigScreen.java index 0ff2e8a..59f5c43 100644 --- a/src/main/java/de/shiewk/widgets/client/screen/WidgetConfigScreen.java +++ b/src/main/java/de/shiewk/widgets/client/screen/WidgetConfigScreen.java @@ -38,7 +38,7 @@ public class WidgetConfigScreen extends Screen { public void close() { WidgetManager.saveWidgets(widgetsEdited); for (ModWidget widget : widgetsEdited) { - widget.onSettingsChanged(widget.getSettings()); + widget.onSettingsChanged(); } assert client != null; client.setScreen(parent); diff --git a/src/main/java/de/shiewk/widgets/client/screen/WidgetSettingsScreen.java b/src/main/java/de/shiewk/widgets/client/screen/WidgetSettingsScreen.java index c22327c..07da815 100644 --- a/src/main/java/de/shiewk/widgets/client/screen/WidgetSettingsScreen.java +++ b/src/main/java/de/shiewk/widgets/client/screen/WidgetSettingsScreen.java @@ -12,7 +12,7 @@ import net.minecraft.util.Util; import java.util.function.Consumer; -public class WidgetSettingsScreen extends AnimatedScreen { +public class WidgetSettingsScreen extends AnimatedScreen implements WidgetVisibilityToggle { private static final Text previewText = Text.translatable("widgets.ui.preview"); private final ModWidget widget; private final Runnable onChange; @@ -20,7 +20,7 @@ public class WidgetSettingsScreen extends AnimatedScreen { super(Text.translatable("widgets.ui.widgetSettings", widget.getName()), parent, 500); this.widget = widget; onChange = () -> { - widget.onSettingsChanged(widget.getSettings()); + widget.onSettingsChanged(); changedWidgetConsumer.accept(widget); }; } @@ -60,4 +60,17 @@ public class WidgetSettingsScreen extends AnimatedScreen { widget.tick(); } } + + @Override + public boolean shouldRenderWidgets() { + return false; + } + + public ModWidget getWidget() { + return widget; + } + + public Runnable getOnChange() { + return onChange; + } } diff --git a/src/main/java/de/shiewk/widgets/client/screen/WidgetVisibilityToggle.java b/src/main/java/de/shiewk/widgets/client/screen/WidgetVisibilityToggle.java new file mode 100644 index 0000000..4da1b3e --- /dev/null +++ b/src/main/java/de/shiewk/widgets/client/screen/WidgetVisibilityToggle.java @@ -0,0 +1,7 @@ +package de.shiewk.widgets.client.screen; + +public interface WidgetVisibilityToggle { + + boolean shouldRenderWidgets(); + +} diff --git a/src/main/java/de/shiewk/widgets/client/screen/components/WidgetDisplayWidget.java b/src/main/java/de/shiewk/widgets/client/screen/components/WidgetDisplayWidget.java new file mode 100644 index 0000000..c02d145 --- /dev/null +++ b/src/main/java/de/shiewk/widgets/client/screen/components/WidgetDisplayWidget.java @@ -0,0 +1,55 @@ +package de.shiewk.widgets.client.screen.components; + +import de.shiewk.widgets.ModWidget; +import net.minecraft.client.font.TextRenderer; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder; +import net.minecraft.client.gui.screen.narration.NarrationPart; +import net.minecraft.client.gui.widget.ClickableWidget; +import net.minecraft.util.Util; + +public class WidgetDisplayWidget extends ClickableWidget { + + protected final ModWidget widget; + protected final TextRenderer textRenderer; + protected final int centerX; + protected final int centerY; + + public WidgetDisplayWidget(ModWidget widget, TextRenderer textRenderer, int centerX, int centerY) { + super(0, 0, (int) widget.scaledWidth(), (int) widget.scaledHeight(), widget.getName()); + this.widget = widget; + this.textRenderer = textRenderer; + this.centerX = centerX; + this.centerY = centerY; + } + + @Override + public int getX() { + return (int) (centerX - (widget.scaledWidth() / 2f)); + } + + @Override + public int getY() { + return (int) (centerY - (widget.scaledHeight() / 2f)); + } + + @Override + public int getWidth() { + return (int) widget.scaledWidth(); + } + + @Override + public int getHeight() { + return (int) widget.scaledHeight(); + } + + @Override + protected void renderWidget(DrawContext context, int mouseX, int mouseY, float deltaTicks) { + widget.render(context, Util.getMeasuringTimeNano(), textRenderer, getX(), getY()); + } + + @Override + protected void appendClickableNarrations(NarrationMessageBuilder builder) { + builder.put(NarrationPart.HINT, widget.getName()); + } +} diff --git a/src/main/java/de/shiewk/widgets/client/screen/components/WidgetSettingsEditWidget.java b/src/main/java/de/shiewk/widgets/client/screen/components/WidgetSettingsEditWidget.java index a194df4..fcd0a81 100644 --- a/src/main/java/de/shiewk/widgets/client/screen/components/WidgetSettingsEditWidget.java +++ b/src/main/java/de/shiewk/widgets/client/screen/components/WidgetSettingsEditWidget.java @@ -31,7 +31,7 @@ public class WidgetSettingsEditWidget extends ScrollableWidget { customSetting.setFocused(false); } setWidth(width); - widget.onSettingsChanged(widget.getSettings()); + widget.onSettingsChanged(); } @Override diff --git a/src/main/java/de/shiewk/widgets/client/screen/gradienteditor/GradientEditorColorSection.java b/src/main/java/de/shiewk/widgets/client/screen/gradienteditor/GradientEditorColorSection.java new file mode 100644 index 0000000..bcc7a9f --- /dev/null +++ b/src/main/java/de/shiewk/widgets/client/screen/gradienteditor/GradientEditorColorSection.java @@ -0,0 +1,221 @@ +package de.shiewk.widgets.client.screen.gradienteditor; + +import de.shiewk.widgets.WidgetsMod; +import de.shiewk.widgets.utils.WidgetUtils; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.font.TextRenderer; +import net.minecraft.client.gl.RenderPipelines; +import net.minecraft.client.gui.Click; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.Element; +import net.minecraft.client.gui.cursor.StandardCursors; +import net.minecraft.client.gui.widget.AlwaysSelectedEntryListWidget; +import net.minecraft.sound.SoundEvents; +import net.minecraft.text.Text; +import net.minecraft.util.Identifier; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.function.IntSupplier; + +import static net.minecraft.text.Text.empty; +import static net.minecraft.text.Text.translatable; + +public class GradientEditorColorSection extends AlwaysSelectedEntryListWidget { + + private final GradientEditorScreen editor; + private static final Identifier TEXTURE_BUTTON_PLUS = Identifier.of(WidgetsMod.MOD_ID, "textures/gui/button_plus.png"), + TEXTURE_ARROW_DOWN = Identifier.of(WidgetsMod.MOD_ID, "textures/gui/arrow_down.png"); + + private final List colorEntries; + + public GradientEditorColorSection(GradientEditorScreen editor, MinecraftClient client, int x, int y, int width, int height, int focusedIndex) { + super(client, width, height, y, 64); + setX(x); + this.editor = editor; + colorEntries = new ObjectArrayList<>(editor.colors.size()); + addEntry(new HeadingEntry(client.textRenderer), 18); + + IntArrayList colors = editor.colors; + for (int i = 0; i < colors.size(); i++) { + int finalI = i; + ColorEntry col = new ColorEntry(() -> colors.getInt(finalI)); + colorEntries.add(col); + addEntry(col); + addEntry(new ArrowDownEntry(), 48); + } + + addEntry(new AddButtonEntry(), 70); + setFocused(colorEntries.get(focusedIndex)); + } + + @Override + public int getRowWidth() { + return 64; + } + + @Override + protected void drawSelectionHighlight(DrawContext context, ListEntry entry, int color) {} + + @Override + protected void drawMenuListBackground(DrawContext context) { + context.fill(getX(), getY(), getX()+getWidth(), getY()+getHeight(), 0x50_00_00_00); + } + + @Override + protected void drawHeaderAndFooterSeparators(DrawContext context) {} + + @Override + protected void drawScrollbar(DrawContext context, int mouseX, int mouseY) {} + + @Override + public void setFocused(@Nullable Element focused) { + if (focused == null) return; + if (focused instanceof ListEntry se && !se.canFocus()) return; + + if (super.getFocused() != focused){ + WidgetUtils.playSound(SoundEvents.BLOCK_COPPER_BULB_TURN_ON); + } + if (focused instanceof ColorEntry color){ + editor.setCurrentColorIndex(colorEntries.indexOf(color)); + } + super.setFocused(focused); + } + + public void focusLast() { + setFocused(colorEntries.getLast()); + } + + public abstract static class ListEntry extends AlwaysSelectedEntryListWidget.Entry { + + public boolean canFocus(){ + return false; + } + + @Override + public Text getNarration() { + return empty(); + } + } + + public static class ColorEntry extends ListEntry { + + private final IntSupplier color; + + public ColorEntry(IntSupplier color) { + this.color = color; + } + + @Override + public void render(DrawContext context, int mouseX, int mouseY, boolean hovered, float deltaTicks) { + int color = this.color.getAsInt(); + int x = this.getX(); + int y = this.getContentY(); + + if (hovered && !isFocused()){ + context.setCursor(StandardCursors.POINTING_HAND); + context.fill(x, y, x+getWidth(), y+getHeight(), 0x30_ff_ff_ff); + } + + int outlineColor; + if (isFocused() || hovered){ + outlineColor = 0xff_ff_ff_00; + } else { + outlineColor = color | 0xff000000; + } + + context.fill(x+1, y+1, x+getWidth()-1, y+getHeight()-1, color); + + // Outline: + context.fill(x+1, y, x+getWidth()-1, y+1, outlineColor); + context.fill(x+1, y+getHeight()-1, x+getWidth()-1, y+getHeight(), outlineColor); + context.fill(x, y+1, x+1, y+getHeight()-1, outlineColor); + context.fill(x+getWidth()-1, y+1, x+getWidth(), y+getHeight()-1, outlineColor); + } + + @Override + public boolean canFocus() { + return true; + } + } + + private static class ArrowDownEntry extends ListEntry { + + @Override + public void render(DrawContext context, int mouseX, int mouseY, boolean hovered, float deltaTicks) { + context.drawTexture( + RenderPipelines.GUI_TEXTURED, + TEXTURE_ARROW_DOWN, + getX() + 16, + getContentY() + 8, + 0, + 0, + 32, + 32, + 32, + 32, + 0xff_8d_8d_8d + ); + } + } + + private static class HeadingEntry extends ListEntry { + + private final TextRenderer textRenderer; + + private HeadingEntry(TextRenderer textRenderer) { + this.textRenderer = textRenderer; + } + + @Override + public void render(DrawContext context, int mouseX, int mouseY, boolean hovered, float deltaTicks) { + Text text = translatable("widgets.ui.gradientEditor.colors"); + + int y = this.getContentY() + 2; + int rowWidth = 64; + + int textWidth = textRenderer.getWidth(text); + int textX = getX() + (rowWidth - textWidth) / 2; + context.drawText(textRenderer, text, textX, y, 0xffffffff, true); + } + } + + private class AddButtonEntry extends ListEntry { + @Override + public void render(DrawContext context, int mouseX, int mouseY, boolean hovered, float deltaTicks) { + context.drawTexture( + RenderPipelines.GUI_TEXTURED, + TEXTURE_BUTTON_PLUS, + getX(), + getContentY(), + 0, + 0, + 64, + 64, + 64, + 64, + hovered ? 0xff_cf_cf_cf : 0xff_8d_8d_8d + ); + if (hovered){ + context.setCursor(StandardCursors.POINTING_HAND); + context.drawTooltip(client.textRenderer, List.of( + translatable("widgets.ui.gradientEditor.colors.add.tooltip.0"), + translatable("widgets.ui.gradientEditor.colors.add.tooltip.1") + ), mouseX, mouseY); + } + } + + @Override + public boolean mouseClicked(Click click, boolean doubled) { + editor.addNewColor(editor.getCurrentColor()); + return true; + } + } + + @Override + public boolean isFocused() { + return true; + } +} diff --git a/src/main/java/de/shiewk/widgets/client/screen/gradienteditor/GradientEditorScreen.java b/src/main/java/de/shiewk/widgets/client/screen/gradienteditor/GradientEditorScreen.java new file mode 100644 index 0000000..0321ed3 --- /dev/null +++ b/src/main/java/de/shiewk/widgets/client/screen/gradienteditor/GradientEditorScreen.java @@ -0,0 +1,200 @@ +package de.shiewk.widgets.client.screen.gradienteditor; + +import de.shiewk.widgets.ModWidget; +import de.shiewk.widgets.client.screen.WidgetVisibilityToggle; +import de.shiewk.widgets.client.screen.components.WidgetDisplayWidget; +import de.shiewk.widgets.color.GradientMode; +import de.shiewk.widgets.color.GradientOptions; +import de.shiewk.widgets.widgets.settings.GradientWidgetSetting; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.text.Text; +import net.minecraft.util.Util; +import org.joml.Matrix3x2fStack; + +import static net.minecraft.text.Text.translatable; + +public class GradientEditorScreen extends Screen implements WidgetVisibilityToggle { + + private static final GradientOptions topBarGradient = new GradientOptions(50, 20, new int[]{0xa0_00_00_00, 0x50_00_00_00}); + + final Screen parent; + final ModWidget widget; + final GradientWidgetSetting setting; + final Runnable onChange; + + GradientMode mode; + final IntArrayList colors; + int gradientSpeed; + int gradientSize; + + int currentColorIndex = 0; + + private GradientEditorColorSection colorSection; + private GradientEditorSettingsSection settingsSection; + + public GradientEditorScreen(Screen parent, ModWidget widget, GradientWidgetSetting setting, Runnable onChange) { + super(translatable("widgets.ui.gradientEditor")); + this.parent = parent; + this.widget = widget; + this.setting = setting; + this.onChange = onChange; + GradientOptions options = setting.getValue(); + this.mode = options.mode(); + this.gradientSpeed = (int) options.gradientSpeed(); + this.gradientSize = (int) options.gradientSize(); + this.colors = IntArrayList.of(options.colors()); + } + + @Override + protected void init() { + super.init(); + reloadComponents(); + } + + private int getTopBarHeight() { + return 8 + this.textRenderer.fontHeight * 2; + } + + @Override + public void render(DrawContext context, int mouseX, int mouseY, float deltaTicks) { + super.render(context, mouseX, mouseY, deltaTicks); + + Matrix3x2fStack matrices = context.getMatrices(); + long timeNanos = Util.getMeasuringTimeNano(); + + // Render top bar + topBarGradient.fillHorizontal(context, timeNanos, 0, 0, this.width, this.getTopBarHeight()); + + Text topBarText = translatable("widgets.ui.gradientEditor"); + int width = textRenderer.getWidth(topBarText); + + matrices.pushMatrix().translate(this.width / 2f - width, 5).scale(2); + context.drawText(textRenderer, topBarText, 0, 0, 0xff_ff_ff_ff, true); + matrices.popMatrix(); + } + + public void reloadComponents(){ + clearChildren(); + + double colorScrollY = 0; + double settingsScrollY = 0; + if (this.colorSection != null){ + colorScrollY = colorSection.getScrollY(); + settingsScrollY = settingsSection.getScrollY(); + } + int topBarHeight = getTopBarHeight(); + + + int colorSectionWidth = 90; + int settingsSectionWidth = 110; + int mainAreaWidth = this.width - colorSectionWidth - settingsSectionWidth; + int mainAreaHeight = this.height - topBarHeight; + + // sidebar + this.colorSection = new GradientEditorColorSection(this, this.client, 0, topBarHeight, colorSectionWidth, mainAreaHeight, this.getCurrentColorIndex()); + this.settingsSection = new GradientEditorSettingsSection(this, this.client, colorSectionWidth, topBarHeight, settingsSectionWidth, mainAreaHeight); + addDrawableChild(this.colorSection); + addDrawableChild(this.settingsSection); + colorSection.setScrollY(colorScrollY); + settingsSection.setScrollY(settingsScrollY); + + // main area + int mainAreaX = colorSectionWidth + settingsSectionWidth; + int mainAreaCenterX = mainAreaX + (mainAreaWidth / 2); + + addDrawable(new WidgetDisplayWidget(this.widget, this.textRenderer, mainAreaCenterX, (mainAreaHeight) / 2 + topBarHeight)); + } + + @Override + public void renderBackground(DrawContext context, int mouseX, int mouseY, float deltaTicks) { + super.renderBackground(context, mouseX, mouseY, deltaTicks); + } + + @Override + public void close() { + refreshSettingValue(); + client.setScreen(parent); + } + + private void refreshSettingValue() { + settingsSection.onUpdatedColor(); + setting.setValue(toGradientOptions()); + widget.onSettingsChanged(); + onChange.run(); + } + + private GradientOptions toGradientOptions() { + return new GradientOptions(this.mode, this.gradientSize, this.gradientSpeed, this.colors.toIntArray()); + } + + @Override + public boolean shouldRenderWidgets() { + return false; + } + + public void addNewColor(int initialColor) { + colors.add(initialColor); + this.reloadComponents(); + this.colorSection.focusLast(); + refreshSettingValue(); + } + + public void setGradientSize(double v) { + this.gradientSize = (int) v; + refreshSettingValue(); + } + + public void setGradientSpeed(double v) { + this.gradientSpeed = (int) v; + refreshSettingValue(); + } + + public int getCurrentColor() { + return colors.getInt(currentColorIndex); + } + + public int getCurrentColorIndex() { + return currentColorIndex; + } + + public void setCurrentColorIndex(int i) { + this.currentColorIndex = i; + if (settingsSection != null) settingsSection.onUpdatedColor(); + } + + public void setCurrentColor(int newColor) { + colors.set(getCurrentColorIndex(), newColor); + refreshSettingValue(); + } + + public void removeCurrentColor() { + colors.removeInt(getCurrentColorIndex()); + setCurrentColorIndex(0); + refreshSettingValue(); + reloadComponents(); + } + + public void cycleMode() { + this.mode = GradientMode.values()[(this.mode.ordinal() + 1) % GradientMode.values().length]; + refreshSettingValue(); + } + + public void swapColors(int[] colors) { + this.colors.clear(); + for (int color : colors) { + this.colors.add(color); + } + setCurrentColorIndex(0); + this.reloadComponents(); + refreshSettingValue(); + } + + public void swap(GradientOptions options) { + setGradientSize(options.gradientSize()); + setGradientSpeed(options.gradientSpeed()); + this.mode = options.mode(); + swapColors(options.colors()); + } +} diff --git a/src/main/java/de/shiewk/widgets/client/screen/gradienteditor/GradientEditorSettingsSection.java b/src/main/java/de/shiewk/widgets/client/screen/gradienteditor/GradientEditorSettingsSection.java new file mode 100644 index 0000000..846ba9d --- /dev/null +++ b/src/main/java/de/shiewk/widgets/client/screen/gradienteditor/GradientEditorSettingsSection.java @@ -0,0 +1,650 @@ +package de.shiewk.widgets.client.screen.gradienteditor; + +import de.shiewk.widgets.WidgetsMod; +import de.shiewk.widgets.client.WidgetManager; +import de.shiewk.widgets.client.screen.ContextMenuScreen; +import de.shiewk.widgets.color.GradientOptions; +import de.shiewk.widgets.color.GradientPreset; +import de.shiewk.widgets.widgets.settings.GradientWidgetSetting; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gl.RenderPipelines; +import net.minecraft.client.gui.Click; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.cursor.StandardCursors; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.tooltip.Tooltip; +import net.minecraft.client.gui.widget.AlwaysSelectedEntryListWidget; +import net.minecraft.client.gui.widget.ButtonWidget; +import net.minecraft.client.gui.widget.SliderWidget; +import net.minecraft.client.gui.widget.TextFieldWidget; +import net.minecraft.client.input.CharInput; +import net.minecraft.client.input.KeyInput; +import net.minecraft.client.texture.NativeImage; +import net.minecraft.client.texture.NativeImageBackedTexture; +import net.minecraft.text.Text; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.MathHelper; +import org.jetbrains.annotations.NotNull; + +import java.awt.*; +import java.util.Arrays; +import java.util.function.IntConsumer; +import java.util.function.IntFunction; + +import static de.shiewk.widgets.utils.WidgetUtils.colorARGBToHexRGBA; +import static net.minecraft.text.Text.empty; +import static net.minecraft.text.Text.translatable; + +public class GradientEditorSettingsSection extends AlwaysSelectedEntryListWidget { + + private final GradientEditorScreen editor; + private final HexValueInputEntry hexInput; + private final ColorPickerHueSliderEntry hueSlider; + + public GradientEditorSettingsSection(GradientEditorScreen editor, MinecraftClient client, int x, int y, int width, int height) { + super(client, width, height, y, 110); + setX(x); + this.editor = editor; + + addEntry(new HeadingEntry(translatable("widgets.ui.gradientEditor.gradientSettings")), 18); + + addEntry(new SliderEntry(1, 100, editor.gradientSize, d -> translatable("widgets.ui.gradientEditor.size", d), editor::setGradientSize), 20); + addEntry(new SliderEntry(0, 100, editor.gradientSpeed, d -> translatable("widgets.ui.gradientEditor.speed", d), editor::setGradientSpeed), 20); + addEntry(new ToggleModeButtonEntry(), 20); + addEntry(new ImportButtonEntry(), 20); + addEntry(new UsePresetButtonEntry(), 20); + + addEntry(new HeadingEntry(translatable("widgets.ui.gradientEditor.editColor")), 18); + + addEntry(new ColorChangerEntry(0), 22); + addEntry(new ColorChangerEntry(1), 22); + addEntry(new ColorChangerEntry(2), 22); + addEntry(new ColorChangerEntry(3), 22); + + addEntry(new HeadingEntry(translatable("widgets.ui.gradientEditor.editColor.orPick")), 18); + + addEntry(new ColorPickerEntry(), getRowWidth()); + addEntry(hueSlider = new ColorPickerHueSliderEntry(), 22); + addEntry(new ColorChangerEntry(3, false), 22); + addEntry(hexInput = new HexValueInputEntry(), 20); + + addEntry(new RemoveColorButtonEntry(), 20); + } + + @Override + public int getRowWidth() { + return 110; + } + + @Override + protected void drawMenuListBackground(DrawContext context) { + context.fill(getX(), getY(), getX()+getWidth(), getY()+getHeight(), 0x20_00_00_00); + } + + @Override protected void drawHeaderAndFooterSeparators(DrawContext context) {} + @Override protected void drawSelectionHighlight(DrawContext context, ListEntry entry, int color) {} + @Override protected void drawScrollbar(DrawContext context, int mouseX, int mouseY) {} + + public abstract static class ListEntry extends Entry { + + @Override + public Text getNarration() { + return empty(); + } + } + + public void onUpdatedColor(){ + hexInput.refreshText(); + hueSlider.refreshHue(); + } + + private float getCurrentHue(){ + float[] hsb = getHsb(); + return hsb[0]; + } + + private float @NotNull [] getHsb() { + int currentRGBA = editor.getCurrentColor(); + return Color.RGBtoHSB((currentRGBA >> 16) & 0xff, (currentRGBA >> 8) & 0xff, currentRGBA & 0xff, null); + } + + + private class HeadingEntry extends ListEntry { + + private final Text text; + + private HeadingEntry(Text text) { + this.text = text; + } + + @Override + public void render(DrawContext context, int mouseX, int mouseY, boolean hovered, float deltaTicks) { + int y = this.getContentY() + 2; + int rowWidth = 110; + + int textWidth = client.textRenderer.getWidth(text); + int textX = getX() + (rowWidth - textWidth) / 2; + context.drawText(client.textRenderer, text, textX, y, 0xffffffff, true); + } + } + + private class SliderEntry extends ListEntry { + + private final SliderWidget slider; + + private SliderEntry(int min, int max, int initial, IntFunction textGetter, IntConsumer onUpdateValue) { + double value = (double) (initial - min) / (max - min); + slider = new Slider(textGetter, value, onUpdateValue, min, max); + } + + @Override + public void render(DrawContext context, int mouseX, int mouseY, boolean hovered, float deltaTicks) { + slider.active = editor.colors.size() > 1; + slider.setDimensionsAndPosition(getContentWidth(), getContentHeight(), getContentX(), getContentY()); + slider.render(context, mouseX, mouseY, deltaTicks); + if (hovered && !slider.active) { + context.setCursor(StandardCursors.NOT_ALLOWED); + slider.setTooltip(Tooltip.of(translatable("widgets.ui.gradientEditor.gradientSettings.addMoreColors"))); + } else { + slider.setTooltip(null); + } + } + + @Override public boolean mouseClicked(Click click, boolean doubled) { return slider.mouseClicked(click, doubled); } + @Override public boolean mouseDragged(Click click, double offsetX, double offsetY) { return slider.mouseDragged(click, offsetX, offsetY); } + @Override public boolean mouseReleased(Click click) { return slider.mouseReleased(click); } + @Override public boolean mouseScrolled(double mouseX, double mouseY, double horizontalAmount, double verticalAmount) { return slider.mouseScrolled(mouseX, mouseY, horizontalAmount, verticalAmount); } + @Override public void mouseMoved(double mouseX, double mouseY) { slider.mouseMoved(mouseX, mouseY); } + + private static class Slider extends SliderWidget { + + private final IntFunction textGetter; + private final IntConsumer onUpdateValue; + private final int min; + private final int max; + + public Slider(IntFunction textGetter, double value, IntConsumer onUpdateValue, int min, int max) { + super(0, 0, 0, 0, empty(), value); + this.textGetter = textGetter; + this.onUpdateValue = onUpdateValue; + this.min = min; + this.max = max; + this.updateMessage(); + } + + @Override + protected void updateMessage() { + this.setMessage(textGetter.apply(getTranslatedValue())); + } + + @Override + protected void applyValue() { + onUpdateValue.accept(getTranslatedValue()); + } + + private int getTranslatedValue() { + return (int) (min + (this.value * (max - min))); + } + + } + } + + private class ToggleModeButtonEntry extends ListEntry { + + private final ButtonWidget button = new ButtonWidget.Builder(empty(), this::press).build(); + + public ToggleModeButtonEntry() { + refreshButtonValues(); + } + + private void press(ButtonWidget button) { + editor.cycleMode(); + refreshButtonValues(); + } + + private void refreshButtonValues() { + button.setMessage(translatable("widgets.ui.gradientEditor.mode", editor.mode.name)); + if (editor.colors.size() > 1){ + button.setTooltip(Tooltip.of(editor.mode.description)); + } else { + button.setTooltip(Tooltip.of(translatable("widgets.ui.gradientEditor.gradientSettings.addMoreColors"))); + } + } + + @Override + public void render(DrawContext context, int mouseX, int mouseY, boolean hovered, float deltaTicks) { + button.active = editor.colors.size() > 1; + button.setDimensionsAndPosition(getContentWidth(), getContentHeight(), getContentX(), getContentY()); + button.render(context, mouseX, mouseY, deltaTicks); + } + + @Override + public boolean mouseClicked(Click click, boolean doubled) { + return button.mouseClicked(click, doubled); + } + } + + private class ImportButtonEntry extends ListEntry { + + private final ButtonWidget button = new ButtonWidget.Builder(translatable("widgets.ui.gradientEditor.importOther"), this::press).build(); + + private void press(ButtonWidget button) { + Screen currentScreen = client.currentScreen; + int menuX = (int) client.mouse.getScaledX(client.getWindow()); + int menuY = (int) client.mouse.getScaledY(client.getWindow()); + client.setScreen(new ContextMenuScreen( + button.getMessage(), + currentScreen, + menuX, menuY, + WidgetManager.getAllWidgets() + .stream() + .map(widget -> new ContextMenuScreen.Option( + widget.getName(), + () -> client.setScreen(new ContextMenuScreen( + widget.getName(), + currentScreen, + menuX, + menuY, + widget.getSettings().getCustomSettings() + .stream() + .filter(o -> o instanceof GradientWidgetSetting) + .map(option -> new ContextMenuScreen.Option( + option.getName(), + () -> editor.swap(((GradientWidgetSetting) option).getValue()) + )).toList() + )) + )).toList() + )); + } + + @Override + public void render(DrawContext context, int mouseX, int mouseY, boolean hovered, float deltaTicks) { + button.setDimensionsAndPosition(getContentWidth(), getContentHeight(), getContentX(), getContentY()); + button.render(context, mouseX, mouseY, deltaTicks); + } + + @Override + public boolean mouseClicked(Click click, boolean doubled) { + return button.mouseClicked(click, doubled); + } + } + + private class UsePresetButtonEntry extends ListEntry { + + private final ButtonWidget button = new ButtonWidget.Builder(translatable("widgets.ui.gradientEditor.usePreset"), this::press).build(); + + private void press(ButtonWidget button) { + Screen currentScreen = client.currentScreen; + int menuX = (int) client.mouse.getScaledX(client.getWindow()); + int menuY = (int) client.mouse.getScaledY(client.getWindow()); + client.setScreen(new ContextMenuScreen( + translatable("widgets.ui.gradientEditor.usePreset"), + currentScreen, + menuX, menuY, + Arrays.stream(GradientPreset.presets).map(preset -> new ContextMenuScreen.Option( + preset.name(), + () -> editor.swap(preset.gradient()) + )).toList() + )); + } + + @Override + public void render(DrawContext context, int mouseX, int mouseY, boolean hovered, float deltaTicks) { + button.setDimensionsAndPosition(getContentWidth(), getContentHeight(), getContentX(), getContentY()); + button.render(context, mouseX, mouseY, deltaTicks); + } + + @Override + public boolean mouseClicked(Click click, boolean doubled) { + return button.mouseClicked(click, doubled); + } + + } + + private class ColorChangerEntry extends ListEntry { + + private final int component; + private final boolean showLabel; + + public ColorChangerEntry(int component) { + this(component, true); + } + + public ColorChangerEntry(int component, boolean showLabel) { + this.component = component; + this.showLabel = showLabel; + } + + @Override + public void render(DrawContext context, int mouseX, int mouseY, boolean hovered, float deltaTicks) { + double sliderProg = getComponentValue() / 255d; + + GradientOptions bg = new GradientOptions(100, 0, new int[]{getBackgroundStartColor(), getBackgroundEndColor()}); + int contentY = getContentY(); + int contentWidth = getContentWidth(); + int contentX = getContentX(); + int contentHeight = getContentHeight(); + + bg.fillHorizontal(context, 0, contentX, contentY + 1, contentX + contentWidth, contentY + contentHeight - 1); + + context.drawVerticalLine((int) (contentX + (contentWidth * sliderProg)), contentY - 1, contentY + contentHeight, 0xff_ff_ff_ff); + if (showLabel){ + String label = getLabelWithValue(); + int w = client.textRenderer.getWidth(label); + context.drawText(client.textRenderer, label, contentX + (contentWidth - w) / 2, contentY + 5, 0xffffffff, true); + } + if (hovered){ + context.setCursor(StandardCursors.RESIZE_EW); + } + } + + private int getComponentValue() { + int currentColor = editor.getCurrentColor(); + return switch (component) { + case 0 -> (currentColor & 0x00_ff_00_00) >>> 16; + case 1 -> (currentColor & 0x00_00_ff_00) >>> 8; + case 2 -> (currentColor & 0x00_00_00_ff); + case 3 -> (currentColor & 0xff_00_00_00) >>> 24; + default -> throw new IllegalStateException(); + }; + } + + @Override + public boolean mouseDragged(Click click, double offsetX, double offsetY) { + double mouseX = click.x(); + int newComponentValue = (int) ((mouseX - getContentX()) / getContentWidth() * 255); + setComponentValue(newComponentValue); + return true; + } + + @Override + public boolean mouseClicked(Click click, boolean doubled) { + return mouseDragged(click, 0, 0); + } + + @Override + public boolean keyPressed(KeyInput input) { + if (input.isLeft()) { + setComponentValue(getComponentValue() - 1); + } else if (input.isRight()) { + setComponentValue(getComponentValue() + 1); + } + return true; + } + + private void setComponentValue(int val) { + val = MathHelper.clamp(val, 0, 255); + final int currentColor = editor.getCurrentColor(); + int newColor = switch (component){ + case 0 -> (currentColor & 0xff_00_ff_ff) | val << 16; + case 1 -> (currentColor & 0xff_ff_00_ff) | val << 8; + case 2 -> (currentColor & 0xff_ff_ff_00) | val; + case 3 -> (currentColor & 0x00_ff_ff_ff) | val << 24; + default -> throw new IllegalStateException(); + }; + editor.setCurrentColor(newColor); + } + + private String getLabelWithValue() { + return switch (component){ + case 0 -> "R: " + getComponentValue(); + case 1 -> "G: " + getComponentValue(); + case 2 -> "B: " + getComponentValue(); + case 3 -> "A: " + getComponentValue(); + default -> throw new IllegalStateException(); + }; + } + + private int getBackgroundEndColor() { + final int currentColor = editor.getCurrentColor(); + return switch (component){ + case 0 -> currentColor | 0x00_ff_00_00; + case 1 -> currentColor | 0x00_00_ff_00; + case 2 -> currentColor | 0x00_00_00_ff; + case 3 -> currentColor | 0xff_00_00_00; + default -> throw new IllegalStateException(); + }; + } + + private int getBackgroundStartColor() { + final int currentColor = editor.getCurrentColor(); + return switch (component){ + case 0 -> currentColor & 0xff_00_ff_ff; + case 1 -> currentColor & 0xff_ff_00_ff; + case 2 -> currentColor & 0xff_ff_ff_00; + case 3 -> currentColor & 0x00_ff_ff_ff; + default -> throw new IllegalStateException(); + }; + } + } + + private class RemoveColorButtonEntry extends ListEntry { + + private final ButtonWidget button = new ButtonWidget.Builder(translatable("widgets.ui.gradientEditor.removeColor"), this::press).build(); + + private void press(ButtonWidget button) { + button.active = false; + editor.removeCurrentColor(); + } + + @Override + public void render(DrawContext context, int mouseX, int mouseY, boolean hovered, float deltaTicks) { + button.active = editor.colors.size() > 1; + button.setDimensionsAndPosition(getContentWidth(), getContentHeight(), getContentX(), getContentY()); + button.render(context, mouseX, mouseY, deltaTicks); + } + + @Override + public boolean mouseClicked(Click click, boolean doubled) { + return button.mouseClicked(click, doubled); + } + } + + private class HexValueInputEntry extends ListEntry { + + private final TextFieldWidget inputField = new TextFieldWidget(client.textRenderer, 0, 0, empty()); + + public HexValueInputEntry() { + this.refreshText(); + inputField.setMaxLength(9); + } + + private void onChangeInputField(String s) { + boolean valid = false; + if (s.startsWith("#")) s = s.substring(1); + if (s.length() == 6) { + s += "ff"; + } + if (s.length() == 8) { + try { + String alpha = s.substring(6, 8); + String rgb = s.substring(0, 6); + String argbHex = alpha + rgb; + + int cl = Integer.parseUnsignedInt(argbHex, 16); + + if (cl != editor.getCurrentColor()) { + editor.setCurrentColor(cl); + } + valid = true; + } catch (NumberFormatException ignored) {} + } + + inputField.setEditableColor(valid ? 0xffffffff : 0xffff0000); + } + + @Override + public void render(DrawContext context, int mouseX, int mouseY, boolean hovered, float deltaTicks) { + inputField.setDimensionsAndPosition(getContentWidth(), getContentHeight(), getContentX(), getContentY()); + inputField.render(context, mouseX, mouseY, deltaTicks); + } + + public void refreshText(){ + inputField.setChangedListener(null); + inputField.setText('#' + colorARGBToHexRGBA(editor.getCurrentColor())); + inputField.setCursorToStart(false); + inputField.setEditableColor(0xffffffff); + inputField.setChangedListener(this::onChangeInputField); + } + + @Override + public void setFocused(boolean focused) { + inputField.setFocused(focused); + } + + @Override public boolean mouseClicked(Click click, boolean doubled) { return inputField.mouseClicked(click, doubled); } + @Override public boolean mouseDragged(Click click, double offsetX, double offsetY) { return inputField.mouseDragged(click, offsetX, offsetY); } + @Override public boolean mouseReleased(Click click) { return inputField.mouseReleased(click); } + @Override public boolean mouseScrolled(double mouseX, double mouseY, double horizontalAmount, double verticalAmount) { return inputField.mouseScrolled(mouseX, mouseY, horizontalAmount, verticalAmount); } + @Override public void mouseMoved(double mouseX, double mouseY) { inputField.mouseMoved(mouseX, mouseY); } + @Override public boolean charTyped(CharInput input) { return inputField.charTyped(input); } + @Override public boolean keyReleased(KeyInput input) { return inputField.keyReleased(input); } + @Override public boolean keyPressed(KeyInput input) { return inputField.keyPressed(input); } + } + + private class ColorPickerEntry extends ListEntry { + + @Override + public void render(DrawContext context, int mouseX, int mouseY, boolean hovered, float deltaTicks) { + context.fill( + getContentX(), + getContentY(), + getContentX() + getContentWidth(), + getContentY() + getContentHeight(), + getBackgroundFillColor() + ); + context.drawTexture( + RenderPipelines.GUI_TEXTURED, + getOverlayTextureIdentifier(), + getContentX(), getContentY(), + 0, 0, + getContentWidth(), getContentHeight(), + getContentWidth(), getContentHeight() + ); + + float[] hsb = getHsb(); + int pointerOffsetX = (int) (hsb[1] * getContentWidth()); + int pointerOffsetY = (int) ((1f - hsb[2]) * getContentHeight()); + context.fill( + getContentX() + pointerOffsetX - 2, getContentY() + pointerOffsetY - 2, + getContentX() + pointerOffsetX + 2, getContentY() + pointerOffsetY + 2, + 0xffffffff + ); + context.fill( + getContentX() + pointerOffsetX - 1, getContentY() + pointerOffsetY - 1, + getContentX() + pointerOffsetX + 1, getContentY() + pointerOffsetY + 1, + editor.getCurrentColor() | 0xff000000 + ); + + if (hovered){ + context.setCursor(StandardCursors.ARROW); + } + } + + @Override + public boolean mouseDragged(Click click, double offsetX, double offsetY) { + double x = click.x() - getContentX(); + double y = click.y() - getContentY(); + float saturation = (float) Math.clamp(x / getContentWidth(), 0d, 1d); + float brightness = (float) Math.clamp(1d - y / getContentHeight(), 0d, 1d); + + int newColorRgb = Color.HSBtoRGB(hueSlider.sliderProgress, saturation, brightness) & 0x00ffffff; + int alpha = editor.getCurrentColor() & 0xff000000; + + editor.setCurrentColor(newColorRgb | alpha); + return true; + } + + @Override + public boolean mouseClicked(Click click, boolean doubled) { + return mouseDragged(click, 0, 0); + } + + private int getBackgroundFillColor() { + return Color.HSBtoRGB(hueSlider.sliderProgress, 1, 1); + } + + private static Identifier OVERLAY_TEXTURE_ID; + + public Identifier getOverlayTextureIdentifier(){ + if (OVERLAY_TEXTURE_ID == null){ + NativeImageBackedTexture texture = generateOverlayTexture(); + OVERLAY_TEXTURE_ID = Identifier.of(WidgetsMod.MOD_ID, "textures/gui/generated/color-picker-overlay"); + client.getTextureManager().registerTexture(OVERLAY_TEXTURE_ID, texture); + } + return OVERLAY_TEXTURE_ID; + } + + private NativeImageBackedTexture generateOverlayTexture() { + NativeImageBackedTexture texture = new NativeImageBackedTexture("widgets:textures/gui/generated/color-picker-overlay", 256, 256, false); + NativeImage image = texture.getImage(); + + for (int x = 0; x < 256; x++) { + for (int y = 0; y < 256; y++) { + double alphaWhite = (255d - x) / 255d; + double alphaBlack = y / 255d; + double outAlpha = alphaBlack + alphaWhite * (1d - alphaBlack); + + int rgb = (int) Math.round(outAlpha > 0 ? (255d * alphaWhite * (1d - alphaBlack)) / outAlpha : 0); + int a = (int) Math.round(outAlpha * 255.0); + + image.setColorArgb(x, y, new Color(rgb, rgb, rgb, a).getRGB()); + } + } + + texture.upload(); + return texture; + } + + } + + private class ColorPickerHueSliderEntry extends ListEntry { + + private static final GradientOptions sliderGradient = new GradientOptions( + 16.666666f, + 0, + new int[]{0xffff0000, 0xffffff00, 0xff00ff00, 0xff00ffff, 0xff0000ff, 0xffff00ff, 0xffff0000} + ); + private float sliderProgress; + + public ColorPickerHueSliderEntry(){ + refreshHue(); + } + + @Override + public void render(DrawContext context, int mouseX, int mouseY, boolean hovered, float deltaTicks) { + sliderGradient.fillHorizontal(context, 0, getContentX(), getContentY() + 1, getContentX() + getContentWidth(), getContentY() + getContentHeight() - 1); + context.drawVerticalLine((int) (getContentX() + (getContentWidth() * sliderProgress)), getContentY() - 1, getContentY() + getContentHeight(), 0xff_ff_ff_ff); + + if (hovered){ + context.setCursor(StandardCursors.RESIZE_EW); + } + } + + @Override + public boolean mouseDragged(Click click, double offsetX, double offsetY) { + double x = click.x() - getContentX(); + float newHue = (float) Math.clamp(x / getContentWidth(), 0, 1); + this.sliderProgress = newHue; + + float[] hsb = getHsb(); + int newRgb = Color.HSBtoRGB(newHue, hsb[1], hsb[2]) & 0x00ffffff; + int alpha = editor.getCurrentColor() & 0xff000000; + int newColor = newRgb | alpha; + + if (newColor != editor.getCurrentColor()){ + editor.setCurrentColor(newColor); + } + return true; + } + + @Override + public boolean mouseClicked(Click click, boolean doubled) { + return this.mouseDragged(click, 0, 0); + } + + public void refreshHue() { + this.sliderProgress = getCurrentHue(); + } + } +} diff --git a/src/main/java/de/shiewk/widgets/color/GradientMode.java b/src/main/java/de/shiewk/widgets/color/GradientMode.java new file mode 100644 index 0000000..75a65a7 --- /dev/null +++ b/src/main/java/de/shiewk/widgets/color/GradientMode.java @@ -0,0 +1,18 @@ +package de.shiewk.widgets.color; + +import net.minecraft.text.Text; + +import static net.minecraft.text.Text.translatable; + +public enum GradientMode { + SWEEP(translatable("widgets.gradient.sweep"), translatable("widgets.gradient.sweep.description")), + PULSE(translatable("widgets.gradient.pulse"), translatable("widgets.gradient.pulse.description")); + + public final Text name; + public final Text description; + + GradientMode(Text name, Text description) { + this.name = name; + this.description = description; + } +} diff --git a/src/main/java/de/shiewk/widgets/color/GradientOptions.java b/src/main/java/de/shiewk/widgets/color/GradientOptions.java new file mode 100644 index 0000000..c4cbba4 --- /dev/null +++ b/src/main/java/de/shiewk/widgets/color/GradientOptions.java @@ -0,0 +1,153 @@ +package de.shiewk.widgets.color; + +import de.shiewk.widgets.render.state.HorizontalGradientGuiRenderState; +import net.minecraft.client.font.TextRenderer; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.text.Text; +import org.joml.Matrix3x2fStack; + +import java.util.Objects; + +import static de.shiewk.widgets.utils.WidgetUtils.fadeColor; + +public record GradientOptions(GradientMode mode, float gradientSize, float gradientSpeed, int[] colors) { + + public GradientOptions(GradientMode mode, float gradientSize, float gradientSpeed, int[] colors) { + this.mode = Objects.requireNonNullElse(mode, GradientMode.SWEEP); + this.gradientSize = gradientSize; + this.gradientSpeed = gradientSpeed; + this.colors = Objects.requireNonNullElse(colors, new int[]{ -1 }); + } + + public GradientOptions(float gradientSize, float gradientSpeed, int[] colors) { + this(GradientMode.SWEEP, gradientSize, gradientSpeed, colors); + } + + public void fillHorizontal(DrawContext context, long timeNanos, int x, int y, int endX, int endY) { + if (colors.length == 1) { + context.fill(x, y, endX, endY, colors[0]); + return; + } + switch (mode){ + case SWEEP -> fillHorizonalSweep(context, timeNanos, x, y, endX, endY); + case PULSE -> fillHorizonalPulse(context, timeNanos, x, y, endX, endY); + } + } + + private void fillHorizonalSweep(DrawContext context, long timeNanos, int x, int y, int endX, int endY) { + context.enableScissor(x, y, endX, endY); + + int width = endX - x; + float sizeFactor = gradientSize / 100f; + int partSize = Math.max(1, Math.round(width * sizeFactor)); + + int totalGradientCycle = colors.length * partSize; + + float speedPxPerSec = width * (gradientSpeed / 100f); + float timeSeconds = timeNanos / 1_000_000_000f; + float offset = gradientSpeed == 0 ? 0 : (timeSeconds * speedPxPerSec + x) % totalGradientCycle; + + Matrix3x2fStack matrices = context.getMatrices().pushMatrix(); + matrices.translate(-offset, 0); + + int currentPos = 0; + int colorIndex = 0; + + while (currentPos < width + totalGradientCycle) { + int color1 = colors[colorIndex % colors.length]; + int color2 = colors[(colorIndex + 1) % colors.length]; + + HorizontalGradientGuiRenderState.draw( + context, x + currentPos, y, x + currentPos + partSize, endY, + color1, color2 + ); + + currentPos += partSize; + colorIndex++; + } + + matrices.popMatrix(); + context.disableScissor(); + } + + private void fillHorizonalPulse(DrawContext context, long timeNanos, int x, int y, int endX, int endY) { + context.fill(x, y, endX, endY, getCurrentPulseColor(timeNanos)); + } + + private int getCurrentPulseColor(long timeNanos) { + float progress = timeNanos / 1_000_000_000f * gradientSpeed / gradientSize; + int color1 = colors[(int) (progress % colors.length)]; + int color2 = colors[(int) ((progress + 1) % colors.length)]; + float delta = progress % 1; + return fadeColor(color1, color2, delta); + } + + public void drawHorizontalLine(DrawContext context, long mt, int posX, int endX, int posY) { + this.fillHorizontal(context, mt, posX, posY, endX, posY + 1); + } + + public void drawText(DrawContext context, TextRenderer textRenderer, long timeNanos, String displayText, int x, int y, boolean shadow) { + if (colors.length == 1){ + context.drawText(textRenderer, displayText, x, y, colors[0], shadow); + } else { + switch (mode){ + case SWEEP -> drawTextSweep(context, textRenderer, timeNanos, displayText, x, y, shadow); + case PULSE -> drawTextPulse(context, textRenderer, timeNanos, displayText, x, y, shadow); + } + } + } + + public void drawText(DrawContext context, TextRenderer textRenderer, long timeNanos, Text displayText, int x, int y, boolean shadow) { + if (colors.length == 1){ + context.drawText(textRenderer, displayText, x, y, colors[0], shadow); + } + this.drawText(context, textRenderer, timeNanos, displayText.getString(), x, y, shadow); + } + + private void drawTextSweep(DrawContext context, TextRenderer textRenderer, long timeNanos, String displayText, int x, int y, boolean shadow) { + int pos = 0; + for (int i = 0; i < displayText.length(); i++) { + String s = String.valueOf(displayText.charAt(i)); + int w = textRenderer.getWidth(s); + int col = computeSweepTextColorAt(gradientSpeed == 0 ? pos : pos + x, timeNanos); + context.drawText(textRenderer, s, x +pos, y, col, shadow); + pos += w; + } + } + + private int computeSweepTextColorAt(float pos, long timeNanos) { + pos += timeNanos / 500_000_000f * gradientSpeed; + + float partSize = gradientSize * 2; + int part = (int) (pos / partSize); + double off = pos % partSize / partSize; + + int color1 = colors[part % colors.length]; + int color2 = colors[(part+1) % colors.length]; + + return fadeColor(color1, color2, off); + } + + private void drawTextPulse(DrawContext context, TextRenderer textRenderer, long timeNanos, String displayText, int x, int y, boolean shadow) { + context.drawText(textRenderer, displayText, x, y, getCurrentPulseColor(timeNanos), shadow); + } + + public static GradientOptions solidColor(int color) { + return new GradientOptions(1, 0, new int[]{color}); + } + + public void drawVerticalLine(DrawContext context, long timeNanos, int posX, int posY, int endY) { + fillHorizontal(context, timeNanos, posX, posY, posX + 1, endY); + } + + public GradientOptions multiplyAlpha(double v) { + int[] newColors = new int[colors.length]; + for (int i = 0; i < colors.length; i++) { + int color = colors[i]; + int rgb = color & 0xffffff; + int newAlpha = (int) (((color & 0xff000000) >>> 24) * v); + newColors[i] = (newAlpha << 24) | rgb; + } + return new GradientOptions(mode, gradientSize, gradientSpeed, newColors); + } +} \ No newline at end of file diff --git a/src/main/java/de/shiewk/widgets/color/GradientPreset.java b/src/main/java/de/shiewk/widgets/color/GradientPreset.java new file mode 100644 index 0000000..d84043b --- /dev/null +++ b/src/main/java/de/shiewk/widgets/color/GradientPreset.java @@ -0,0 +1,29 @@ +package de.shiewk.widgets.color; + +import net.minecraft.text.Text; + +import static net.minecraft.text.Text.translatable; + +public record GradientPreset(Text name, GradientOptions gradient) { + + public static final GradientPreset[] presets = new GradientPreset[]{ + new GradientPreset( + translatable("widgets.gradient.preset.rainbow"), + new GradientOptions( + GradientMode.PULSE, + 14, + 20, + new int[]{ + 0xff_ff_00_00, + 0xff_ff_88_00, + 0xff_ff_ff_00, + 0xff_00_ff_00, + 0xff_00_ff_ff, + 0xff_50_00_80, + 0xff_ff_00_ff, + } + ) + ) + }; + +} diff --git a/src/main/java/de/shiewk/widgets/render/state/HorizontalGradientGuiRenderState.java b/src/main/java/de/shiewk/widgets/render/state/HorizontalGradientGuiRenderState.java new file mode 100644 index 0000000..87803d4 --- /dev/null +++ b/src/main/java/de/shiewk/widgets/render/state/HorizontalGradientGuiRenderState.java @@ -0,0 +1,102 @@ +package de.shiewk.widgets.render.state; + +import com.mojang.blaze3d.pipeline.RenderPipeline; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.client.gl.RenderPipelines; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.ScreenRect; +import net.minecraft.client.gui.render.state.SimpleGuiElementRenderState; +import net.minecraft.client.render.VertexConsumer; +import net.minecraft.client.texture.TextureSetup; +import org.jetbrains.annotations.Nullable; +import org.joml.Matrix3x2f; + +@Environment(EnvType.CLIENT) +public final class HorizontalGradientGuiRenderState implements SimpleGuiElementRenderState { + + private final RenderPipeline pipeline; + private final TextureSetup textureSetup; + private final Matrix3x2f pose; + private final ScreenRect scissorArea; + private final ScreenRect bounds; + + private final int x; + private final int y; + + private final int endX; + private final int endY; + + private final int colorLeft; + private final int colorRight; + + private HorizontalGradientGuiRenderState( + RenderPipeline pipeline, + TextureSetup textureSetup, + Matrix3x2f pose, + ScreenRect scissorArea, + int x, + int y, + int endX, + int endY, int colorLeft, int colorRight + ) { + this.pipeline = pipeline; + this.textureSetup = textureSetup; + this.pose = pose; + this.scissorArea = scissorArea; + this.x = x; + this.y = y; + this.endX = endX; + this.endY = endY; + this.colorLeft = colorLeft; + this.colorRight = colorRight; + this.bounds = createBounds(x, y, endX, endY, pose, scissorArea); + } + + @Override + public void setupVertices(VertexConsumer vertices) { + vertices.vertex(pose, x, y).color(colorLeft); + vertices.vertex(pose, x, endY).color(colorLeft); + vertices.vertex(pose, endX, endY).color(colorRight); + vertices.vertex(pose, endX, y).color(colorRight); + } + + @Override + public RenderPipeline pipeline() { + return pipeline; + } + + @Override + public TextureSetup textureSetup() { + return textureSetup; + } + + @Override + public @Nullable ScreenRect scissorArea() { + return scissorArea; + } + + @Nullable + private static ScreenRect createBounds(int x0, int y0, int x1, int y1, Matrix3x2f pose, @Nullable ScreenRect scissorArea) { + ScreenRect screenRect = (new ScreenRect(x0, y0, x1 - x0 + 1, y1 - y0)).transformEachVertex(pose); + return scissorArea != null ? scissorArea.intersection(screenRect) : screenRect; + } + + @Override + public @Nullable ScreenRect bounds() { + return bounds; + } + + public static void draw(DrawContext context, int x, int y, int endX, int endY, int colorLeft, int colorRight) { + context.state.addSimpleElement( + new HorizontalGradientGuiRenderState( + RenderPipelines.GUI, + TextureSetup.empty(), + new Matrix3x2f(context.getMatrices()), + context.scissorStack.peekLast(), + x, y, endX, endY, + colorLeft, colorRight + ) + ); + } +} diff --git a/src/main/java/de/shiewk/widgets/utils/WidgetUtils.java b/src/main/java/de/shiewk/widgets/utils/WidgetUtils.java index 5129f14..8cb7236 100644 --- a/src/main/java/de/shiewk/widgets/utils/WidgetUtils.java +++ b/src/main/java/de/shiewk/widgets/utils/WidgetUtils.java @@ -3,6 +3,7 @@ package de.shiewk.widgets.utils; import net.minecraft.client.MinecraftClient; import net.minecraft.client.sound.PositionedSoundInstance; import net.minecraft.sound.SoundEvent; +import net.minecraft.util.math.MathHelper; import net.minecraft.world.tick.TickManager; import java.util.function.BooleanSupplier; @@ -35,4 +36,25 @@ public class WidgetUtils { MinecraftClient.getInstance().getSoundManager().play(PositionedSoundInstance.master(ev, 1f, 1f)); } + public static int fadeColor(int color1, int color2, double delta) { + int alpha = (int) MathHelper.lerp(delta, (color1 >> 24) & 0xff, (color2 >> 24) & 0xff); + int red = (int) MathHelper.lerp(delta, (color1 >> 16) & 0xff, (color2 >> 16) & 0xff); + int green = (int) MathHelper.lerp(delta, (color1 >> 8) & 0xff, (color2 >> 8) & 0xff); + int blue = (int) MathHelper.lerp(delta, color1 & 0xff, color2 & 0xff); + return (alpha << 24) | (red << 16) | (green << 8) | blue; + } + + public static String colorARGBToHexRGBA(int color) { + int a = color >> 24 & 0xff; + int r = color >> 16 & 0xff; + int g = color >> 8 & 0xff; + int b = color & 0xff; + return toHexSingle(r) + toHexSingle(g) + toHexSingle(b) + toHexSingle(a); + } + + private static String toHexSingle(int comp){ + String s = Integer.toHexString(comp); + return "0".repeat(2 - s.length()) + s; + } + } diff --git a/src/main/java/de/shiewk/widgets/widgets/ArmorHudWidget.java b/src/main/java/de/shiewk/widgets/widgets/ArmorHudWidget.java index 08fc655..374df5b 100644 --- a/src/main/java/de/shiewk/widgets/widgets/ArmorHudWidget.java +++ b/src/main/java/de/shiewk/widgets/widgets/ArmorHudWidget.java @@ -1,10 +1,8 @@ package de.shiewk.widgets.widgets; import de.shiewk.widgets.WidgetSettings; -import de.shiewk.widgets.widgets.settings.EnumWidgetSetting; -import de.shiewk.widgets.widgets.settings.IntSliderWidgetSetting; -import de.shiewk.widgets.widgets.settings.RGBAColorWidgetSetting; -import de.shiewk.widgets.widgets.settings.ToggleWidgetSetting; +import de.shiewk.widgets.color.GradientOptions; +import de.shiewk.widgets.widgets.settings.*; import net.minecraft.client.MinecraftClient; import net.minecraft.client.font.TextRenderer; import net.minecraft.client.gui.DrawContext; @@ -28,17 +26,13 @@ public class ArmorHudWidget extends ResizableWidget { new ToggleWidgetSetting("show_durability", translatable("widgets.widgets.armorHud.showDurability"), true), new IntSliderWidgetSetting("width", translatable("widgets.widgets.basictext.width"), 16, 42, 128), new EnumWidgetSetting<>("alignment", translatable("widgets.widgets.basictext.alignment"), BasicTextWidget.TextAlignment.class, BasicTextWidget.TextAlignment.CENTER, BasicTextWidget.TextAlignment::displayText), - new RGBAColorWidgetSetting("backgroundcolor", translatable("widgets.widgets.basictext.background"), 0, 0, 0, 80), + new GradientWidgetSetting("backgroundcolor", translatable("widgets.widgets.basictext.background"), 0x50_00_00_00), new EnumWidgetSetting<>("durability_style", translatable("widgets.widgets.armorHud.durabilityStyle"), DurabilityStyle.class, DurabilityStyle.NUMBER, DurabilityStyle::getDisplayName), - new ToggleWidgetSetting("rainbow", translatable("widgets.widgets.common.rainbow"), false), - new IntSliderWidgetSetting("rainbow_speed", translatable("widgets.widgets.common.rainbow.speed"), 1, 3, 10), - new RGBAColorWidgetSetting("textcolor", translatable("widgets.widgets.basictext.textcolor"), 255, 255, 255, 255) + new GradientWidgetSetting("textcolor", translatable("widgets.widgets.basictext.textcolor"), 0xff_ff_ff_ff) )); getSettings().optionById("width").setShowCondition(() -> this.showDurability); getSettings().optionById("alignment").setShowCondition(() -> this.showDurability); getSettings().optionById("durability_style").setShowCondition(() -> this.showDurability); - getSettings().optionById("rainbow_speed").setShowCondition(() -> this.rainbow); - getSettings().optionById("textcolor").setShowCondition(() -> !this.rainbow); } public enum DurabilityStyle { @@ -50,22 +44,25 @@ public class ArmorHudWidget extends ResizableWidget { } } - private int padding = 1; - private boolean showDurability = true; - private DurabilityStyle durabilityStyle; + protected int padding = 1; + protected boolean showDurability = true; + protected DurabilityStyle durabilityStyle; protected ItemStack helmet; protected ItemStack chestplate; protected ItemStack leggings; protected ItemStack boots; - protected boolean rainbow = false; - protected int rainbowSpeed = 3; protected int preferredWidth = 42; protected BasicTextWidget.TextAlignment textAlignment = BasicTextWidget.TextAlignment.CENTER; - protected int backgroundColor = 0x50_00_00_00, textColor = 0xff_ff_ff_ff; + protected GradientOptions backgroundColor, textColor; @Override public void renderScaled(DrawContext context, long measuringTimeNano, TextRenderer textRenderer, int posX, int posY) { - context.fill(posX, posY, posX+width(), posY+height(), backgroundColor); + backgroundColor.fillHorizontal( + context, + measuringTimeNano, + posX, posY, + posX+width(), posY+height() + ); if (helmet != null){ renderItem(context, measuringTimeNano, textRenderer, helmet, posX + padding, posY + padding); } @@ -99,14 +96,14 @@ public class ArmorHudWidget extends ResizableWidget { switch (textAlignment){ case RIGHT -> { int width = textRenderer.getWidth(text); - context.drawText(textRenderer, text, posX + width() - width - padding * 2, posY + 5, rainbow ? BasicTextWidget.rainbowColor(mt, rainbowSpeed) : textColor, true); + textColor.drawText(context, textRenderer, mt, text, posX + width() - width - padding * 2, posY + 5, true); } case CENTER -> { int width = textRenderer.getWidth(text); - context.drawText(textRenderer, text, posX + ((preferredWidth + padding*2) - width) / 2 + 8, posY + 5, rainbow ? BasicTextWidget.rainbowColor(mt, rainbowSpeed) : textColor, true); + textColor.drawText(context, textRenderer, mt, text, posX + ((preferredWidth + padding*2) - width) / 2 + 8, posY + 5, true); } case LEFT -> { - context.drawText(textRenderer, text, posX + 16 + padding, posY + 5, rainbow ? BasicTextWidget.rainbowColor(mt, rainbowSpeed) : textColor, true); + textColor.drawText(context, textRenderer, mt, text, posX + 16 + padding, posY + 5, true); } } } @@ -156,10 +153,8 @@ public class ArmorHudWidget extends ResizableWidget { this.padding = (int) settings.optionById("padding").getValue(); this.showDurability = (boolean) settings.optionById("show_durability").getValue(); this.durabilityStyle = (DurabilityStyle) settings.optionById("durability_style").getValue(); - this.rainbow = (boolean) settings.optionById("rainbow").getValue(); - this.rainbowSpeed = (int) settings.optionById("rainbow_speed").getValue(); - this.textColor = (int) settings.optionById("textcolor").getValue(); - this.backgroundColor = (int) settings.optionById("backgroundcolor").getValue(); + this.textColor = (GradientOptions) settings.optionById("textcolor").getValue(); + this.backgroundColor = (GradientOptions) settings.optionById("backgroundcolor").getValue(); this.preferredWidth = (int) settings.optionById("width").getValue(); this.textAlignment = (BasicTextWidget.TextAlignment) settings.optionById("alignment").getValue(); } diff --git a/src/main/java/de/shiewk/widgets/widgets/BandwidthWidget.java b/src/main/java/de/shiewk/widgets/widgets/BandwidthWidget.java index a780e69..50f712e 100644 --- a/src/main/java/de/shiewk/widgets/widgets/BandwidthWidget.java +++ b/src/main/java/de/shiewk/widgets/widgets/BandwidthWidget.java @@ -1,6 +1,7 @@ package de.shiewk.widgets.widgets; import de.shiewk.widgets.WidgetSettings; +import de.shiewk.widgets.color.GradientOptions; import de.shiewk.widgets.utils.WidgetUtils; import de.shiewk.widgets.widgets.settings.EnumWidgetSetting; import de.shiewk.widgets.widgets.settings.ToggleWidgetSetting; @@ -52,9 +53,7 @@ public class BandwidthWidget extends BasicTextWidget { new EnumWidgetSetting<>("unit", Text.translatable("widgets.widgets.bandwidth.unit"), Unit.class, Unit.KB, unit -> literal(unit.name)), new ToggleWidgetSetting("fastupdate", translatable("widgets.widgets.bandwidth.fastupdate"), false) )); - getSettings().optionById("textcolor").setShowCondition(() -> !this.dynamicColor && !this.rainbow); - getSettings().optionById("rainbow").setShowCondition(() -> !this.dynamicColor); - getSettings().optionById("rainbow_speed").setShowCondition(() -> !this.dynamicColor && this.rainbow); + getSettings().optionById("textcolor").setShowCondition(() -> !this.dynamicColor); } private int t = 0; @@ -75,11 +74,11 @@ public class BandwidthWidget extends BasicTextWidget { formatAndSetRenderText(literal(unit.sizeFormatter.apply(avgBytesPerSecond))); if (this.dynamicColor){ if (avgBytesPerSecond < 100000){ - this.textColor = 0xff00ff00; + this.textColor = GradientOptions.solidColor(0xff00ff00); } else if (avgBytesPerSecond < 750000) { - this.textColor = 0xffffff00; + this.textColor = GradientOptions.solidColor(0xffffff00); } else { - this.textColor = 0xffff3030; + this.textColor = GradientOptions.solidColor(0xffff3030); } } } diff --git a/src/main/java/de/shiewk/widgets/widgets/BasicTextWidget.java b/src/main/java/de/shiewk/widgets/widgets/BasicTextWidget.java index ef18540..778c708 100644 --- a/src/main/java/de/shiewk/widgets/widgets/BasicTextWidget.java +++ b/src/main/java/de/shiewk/widgets/widgets/BasicTextWidget.java @@ -1,12 +1,9 @@ package de.shiewk.widgets.widgets; -import de.shiewk.widgets.widgets.settings.WidgetSettingOption; +import de.shiewk.widgets.color.GradientOptions; +import de.shiewk.widgets.widgets.settings.*; import de.shiewk.widgets.WidgetSettings; import de.shiewk.widgets.client.WidgetRenderer; -import de.shiewk.widgets.widgets.settings.EnumWidgetSetting; -import de.shiewk.widgets.widgets.settings.IntSliderWidgetSetting; -import de.shiewk.widgets.widgets.settings.RGBAColorWidgetSetting; -import de.shiewk.widgets.widgets.settings.ToggleWidgetSetting; import it.unimi.dsi.fastutil.objects.ObjectArrayList; import net.minecraft.client.font.TextRenderer; import net.minecraft.client.gui.DrawContext; @@ -67,15 +64,11 @@ public abstract class BasicTextWidget extends ResizableWidget { private int padding = 0; private TextRenderer renderer = null; private boolean textShadow = true; - protected boolean rainbow = false; - private int rainbowSpeed = 3; private static ObjectArrayList> getCustomSettings(List> otherCustomOptions) { final ObjectArrayList> list = new ObjectArrayList<>(otherCustomOptions); - list.add(new RGBAColorWidgetSetting("backgroundcolor", translatable("widgets.widgets.basictext.background"), 0, 0, 0, 80)); - list.add(new ToggleWidgetSetting("rainbow", translatable("widgets.widgets.common.rainbow"), false)); - list.add(new RGBAColorWidgetSetting("textcolor", translatable("widgets.widgets.basictext.textcolor"), 255, 255, 255, 255)); - list.add(new IntSliderWidgetSetting("rainbow_speed", translatable("widgets.widgets.common.rainbow.speed"), 1, 3, 10)); + list.add(new GradientWidgetSetting("backgroundcolor", translatable("widgets.widgets.basictext.background"), 0x50_00_00_00)); + list.add(new GradientWidgetSetting("textcolor", translatable("widgets.widgets.basictext.textcolor"), 0xff_ff_ff_ff)); list.add(new IntSliderWidgetSetting("width", translatable("widgets.widgets.basictext.width"), 10, DEFAULT_WIDTH, 80*3)); list.add(new IntSliderWidgetSetting("height", translatable("widgets.widgets.basictext.height"), 9, DEFAULT_HEIGHT, 80)); list.add(new ToggleWidgetSetting("shadow", translatable("widgets.widgets.basictext.textshadow"), true)); @@ -87,17 +80,14 @@ public abstract class BasicTextWidget extends ResizableWidget { protected BasicTextWidget(Identifier id, List> otherCustomOptions) { super(id, getCustomSettings(otherCustomOptions)); getSettings().optionById("padding").setShowCondition(() -> this.textAlignment != TextAlignment.CENTER); - getSettings().optionById("textcolor").setShowCondition(() -> !this.rainbow); - getSettings().optionById("rainbow_speed").setShowCondition(() -> this.rainbow); } protected static final int DEFAULT_WIDTH = 80, - DEFAULT_HEIGHT = 9 + 12, - DEFAULT_BACKGROUND_COLOR = new Color(0, 0, 0, 80).getRGB(), - DEFAULT_TEXT_COLOR = new Color(255, 255 ,255, 255).getRGB(); + DEFAULT_HEIGHT = 9 + 12; - protected int backgroundColor = DEFAULT_BACKGROUND_COLOR, textColor = DEFAULT_TEXT_COLOR, width = DEFAULT_WIDTH, height = DEFAULT_HEIGHT; + protected int width = DEFAULT_WIDTH, height = DEFAULT_HEIGHT; + protected GradientOptions backgroundColor, textColor; protected TextAlignment textAlignment = TextAlignment.CENTER; protected TextStyle textStyle = TextStyle.PLAIN; @@ -115,18 +105,14 @@ public abstract class BasicTextWidget extends ResizableWidget { public void renderScaled(DrawContext context, long n, TextRenderer textRenderer, int posX, int posY) { if (!shouldRender) return; renderer = textRenderer; - context.fill(posX, posY, posX + width(), posY + height(), this.backgroundColor); + this.backgroundColor.fillHorizontal(context, n, posX, posY, posX + width(), posY + height()); Matrix3x2fStack matrices = context.getMatrices() .pushMatrix(); matrices.translate(posX + textX, posY + textY, matrices); - context.drawText(textRenderer, renderText, 0, 0, rainbow ? rainbowColor(n, rainbowSpeed) : this.textColor, textShadow); + this.textColor.drawText(context, textRenderer, n, renderText, 0, 0, textShadow); matrices.popMatrix(); } - public static int rainbowColor(long n, float speed) { - return Color.HSBtoRGB(n / 10_000_000_000f * speed, 1, 1); - } - @Override public final void tick() { tickWidget(); @@ -165,15 +151,13 @@ public abstract class BasicTextWidget extends ResizableWidget { @Override public void onSettingsChanged(WidgetSettings settings) { super.onSettingsChanged(settings); - this.backgroundColor = (int) settings.optionById("backgroundcolor").getValue(); - this.textColor = (int) settings.optionById("textcolor").getValue(); + this.backgroundColor = (GradientOptions) settings.optionById("backgroundcolor").getValue(); + this.textColor = (GradientOptions) settings.optionById("textcolor").getValue(); this.width = (int) settings.optionById("width").getValue(); this.height = (int) settings.optionById("height").getValue(); this.textAlignment = (TextAlignment) settings.optionById("alignment").getValue(); this.padding = (int) settings.optionById("padding").getValue(); this.textShadow = (boolean) settings.optionById("shadow").getValue(); this.textStyle = (TextStyle) settings.optionById("text_style").getValue(); - this.rainbow = (boolean) settings.optionById("rainbow").getValue(); - this.rainbowSpeed = (int) settings.optionById("rainbow_speed").getValue(); } } diff --git a/src/main/java/de/shiewk/widgets/widgets/CoordinatesWidget.java b/src/main/java/de/shiewk/widgets/widgets/CoordinatesWidget.java index e4e9a96..ea6addc 100644 --- a/src/main/java/de/shiewk/widgets/widgets/CoordinatesWidget.java +++ b/src/main/java/de/shiewk/widgets/widgets/CoordinatesWidget.java @@ -1,8 +1,9 @@ package de.shiewk.widgets.widgets; import de.shiewk.widgets.WidgetSettings; +import de.shiewk.widgets.color.GradientOptions; +import de.shiewk.widgets.widgets.settings.GradientWidgetSetting; import de.shiewk.widgets.widgets.settings.IntSliderWidgetSetting; -import de.shiewk.widgets.widgets.settings.RGBAColorWidgetSetting; import de.shiewk.widgets.widgets.settings.ToggleWidgetSetting; import net.minecraft.client.MinecraftClient; import net.minecraft.client.font.TextRenderer; @@ -14,58 +15,48 @@ import net.minecraft.util.Identifier; import java.awt.*; import java.util.List; -import static net.minecraft.text.Text.translatable; - public class CoordinatesWidget extends ResizableWidget { public CoordinatesWidget(Identifier id) { super(id, List.of( new ToggleWidgetSetting("x", Text.translatable("widgets.widgets.coordinates.showX"), true), new ToggleWidgetSetting("y", Text.translatable("widgets.widgets.coordinates.showY"), true), new ToggleWidgetSetting("z", Text.translatable("widgets.widgets.coordinates.showZ"), true), - new RGBAColorWidgetSetting("backgroundcolor", Text.translatable("widgets.widgets.basictext.background"), 0, 0, 0, 80), - new ToggleWidgetSetting("rainbow", translatable("widgets.widgets.common.rainbow"), false), - new RGBAColorWidgetSetting("textcolor", Text.translatable("widgets.widgets.basictext.textcolor"), 255, 255, 255, 255), - new IntSliderWidgetSetting("rainbow_speed", translatable("widgets.widgets.common.rainbow.speed"), 1, 3, 10), + new GradientWidgetSetting("backgroundcolor", Text.translatable("widgets.widgets.basictext.background"), 0x50_00_00_00), + new GradientWidgetSetting("textcolor", Text.translatable("widgets.widgets.basictext.textcolor"), 0xffffffff), new IntSliderWidgetSetting("width", Text.translatable("widgets.widgets.basictext.width"), 10, WIDTH, 80*3), new IntSliderWidgetSetting("paddingX", Text.translatable("widgets.widgets.basictext.paddingX"), 0, 5, 20), new IntSliderWidgetSetting("paddingY", Text.translatable("widgets.widgets.basictext.paddingY"), 0, 5, 20), new ToggleWidgetSetting("shadow", Text.translatable("widgets.widgets.basictext.textshadow"), true) )); - getSettings().optionById("textcolor").setShowCondition(() -> !this.rainbow); - getSettings().optionById("rainbow_speed").setShowCondition(() -> this.rainbow); } private String textX = "X", textY = "Y", textZ = "Z"; - private int txc = 0, tyc = 0, tzc = 0, rainbowSpeed = 3; - private boolean shadow = true, rainbow = false; + private int txc = 0, tyc = 0, tzc = 0; + private boolean shadow = true; @Override public void renderScaled(DrawContext context, long mt, TextRenderer textRenderer, int posX, int posY) { - context.fill(posX, posY, posX + width(), posY + height(), this.backgroundColor); + this.backgroundColor.fillHorizontal(context, mt, posX, posY, posX + width(), posY + height()); int y = this.paddingY; if (showX){ y++; - context.drawText(textRenderer, "X: ", posX + paddingX, posY + y, textColor(mt), shadow); - context.drawText(textRenderer, textX, posX + txc, posY + y, textColor(mt), shadow); + this.textColor.drawText(context, textRenderer, mt, "X: ", posX + paddingX, posY + y, shadow); + this.textColor.drawText(context, textRenderer, mt, textX, posX + txc, posY + y, shadow); y += textRenderer.fontHeight + 1; } if (showY){ y++; - context.drawText(textRenderer, "Y: ", posX + paddingX, posY + y, textColor(mt), shadow); - context.drawText(textRenderer, textY, posX + tyc, posY + y, textColor(mt), shadow); + this.textColor.drawText(context, textRenderer, mt, "Y: ", posX + paddingX, posY + y, shadow); + this.textColor.drawText(context, textRenderer, mt, textY, posX + tyc, posY + y, shadow); y += textRenderer.fontHeight + 1; } if (showZ){ y++; - context.drawText(textRenderer, "Z: ", posX + paddingX, posY + y, textColor(mt), shadow); - context.drawText(textRenderer, textZ, posX + tzc, posY + y, textColor(mt), shadow); + this.textColor.drawText(context, textRenderer, mt, "Z: ", posX + paddingX, posY + y, shadow); + this.textColor.drawText(context, textRenderer, mt, textZ, posX + tzc, posY + y, shadow); } } - private int textColor(long n) { - return rainbow ? BasicTextWidget.rainbowColor(n, rainbowSpeed) : textColor; - } - @Override public void tick() { final TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer; @@ -95,20 +86,17 @@ public class CoordinatesWidget extends ResizableWidget { return Text.translatable("widgets.widgets.coordinates.description"); } - protected static final int - WIDTH = 80, - PADDING = 6, - DEFAULT_BACKGROUND_COLOR = new Color(0, 0, 0, 80).getRGB(), - DEFAULT_TEXT_COLOR = new Color(255, 255 ,255, 255).getRGB(); + protected static final int WIDTH = 80, PADDING = 6; - protected int backgroundColor = DEFAULT_BACKGROUND_COLOR, textColor = DEFAULT_TEXT_COLOR, paddingX = PADDING, paddingY = PADDING, width = WIDTH; + protected GradientOptions backgroundColor, textColor; + protected int paddingX = PADDING, paddingY = PADDING, width = WIDTH; protected boolean showX = true, showY = true, showZ = true; @Override public void onSettingsChanged(WidgetSettings settings) { super.onSettingsChanged(settings); - this.backgroundColor = (int) settings.optionById("backgroundcolor").getValue(); - this.textColor = (int) settings.optionById("textcolor").getValue(); + this.backgroundColor = (GradientOptions) settings.optionById("backgroundcolor").getValue(); + this.textColor = (GradientOptions) settings.optionById("textcolor").getValue(); this.showX = (boolean) settings.optionById("x").getValue(); this.showY = (boolean) settings.optionById("y").getValue(); this.showZ = (boolean) settings.optionById("z").getValue(); @@ -116,8 +104,6 @@ public class CoordinatesWidget extends ResizableWidget { this.paddingY = (int) settings.optionById("paddingY").getValue(); this.width = (int) settings.optionById("width").getValue(); this.shadow = (boolean) settings.optionById("shadow").getValue(); - this.rainbow = (boolean) settings.optionById("rainbow").getValue(); - this.rainbowSpeed = (int) settings.optionById("rainbow_speed").getValue(); } @Override diff --git a/src/main/java/de/shiewk/widgets/widgets/InventoryWidget.java b/src/main/java/de/shiewk/widgets/widgets/InventoryWidget.java index 473bcbe..d729877 100644 --- a/src/main/java/de/shiewk/widgets/widgets/InventoryWidget.java +++ b/src/main/java/de/shiewk/widgets/widgets/InventoryWidget.java @@ -1,10 +1,8 @@ package de.shiewk.widgets.widgets; import de.shiewk.widgets.WidgetSettings; -import de.shiewk.widgets.widgets.settings.EnumWidgetSetting; -import de.shiewk.widgets.widgets.settings.IntSliderWidgetSetting; -import de.shiewk.widgets.widgets.settings.RGBAColorWidgetSetting; -import de.shiewk.widgets.widgets.settings.ToggleWidgetSetting; +import de.shiewk.widgets.color.GradientOptions; +import de.shiewk.widgets.widgets.settings.*; import net.minecraft.client.MinecraftClient; import net.minecraft.client.font.TextRenderer; import net.minecraft.client.gl.RenderPipelines; @@ -51,34 +49,18 @@ public class InventoryWidget extends ResizableWidget { super(id, List.of( new EnumWidgetSetting<>("mode", translatable("widgets.widgets.inventory.mode"), InventoryMode.class, InventoryMode.TEXTURE_PACK, InventoryMode::display), new ToggleWidgetSetting("show_hotbar", translatable("widgets.widgets.inventory.showHotbar"), true), - new ToggleWidgetSetting("rainbow_grid", translatable("widgets.widgets.inventory.rainbowGrid"), false), - new IntSliderWidgetSetting("grid_rainbow_speed", translatable("widgets.widgets.common.rainbow.speed"), 1, 3, 10), - new RGBAColorWidgetSetting("grid_color", translatable("widgets.widgets.inventory.gridColor"), 0, 0, 0, 255), - new ToggleWidgetSetting("rainbow_boxes", translatable("widgets.widgets.inventory.rainbowBoxes"), false), - new IntSliderWidgetSetting("box_rainbow_speed", translatable("widgets.widgets.common.rainbow.speed"), 1, 3, 10), - new RGBAColorWidgetSetting("box_color", translatable("widgets.widgets.inventory.boxColor"), 80, 80, 80, 128) + new GradientWidgetSetting("grid_color", translatable("widgets.widgets.inventory.gridColor"), 0xff000000), + new GradientWidgetSetting("box_color", translatable("widgets.widgets.inventory.boxColor"), 0x88505050) )); - getSettings().optionById("rainbow_grid").setShowCondition(() -> this.mode == InventoryMode.GRID); - getSettings().optionById("grid_rainbow_speed").setShowCondition(() -> this.mode == InventoryMode.GRID && this.rainbowGrid); - getSettings().optionById("grid_color").setShowCondition(() -> this.mode == InventoryMode.GRID && !this.rainbowGrid); - - getSettings().optionById("rainbow_boxes").setShowCondition(() -> this.mode == InventoryMode.BOXES); - getSettings().optionById("box_rainbow_speed").setShowCondition(() -> this.mode == InventoryMode.BOXES && this.rainbowBoxes); - getSettings().optionById("box_color").setShowCondition(() -> this.mode == InventoryMode.BOXES && !this.rainbowBoxes); - + getSettings().optionById("grid_color").setShowCondition(() -> this.mode == InventoryMode.GRID); + getSettings().optionById("box_color").setShowCondition(() -> this.mode == InventoryMode.BOXES); getSettings().optionById("show_hotbar").setShowCondition(() -> this.mode.canDisableHotbar); } private InventoryMode mode = InventoryMode.TEXTURE_PACK; private PlayerInventory inventory; - private boolean rainbowGrid = false; - private int gridColor = 0xff000000; - private int gridRainbowSpeed = 3; - - private boolean rainbowBoxes = false; - private int boxColor = 0xff000000; - private int boxRainbowSpeed = 3; + private GradientOptions gridColor, boxColor; private boolean showHotbar = false; @Override @@ -109,30 +91,28 @@ public class InventoryWidget extends ResizableWidget { context.disableScissor(); } case GRID -> { - int gridColor = rainbowGrid ? BasicTextWidget.rainbowColor(mt, gridRainbowSpeed) : this.gridColor; - context.drawHorizontalLine(posX, posX+width(), posY, gridColor); - context.drawHorizontalLine(posX, posX+width(), posY + 18, gridColor); - context.drawHorizontalLine(posX, posX+width(), posY + 36, gridColor); - context.drawHorizontalLine(posX, posX+width(), posY + 54, gridColor); + gridColor.drawHorizontalLine(context, mt, posX, posX+width(), posY); + gridColor.drawHorizontalLine(context, mt, posX, posX+width(), posY + 18); + gridColor.drawHorizontalLine(context, mt, posX, posX+width(), posY + 36); + gridColor.drawHorizontalLine(context, mt, posX, posX+width(), posY + 54); if (showHotbar){ - context.drawHorizontalLine(posX, posX+width(), posY + 58, gridColor); - context.drawHorizontalLine(posX, posX+width(), posY + 76, gridColor); + gridColor.drawHorizontalLine(context, mt, posX, posX+width(), posY + 58); + gridColor.drawHorizontalLine(context, mt, posX, posX+width(), posY + 76); } - context.drawVerticalLine(posX, posY, posY+height(), gridColor); - context.drawVerticalLine(posX+18, posY, posY+height(), gridColor); - context.drawVerticalLine(posX+18*2, posY, posY+height(), gridColor); - context.drawVerticalLine(posX+18*3, posY, posY+height(), gridColor); - context.drawVerticalLine(posX+18*4, posY, posY+height(), gridColor); - context.drawVerticalLine(posX+18*5, posY, posY+height(), gridColor); - context.drawVerticalLine(posX+18*6, posY, posY+height(), gridColor); - context.drawVerticalLine(posX+18*7, posY, posY+height(), gridColor); - context.drawVerticalLine(posX+18*8, posY, posY+height(), gridColor); - context.drawVerticalLine(posX+18*9, posY, posY+height(), gridColor); + gridColor.drawVerticalLine(context, mt, posX, posY, posY+height()); + gridColor.drawVerticalLine(context, mt, posX+18, posY, posY+height()); + gridColor.drawVerticalLine(context, mt, posX+18*2, posY, posY+height()); + gridColor.drawVerticalLine(context, mt, posX+18*3, posY, posY+height()); + gridColor.drawVerticalLine(context, mt, posX+18*4, posY, posY+height()); + gridColor.drawVerticalLine(context, mt, posX+18*5, posY, posY+height()); + gridColor.drawVerticalLine(context, mt, posX+18*6, posY, posY+height()); + gridColor.drawVerticalLine(context, mt, posX+18*7, posY, posY+height()); + gridColor.drawVerticalLine(context, mt, posX+18*8, posY, posY+height()); + gridColor.drawVerticalLine(context, mt, posX+18*9, posY, posY+height()); } case BOXES -> { - int boxColor = rainbowBoxes ? ((BasicTextWidget.rainbowColor(mt, boxRainbowSpeed) & 0xffffff) | (this.boxColor & 0xff000000)) : this.boxColor; for (int ry = 0; ry < 4; ry++) { if (ry == 0 && !showHotbar) continue; for (int rx = 0; rx < 9; rx++) { @@ -140,7 +120,7 @@ public class InventoryWidget extends ResizableWidget { int itemY = ry == 0 ? posY + 58 : posY + (ry-1) * 18; int itemX = posX + rx * 18; - context.fill(itemX, itemY, itemX + 16, itemY + 16, boxColor); + boxColor.fillHorizontal(context, mt, itemX, itemY, itemX + 16, itemY + 16); } } } @@ -206,13 +186,9 @@ public class InventoryWidget extends ResizableWidget { public void onSettingsChanged(WidgetSettings settings) { super.onSettingsChanged(settings); this.mode = (InventoryMode) settings.optionById("mode").getValue(); - this.rainbowGrid = (boolean) settings.optionById("rainbow_grid").getValue(); - this.gridRainbowSpeed = (int) settings.optionById("grid_rainbow_speed").getValue(); - this.gridColor = (int) settings.optionById("grid_color").getValue(); - this.rainbowBoxes = (boolean) settings.optionById("rainbow_boxes").getValue(); - this.boxRainbowSpeed = (int) settings.optionById("box_rainbow_speed").getValue(); - this.boxColor = (int) settings.optionById("box_color").getValue(); + this.gridColor = (GradientOptions) settings.optionById("grid_color").getValue(); + this.boxColor = (GradientOptions) settings.optionById("box_color").getValue(); this.showHotbar = (boolean) settings.optionById("show_hotbar").getValue() || !mode.canDisableHotbar; } diff --git a/src/main/java/de/shiewk/widgets/widgets/KeyStrokesWidget.java b/src/main/java/de/shiewk/widgets/widgets/KeyStrokesWidget.java index 79ad3ee..411feb5 100644 --- a/src/main/java/de/shiewk/widgets/widgets/KeyStrokesWidget.java +++ b/src/main/java/de/shiewk/widgets/widgets/KeyStrokesWidget.java @@ -1,8 +1,8 @@ package de.shiewk.widgets.widgets; import de.shiewk.widgets.WidgetSettings; -import de.shiewk.widgets.widgets.settings.IntSliderWidgetSetting; -import de.shiewk.widgets.widgets.settings.RGBAColorWidgetSetting; +import de.shiewk.widgets.color.GradientOptions; +import de.shiewk.widgets.widgets.settings.GradientWidgetSetting; import de.shiewk.widgets.widgets.settings.ToggleWidgetSetting; import net.minecraft.client.MinecraftClient; import net.minecraft.client.font.TextRenderer; @@ -11,40 +11,24 @@ import net.minecraft.client.option.KeyBinding; import net.minecraft.text.Text; import net.minecraft.util.Identifier; import net.minecraft.util.Util; -import net.minecraft.util.math.MathHelper; -import java.awt.*; import java.util.List; import java.util.Objects; -import static net.minecraft.text.Text.translatable; - public class KeyStrokesWidget extends ResizableWidget { public KeyStrokesWidget(Identifier id) { super(id, List.of( new ToggleWidgetSetting("showjump", Text.translatable("widgets.widgets.keystrokes.showJumpKey"), true), - new RGBAColorWidgetSetting("bgpressed", Text.translatable("widgets.widgets.keystrokes.colorBackgroundPressed"), 255, 255, 255, 80), - new RGBAColorWidgetSetting("bgunpressed", Text.translatable("widgets.widgets.keystrokes.colorBackgroundUnpressed"), 0, 0, 0, 80), - new ToggleWidgetSetting("rainbow", translatable("widgets.widgets.common.rainbow"), false), - new IntSliderWidgetSetting("rainbow_speed", translatable("widgets.widgets.common.rainbow.speed"), 1, 3, 10), - new RGBAColorWidgetSetting("keypressed", Text.translatable("widgets.widgets.keystrokes.colorKeyPressed"), 255, 255, 255, 255), - new RGBAColorWidgetSetting("keyunpressed", Text.translatable("widgets.widgets.keystrokes.colorKeyUnpressed"), 255, 255, 255, 255) + new GradientWidgetSetting("bgpressed", Text.translatable("widgets.widgets.keystrokes.colorBackgroundPressed"), 0x50ffffff), + new GradientWidgetSetting("bgunpressed", Text.translatable("widgets.widgets.keystrokes.colorBackgroundUnpressed"), 0x50000000), + new GradientWidgetSetting("keypressed", Text.translatable("widgets.widgets.keystrokes.colorKeyPressed"), 0xffffffff), + new GradientWidgetSetting("keyunpressed", Text.translatable("widgets.widgets.keystrokes.colorKeyUnpressed"), 0xffffffff) )); - getSettings().optionById("keypressed").setShowCondition(() -> !this.rainbow); - getSettings().optionById("keyunpressed").setShowCondition(() -> !this.rainbow); - getSettings().optionById("rainbow_speed").setShowCondition(() -> this.rainbow); } + private GradientOptions colorBackgroundPressed, colorBackgroundUnpressed, colorKeyUnpressed, colorKeyPressed; private boolean showJumpKey = true; - private int colorBackgroundPressed = new Color(255, 255, 255, 80).getRGB(), - colorBackgroundUnpressed = new Color(0, 0, 0, 80).getRGB(), - colorKeyUnpressed = 0xffffffff, - colorKeyPressed = 0xffffffff; - - protected boolean rainbow = false; - protected int rainbowSpeed = 3; - protected static class Key { protected final KeyBinding binding; protected boolean isPressed; @@ -78,49 +62,28 @@ public class KeyStrokesWidget extends ResizableWidget { if (showJumpKey) renderSpaceBar(context, measuringTimeNano, posX, posY + 44, KEY_JUMP); } - protected void renderSpaceBar(final DrawContext context, - final long measuringTimeNano, - final int posX, - final int posY, - final Key key){ - long l = measuringTimeNano - key.lastChanged; + protected void renderSpaceBar(final DrawContext context, long mt, int posX, int posY, Key key){ + long l = mt - key.lastChanged; if (l < 100000000){ - if (key.isPressed){ - context.fill(posX, posY, posX + 64, posY + 10, fadeColor(colorBackgroundUnpressed, colorBackgroundPressed, 0.00000001d * l)); - } else { - context.fill(posX, posY, posX + 64, posY + 10, fadeColor(colorBackgroundPressed, colorBackgroundUnpressed, 0.00000001d * l)); - } + double alpha = 0.00000001d * l; + colorBackgroundUnpressed.multiplyAlpha(key.isPressed ? 1-alpha : alpha).fillHorizontal(context, mt, posX, posY, posX + 64, posY + 10); + colorBackgroundPressed.multiplyAlpha(key.isPressed ? alpha : 1-alpha).fillHorizontal(context, mt, posX, posY, posX + 64, posY + 10); } else { - context.fill(posX, posY, posX + 64, posY + 10, key.isPressed ? colorBackgroundPressed : colorBackgroundUnpressed); + (key.isPressed ? colorBackgroundPressed : colorBackgroundUnpressed).fillHorizontal(context, mt, posX, posY, posX + 64, posY + 10); } - context.fill(posX + 5, posY + 4, posX + 59, posY + 5, rainbow ? BasicTextWidget.rainbowColor(measuringTimeNano, rainbowSpeed) : (key.isPressed ? colorKeyPressed : colorKeyUnpressed)); + (key.isPressed ? colorKeyPressed : colorKeyUnpressed).fillHorizontal(context, mt, posX + 5, posY + 4, posX + 59, posY + 5); } - protected void renderKeyStroke(final DrawContext context, - final TextRenderer textRenderer, - final long measuringTimeNano, - final int posX, - final int posY, - final KeyLarge key){ - long l = measuringTimeNano - key.lastChanged; + protected void renderKeyStroke(DrawContext context, TextRenderer textRenderer, long mt, int posX, int posY, KeyLarge key){ + long l = mt - key.lastChanged; if (l < 100000000){ - if (key.isPressed){ - context.fill(posX, posY, posX+20, posY+20, fadeColor(colorBackgroundUnpressed, colorBackgroundPressed, 0.00000001d * l)); - } else { - context.fill(posX, posY, posX+20, posY+20, fadeColor(colorBackgroundPressed, colorBackgroundUnpressed, 0.00000001d * l)); - } + double alpha = 0.00000001d * l; + colorBackgroundUnpressed.multiplyAlpha(key.isPressed ? 1-alpha : alpha).fillHorizontal(context, mt, posX, posY, posX + 20, posY + 20); + colorBackgroundPressed.multiplyAlpha(key.isPressed ? alpha : 1-alpha).fillHorizontal(context, mt, posX, posY, posX + 20, posY + 20); } else { - context.fill(posX, posY, posX+20, posY+20, key.isPressed ? colorBackgroundPressed : colorBackgroundUnpressed); + (key.isPressed ? colorBackgroundPressed : colorBackgroundUnpressed).fillHorizontal(context, mt, posX, posY, posX + 20, posY + 20); } - context.drawText(textRenderer, key.boundToKey, posX+10-(key.boundToLength/2), posY + 6, rainbow ? BasicTextWidget.rainbowColor(measuringTimeNano, rainbowSpeed) : (key.isPressed ? colorKeyPressed : colorKeyUnpressed), true); - } - - private int fadeColor(int color1, int color2, double delta) { - int alpha = (int) MathHelper.lerp(delta, (color1 >> 24) & 0xff, (color2 >> 24) & 0xff); - int red = (int) MathHelper.lerp(delta, (color1 >> 16) & 0xff, (color2 >> 16) & 0xff); - int green = (int) MathHelper.lerp(delta, (color1 >> 8) & 0xff, (color2 >> 8) & 0xff); - int blue = (int) MathHelper.lerp(delta, color1 & 0xff, color2 & 0xff); - return (alpha << 24) | (red << 16) | (green << 8) | blue; + (key.isPressed ? colorKeyPressed : colorKeyUnpressed).drawText(context, textRenderer, mt, key.boundToKey, posX+10-(key.boundToLength/2), posY+6, true); } @Override @@ -168,11 +131,9 @@ public class KeyStrokesWidget extends ResizableWidget { public void onSettingsChanged(WidgetSettings settings) { super.onSettingsChanged(settings); this.showJumpKey = (boolean) settings.optionById("showjump").getValue(); - this.colorBackgroundPressed = (int) settings.optionById("bgpressed").getValue(); - this.colorBackgroundUnpressed = (int) settings.optionById("bgunpressed").getValue(); - this.colorKeyPressed = (int) settings.optionById("keypressed").getValue(); - this.colorKeyUnpressed = (int) settings.optionById("keyunpressed").getValue(); - this.rainbow = (boolean) settings.optionById("rainbow").getValue(); - this.rainbowSpeed = (int) settings.optionById("rainbow_speed").getValue(); + this.colorBackgroundPressed = (GradientOptions) settings.optionById("bgpressed").getValue(); + this.colorBackgroundUnpressed = (GradientOptions) settings.optionById("bgunpressed").getValue(); + this.colorKeyPressed = (GradientOptions) settings.optionById("keypressed").getValue(); + this.colorKeyUnpressed = (GradientOptions) settings.optionById("keyunpressed").getValue(); } } diff --git a/src/main/java/de/shiewk/widgets/widgets/PingWidget.java b/src/main/java/de/shiewk/widgets/widgets/PingWidget.java index 8d73ad8..da2a76f 100644 --- a/src/main/java/de/shiewk/widgets/widgets/PingWidget.java +++ b/src/main/java/de/shiewk/widgets/widgets/PingWidget.java @@ -1,6 +1,7 @@ package de.shiewk.widgets.widgets; import de.shiewk.widgets.WidgetSettings; +import de.shiewk.widgets.color.GradientOptions; import de.shiewk.widgets.utils.WidgetUtils; import de.shiewk.widgets.widgets.settings.ToggleWidgetSetting; import net.minecraft.client.MinecraftClient; @@ -21,9 +22,7 @@ public class PingWidget extends BasicTextWidget { new ToggleWidgetSetting("dynamic_color", Text.translatable("widgets.widgets.ping.dynamicColor"), true), new ToggleWidgetSetting("hide_in_singleplayer", Text.translatable("widgets.widgets.common.hideInSingleplayer"), false) )); - getSettings().optionById("textcolor").setShowCondition(() -> !this.dynamicColor && !this.rainbow); - getSettings().optionById("rainbow").setShowCondition(() -> !this.dynamicColor); - getSettings().optionById("rainbow_speed").setShowCondition(() -> !this.dynamicColor && this.rainbow); + getSettings().optionById("textcolor").setShowCondition(() -> !this.dynamicColor); } private boolean dynamicColor = false; @@ -53,18 +52,18 @@ public class PingWidget extends BasicTextWidget { } if (valuesRead == 0){ formatAndSetRenderText(literal("??? ms")); - if (this.dynamicColor) this.textColor = 0xff00ff00; + if (this.dynamicColor) this.textColor = GradientOptions.solidColor(0xff00ff00); return; } long avgPing = ping / valuesRead; formatAndSetRenderText(literal(avgPing + " ms")); if (this.dynamicColor){ if (avgPing < 50){ - this.textColor = 0xff00ff00; + this.textColor = GradientOptions.solidColor(0xff00ff00); } else if (avgPing < 120) { - this.textColor = 0xffffff00; + this.textColor = GradientOptions.solidColor(0xffffff00); } else { - this.textColor = 0xffff3030; + this.textColor = GradientOptions.solidColor(0xffff3030); } } } diff --git a/src/main/java/de/shiewk/widgets/widgets/TPSWidget.java b/src/main/java/de/shiewk/widgets/widgets/TPSWidget.java index 9b7786e..a477c66 100644 --- a/src/main/java/de/shiewk/widgets/widgets/TPSWidget.java +++ b/src/main/java/de/shiewk/widgets/widgets/TPSWidget.java @@ -2,6 +2,7 @@ package de.shiewk.widgets.widgets; import de.shiewk.widgets.WidgetSettings; import de.shiewk.widgets.WidgetsMod; +import de.shiewk.widgets.color.GradientOptions; import de.shiewk.widgets.widgets.settings.IntSliderWidgetSetting; import de.shiewk.widgets.widgets.settings.ToggleWidgetSetting; import net.minecraft.client.MinecraftClient; @@ -22,9 +23,7 @@ public class TPSWidget extends BasicTextWidget { new ToggleWidgetSetting("dynamic_color", translatable("widgets.widgets.tps.dynamicColor"), true), new IntSliderWidgetSetting("window_size", translatable("widgets.widgets.tps.windowSize"), 2, 5, 20) )); - getSettings().optionById("textcolor").setShowCondition(() -> !this.dynamicColor && !this.rainbow); - getSettings().optionById("rainbow").setShowCondition(() -> !this.dynamicColor); - getSettings().optionById("rainbow_speed").setShowCondition(() -> !this.dynamicColor && this.rainbow); + getSettings().optionById("textcolor").setShowCondition(() -> !this.dynamicColor); } public static final TPSWidget INSTANCE = new TPSWidget(Identifier.of(WidgetsMod.MOD_ID, "tps")); @@ -86,7 +85,7 @@ public class TPSWidget extends BasicTextWidget { } else { formatAndSetRenderText(literal("???")); } - if (dynamicColor) this.textColor = 0xff00ff00; + if (dynamicColor) this.textColor = GradientOptions.solidColor(0xff00ff00); } else { tps = Math.round(tps * 10f) / 10f; if (showLabel){ @@ -96,11 +95,11 @@ public class TPSWidget extends BasicTextWidget { } if (dynamicColor){ if (tps >= targetTickRate * 0.990){ - this.textColor = 0xff00ff00; + this.textColor = GradientOptions.solidColor(0xff00ff00); } else if (tps >= targetTickRate * 0.740){ - this.textColor = 0xffffff00; + this.textColor = GradientOptions.solidColor(0xffffff00); } else { - this.textColor = 0xffff0000; + this.textColor = GradientOptions.solidColor(0xffff0000); } } } diff --git a/src/main/java/de/shiewk/widgets/widgets/settings/GradientWidgetSetting.java b/src/main/java/de/shiewk/widgets/widgets/settings/GradientWidgetSetting.java new file mode 100644 index 0000000..a1fb94b --- /dev/null +++ b/src/main/java/de/shiewk/widgets/widgets/settings/GradientWidgetSetting.java @@ -0,0 +1,129 @@ +package de.shiewk.widgets.widgets.settings; + +import com.google.gson.JsonElement; +import de.shiewk.widgets.client.screen.WidgetSettingsScreen; +import de.shiewk.widgets.client.screen.gradienteditor.GradientEditorScreen; +import de.shiewk.widgets.color.GradientMode; +import de.shiewk.widgets.color.GradientOptions; +import de.shiewk.widgets.utils.WidgetUtils; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.font.TextRenderer; +import net.minecraft.client.gui.Click; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.cursor.StandardCursors; +import net.minecraft.sound.SoundEvents; +import net.minecraft.text.Text; +import net.minecraft.util.Util; + +import static de.shiewk.widgets.client.WidgetManager.gson; +import static de.shiewk.widgets.utils.WidgetUtils.colorARGBToHexRGBA; + +public class GradientWidgetSetting extends WidgetSettingOption { + + public GradientWidgetSetting(String id, Text name, GradientMode defaultMode, int defaultGradientSize, int defaultGradientSpeed, int defaultColor) { + super(id, name); + this.value = new GradientOptions( + defaultMode, + defaultGradientSize, + defaultGradientSpeed, + new int[]{defaultColor} + ); + } + + public GradientWidgetSetting(String id, Text name, int defaultColor) { + this(id, name, GradientMode.SWEEP, 40, 10, defaultColor); + } + + private GradientOptions value; + + @Override + public JsonElement saveState() { + return gson.toJsonTree(getValue()); + } + + @Override + public void loadState(JsonElement state) { + if (state.isJsonPrimitive() && state.getAsJsonPrimitive().isNumber()){ + this.value = new GradientOptions( + this.value.mode(), + this.value.gradientSize(), + this.value.gradientSpeed(), + new int[]{ state.getAsInt() } + ); + } else { + this.value = gson.fromJson(state, GradientOptions.class); + } + } + + @Override + public GradientOptions getValue() { + return this.value; + } + + @Override + public void render(DrawContext context, int mouseX, int mouseY, float deltaTicks) { + final TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer; + final long n = Util.getMeasuringTimeNano(); + GradientOptions gradient = this.getValue(); + gradient.fillHorizontal( + context, + n, + getX(), + getY(), + getX() + getWidth(), + getY() + getHeight() + ); + int outlineColor; + String displayText; + int[] colors = gradient.colors(); + if (colors.length == 1){ + outlineColor = colors[0] | 0xff_00_00_00; + displayText = "#" + colorARGBToHexRGBA(colors[0]); + } else { + outlineColor = 0xff_ff_ff_ff; + displayText = Text.translatable("widgets.ui.widgetSettings.colors", colors.length).getString(); + } + context.drawHorizontalLine(getX(), getX()+getWidth(), getY(), outlineColor); + context.drawHorizontalLine(getX(), getX()+getWidth(), getY()+getHeight(), outlineColor); + context.drawVerticalLine(getX(), getY(), getY() + getHeight(), outlineColor); + context.drawVerticalLine(getX() + getWidth(), getY(), getY() + getHeight(), outlineColor); + + int width = textRenderer.getWidth(displayText); + context.drawText( + textRenderer, + displayText, + getX() + (getWidth() / 2 - (width / 2)), + getY() + (getHeight() / 2 - 4), + 0xff_ff_ff_ff, + true + ); + + if (this.isHovered(mouseX, mouseY)){ + context.setCursor(StandardCursors.POINTING_HAND); + } + } + + @Override + public int getWidth() { + return 72; + } + + @Override + public int getHeight() { + return 24; + } + + @Override + public boolean mouseClicked(Click click, boolean doubled) { + MinecraftClient client = MinecraftClient.getInstance(); + if (client.currentScreen instanceof WidgetSettingsScreen screen) { + WidgetUtils.playSound(SoundEvents.BLOCK_COPPER_BULB_TURN_ON); + client.setScreen(new GradientEditorScreen(client.currentScreen, screen.getWidget(), this, screen.getOnChange())); + } + return true; + } + + public void setValue(GradientOptions value) { + this.value = value; + } +} diff --git a/src/main/java/de/shiewk/widgets/widgets/settings/RGBAColorWidgetSetting.java b/src/main/java/de/shiewk/widgets/widgets/settings/RGBAColorWidgetSetting.java index 69e975f..01694c5 100644 --- a/src/main/java/de/shiewk/widgets/widgets/settings/RGBAColorWidgetSetting.java +++ b/src/main/java/de/shiewk/widgets/widgets/settings/RGBAColorWidgetSetting.java @@ -2,6 +2,7 @@ package de.shiewk.widgets.widgets.settings; import com.google.gson.JsonElement; import com.google.gson.JsonPrimitive; +import de.shiewk.widgets.client.screen.WidgetVisibilityToggle; import de.shiewk.widgets.utils.WidgetUtils; import net.minecraft.client.MinecraftClient; import net.minecraft.client.font.TextRenderer; @@ -19,6 +20,10 @@ import org.jetbrains.annotations.NotNull; import java.awt.*; +/** + * @deprecated Use {@link GradientWidgetSetting} + */ +@Deprecated(forRemoval = true, since = "2.3.0") public class RGBAColorWidgetSetting extends WidgetSettingOption { public RGBAColorWidgetSetting(String id, Text name, int defaultR, int defaultG, int defaultB, int defaultAlpha) { super(id, name); @@ -118,7 +123,7 @@ public class RGBAColorWidgetSetting extends WidgetSettingOption { return true; } - public class ChangeScreen extends Screen { + public class ChangeScreen extends Screen implements WidgetVisibilityToggle { private final Screen parent; private int x; @@ -205,6 +210,11 @@ public class RGBAColorWidgetSetting extends WidgetSettingOption { return super.mouseClicked(click, doubled); } + @Override + public boolean shouldRenderWidgets() { + return false; + } + public class ColorBar extends ClickableWidget { private final int component; diff --git a/src/main/java/de/shiewk/widgets/widgets/settings/WidgetSettingOption.java b/src/main/java/de/shiewk/widgets/widgets/settings/WidgetSettingOption.java index aad88d9..303d1b3 100644 --- a/src/main/java/de/shiewk/widgets/widgets/settings/WidgetSettingOption.java +++ b/src/main/java/de/shiewk/widgets/widgets/settings/WidgetSettingOption.java @@ -14,6 +14,7 @@ import java.util.function.BooleanSupplier; import java.util.function.Consumer; public abstract class WidgetSettingOption implements Drawable, Widget { + private final String id; private final Text name; private int x = 0; diff --git a/src/main/resources/assets/widgets/lang/de_de.json b/src/main/resources/assets/widgets/lang/de_de.json index b76c3d0..f029dec 100644 --- a/src/main/resources/assets/widgets/lang/de_de.json +++ b/src/main/resources/assets/widgets/lang/de_de.json @@ -1,6 +1,11 @@ { + "widgets.gradient.preset.rainbow": "Regenbogen", + "widgets.gradient.pulse": "Pulse", + "widgets.gradient.pulse.description": "Färbt Text oder Oberfläche mit einer einzelnen Farbe und geht dabei zwischen den ausgewählten Farben über", + "widgets.gradient.sweep": "Sweep", + "widgets.gradient.sweep.description": "Färbt Text oder Oberfläche mit einem sich bewegenden Farbverlauf", "widgets.key.category": "Widgets", - "widgets.key.config": "Öffne Widgets-Einstellungen", + "widgets.key.config": "Widgets-Einstellungen öffnen", "widgets.ui.anchor.bottom_center": "Unten mittig", "widgets.ui.anchor.bottom_left": "Unten links", "widgets.ui.anchor.bottom_right": "Unten rechts", @@ -20,9 +25,24 @@ "widgets.ui.editPositions.snap": "Positionen ausrichten: %s", "widgets.ui.editPositions.snap.help": "Richtet die Position des Widgets mit Positionen der anderen Widgets aus", "widgets.ui.enabled": "Aktiviert", + "widgets.ui.gradientEditor": "Farbe bearbeiten", + "widgets.ui.gradientEditor.colors": "Dieser Verlauf:", + "widgets.ui.gradientEditor.colors.add.tooltip.0": "Diesem Farbverlauf eine Farbe hinzufügen", + "widgets.ui.gradientEditor.colors.add.tooltip.1": "(Bleibe bei einer Farbe, um den Farbverlauf zu deaktivieren)", + "widgets.ui.gradientEditor.editColor": "Farbe bearbeiten:", + "widgets.ui.gradientEditor.editColor.orPick": "Alternative Auswahl:", + "widgets.ui.gradientEditor.gradientSettings": "Verlaufsoptionen:", + "widgets.ui.gradientEditor.gradientSettings.addMoreColors": "Füge mindestens zwei Farben hinzu, um den Farbverlauf zu bearbeiten.", + "widgets.ui.gradientEditor.importOther": "Von Widget importieren", + "widgets.ui.gradientEditor.mode": "Modus: %s", + "widgets.ui.gradientEditor.removeColor": "Farbe entfernen", + "widgets.ui.gradientEditor.size": "Größe: %s%%", + "widgets.ui.gradientEditor.speed": "Geschwindigkeit: %s%%", + "widgets.ui.gradientEditor.usePreset": "Voreinstellung nutzen", "widgets.ui.preview": "Vorschau", "widgets.ui.search": "Suchen...", "widgets.ui.widgetSettings": "Bearbeite %s Einstellungen", + "widgets.ui.widgetSettings.colors": "%s Farben", "widgets.widgets.armorHud": "Rüstungsstatus", "widgets.widgets.armorHud.description": "Zeigt die Rüstung, die du gerade trägst, und ihre Haltbarkeit.", "widgets.widgets.armorHud.durabilityStyle": "Haltbarkeitsstil", @@ -31,7 +51,7 @@ "widgets.widgets.armorHud.padding": "Abstand", "widgets.widgets.armorHud.showDurability": "Haltbarkeitsbeschriftung anzeigen", "widgets.widgets.bandwidth": "Bandbreite", - "widgets.widgets.bandwidth.description": "Zeigt, wie viele Daten an den aktuell Server gesendet werden/vom Server an den Klient gesendet werden.", + "widgets.widgets.bandwidth.description": "Zeigt, wie viele Daten aktuell vom Server an den Klienten gesendet werden.", "widgets.widgets.bandwidth.dynamicColor": "Farbe dynamisch anzeigen", "widgets.widgets.bandwidth.fastupdate": "Schnelle Aktualisierung", "widgets.widgets.bandwidth.unit": "Einheit", @@ -70,8 +90,6 @@ "widgets.widgets.combo.description": "Zeigt deine momentane Combo an", "widgets.widgets.combo.displayThreshold": "Minimum sichtbar", "widgets.widgets.common.hideInSingleplayer": "In Einzelspielerwelten verbergen", - "widgets.widgets.common.rainbow": "Regenbogen-Text", - "widgets.widgets.common.rainbow.speed": "Regenbogengeschwindigkeit", "widgets.widgets.common.realtime": "Echtzeit-Update (braucht mehr Leistung)", "widgets.widgets.common.showLabel": "Beschriftung anzeigen", "widgets.widgets.common.sizePercent": "Widgetgröße (%)", @@ -101,8 +119,6 @@ "widgets.widgets.inventory.mode.texture_pack": "Texturenpaket", "widgets.widgets.inventory.mode.transparent": "Transparent", "widgets.widgets.inventory.mode.vanilla": "Standard", - "widgets.widgets.inventory.rainbowBoxes": "Regenbogenzellen", - "widgets.widgets.inventory.rainbowGrid": "Regenbogengitter", "widgets.widgets.inventory.showHotbar": "Schnellzugriffsleiste anzeigen", "widgets.widgets.keystrokes": "Keystrokes", "widgets.widgets.keystrokes.colorBackgroundPressed": "Hintergrundfarbe (Taste gedrückt)", diff --git a/src/main/resources/assets/widgets/lang/en_us.json b/src/main/resources/assets/widgets/lang/en_us.json index 9b4b852..47d6577 100644 --- a/src/main/resources/assets/widgets/lang/en_us.json +++ b/src/main/resources/assets/widgets/lang/en_us.json @@ -1,4 +1,9 @@ { + "widgets.gradient.preset.rainbow": "Rainbow", + "widgets.gradient.pulse": "Pulse", + "widgets.gradient.pulse.description": "Paints the text or surface with one solid color, cycling through your selected palette", + "widgets.gradient.sweep": "Sweep", + "widgets.gradient.sweep.description": "Paints the text or surface with a moving gradient", "widgets.key.category": "Widgets", "widgets.key.config": "Open Widget Management", "widgets.ui.anchor.bottom_center": "Bottom center", @@ -20,9 +25,24 @@ "widgets.ui.editPositions.snap": "Align positions: %s", "widgets.ui.editPositions.snap.help": "Aligns the widget with positions of other widgets", "widgets.ui.enabled": "Enabled", + "widgets.ui.gradientEditor": "Color editor", + "widgets.ui.gradientEditor.colors": "This gradient:", + "widgets.ui.gradientEditor.colors.add.tooltip.0": "Add a color to this gradient", + "widgets.ui.gradientEditor.colors.add.tooltip.1": "(Leave this as one color to disable the gradient)", + "widgets.ui.gradientEditor.editColor": "Edit color:", + "widgets.ui.gradientEditor.editColor.orPick": "Alternative picker:", + "widgets.ui.gradientEditor.gradientSettings": "Gradient settings:", + "widgets.ui.gradientEditor.gradientSettings.addMoreColors": "Add more than one color to customize the gradient", + "widgets.ui.gradientEditor.importOther": "Import from widget", + "widgets.ui.gradientEditor.mode": "Mode: %s", + "widgets.ui.gradientEditor.removeColor": "Remove color", + "widgets.ui.gradientEditor.size": "Size: %s%%", + "widgets.ui.gradientEditor.speed": "Speed: %s%%", + "widgets.ui.gradientEditor.usePreset": "Use preset", "widgets.ui.preview": "Preview", "widgets.ui.search": "Search...", "widgets.ui.widgetSettings": "Edit %s settings", + "widgets.ui.widgetSettings.colors": "%s colors", "widgets.widgets.armorHud": "Armor Status", "widgets.widgets.armorHud.description": "Shows the armor you are currently wearing and its durability.", "widgets.widgets.armorHud.durabilityStyle": "Durability style", @@ -31,7 +51,7 @@ "widgets.widgets.armorHud.padding": "Padding", "widgets.widgets.armorHud.showDurability": "Show durability label", "widgets.widgets.bandwidth": "Bandwidth", - "widgets.widgets.bandwidth.description": "Shows how much data is being read/sent from the server you're connected to.", + "widgets.widgets.bandwidth.description": "Shows how much data the server you're connected to is sending.", "widgets.widgets.bandwidth.dynamicColor": "Dynamic Color", "widgets.widgets.bandwidth.fastupdate": "Fast update", "widgets.widgets.bandwidth.unit": "Unit", @@ -70,8 +90,6 @@ "widgets.widgets.combo.description": "Shows your current Combo.", "widgets.widgets.combo.displayThreshold": "Display Threshold", "widgets.widgets.common.hideInSingleplayer": "Hide in singleplayer worlds", - "widgets.widgets.common.rainbow": "Rainbow text", - "widgets.widgets.common.rainbow.speed": "Rainbow speed", "widgets.widgets.common.realtime": "Real-time updating (may use more performance)", "widgets.widgets.common.showLabel": "Show label", "widgets.widgets.common.sizePercent": "Widget size (%)", @@ -101,8 +119,6 @@ "widgets.widgets.inventory.mode.texture_pack": "Texture pack", "widgets.widgets.inventory.mode.transparent": "Transparent", "widgets.widgets.inventory.mode.vanilla": "Vanilla", - "widgets.widgets.inventory.rainbowBoxes": "Rainbow boxes", - "widgets.widgets.inventory.rainbowGrid": "Rainbow Grid", "widgets.widgets.inventory.showHotbar": "Show hotbar", "widgets.widgets.keystrokes": "Keystrokes", "widgets.widgets.keystrokes.colorBackgroundPressed": "Background color (key pressed)", diff --git a/src/main/resources/assets/widgets/textures/gui/arrow_down.png b/src/main/resources/assets/widgets/textures/gui/arrow_down.png new file mode 100644 index 0000000..e7bcf16 Binary files /dev/null and b/src/main/resources/assets/widgets/textures/gui/arrow_down.png differ diff --git a/src/main/resources/assets/widgets/textures/gui/button_plus.png b/src/main/resources/assets/widgets/textures/gui/button_plus.png new file mode 100644 index 0000000..93ca122 Binary files /dev/null and b/src/main/resources/assets/widgets/textures/gui/button_plus.png differ