From d339b3d1bd59453697ae6fe584a7d38d3795fb77 Mon Sep 17 00:00:00 2001 From: Superkat32 Date: Sun, 6 Oct 2024 13:41:26 -0400 Subject: [PATCH 1/5] Text block screen formatting buttons and hotkeys FEAT: Added new buttons which allow for the already implemented text formatting tags to be added to the text block edit screen. Globally used/agreed upon hotkeys, such as ctrl+b for bold, have also been implemented for this screen --- .../screen/ingame/TextBlockEditScreen.java | 89 +++++++++++++++++++ .../glowcase/mixin/KeyboardMixin.java | 22 +++++ src/main/resources/glowcase.mixins.json | 4 +- 3 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 src/main/java/dev/hephaestus/glowcase/mixin/KeyboardMixin.java diff --git a/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/TextBlockEditScreen.java b/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/TextBlockEditScreen.java index 05af351..9d6c2fb 100644 --- a/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/TextBlockEditScreen.java +++ b/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/TextBlockEditScreen.java @@ -4,8 +4,11 @@ import com.mojang.blaze3d.systems.RenderSystem; import dev.hephaestus.glowcase.block.entity.TextBlockEntity; import dev.hephaestus.glowcase.packet.C2SEditTextBlock; +import eu.pb4.placeholders.api.parsers.tag.TagRegistry; +import eu.pb4.placeholders.api.parsers.tag.TextTag; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.gui.widget.ButtonWidget; import net.minecraft.client.gui.widget.TextFieldWidget; import net.minecraft.client.render.BufferBuilder; @@ -16,9 +19,13 @@ import net.minecraft.client.util.SelectionManager; import net.minecraft.text.Text; import net.minecraft.text.TextColor; +import net.minecraft.util.Formatting; import net.minecraft.util.math.MathHelper; import org.lwjgl.glfw.GLFW; +import java.util.Arrays; +import java.util.Comparator; + //TODO: multi-character selection at some point? it may be a bit complex but it'd be nice public class TextBlockEditScreen extends GlowcaseScreen { private final TextBlockEntity textBlockEntity; @@ -114,6 +121,55 @@ public void init() { this.addDrawableChild(this.shadowToggle); this.addDrawableChild(this.zOffsetToggle); this.addDrawableChild(this.colorEntryWidget); + + addFormattingButtons(280, 20, innerPadding, 20, 2); + } + + private void addFormattingButtons(int x, int y, int innerPadding, int buttonSize, int buttonPadding) { + int buttonX = x + innerPadding * 2; //adding numbers to this variable because I personally find that more readable, that's all + int buttonY = y + innerPadding; //reduce the times this is calculated + ButtonWidget boldText = ButtonWidget.builder(Text.literal("B").formatted(Formatting.BOLD), action -> { + insertTag(TagRegistry.SAFE.getTag("bold")); + }).dimensions(buttonX, buttonY, buttonSize, buttonSize).build(); + + buttonX += buttonSize + buttonPadding; + ButtonWidget italicizeText = ButtonWidget.builder(Text.literal("I").formatted(Formatting.ITALIC), action -> { + insertTag(TagRegistry.SAFE.getTag("italic")); + }).dimensions(buttonX, buttonY, buttonSize, buttonSize).build(); + + buttonX += buttonSize + buttonPadding; + ButtonWidget strikeText = ButtonWidget.builder(Text.literal("S").formatted(Formatting.STRIKETHROUGH), action -> { + insertTag(TagRegistry.SAFE.getTag("strikethrough")); + }).dimensions(buttonX, buttonY, buttonSize, buttonSize).build(); + + buttonX += buttonSize + buttonPadding; + ButtonWidget underlineText = ButtonWidget.builder(Text.literal("U").formatted(Formatting.UNDERLINE), action -> { + insertTag(TagRegistry.SAFE.getTag("underline")); + }).dimensions(buttonX, buttonY, buttonSize, buttonSize).build(); + + buttonX += buttonSize + buttonPadding; + //not using the actual obfuscated formatting here because the movement can be annoying + ButtonWidget obfuscateText = ButtonWidget.builder(Text.literal("@"), action -> { + insertTag(TagRegistry.SAFE.getTag("obfuscated")); + }).dimensions(buttonX, buttonY, buttonSize, buttonSize).build(); + +// buttonX += buttonSize + buttonPadding; // + 4? (only works on padding of 2) +// ButtonWidget colorText = ButtonWidget.builder(Text.literal("\uD83D\uDD8C"), action -> { +// //TODO - color picker widget? +// }).dimensions(buttonX, buttonY, buttonSize, buttonSize).build(); +// +// buttonX += buttonSize + buttonPadding; +// ButtonWidget testText = ButtonWidget.builder(Text.literal("t"), action -> { +// +// }).dimensions(buttonX, buttonY, 50, 100).build(); + + this.addDrawableChild(boldText); + this.addDrawableChild(italicizeText); + this.addDrawableChild(strikeText); + this.addDrawableChild(underlineText); + this.addDrawableChild(obfuscateText); +// this.addDrawableChild(colorText); +// this.addDrawableChild(testText); } @Override @@ -195,6 +251,15 @@ public void render(DrawContext context, int mouseX, int mouseY, float delta) { } } + public void insertTag(TextTag tag) { + if(tag == null) return; + //find the alias with the least amount of characters + String name = Arrays.stream(tag.aliases()).min(Comparator.comparing(String::length)).get(); + + this.selectionManager.insert("<" + name + ">"); + this.selectionManager.moveCursor(-name.length() - 3, false, SelectionManager.SelectionType.CHARACTER); + } + @Override public boolean charTyped(char chr, int keyCode) { if (this.colorEntryWidget.isActive()) { @@ -245,6 +310,30 @@ public boolean keyPressed(int keyCode, int scanCode, int modifiers) { deleteLine(); return true; } else { + + //formatting hotkeys + if(Screen.hasControlDown()) { + if(keyCode == GLFW.GLFW_KEY_B) { + insertTag(TagRegistry.SAFE.getTag("bold")); + return true; + } else if(keyCode == GLFW.GLFW_KEY_I) { + insertTag(TagRegistry.SAFE.getTag("italic")); + return true; + } else if(keyCode == GLFW.GLFW_KEY_U) { + insertTag(TagRegistry.SAFE.getTag("underline")); + return true; + } else if(keyCode == GLFW.GLFW_KEY_5 || keyCode == GLFW.GLFW_KEY_S) { + //There isn't a commonly agreed upon hotkey for strikethrough unlike the rest above + //apparently 5 is commonly used for strikethrough ¯\_(ツ)_/¯ + //Google Docs and Microsoft Word have 5 in their hotkeys, while Discord has S in its hotkey + insertTag(TagRegistry.SAFE.getTag("strikethrough")); + return true; + } else if(keyCode == GLFW.GLFW_KEY_O) { + insertTag(TagRegistry.SAFE.getTag("obfuscated")); + return true; + } + } + try { boolean val = this.selectionManager.handleSpecialKey(keyCode) || super.keyPressed(keyCode, scanCode, modifiers); int selectionOffset = this.textBlockEntity.getRawLine(this.currentRow).length() - this.selectionManager.getSelectionStart(); diff --git a/src/main/java/dev/hephaestus/glowcase/mixin/KeyboardMixin.java b/src/main/java/dev/hephaestus/glowcase/mixin/KeyboardMixin.java new file mode 100644 index 0000000..02281c1 --- /dev/null +++ b/src/main/java/dev/hephaestus/glowcase/mixin/KeyboardMixin.java @@ -0,0 +1,22 @@ +package dev.hephaestus.glowcase.mixin; + +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; +import dev.hephaestus.glowcase.client.gui.screen.ingame.TextBlockEditScreen; +import net.minecraft.client.Keyboard; +import net.minecraft.client.MinecraftClient; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +@Mixin(Keyboard.class) +public class KeyboardMixin { + + @ModifyExpressionValue( + method = "onKey", + at = @At(value = "INVOKE", target = "Lnet/minecraft/client/util/NarratorManager;isActive()Z") + ) + private boolean preventNarratorToggleOnTextBlockScreen(boolean original) { + //prevents the narrator from being toggled when pressing "ctrl+b" to hotkey bold formatting in the text block + return original && !(MinecraftClient.getInstance().currentScreen instanceof TextBlockEditScreen); + } + +} diff --git a/src/main/resources/glowcase.mixins.json b/src/main/resources/glowcase.mixins.json index bf5db56..2dd784e 100644 --- a/src/main/resources/glowcase.mixins.json +++ b/src/main/resources/glowcase.mixins.json @@ -7,7 +7,9 @@ "LockableContainerBlockEntityAccessor", "PlayerEntityMixin" ], - "client": [], + "client": [ + "KeyboardMixin" + ], "server": [], "injectors": { "defaultRequire": 1 From fd7ad49e2b9214c7d6b723fd5ac391ccd3218370 Mon Sep 17 00:00:00 2001 From: Superkat32 Date: Mon, 7 Oct 2024 20:21:59 -0400 Subject: [PATCH 2/5] Color Picker FEAT: Added the color picker widget, currently used to allow for custom color tags --- .../ingame/ColorPickerIncludedScreen.java | 11 + .../screen/ingame/TextBlockEditScreen.java | 93 +++- .../gui/widget/ingame/ColorPickerWidget.java | 512 ++++++++++++++++++ .../gui/widget/ingame/ColorPresetWidget.java | 77 +++ .../gui/widget/ingame/IconButtonWidget.java | 97 ++++ 5 files changed, 766 insertions(+), 24 deletions(-) create mode 100644 src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/ColorPickerIncludedScreen.java create mode 100644 src/main/java/dev/hephaestus/glowcase/client/gui/widget/ingame/ColorPickerWidget.java create mode 100644 src/main/java/dev/hephaestus/glowcase/client/gui/widget/ingame/ColorPresetWidget.java create mode 100644 src/main/java/dev/hephaestus/glowcase/client/gui/widget/ingame/IconButtonWidget.java diff --git a/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/ColorPickerIncludedScreen.java b/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/ColorPickerIncludedScreen.java new file mode 100644 index 0000000..caebf76 --- /dev/null +++ b/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/ColorPickerIncludedScreen.java @@ -0,0 +1,11 @@ +package dev.hephaestus.glowcase.client.gui.screen.ingame; + +import dev.hephaestus.glowcase.client.gui.widget.ingame.ColorPickerWidget; +import net.minecraft.util.Formatting; + +public interface ColorPickerIncludedScreen { + ColorPickerWidget colorPickerWidget(); + void toggleColorPicker(boolean active); + void insertHexTag(String hex); + void insertFormattingTag(Formatting formatting); +} diff --git a/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/TextBlockEditScreen.java b/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/TextBlockEditScreen.java index 9d6c2fb..4d23620 100644 --- a/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/TextBlockEditScreen.java +++ b/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/TextBlockEditScreen.java @@ -3,6 +3,7 @@ import com.mojang.blaze3d.platform.GlStateManager; import com.mojang.blaze3d.systems.RenderSystem; import dev.hephaestus.glowcase.block.entity.TextBlockEntity; +import dev.hephaestus.glowcase.client.gui.widget.ingame.ColorPickerWidget; import dev.hephaestus.glowcase.packet.C2SEditTextBlock; import eu.pb4.placeholders.api.parsers.tag.TagRegistry; import eu.pb4.placeholders.api.parsers.tag.TextTag; @@ -27,12 +28,14 @@ import java.util.Comparator; //TODO: multi-character selection at some point? it may be a bit complex but it'd be nice -public class TextBlockEditScreen extends GlowcaseScreen { +public class TextBlockEditScreen extends GlowcaseScreen implements ColorPickerIncludedScreen{ private final TextBlockEntity textBlockEntity; private SelectionManager selectionManager; private int currentRow; private long ticksSinceOpened = 0; + private ColorPickerWidget colorPickerWidget; + private ButtonWidget colorText; private ButtonWidget changeAlignment; private TextFieldWidget colorEntryWidget; private ButtonWidget zOffsetToggle; @@ -115,6 +118,10 @@ public void init() { this.zOffsetToggle.setMessage(Text.literal(this.textBlockEntity.zOffset.name())); }).dimensions(330 + innerPadding * 3, 0, 80, 20).build(); + this.colorPickerWidget = ColorPickerWidget.builder(this,216, 10).size(182, 104).build(); + this.colorPickerWidget.toggle(false); //start deactivated + + this.addDrawableChild(colorPickerWidget); this.addDrawableChild(increaseSize); this.addDrawableChild(decreaseSize); this.addDrawableChild(this.changeAlignment); @@ -129,47 +136,41 @@ private void addFormattingButtons(int x, int y, int innerPadding, int buttonSize int buttonX = x + innerPadding * 2; //adding numbers to this variable because I personally find that more readable, that's all int buttonY = y + innerPadding; //reduce the times this is calculated ButtonWidget boldText = ButtonWidget.builder(Text.literal("B").formatted(Formatting.BOLD), action -> { - insertTag(TagRegistry.SAFE.getTag("bold")); + insertTag(TagRegistry.SAFE.getTag("bold"), true); }).dimensions(buttonX, buttonY, buttonSize, buttonSize).build(); buttonX += buttonSize + buttonPadding; ButtonWidget italicizeText = ButtonWidget.builder(Text.literal("I").formatted(Formatting.ITALIC), action -> { - insertTag(TagRegistry.SAFE.getTag("italic")); + insertTag(TagRegistry.SAFE.getTag("italic"), true); }).dimensions(buttonX, buttonY, buttonSize, buttonSize).build(); buttonX += buttonSize + buttonPadding; ButtonWidget strikeText = ButtonWidget.builder(Text.literal("S").formatted(Formatting.STRIKETHROUGH), action -> { - insertTag(TagRegistry.SAFE.getTag("strikethrough")); + insertTag(TagRegistry.SAFE.getTag("strikethrough"), true); }).dimensions(buttonX, buttonY, buttonSize, buttonSize).build(); buttonX += buttonSize + buttonPadding; ButtonWidget underlineText = ButtonWidget.builder(Text.literal("U").formatted(Formatting.UNDERLINE), action -> { - insertTag(TagRegistry.SAFE.getTag("underline")); + insertTag(TagRegistry.SAFE.getTag("underline"), true); }).dimensions(buttonX, buttonY, buttonSize, buttonSize).build(); buttonX += buttonSize + buttonPadding; //not using the actual obfuscated formatting here because the movement can be annoying ButtonWidget obfuscateText = ButtonWidget.builder(Text.literal("@"), action -> { - insertTag(TagRegistry.SAFE.getTag("obfuscated")); + insertTag(TagRegistry.SAFE.getTag("obfuscated"), true); }).dimensions(buttonX, buttonY, buttonSize, buttonSize).build(); -// buttonX += buttonSize + buttonPadding; // + 4? (only works on padding of 2) -// ButtonWidget colorText = ButtonWidget.builder(Text.literal("\uD83D\uDD8C"), action -> { -// //TODO - color picker widget? -// }).dimensions(buttonX, buttonY, buttonSize, buttonSize).build(); -// -// buttonX += buttonSize + buttonPadding; -// ButtonWidget testText = ButtonWidget.builder(Text.literal("t"), action -> { -// -// }).dimensions(buttonX, buttonY, 50, 100).build(); + buttonX += buttonSize + buttonPadding; // + 4? (only works on padding of 2) + this.colorText = ButtonWidget.builder(Text.literal("\uD83D\uDD8C"), action -> { + toggleColorPicker(!this.colorPickerWidget.active); + }).dimensions(buttonX, buttonY, buttonSize, buttonSize).build(); this.addDrawableChild(boldText); this.addDrawableChild(italicizeText); this.addDrawableChild(strikeText); this.addDrawableChild(underlineText); this.addDrawableChild(obfuscateText); -// this.addDrawableChild(colorText); -// this.addDrawableChild(testText); + this.addDrawableChild(colorText); } @Override @@ -251,10 +252,14 @@ public void render(DrawContext context, int mouseX, int mouseY, float delta) { } } - public void insertTag(TextTag tag) { + public void insertTag(TextTag tag, boolean findShortest) { if(tag == null) return; //find the alias with the least amount of characters - String name = Arrays.stream(tag.aliases()).min(Comparator.comparing(String::length)).get(); + String name = tag.name(); + if(findShortest && tag.aliases().length > 1) { + String shortest = Arrays.stream(tag.aliases()).min(Comparator.comparing(String::length)).get(); + name = Arrays.stream(tag.aliases()).min(Comparator.comparing(String::length)).get(); + } this.selectionManager.insert("<" + name + ">"); this.selectionManager.moveCursor(-name.length() - 3, false, SelectionManager.SelectionType.CHARACTER); @@ -279,6 +284,13 @@ public boolean keyPressed(int keyCode, int scanCode, int modifiers) { } else { return this.colorEntryWidget.keyPressed(keyCode, scanCode, modifiers); } + } if(this.colorPickerWidget.active && (keyCode == GLFW.GLFW_KEY_ENTER || keyCode == GLFW.GLFW_KEY_ESCAPE)) { + if(keyCode == GLFW.GLFW_KEY_ENTER) { + this.colorPickerWidget.confirmColor(); + } else { + this.colorPickerWidget.cancel(); + } + return true; } else { setFocused(null); if (keyCode == GLFW.GLFW_KEY_ENTER || keyCode == GLFW.GLFW_KEY_KP_ENTER) { @@ -314,22 +326,22 @@ public boolean keyPressed(int keyCode, int scanCode, int modifiers) { //formatting hotkeys if(Screen.hasControlDown()) { if(keyCode == GLFW.GLFW_KEY_B) { - insertTag(TagRegistry.SAFE.getTag("bold")); + insertTag(TagRegistry.SAFE.getTag("bold"), true); return true; } else if(keyCode == GLFW.GLFW_KEY_I) { - insertTag(TagRegistry.SAFE.getTag("italic")); + insertTag(TagRegistry.SAFE.getTag("italic"), true); return true; } else if(keyCode == GLFW.GLFW_KEY_U) { - insertTag(TagRegistry.SAFE.getTag("underline")); + insertTag(TagRegistry.SAFE.getTag("underline"), true); return true; } else if(keyCode == GLFW.GLFW_KEY_5 || keyCode == GLFW.GLFW_KEY_S) { //There isn't a commonly agreed upon hotkey for strikethrough unlike the rest above //apparently 5 is commonly used for strikethrough ¯\_(ツ)_/¯ //Google Docs and Microsoft Word have 5 in their hotkeys, while Discord has S in its hotkey - insertTag(TagRegistry.SAFE.getTag("strikethrough")); + insertTag(TagRegistry.SAFE.getTag("strikethrough"), true); return true; } else if(keyCode == GLFW.GLFW_KEY_O) { - insertTag(TagRegistry.SAFE.getTag("obfuscated")); + insertTag(TagRegistry.SAFE.getTag("obfuscated"), true); return true; } } @@ -381,6 +393,18 @@ public boolean mouseClicked(double mouseX, double mouseY, int button) { if (!this.colorEntryWidget.mouseClicked(mouseX, mouseY, button)) { this.colorEntryWidget.setFocused(false); } + if(colorPickerWidget.active && colorPickerWidget.visible) { + if(colorPickerWidget.isMouseOver(mouseX, mouseY)) { + colorPickerWidget.mouseClicked(mouseX, mouseY, button); + this.setFocused(colorPickerWidget); + this.setDragging(true); + return true; + } else { + if(!this.colorText.isMouseOver(mouseX, mouseY)) { + toggleColorPicker(false); + } + } + } if (mouseY > topOffset) { this.currentRow = MathHelper.clamp((int) (mouseY - topOffset) / 12, 0, this.textBlockEntity.lines.size() - 1); this.setFocused(null); @@ -433,4 +457,25 @@ public boolean mouseClicked(double mouseX, double mouseY, int button) { return super.mouseClicked(mouseX, mouseY, button); } } + + @Override + public ColorPickerWidget colorPickerWidget() { + return this.colorPickerWidget; + } + + @Override + public void toggleColorPicker(boolean active) { + this.colorPickerWidget.toggle(active); + } + + @Override + public void insertHexTag(String hex) { + this.selectionManager.insert("<" + hex + ">"); + this.selectionManager.moveCursor(-hex.length() - 3, false, SelectionManager.SelectionType.CHARACTER); + } + + @Override + public void insertFormattingTag(Formatting formatting) { + this.insertTag(TagRegistry.SAFE.getTag(formatting.getName()), false); + } } diff --git a/src/main/java/dev/hephaestus/glowcase/client/gui/widget/ingame/ColorPickerWidget.java b/src/main/java/dev/hephaestus/glowcase/client/gui/widget/ingame/ColorPickerWidget.java new file mode 100644 index 0000000..d910b4d --- /dev/null +++ b/src/main/java/dev/hephaestus/glowcase/client/gui/widget/ingame/ColorPickerWidget.java @@ -0,0 +1,512 @@ +package dev.hephaestus.glowcase.client.gui.widget.ingame; + +import com.mojang.blaze3d.systems.RenderSystem; +import dev.hephaestus.glowcase.client.gui.screen.ingame.ColorPickerIncludedScreen; +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder; +import net.minecraft.client.gui.widget.PressableWidget; +import net.minecraft.client.render.RenderLayer; +import net.minecraft.client.render.VertexConsumer; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import net.minecraft.util.Identifier; +import org.apache.commons.compress.utils.Lists; +import org.joml.Matrix4f; +import java.awt.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class ColorPickerWidget extends PressableWidget { + static final Identifier CONFIRM_TEXTURE = Identifier.ofVanilla("pending_invite/accept"); + static final Identifier CONFIRM_HIGHLIGHTED_TEXTURE = Identifier.ofVanilla("pending_invite/accept_highlighted"); + static final Identifier CANCEL_TEXTURE = Identifier.ofVanilla("pending_invite/reject"); + static final Identifier CANCEL_HIGHLIGHTED_TEXTURE = Identifier.ofVanilla("pending_invite/reject_highlighted"); + + public final ColorPickerIncludedScreen screen; + public Color color = Color.red; + public boolean includePresets = true; + public ArrayList presetWidgets = Lists.newArrayList(); + public boolean confirmOrCancelButtonDown = false; + public IconButtonWidget confirmButton; + public IconButtonWidget cancelButton; + + private boolean mouseDown = false; + private int presetY, presetSize, presetPadding, presetHeight; + private boolean presetDown = false; + private int previewX, previewY, previewWidth, previewHeight; + private int hueX, hueY, hueWidth, hueHeight; + private boolean hueDown = false; + private int satLightX, satLightY, satLightWidth, satLightHeight; + private boolean satLightDown = false; + public int hueThumbX; + public int satLightThumbX, satLightThumbY; + + private float[] HSL; + private float hue; + private float saturation; + private float light; + + public static ColorPickerWidget.Builder builder(ColorPickerIncludedScreen screen, int x, int y) { + return new ColorPickerWidget.Builder(screen, x, y); + } + + public ColorPickerWidget(ColorPickerIncludedScreen screen, int x, int y, int width, int height, Text message) { + super(x, y, width, height, message); + this.screen = screen; + + this.confirmButton = IconButtonWidget.builder(CONFIRM_TEXTURE, action -> this.confirmColor()) + .hoverIcon(CONFIRM_HIGHLIGHTED_TEXTURE).build(); + + this.cancelButton = IconButtonWidget.builder(CANCEL_TEXTURE, action -> this.cancel()) + .hoverIcon(CANCEL_HIGHLIGHTED_TEXTURE).build(); + + updatePositions(); + updateHSL(); + updateThumbPositions(); + } + + public void setIncludePresets(boolean shouldInclude) { + this.includePresets = shouldInclude; + } + + public void setPresets(boolean includeDefaultPresets, List addedPresets) { + if(includeDefaultPresets) { + addDefaultPresets(); + } + if(!addedPresets.isEmpty()) { + for (Color preset : addedPresets) { + this.presetWidgets.add(ColorPresetWidget.fromColor(this, preset)); + } + } + } + + public void confirmColor() { + this.insertColor(this.color); + this.toggle(false); + } + + public void cancel() { + this.toggle(false); + } + + public void toggle(boolean active) { + this.active = active; + this.visible = active; + if(this.active) { + this.updatePositions(); + this.updateHSL(); + this.updateThumbPositions(); + } + } + + public void insertColor(Color color) { + String hex = getHexCode(color); + this.screen.insertHexTag(hex); + } + + public void insertFormatting(Formatting formatting) { + this.screen.insertFormattingTag(formatting); + } + + public void setColor(Color color) { + this.color = color; + } + + @Override + protected void renderWidget(DrawContext context, int mouseX, int mouseY, float delta) { + if(!visible) return; + updateHSL(); + + context.setShaderColor(1f, 1f, 1f, this.alpha); + RenderSystem.enableBlend(); + RenderSystem.enableDepthTest(); + + int x = this.getX(); + int y = this.getY(); + int z = 1; //prevent z-fighting with other widgets + int width = this.getWidth(); + int height = this.getHeight(); + + //background + context.drawTexture(Identifier.ofVanilla("textures/gui/inworld_menu_list_background.png"), x, y, z, 0, 0, width, height, 32, 32); + if(this.isSelected()) { + //outline + drawOutline(context, x, y, width, height, z, Color.white); + } + + //color picker stuff + updatePositions(); + this.confirmButton.setPosition(x + width - presetSize - presetPadding, y + height - presetSize - 2, z + 1, presetSize, presetSize + 2); + this.cancelButton.setPosition(x + width - presetSize * 2 - presetPadding * 2 - 1, y + height - presetSize - 2, z + 1, presetSize, presetSize + 2); + + drawColorPreview(context, previewX, previewY, previewWidth, previewHeight, z + 1); + drawSatLight(context, satLightX, satLightY, satLightWidth, satLightHeight, z + 3); + drawHueBar(context, hueX, hueY, hueWidth, hueHeight, z + 1); + if(this.includePresets) { + //sorta dynamic but also really specific to keep it all aligned + //I'm not going to worry about it a lot though because I do not see the custom preset thing being used a lot if at all + drawPresets(context, mouseX, mouseY, delta, previewX, presetY, height - presetY, z + 1, presetSize, width / (presetSize + presetPadding), presetPadding); + } + + this.confirmButton.renderWidget(context, mouseX, mouseY, delta); + this.cancelButton.renderWidget(context, mouseX, mouseY, delta); + + context.setShaderColor(1f, 1f, 1f, 1f); + } + + public void updatePositions() { + int x = this.getX(); + int y = this.getY(); + int width = this.getWidth(); + int height = this.getHeight(); + + presetSize = (int) (height / 6.5); + presetPadding = 2; + presetHeight = presetSize * 2 + presetPadding * 2; + + previewX = x + 2; + previewY = y + 2; + previewWidth = width / 3; + previewHeight = height - 16 - (includePresets ? presetHeight : 0); + satLightX = previewX + previewWidth + 2; + satLightY = y + 2; + satLightWidth = width - previewWidth - 6; + satLightHeight = previewHeight; + hueX = x + 2; + hueY = previewY + previewHeight + 2; + hueWidth = width - 4; + hueHeight = height - previewHeight - 6 - (includePresets ? presetHeight : 0); + presetY = hueY + hueHeight + presetPadding; + } + + private void drawColorPreview(DrawContext context, int x, int y, int width, int height, int z) { + context.fill(x, y, x + width, y + height, z, this.color.getRGB()); + } + + private void drawHueBar(DrawContext context, int x, int y, int width, int height, int z) { + //rainbow gradient + int[] colors = new int[] {Color.red.getRGB(), Color.yellow.getRGB(), Color.green.getRGB(), + Color.cyan.getRGB(), Color.blue.getRGB(), Color.magenta.getRGB(), Color.red.getRGB()}; + + int maxColors = colors.length - 1; + for (int color = 0; color < maxColors; color++) { + sidewaysGradient(context, + x + ((float) width / maxColors * (color)), y, + (float) width / maxColors, height, + z + 1, colors[color], colors[color + 1]); + } + + //thumb + drawOutline(context, hueThumbX - 3, y - 1, 6, height + 2, z + 2, Color.white); + context.fill(hueThumbX - 3, y - 1, hueThumbX + 3, y + height + 1, z + 1, getRgbFromHueThumb()); + } + + private void drawSatLight(DrawContext context, int x, int y, int width, int height, int z) { + //white to current color's hue, left to right + sidewaysGradient(context, x, y, width, height, z, Color.white.getRGB(), getRgbFromHueThumb()); + + //transparent to black, top to bottom + context.fillGradient(x, y, x + width, y + height, z, 0x00000000, Color.black.getRGB()); + + //thumb + context.fill(satLightThumbX - 4, satLightThumbY - 4, satLightThumbX + 4, satLightThumbY + 4, z + 1, this.color.getRGB()); + drawOutline(context, satLightThumbX - 4, satLightThumbY - 4, 8, 8, z + 2, Color.white); + } + + private void drawOutline(DrawContext context, int x, int y, int width, int height, int z, Color outlineColor) { + int color = outlineColor.getRGB(); + context.fill(x, y, x + width, y + 1, z, color); + context.fill(x, y, x + 1, y + height, z, color); + context.fill(x + width, y, x + width - 1, y + height, z, color); + context.fill(x, y + height, x + width, y + height - 1, z, color); + } + + private void sidewaysGradient(DrawContext context, float x, float y, float width, float height, float z, int startColor, int endColor) { + RenderLayer layer = RenderLayer.getGui(); + VertexConsumer vertexConsumer = context.getVertexConsumers().getBuffer(layer); + + Matrix4f matrix = context.getMatrices().peek().getPositionMatrix(); + vertexConsumer.vertex(matrix, x, y, z).color(startColor); + vertexConsumer.vertex(matrix, x, y + height, z).color(startColor); + vertexConsumer.vertex(matrix, x + width, y + height, z).color(endColor); + vertexConsumer.vertex(matrix, x + width, y, z).color(endColor); + } + + private void drawPresets(DrawContext context, int mouseX, int mouseY, float delta, int x, int y, int height, int z, int presetSize, int presetsPerLine, int presetPadding) { + int presetX = x; + int presetY = y; + int renderedPresets = 0; + for (ColorPresetWidget preset : this.presetWidgets) { + preset.setPosition(presetX, presetY, z, presetSize); + preset.renderWidget(context, mouseX, mouseY, delta); + presetX += presetSize + presetPadding; + renderedPresets++; + if(renderedPresets % presetsPerLine == 0) { + presetY += presetSize + presetPadding; + if(presetY > y + height) { //prevent overflow + return; + } + presetX = x; + } + } + } + + //done manually to keep list order instead of looping through Formatting.values() + public void addDefaultPresets() { + ColorPresetWidget darkRed = ColorPresetWidget.fromFormatting(this, Formatting.DARK_RED); + ColorPresetWidget red = ColorPresetWidget.fromFormatting(this, Formatting.RED); + ColorPresetWidget gold = ColorPresetWidget.fromFormatting(this, Formatting.GOLD); + ColorPresetWidget yellow = ColorPresetWidget.fromFormatting(this, Formatting.YELLOW); + ColorPresetWidget green = ColorPresetWidget.fromFormatting(this, Formatting.GREEN); + ColorPresetWidget darkGreen = ColorPresetWidget.fromFormatting(this, Formatting.DARK_GREEN); + ColorPresetWidget aqua = ColorPresetWidget.fromFormatting(this, Formatting.AQUA); + ColorPresetWidget darkAqua = ColorPresetWidget.fromFormatting(this, Formatting.DARK_AQUA); + ColorPresetWidget blue = ColorPresetWidget.fromFormatting(this, Formatting.BLUE); + ColorPresetWidget darkBlue = ColorPresetWidget.fromFormatting(this, Formatting.DARK_BLUE); + ColorPresetWidget lightPurple = ColorPresetWidget.fromFormatting(this, Formatting.LIGHT_PURPLE); + ColorPresetWidget darkPurple = ColorPresetWidget.fromFormatting(this, Formatting.DARK_PURPLE); + ColorPresetWidget white = ColorPresetWidget.fromFormatting(this, Formatting.WHITE); + ColorPresetWidget grey = ColorPresetWidget.fromFormatting(this, Formatting.GRAY); + ColorPresetWidget darkGrey = ColorPresetWidget.fromFormatting(this, Formatting.DARK_GRAY); + ColorPresetWidget black = ColorPresetWidget.fromFormatting(this, Formatting.BLACK); + this.presetWidgets.addAll(List.of(darkRed, red, gold, yellow, green, darkGreen, aqua, darkAqua, + blue, darkBlue, lightPurple, darkPurple, white, grey, darkGrey, black)); + } + + @Override + public void onClick(double mouseX, double mouseY) { + this.mouseDown = true; + this.satLightDown = false; + this.hueDown = false; + this.presetDown = false; + this.confirmOrCancelButtonDown = false; + setColorFromMouse(mouseX, mouseY); + } + + public void setColorFromMouse(double mouseX, double mouseY) { + if(clickedSatLight(mouseX, mouseY)) { + setSatLightFromMouse(mouseX, mouseY); + } else if(clickedHue(mouseX, mouseY)) { + setHueFromMouse(mouseX); + } else if(this.confirmButton.isMouseOver(mouseX, mouseY)) { + if(satLightDown || hueDown || presetDown || confirmOrCancelButtonDown) return; + this.confirmButton.onClick(mouseX, mouseY); + confirmOrCancelButtonDown = true; + } else if(this.cancelButton.isMouseOver(mouseX, mouseY)) { + if(satLightDown || hueDown || presetDown || confirmOrCancelButtonDown) return; + this.cancelButton.onClick(mouseX, mouseY); + confirmOrCancelButtonDown = true; + } else { + //clickedPreset also sets the preset to avoid an extra calculation + checkAndSetPreset(mouseX, mouseY); + } + + } + + public boolean clickedSatLight(double mouseX, double mouseY) { + if(hueDown || presetDown || confirmOrCancelButtonDown) return false; + + if(mouseX >= satLightX + && mouseX <= satLightX + satLightWidth + && mouseY >= satLightY + && mouseY <= satLightY + satLightHeight) { + satLightDown = true; + } + + if(satLightDown) { + satLightThumbX = (int) Math.clamp(mouseX, satLightX, satLightX + satLightWidth); + satLightThumbY = (int) Math.clamp(mouseY, satLightY, satLightY + satLightHeight); + } + return satLightDown; + } + + public boolean clickedHue(double mouseX, double mouseY) { + if(satLightDown || presetDown || confirmOrCancelButtonDown) return false; + + if(mouseY >= hueY && mouseY <= hueY + hueHeight + && mouseX >= hueX && mouseX <= hueX + hueWidth) { + hueDown = true; + } + + if(hueDown) { + hueThumbX = (int) Math.clamp(mouseX, hueX, hueX + hueWidth); + } + return hueDown; + } + + public boolean checkAndSetPreset(double mouseX, double mouseY) { + if(satLightDown || hueDown || presetDown || confirmOrCancelButtonDown) return false; + + //just checks for each preset here, and also sets here so it doesn't have to check again + for (ColorPresetWidget preset : this.presetWidgets) { + if(preset.isMouseOver(mouseX, mouseY)) { + preset.onClick(mouseX, mouseY); + //even though the preset closes the color picker, + //this is added to prevent spamming tags when holding down the mouse button + presetDown = true; + } + } + return presetDown; + } + + @Override + protected void onDrag(double mouseX, double mouseY, double deltaX, double deltaY) { + if(mouseDown || isMouseOver(mouseX, mouseY)) { + setColorFromMouse(mouseX, mouseY); + } + } + + @Override + public void onRelease(double mouseX, double mouseY) { + this.mouseDown = false; + } + + @Override + public boolean isMouseOver(double mouseX, double mouseY) { + return super.isMouseOver(mouseX, mouseY); + } + + @Override + public void onPress() {} + + public void setSatLightFromMouse(double mouseX, double mouseY) { + if(mouseX < satLightX) { + this.saturation = 0f; + } else if(mouseX > satLightX + satLightWidth) { + this.saturation = 1f; + } else { + float newSat = (float) (mouseX - satLightX) / satLightWidth; + this.saturation = Math.clamp(newSat, 0f, 1f); + } + + if(mouseY < satLightY) { + this.light = 1f; + } else if (mouseY > satLightY + satLightHeight) { + this.light = 0f; + } else { + float newLight = (float) (mouseY - satLightY) / satLightHeight; + this.light = Math.clamp(1f - newLight, 0f, 1f); + } + setColorFromHSL(); + } + + public void setHueFromMouse(double mouseX) { + if(mouseX < hueX) { + this.hue = 0f; + } else if(mouseX > hueX + hueWidth) { + this.hue = 1f; + } else { + float newHue = (float) (mouseX - hueX) / hueWidth; + this.hue = Math.clamp(newHue, 0f, 1f); + } + setColorFromHSL(); + } + + public void updateThumbPositions() { + this.satLightThumbX = getSatLightThumbX(); + this.satLightThumbY = getSatLightThumbY(); + this.hueThumbX = getHueThumbX(); + } + + private int getSatLightThumbX() { + int min = satLightX; + int max = satLightX + satLightWidth; + int value = (int) (min + (satLightWidth * this.saturation)); + return Math.clamp(value, min, max); + } + + private int getSatLightThumbY() { + int min = satLightY; + int max = satLightY + satLightHeight; + int value = (int) (min + (satLightHeight * (1.0f - this.light))); + return Math.clamp(value, min, max); + } + + private int getHueThumbX() { + int min = hueX; + int max = hueX + hueWidth; + int value = (int) (min + hueWidth * this.hue); + return Math.clamp(value, min, max); + } + + public Color getCurrentColor() { + return this.color; + } + + public void setColorFromHSL() { + float trueHue = (float) (hueThumbX - hueX) / hueWidth; + this.color = Color.getHSBColor(trueHue, this.saturation, this.light); + } + + public int getRgbFromHueThumb() { + float trueHue = (float) (hueThumbX - hueX) / hueWidth; + return Color.HSBtoRGB(trueHue, 1, 1); + } + + protected void updateHSL() { + this.HSL = getHSL(); + this.hue = HSL[0]; + this.saturation = HSL[1]; + this.light = HSL[2]; + } + + protected float[] getHSL() { + return Color.RGBtoHSB(this.color.getRed(), this.color.getGreen(), this.color.getBlue(), null); + } + + @Override + public void appendClickableNarrations(NarrationMessageBuilder builder) { + this.appendDefaultNarrations(builder); + } + + public static String getHexCode(Color color) { + return "#" + String.format("%1$06X", color.getRGB() & 0x00FFFFFF); + } + + @Environment(EnvType.CLIENT) + public static class Builder { + private ColorPickerIncludedScreen screen; + private int x; + private int y; + private int width = 150; + private int height = 200; + private boolean includePresets = true; + private boolean includeDefaultPresets = true; + private List presets = Lists.newArrayList(); + + public Builder(ColorPickerIncludedScreen screen, int x, int y) { + this.screen = screen; + this.x = x; + this.y = y; + } + + public ColorPickerWidget.Builder size(int width, int height) { + this.width = width; + this.height = height; + return this; + } + + public ColorPickerWidget.Builder includePresets(boolean shouldInclude) { + this.includePresets = shouldInclude; + return this; + } + + public ColorPickerWidget.Builder withPreset(boolean includeDefault, Color... presets) { + this.includeDefaultPresets = includeDefault; + this.presets.addAll(Arrays.asList(presets)); + return this; + } + + public ColorPickerWidget build() { + ColorPickerWidget colorPickerWidget = new ColorPickerWidget(this.screen, this.x, this.y, this.width, this.height, Text.of("")); + colorPickerWidget.setIncludePresets(this.includePresets); + if(this.includePresets) { + colorPickerWidget.setPresets(this.includeDefaultPresets, this.presets); + } + return colorPickerWidget; + } + } +} diff --git a/src/main/java/dev/hephaestus/glowcase/client/gui/widget/ingame/ColorPresetWidget.java b/src/main/java/dev/hephaestus/glowcase/client/gui/widget/ingame/ColorPresetWidget.java new file mode 100644 index 0000000..6cbcb15 --- /dev/null +++ b/src/main/java/dev/hephaestus/glowcase/client/gui/widget/ingame/ColorPresetWidget.java @@ -0,0 +1,77 @@ +package dev.hephaestus.glowcase.client.gui.widget.ingame; + +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder; +import net.minecraft.client.gui.widget.PressableWidget; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import org.jetbrains.annotations.Nullable; + +import java.awt.*; + +public class ColorPresetWidget extends PressableWidget { + public final ColorPickerWidget colorPickerWidget; + public final Color color; + @Nullable + public Formatting formatting = null; + public int z = 0; + + public ColorPresetWidget(ColorPickerWidget colorPicker, int x, int y, int width, int height, Color color) { + super(x, y, width, height, Text.of("")); + this.colorPickerWidget = colorPicker; + this.color = color; + } + + public void setPosition(int x, int y, int z, int size) { + this.setX(x); + this.setY(y); + this.setDimensions(size, size); + this.z = z; + } + + public static ColorPresetWidget fromFormatting(ColorPickerWidget colorPicker, Formatting formatting) { + if(formatting.isColor()) { + //noinspection DataFlowIssue + ColorPresetWidget presetWidget = new ColorPresetWidget(colorPicker,0, 0, 0, 0, new Color(formatting.getColorValue())); + presetWidget.formatting = formatting; + return presetWidget; + } + return new ColorPresetWidget(colorPicker, 0, 0, 0, 0, Color.white); //fallback + } + + public static ColorPresetWidget fromColor(ColorPickerWidget colorPicker, Color color) { + return new ColorPresetWidget(colorPicker, 0, 0, 0, 0, color); + } + + @Override + protected void renderWidget(DrawContext context, int mouseX, int mouseY, float delta) { + context.fill(this.getX(), this.getY(), this.getX() + this.getWidth(), this.getY() + this.getHeight(), this.z, this.color.getRGB()); + if(isMouseOver(mouseX, mouseY)) { + drawOutline(context, this.getX() - 1, this.getY() - 1, this.getWidth() + 2, this.getHeight() + 2, this.z + 1); + } + } + + private void drawOutline(DrawContext context, int x, int y, int width, int height, int z) { + int color = Color.white.getRGB(); + context.fill(x, y, x + width, y + 1, z, color); + context.fill(x, y, x + 1, y + height, z, color); + context.fill(x + width, y, x + width - 1, y + height, z, color); + context.fill(x, y + height, x + width, y + height - 1, z, color); + } + + @Override + public void onPress() { + if(this.formatting != null && formatting.isColor()) { + this.colorPickerWidget.color = this.color; + this.colorPickerWidget.insertFormatting(this.formatting); + this.colorPickerWidget.toggle(false); + } else { + this.colorPickerWidget.setColor(this.color); + } + } + + @Override + protected void appendClickableNarrations(NarrationMessageBuilder builder) { + + } +} diff --git a/src/main/java/dev/hephaestus/glowcase/client/gui/widget/ingame/IconButtonWidget.java b/src/main/java/dev/hephaestus/glowcase/client/gui/widget/ingame/IconButtonWidget.java new file mode 100644 index 0000000..dedbae4 --- /dev/null +++ b/src/main/java/dev/hephaestus/glowcase/client/gui/widget/ingame/IconButtonWidget.java @@ -0,0 +1,97 @@ +package dev.hephaestus.glowcase.client.gui.widget.ingame; + +import net.fabricmc.api.EnvType; +import net.fabricmc.api.Environment; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.widget.ButtonWidget; +import net.minecraft.text.Text; +import net.minecraft.util.Identifier; +import org.jetbrains.annotations.Nullable; + +public class IconButtonWidget extends ButtonWidget { + public Identifier icon; + @Nullable + public Identifier hoverIcon; + public int iconWidth; + public int iconHeight; + public int z; + + public static IconButtonWidget.Builder builder(Identifier icon, ButtonWidget.PressAction onPress) { + return new IconButtonWidget.Builder(icon, onPress); + } + + public IconButtonWidget(int x, int y, int width, int height, int iconWidth, int iconHeight, Identifier icon, @Nullable Identifier hoverIcon, PressAction onPress) { + super(x, y, width, height, Text.of(""), onPress, ButtonWidget.DEFAULT_NARRATION_SUPPLIER); + this.icon = icon; + this.hoverIcon = hoverIcon; + this.iconWidth = iconWidth; + this.iconHeight = iconHeight; + } + + @Override + protected void renderWidget(DrawContext context, int mouseX, int mouseY, float delta) { + Identifier drawnIcon = this.icon; + if(this.hoverIcon != null && this.isMouseOver(mouseX, mouseY)) { + drawnIcon = this.hoverIcon; + } + context.drawGuiTexture(drawnIcon, this.getX(), this.getY(), z, this.iconWidth, this.iconHeight); + } + + public void setPosition(int x, int y, int z, int size, int iconSize) { + this.setX(x); + this.setY(y); + this.setDimensions(size, size); + this.iconWidth = iconSize; + this.iconHeight = iconSize; + this.z = z; + } + + @Environment(EnvType.CLIENT) + public static class Builder { + private final Identifier icon; + @Nullable + private Identifier hoverIcon = null; + private final ButtonWidget.PressAction onPress; + private int x; + private int y; + private int iconWidth = 16; + private int iconHeight = 16; + private int width = 150; + private int height = 150; + + public Builder(Identifier icon, ButtonWidget.PressAction onPress) { + this.icon = icon; + this.onPress = onPress; + } + + public IconButtonWidget.Builder position(int x, int y) { + this.x = x; + this.y = y; + return this; + } + + public IconButtonWidget.Builder size(int width, int height, int iconWidth, int iconHeight) { + this.width = width; + this.height = height; + this.iconWidth = iconWidth; + this.iconHeight = iconHeight; + return this; + } + + public IconButtonWidget.Builder dimensions(int x, int y, int width, int height, int iconWidth, int iconHeight) { + return this.position(x, y).size(width, height, iconWidth, iconHeight); + } + + public IconButtonWidget.Builder hoverIcon(Identifier hoverIcon) { + this.hoverIcon = hoverIcon; + return this; + } + + public IconButtonWidget build() { + return new IconButtonWidget(this.x, this.y, this.width, this.height, this.iconWidth, this.iconHeight, this.icon, this.hoverIcon, this.onPress); + } + + } + + +} From 16adb07919b09a634b2873f86727010c85c675fd Mon Sep 17 00:00:00 2001 From: Superkat32 Date: Mon, 7 Oct 2024 21:54:54 -0400 Subject: [PATCH 3/5] Color picker improvements + color picker for all of text color FEAT: Added the ability to pick a color for a text block's entire color(the hex code at the top). Pressing cancel will revert the color back to before the color picker was moved, and pressing accept will close the picker and unfocus the text input. REFACTOR: Updated how accepting, canceling, and choosing a preset is handled with the color picker, allowing for different code to be executed at different times --- .../screen/ingame/TextBlockEditScreen.java | 50 +++++++++++++++- .../gui/widget/ingame/ColorPickerWidget.java | 57 +++++++++++++++++-- .../gui/widget/ingame/ColorPresetWidget.java | 17 ++++-- 3 files changed, 111 insertions(+), 13 deletions(-) diff --git a/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/TextBlockEditScreen.java b/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/TextBlockEditScreen.java index 4d23620..2e3e857 100644 --- a/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/TextBlockEditScreen.java +++ b/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/TextBlockEditScreen.java @@ -24,6 +24,7 @@ import net.minecraft.util.math.MathHelper; import org.lwjgl.glfw.GLFW; +import java.awt.*; import java.util.Arrays; import java.util.Comparator; @@ -38,6 +39,7 @@ public class TextBlockEditScreen extends GlowcaseScreen implements ColorPickerIn private ButtonWidget colorText; private ButtonWidget changeAlignment; private TextFieldWidget colorEntryWidget; + private Color colorEntryPreColorPicker; //used for color picker cancel button private ButtonWidget zOffsetToggle; private ButtonWidget shadowToggle; @@ -102,9 +104,13 @@ public void init() { this.colorEntryWidget.setText("#" + String.format("%1$06X", this.textBlockEntity.color & 0x00FFFFFF)); this.colorEntryWidget.setChangedListener(string -> { TextColor.parse(this.colorEntryWidget.getText()).ifSuccess(color -> { - this.textBlockEntity.color = color == null ? 0xFFFFFFFF : color.getRgb() | 0xFF000000; + int newColor = color == null ? 0xFFFFFFFF : color.getRgb() | 0xFF000000; + this.textBlockEntity.color = newColor; this.textBlockEntity.renderDirty = true; + this.colorPickerWidget.setColor(new Color(newColor)); }); + this.colorPickerWidget.updateHSL(); + this.colorPickerWidget.updateThumbPositions(); }); this.zOffsetToggle = ButtonWidget.builder(Text.literal(this.textBlockEntity.zOffset.name()), action -> { @@ -162,6 +168,22 @@ private void addFormattingButtons(int x, int y, int innerPadding, int buttonSize buttonX += buttonSize + buttonPadding; // + 4? (only works on padding of 2) this.colorText = ButtonWidget.builder(Text.literal("\uD83D\uDD8C"), action -> { + this.colorPickerWidget.setPosition(216, 10); + this.colorPickerWidget.setTargetElement(this.colorText); + this.colorPickerWidget.setOnAccept(picker -> { + picker.insertColor(picker.color); + picker.toggle(false); + }); + this.colorPickerWidget.setOnCancel(picker -> picker.toggle(false)); + this.colorPickerWidget.setPresetListener((color, formatting) -> { + if(formatting != null) { + insertFormattingTag(formatting); + } else { + insertHexTag(ColorPickerWidget.getHexCode(color)); + } + this.toggleColorPicker(false); + }); + this.colorPickerWidget.setChangeListener(null); toggleColorPicker(!this.colorPickerWidget.active); }).dimensions(buttonX, buttonY, buttonSize, buttonSize).build(); @@ -391,8 +413,30 @@ private void deleteLine() { public boolean mouseClicked(double mouseX, double mouseY, int button) { int topOffset = (int) (40 + 2 * this.width / 100F); if (!this.colorEntryWidget.mouseClicked(mouseX, mouseY, button)) { - this.colorEntryWidget.setFocused(false); + if(this.colorPickerWidget.targetElement != this.colorEntryWidget || !this.colorPickerWidget.isMouseOver(mouseX, mouseY)) { + this.colorEntryWidget.setFocused(false); + } + } else { //colorEntry clicked +// this.colorPickerWidget.setPosition(this.colorEntryWidget.getX() - this.colorPickerWidget.getWidth(), this.colorEntryWidget.getY() + 2); + this.colorPickerWidget.setPosition(this.colorEntryWidget.getX(), this.colorEntryWidget.getY() + this.colorEntryWidget.getHeight()); + this.colorPickerWidget.setTargetElement(this.colorEntryWidget); + this.colorPickerWidget.setOnAccept(null); + this.colorPickerWidget.setOnCancel(picker -> { + picker.setColor(this.colorEntryPreColorPicker); + }); + this.colorPickerWidget.setChangeListener(color -> { + this.colorEntryWidget.setText(ColorPickerWidget.getHexCode(color)); + }); + this.colorPickerWidget.setPresetListener((color, formatting) -> { + this.colorPickerWidget.setColor(color); + }); + TextColor.parse(this.colorEntryWidget.getText()).ifSuccess(color -> { + int prePickerColor = color == null ? 0xFFFFFFFF : color.getRgb() | 0xFF000000; + this.colorEntryPreColorPicker = new Color(prePickerColor); + }).ifError(textColorError -> this.colorEntryPreColorPicker = this.colorPickerWidget.getCurrentColor()); + toggleColorPicker(true); } + if(colorPickerWidget.active && colorPickerWidget.visible) { if(colorPickerWidget.isMouseOver(mouseX, mouseY)) { colorPickerWidget.mouseClicked(mouseX, mouseY, button); @@ -400,7 +444,7 @@ public boolean mouseClicked(double mouseX, double mouseY, int button) { this.setDragging(true); return true; } else { - if(!this.colorText.isMouseOver(mouseX, mouseY)) { + if(!this.colorPickerWidget.targetElement.isMouseOver(mouseX, mouseY)) { toggleColorPicker(false); } } diff --git a/src/main/java/dev/hephaestus/glowcase/client/gui/widget/ingame/ColorPickerWidget.java b/src/main/java/dev/hephaestus/glowcase/client/gui/widget/ingame/ColorPickerWidget.java index d910b4d..9378d07 100644 --- a/src/main/java/dev/hephaestus/glowcase/client/gui/widget/ingame/ColorPickerWidget.java +++ b/src/main/java/dev/hephaestus/glowcase/client/gui/widget/ingame/ColorPickerWidget.java @@ -5,6 +5,7 @@ import net.fabricmc.api.EnvType; import net.fabricmc.api.Environment; import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.Element; import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder; import net.minecraft.client.gui.widget.PressableWidget; import net.minecraft.client.render.RenderLayer; @@ -13,11 +14,14 @@ import net.minecraft.util.Formatting; import net.minecraft.util.Identifier; import org.apache.commons.compress.utils.Lists; +import org.jetbrains.annotations.Nullable; import org.joml.Matrix4f; import java.awt.*; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.function.BiConsumer; +import java.util.function.Consumer; public class ColorPickerWidget extends PressableWidget { static final Identifier CONFIRM_TEXTURE = Identifier.ofVanilla("pending_invite/accept"); @@ -26,12 +30,17 @@ public class ColorPickerWidget extends PressableWidget { static final Identifier CANCEL_HIGHLIGHTED_TEXTURE = Identifier.ofVanilla("pending_invite/reject_highlighted"); public final ColorPickerIncludedScreen screen; + public Element targetElement; public Color color = Color.red; public boolean includePresets = true; public ArrayList presetWidgets = Lists.newArrayList(); public boolean confirmOrCancelButtonDown = false; public IconButtonWidget confirmButton; public IconButtonWidget cancelButton; + private Consumer changeListener; + private BiConsumer presetListener; + private Consumer onAccept; + private Consumer onCancel; private boolean mouseDown = false; private int presetY, presetSize, presetPadding, presetHeight; @@ -68,6 +77,10 @@ public ColorPickerWidget(ColorPickerIncludedScreen screen, int x, int y, int wid updateThumbPositions(); } + public void setTargetElement(Element element) { + this.targetElement = element; + } + public void setIncludePresets(boolean shouldInclude) { this.includePresets = shouldInclude; } @@ -84,12 +97,21 @@ public void setPresets(boolean includeDefaultPresets, List addedPresets) } public void confirmColor() { - this.insertColor(this.color); - this.toggle(false); +// this.insertColor(this.color); + if(this.onAccept != null) { + this.onAccept.accept(this); + } else { + this.toggle(false); + } } public void cancel() { - this.toggle(false); +// this.toggle(false); + if(this.onCancel != null) { + this.onCancel.accept(this); + } else { + this.toggle(false); + } } public void toggle(boolean active) { @@ -113,6 +135,8 @@ public void insertFormatting(Formatting formatting) { public void setColor(Color color) { this.color = color; + this.updateHSL(); + this.updateThumbPositions(); } @Override @@ -148,7 +172,7 @@ protected void renderWidget(DrawContext context, int mouseX, int mouseY, float d if(this.includePresets) { //sorta dynamic but also really specific to keep it all aligned //I'm not going to worry about it a lot though because I do not see the custom preset thing being used a lot if at all - drawPresets(context, mouseX, mouseY, delta, previewX, presetY, height - presetY, z + 1, presetSize, width / (presetSize + presetPadding), presetPadding); + drawPresets(context, mouseX, mouseY, delta, previewX, presetY, y + height - presetY, z + 1, presetSize, width / (presetSize + presetPadding), presetPadding); } this.confirmButton.renderWidget(context, mouseX, mouseY, delta); @@ -304,6 +328,9 @@ public void setColorFromMouse(double mouseX, double mouseY) { checkAndSetPreset(mouseX, mouseY); } + if(this.changeListener != null) { + this.changeListener.accept(this.color); + } } public boolean clickedSatLight(double mouseX, double mouseY) { @@ -446,7 +473,7 @@ public int getRgbFromHueThumb() { return Color.HSBtoRGB(trueHue, 1, 1); } - protected void updateHSL() { + public void updateHSL() { this.HSL = getHSL(); this.hue = HSL[0]; this.saturation = HSL[1]; @@ -462,6 +489,26 @@ public void appendClickableNarrations(NarrationMessageBuilder builder) { this.appendDefaultNarrations(builder); } + public void setChangeListener(Consumer changeListener) { + this.changeListener = changeListener; + } + + public void setPresetListener(BiConsumer presetListener) { + this.presetListener = presetListener; + } + + public void setOnAccept(Consumer onAccept) { + this.onAccept = onAccept; + } + + public void setOnCancel(Consumer onCancel) { + this.onCancel = onCancel; + } + + public BiConsumer getPresetListener() { + return presetListener; + } + public static String getHexCode(Color color) { return "#" + String.format("%1$06X", color.getRGB() & 0x00FFFFFF); } diff --git a/src/main/java/dev/hephaestus/glowcase/client/gui/widget/ingame/ColorPresetWidget.java b/src/main/java/dev/hephaestus/glowcase/client/gui/widget/ingame/ColorPresetWidget.java index 6cbcb15..f67445b 100644 --- a/src/main/java/dev/hephaestus/glowcase/client/gui/widget/ingame/ColorPresetWidget.java +++ b/src/main/java/dev/hephaestus/glowcase/client/gui/widget/ingame/ColorPresetWidget.java @@ -8,6 +8,7 @@ import org.jetbrains.annotations.Nullable; import java.awt.*; +import java.util.function.BiConsumer; public class ColorPresetWidget extends PressableWidget { public final ColorPickerWidget colorPickerWidget; @@ -61,13 +62,19 @@ private void drawOutline(DrawContext context, int x, int y, int width, int heigh @Override public void onPress() { - if(this.formatting != null && formatting.isColor()) { - this.colorPickerWidget.color = this.color; - this.colorPickerWidget.insertFormatting(this.formatting); - this.colorPickerWidget.toggle(false); + BiConsumer presetListener = this.colorPickerWidget.getPresetListener(); + if(presetListener != null) { + presetListener.accept(this.color, this.formatting != null && this.formatting.isColor() ? this.formatting : null); } else { - this.colorPickerWidget.setColor(this.color); + if(this.formatting != null && formatting.isColor()) { + this.colorPickerWidget.color = this.color; +// this.colorPickerWidget.insertFormatting(this.formatting); + this.colorPickerWidget.toggle(false); + } else { + this.colorPickerWidget.setColor(this.color); + } } + } @Override From 046d45ea5da84cd4ff083a7edfbd1ed066c51b75 Mon Sep 17 00:00:00 2001 From: Superkat32 Date: Mon, 7 Oct 2024 23:16:46 -0400 Subject: [PATCH 4/5] Inserting tag with selection now surrounds that selection with the tags FEAT: Made a QoL change where inserting a formatting tag with selected text now surrounds that text with the tags instead of replacing it --- .../screen/ingame/TextBlockEditScreen.java | 34 ++++++++++++++++--- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/TextBlockEditScreen.java b/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/TextBlockEditScreen.java index 2e3e857..3fdd18c 100644 --- a/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/TextBlockEditScreen.java +++ b/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/TextBlockEditScreen.java @@ -283,8 +283,21 @@ public void insertTag(TextTag tag, boolean findShortest) { name = Arrays.stream(tag.aliases()).min(Comparator.comparing(String::length)).get(); } - this.selectionManager.insert("<" + name + ">"); - this.selectionManager.moveCursor(-name.length() - 3, false, SelectionManager.SelectionType.CHARACTER); + int selectedStart = this.selectionManager.getSelectionStart(); + int selectedEnd = this.selectionManager.getSelectionEnd(); + if(selectedStart != selectedEnd) { + int selectedAmount = Math.abs(selectedEnd - selectedStart); + //text is selected/highlighted - selection is determined based on the direction it happens, so an extra check is needed + this.selectionManager.moveCursor(selectedStart < selectedEnd ? 0 : -selectedAmount, false, SelectionManager.SelectionType.CHARACTER); + this.selectionManager.insert("<" + name + ">"); + this.selectionManager.moveCursor(selectedAmount, false, SelectionManager.SelectionType.CHARACTER); + this.selectionManager.insert(""); + this.selectionManager.moveCursor(-name.length() - 3, false, SelectionManager.SelectionType.CHARACTER); + this.selectionManager.setSelection(selectedStart + name.length() + 2, selectedEnd + name.length() + 2); + } else { + this.selectionManager.insert("<" + name + ">"); + this.selectionManager.moveCursor(-name.length() - 3, false, SelectionManager.SelectionType.CHARACTER); + } } @Override @@ -514,8 +527,21 @@ public void toggleColorPicker(boolean active) { @Override public void insertHexTag(String hex) { - this.selectionManager.insert("<" + hex + ">"); - this.selectionManager.moveCursor(-hex.length() - 3, false, SelectionManager.SelectionType.CHARACTER); + int selectedStart = this.selectionManager.getSelectionStart(); + int selectedEnd = this.selectionManager.getSelectionEnd(); + if(selectedStart != selectedEnd) { + int selectedAmount = Math.abs(selectedEnd - selectedStart); + //text is selected/highlighted - selection is determined based on the direction it happens, so an extra check is needed + this.selectionManager.moveCursor(selectedStart < selectedEnd ? 0 : -selectedAmount, false, SelectionManager.SelectionType.CHARACTER); + this.selectionManager.insert("<" + hex + ">"); + this.selectionManager.moveCursor(selectedAmount, false, SelectionManager.SelectionType.CHARACTER); + this.selectionManager.insert(""); + this.selectionManager.moveCursor(-hex.length() - 3, false, SelectionManager.SelectionType.CHARACTER); + this.selectionManager.setSelection(selectedStart + hex.length() + 2, selectedEnd + hex.length() + 2); + } else { + this.selectionManager.insert("<" + hex + ">"); + this.selectionManager.moveCursor(-hex.length() - 3, false, SelectionManager.SelectionType.CHARACTER); + } } @Override From 0b755f643c00af48bd7d06a24c2670c0418a7d33 Mon Sep 17 00:00:00 2001 From: Superkat32 Date: Mon, 7 Oct 2024 23:38:16 -0400 Subject: [PATCH 5/5] Fix color picker not correctly showing thumb positions with the color entry widget FIX: Fixed the color picker from incorrectly showing the thumb's position while in use from the color entry widget --- .../client/gui/screen/ingame/TextBlockEditScreen.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/TextBlockEditScreen.java b/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/TextBlockEditScreen.java index 3fdd18c..ec32daf 100644 --- a/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/TextBlockEditScreen.java +++ b/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/TextBlockEditScreen.java @@ -106,11 +106,12 @@ public void init() { TextColor.parse(this.colorEntryWidget.getText()).ifSuccess(color -> { int newColor = color == null ? 0xFFFFFFFF : color.getRgb() | 0xFF000000; this.textBlockEntity.color = newColor; + //make sure it doesn't update from the color picker updating the text + if(this.colorEntryWidget.isFocused()) { + this.colorPickerWidget.setColor(new Color(newColor)); + } this.textBlockEntity.renderDirty = true; - this.colorPickerWidget.setColor(new Color(newColor)); }); - this.colorPickerWidget.updateHSL(); - this.colorPickerWidget.updateThumbPositions(); }); this.zOffsetToggle = ButtonWidget.builder(Text.literal(this.textBlockEntity.zOffset.name()), action -> {