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
index 531b9f90..e2ef616b 100644
--- 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
@@ -1,11 +1,90 @@
package dev.hephaestus.glowcase.client.gui.screen.ingame;
-import dev.hephaestus.glowcase.client.gui.widget.ingame.ColorPickerWidget;
+import dev.hephaestus.glowcase.client.gui.widget.ingame.color.picker.ColorPickerWidget;
+import dev.hephaestus.glowcase.client.util.ColorUtil;
import net.minecraft.ChatFormatting;
+import net.minecraft.client.gui.GuiGraphicsExtractor;
+import net.minecraft.client.gui.components.events.GuiEventListener;
+import net.minecraft.client.gui.screens.Screen;
+import net.minecraft.client.input.KeyEvent;
+import net.minecraft.client.input.MouseButtonEvent;
+import org.lwjgl.glfw.GLFW;
+/**
+ * Main interface for any Screen wishing to implement a {@link ColorPickerWidget}.
+ * Each ColorPickerIncludedScreen has *one* Color Picker widget which gets shared among all things using it.
+ * Steps for adding a Color Picker to a screen:
+ *
+ * - Implement this interface, and return a private ColorPickerWidget in {@link ColorPickerIncludedScreen#getColorPickerWidget()}.
+ * - Initialize your ColorPickerWidget in {@link Screen#init()} via {@link ColorPickerWidget#builder(ColorPickerIncludedScreen)}. You do *not* need to add it as a renderableWidget.
+ * - Include {@link ColorPickerIncludedScreen#extractColorPicker(GuiGraphicsExtractor, int, int, float)} at the very bottom of {@link Screen#extractRenderState(GuiGraphicsExtractor, int, int, float)} to ensure it renders atop of everything else.
+ * - Include {@link ColorPickerIncludedScreen#mouseClickedColorPicker(MouseButtonEvent, boolean)} at the very top of {@link Screen#mouseClicked(MouseButtonEvent, boolean)}, return true if color picker was clicked, otherwise continue with method.
+ * - Include {@link ColorPickerIncludedScreen#keyPressedColorPicker(KeyEvent)} at the very top of {@link Screen#keyPressed(KeyEvent)}, return true if color picker key pressed, otherwise continue with method.
+ * - Booyah!
+ *
+ * @author Superkat32
+ */
public interface ColorPickerIncludedScreen {
- ColorPickerWidget colorPickerWidget();
- void toggleColorPicker(boolean active);
- void insertHexTag(String hex);
- void insertFormattingTag(ChatFormatting formatting);
+ ColorPickerWidget getColorPickerWidget();
+
+ default ColorPickerWidget createColorPickerWidget() {
+ return ColorPickerWidget.builder(this).build();
+ }
+
+ default void extractColorPicker(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float delta) {
+ this.getColorPickerWidget().extractRenderState(graphics, mouseX, mouseY, delta);
+ }
+
+ default boolean mouseClickedColorPicker(MouseButtonEvent event, boolean doubleClick) {
+ double mouseX = event.x();
+ double mouseY = event.y();
+ ColorPickerWidget colorPickerWidget = this.getColorPickerWidget();
+
+ if (colorPickerWidget.isActive() && colorPickerWidget.visible) {
+ if (colorPickerWidget.isMouseOver(mouseX, mouseY)) {
+ colorPickerWidget.mouseClicked(event, doubleClick);
+ Screen self = (Screen) this;
+ self.setFocused(colorPickerWidget);
+ self.setDragging(true);
+ return true;
+ } else if (colorPickerWidget.targetElement == null || !colorPickerWidget.targetElement.isMouseOver(mouseX, mouseY)) {
+ this.hideColorPickerWidget();
+ }
+ }
+ return false;
+ }
+
+ default boolean keyPressedColorPicker(KeyEvent event) {
+ int keyCode = event.key();
+ ColorPickerWidget colorPickerWidget = this.getColorPickerWidget();
+ if (colorPickerWidget.isActive()) {
+ Screen self = (Screen) this;
+ switch (keyCode) {
+ case GLFW.GLFW_KEY_ENTER, GLFW.GLFW_KEY_KP_ENTER -> colorPickerWidget.confirm();
+ case GLFW.GLFW_KEY_ESCAPE -> colorPickerWidget.cancel();
+ default -> {
+ GuiEventListener pickerTarget = colorPickerWidget.targetElement;
+ if (pickerTarget != null) {
+ self.setFocused(pickerTarget);
+ pickerTarget.keyPressed(event);
+ return true;
+ }
+ }
+ }
+ self.setFocused(null);
+ return true;
+ }
+ return false;
+ }
+
+ default void hideColorPickerWidget() {
+ this.getColorPickerWidget().hide();
+ }
+
+ default void insertHexTag(String hex) {}
+ default void insertFormattingTag(ChatFormatting formatting) {}
+ default void insertHexColor(int color) {
+ String hex = ColorUtil.toHex(color);
+ this.insertHexTag(hex);
+ }
}
diff --git a/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/NoteEditScreen.java b/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/NoteEditScreen.java
index 6e8d6253..f4614d78 100644
--- a/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/NoteEditScreen.java
+++ b/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/NoteEditScreen.java
@@ -2,7 +2,7 @@
import com.mojang.datafixers.util.Pair;
import dev.hephaestus.glowcase.Glowcase;
-import dev.hephaestus.glowcase.client.gui.widget.ingame.ColorPickerWidget;
+import dev.hephaestus.glowcase.client.gui.widget.ingame.color.picker.ColorPickerWidget;
import dev.hephaestus.glowcase.client.util.NoteTextColorResource;
import dev.hephaestus.glowcase.item.component.NoteComponent;
import dev.hephaestus.glowcase.packet.C2SEditNoteItem;
@@ -187,10 +187,7 @@ protected void init() {
}).bounds(width / 2 + BG_WIDTH / 2 - (BG_WIDTH / 2 - 3), height / 2 + BG_HEIGHT / 2 + offset, BG_WIDTH / 2 - 3, 20).build();
- this.colorPickerWidget = ColorPickerWidget.builder(this, 216, 10).size(182, 104).build();
- this.colorPickerWidget.toggle(false); //start deactivated
-
- this.addRenderableWidget(colorPickerWidget);
+ this.colorPickerWidget = this.createColorPickerWidget();
addRenderableWidget(changeAlignment);
addRenderableWidget(doneButton);
@@ -329,6 +326,7 @@ public void extractRenderState(GuiGraphicsExtractor graphics, int mouseX, int mo
}
graphics.disableScissor();
+ this.extractColorPicker(graphics, mouseX, mouseY, delta);
}
@Override
@@ -347,12 +345,7 @@ public boolean keyPressed(KeyEvent event) {
boolean result;
int keyCode = event.key();
- 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();
- }
+ if (keyPressedColorPicker(event)) {
result = true;
} else {
setFocused(null);
@@ -444,18 +437,7 @@ public boolean charTyped(CharacterEvent event) {
public boolean mouseClicked(MouseButtonEvent event, boolean doubleClick) {
double mouseX = event.x();
double mouseY = event.y();
- if (colorPickerWidget.active && colorPickerWidget.visible) {
- if (colorPickerWidget.isMouseOver(mouseX, mouseY)) {
- colorPickerWidget.mouseClicked(event, doubleClick);
- this.setFocused(colorPickerWidget);
- this.setDragging(true);
- return true;
- } else {
- if (!this.colorPickerWidget.targetElement.isMouseOver(mouseX, mouseY)) {
- toggleColorPicker(false);
- }
- }
- }
+ if (this.mouseClickedColorPicker(event, doubleClick)) return true;
boolean withinX = (mouseX >= width / 2f - BG_WIDTH / 2f && mouseX <= width / 2f + BG_WIDTH / 2f);
boolean withinY = (mouseY >= height / 2f - BG_HEIGHT / 2f && mouseY <= height / 2f + BG_HEIGHT / 2f);
@@ -597,13 +579,8 @@ public CustomPacketPayload getUpdatePayload() {
}
@Override
- public ColorPickerWidget colorPickerWidget() {
- return colorPickerWidget;
- }
-
- @Override
- public void toggleColorPicker(boolean active) {
- colorPickerWidget.toggle(active);
+ public ColorPickerWidget getColorPickerWidget() {
+ return this.colorPickerWidget;
}
@Override
diff --git a/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/OutlineBlockEditScreen.java b/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/OutlineBlockEditScreen.java
index 4ae93263..38b65a81 100644
--- a/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/OutlineBlockEditScreen.java
+++ b/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/OutlineBlockEditScreen.java
@@ -3,19 +3,23 @@
import com.google.common.primitives.Ints;
import dev.hephaestus.glowcase.block.entity.OutlineBlockEntity;
import dev.hephaestus.glowcase.client.gui.widget.ingame.GlowcaseEditBox;
+import dev.hephaestus.glowcase.client.gui.widget.ingame.color.HexColorEditBox;
+import dev.hephaestus.glowcase.client.gui.widget.ingame.color.picker.ColorPickerWidget;
import dev.hephaestus.glowcase.packet.C2SEditOutlineBlock;
import dev.hephaestus.glowcase.util.InputFilters;
import dev.hephaestus.glowcase.util.TextUtils;
+import net.minecraft.client.gui.GuiGraphicsExtractor;
import net.minecraft.client.gui.components.StringWidget;
+import net.minecraft.client.input.KeyEvent;
+import net.minecraft.client.input.MouseButtonEvent;
import net.minecraft.core.Vec3i;
import net.minecraft.network.chat.Component;
-import net.minecraft.network.chat.TextColor;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import org.jetbrains.annotations.Nullable;
import java.util.concurrent.atomic.AtomicInteger;
-public class OutlineBlockEditScreen extends BlockEditorScreen {
+public class OutlineBlockEditScreen extends BlockEditorScreen implements ColorPickerIncludedScreen {
private StringWidget offsetTextWidget;
private StringWidget scaleTextWidget;
private StringWidget colorTextWidget;
@@ -27,9 +31,11 @@ public class OutlineBlockEditScreen extends BlockEditorScreen {
- TextColor.parseColor(this.colorWidget.getValue())
- .ifSuccess(color -> this.blockEntity.color = color.getValue() | 0xFF000000);
- });
+ this.colorPickerWidget = this.createColorPickerWidget();
+
+ this.colorWidget = HexColorEditBox.builder(this.minecraft.font, width / 2 - 65, widgetY.getAndAdd(lineOffset),
+ () -> this.blockEntity.color, color -> this.blockEntity.color = color
+ )
+ .setWidth(50)
+ .setColorPickerWidget(this.colorPickerWidget)
+ .build();
this.widthWidget = new GlowcaseEditBox(this.minecraft.font, width / 2 - 65, widgetY.getAndAdd(lineOffset), 50, 20, Component.empty());
this.widthWidget.setValue(String.valueOf(this.blockEntity.width));
@@ -147,8 +155,31 @@ public void init() {
this.addRenderableWidget(this.widthWidget);
}
+ @Override
+ public void extractRenderState(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float delta) {
+ super.extractRenderState(graphics, mouseX, mouseY, delta);
+ this.extractColorPicker(graphics, mouseX, mouseY, delta);
+ }
+
+ @Override
+ public boolean mouseClicked(MouseButtonEvent event, boolean doubleClick) {
+ if (this.mouseClickedColorPicker(event, doubleClick)) return true;
+ return super.mouseClicked(event, doubleClick);
+ }
+
+ @Override
+ public boolean keyPressed(KeyEvent event) {
+ if (this.keyPressedColorPicker(event)) return true;
+ return super.keyPressed(event);
+ }
+
@Override
public @Nullable CustomPacketPayload getUpdatePayload() {
return C2SEditOutlineBlock.of(blockEntity);
}
+
+ @Override
+ public ColorPickerWidget getColorPickerWidget() {
+ return this.colorPickerWidget;
+ }
}
diff --git a/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/PopupBlockEditScreen.java b/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/PopupBlockEditScreen.java
index 2b8c3f20..b9d7b844 100644
--- a/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/PopupBlockEditScreen.java
+++ b/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/PopupBlockEditScreen.java
@@ -3,6 +3,8 @@
import dev.hephaestus.glowcase.block.entity.HyperlinkBlockEntity;
import dev.hephaestus.glowcase.block.entity.PopupBlockEntity;
import dev.hephaestus.glowcase.block.entity.TextBlockEntity;
+import dev.hephaestus.glowcase.client.gui.widget.ingame.color.HexColorEditBox;
+import dev.hephaestus.glowcase.client.gui.widget.ingame.color.picker.ColorPickerWidget;
import dev.hephaestus.glowcase.packet.C2SEditPopupBlock;
import dev.hephaestus.glowcase.util.TextUtils;
import net.minecraft.client.Minecraft;
@@ -14,20 +16,21 @@
import net.minecraft.client.input.KeyEvent;
import net.minecraft.client.input.MouseButtonEvent;
import net.minecraft.network.chat.Component;
-import net.minecraft.network.chat.TextColor;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.util.Mth;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.glfw.GLFW;
//TODO: multi-character selection at some point? it may be a bit complex but it'd be nice
-public class PopupBlockEditScreen extends BlockEditorScreen {
+public class PopupBlockEditScreen extends BlockEditorScreen implements ColorPickerIncludedScreen {
private TextFieldHelper selectionManager;
private int currentRow;
private long ticksSinceOpened = 0;
private EditBox titleEntryWidget;
private Button changeAlignment;
- private EditBox colorEntryWidget;
+ private HexColorEditBox colorEntryWidget;
+
+ private ColorPickerWidget colorPickerWidget;
public PopupBlockEditScreen(PopupBlockEntity blockEntity) {
super(blockEntity);
@@ -76,14 +79,13 @@ public void init() {
));
}).bounds(120 + innerPadding, 20 + innerPadding, 160, 20).build();
- this.colorEntryWidget = new EditBox(this.minecraft.font, 280 + innerPadding * 2, 20 + innerPadding, 50, 20, Component.empty());
- this.colorEntryWidget.setValue("#" + Integer.toHexString(this.blockEntity.color & 0x00FFFFFF));
- this.colorEntryWidget.setResponder(string -> {
- TextColor.parseColor(this.colorEntryWidget.getValue()).ifSuccess(color -> {
- this.blockEntity.color = color == null ? 0xFFFFFFFF : color.getValue() | 0xFF000000;
- this.blockEntity.renderDirty = true;
- });
- });
+ this.colorPickerWidget = createColorPickerWidget();
+ this.colorEntryWidget = HexColorEditBox.builder(this.minecraft.font, 280 + innerPadding, 20 + innerPadding,
+ () -> this.blockEntity.color, color -> { this.blockEntity.color = color; this.blockEntity.renderDirty = true; }
+ )
+ .setWidth(50)
+ .setColorPickerWidget(this.colorPickerWidget)
+ .build();
this.addRenderableWidget(this.titleEntryWidget);
this.addRenderableWidget(this.changeAlignment);
@@ -169,6 +171,7 @@ public void extractRenderState(GuiGraphicsExtractor graphics, int mouseX, int mo
}
graphics.pose().popMatrix();
+ this.extractColorPicker(graphics, mouseX, mouseY, delta);
}
@Override
@@ -186,6 +189,7 @@ public boolean charTyped(CharacterEvent event) {
@Override
public boolean keyPressed(KeyEvent event) {
int keyCode = event.key();
+ if (this.keyPressedColorPicker(event)) return true;
if (this.titleEntryWidget.canConsumeInput()) {
if (keyCode == GLFW.GLFW_KEY_ESCAPE) {
this.onClose();
@@ -296,6 +300,7 @@ public boolean mouseClicked(MouseButtonEvent event, boolean doubleClick) {
double mouseX = event.x();
double mouseY = event.y();
int topOffset = (int) (40 + 2 * this.width / 100F);
+ if (this.mouseClickedColorPicker(event, doubleClick)) return true;
if (!this.titleEntryWidget.mouseClicked(event, doubleClick)) {
this.titleEntryWidget.setFocused(false);
}
@@ -354,4 +359,9 @@ public boolean mouseClicked(MouseButtonEvent event, boolean doubleClick) {
return super.mouseClicked(event, doubleClick);
}
}
+
+ @Override
+ public ColorPickerWidget getColorPickerWidget() {
+ return this.colorPickerWidget;
+ }
}
diff --git a/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/SpriteBlockEditScreen.java b/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/SpriteBlockEditScreen.java
index 4e0a0ea9..325978d2 100644
--- a/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/SpriteBlockEditScreen.java
+++ b/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/SpriteBlockEditScreen.java
@@ -4,6 +4,8 @@
import dev.hephaestus.glowcase.block.entity.TextBlockEntity;
import dev.hephaestus.glowcase.client.gui.widget.ingame.GlowcaseEditBox;
import dev.hephaestus.glowcase.client.gui.widget.ingame.SuggestionListWidget;
+import dev.hephaestus.glowcase.client.gui.widget.ingame.color.HexColorEditBox;
+import dev.hephaestus.glowcase.client.gui.widget.ingame.color.picker.ColorPickerWidget;
import dev.hephaestus.glowcase.packet.C2SEditSpriteBlock;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.client.gui.GuiGraphicsExtractor;
@@ -14,7 +16,6 @@
import net.minecraft.client.input.MouseButtonEvent;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.network.chat.Component;
-import net.minecraft.network.chat.TextColor;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.resources.Identifier;
import net.minecraft.server.packs.resources.ResourceManager;
@@ -24,16 +25,17 @@
import java.util.List;
import java.util.function.Function;
-public class SpriteBlockEditScreen extends BlockEditorScreen {
+public class SpriteBlockEditScreen extends BlockEditorScreen implements ColorPickerIncludedScreen {
private EditBox spriteWidget;
private Button spriteWidgetHelpButton;
private Button rotationWidget;
private Button zOffsetToggle;
- private EditBox colorEntryWidget;
+ private HexColorEditBox colorEntryWidget;
private EditBox scaleEntryWidget;
private List spriteHelpTooltipText;
+ private ColorPickerWidget colorPickerWidget;
private SuggestionListWidget suggestionWidget;
private List validSprites = new ArrayList<>();
@@ -73,13 +75,14 @@ public void init() {
this.zOffsetToggle.setMessage(Component.literal(this.blockEntity.zOffset.name()));
}).bounds(width / 2 - 90, height / 2 + 5, 180, 20).build();
- this.colorEntryWidget = new EditBox(this.minecraft.font, width / 2 - 90, height / 2 + 35, 180, 20, Component.empty());
- this.colorEntryWidget.setValue("#" + String.format("%1$06X", this.blockEntity.color & 0x00FFFFFF));
- this.colorEntryWidget.setResponder(string -> {
- TextColor.parseColor(this.colorEntryWidget.getValue()).ifSuccess(color -> {
- this.blockEntity.color = color == null ? 0xFFFFFFFF : color.getValue() | 0xFF000000;
- });
- });
+ this.colorPickerWidget = this.createColorPickerWidget();
+
+ this.colorEntryWidget = HexColorEditBox.builder(this.minecraft.font, width / 2 - 90, height / 2 + 35,
+ () -> this.blockEntity.color, color -> this.blockEntity.color = color
+ )
+ .setWidth(180)
+ .setColorPickerWidget(this.colorPickerWidget)
+ .build();
this.scaleEntryWidget = new EditBox(this.minecraft.font, width / 2 - 90, height / 2 + 65, 180, 20, Component.empty());
this.scaleEntryWidget.setValue(String.valueOf(this.blockEntity.scale));
@@ -143,6 +146,7 @@ public void extractRenderState(GuiGraphicsExtractor graphics, int mouseX, int mo
setTooltip(this.spriteHelpTooltipText);
}*/
+ this.extractColorPicker(graphics, mouseX, mouseY, delta);
// render the list over everything
suggestionWidget.extractRenderState(graphics, mouseX, mouseY, delta);
}
@@ -151,6 +155,9 @@ public void extractRenderState(GuiGraphicsExtractor graphics, int mouseX, int mo
public boolean mouseClicked(MouseButtonEvent event, boolean doubleClick) {
double mouseX = event.x();
double mouseY = event.y();
+
+ if (mouseClickedColorPicker(event, doubleClick)) return true;
+
if (suggestionWidget.isMouseOver(mouseX, mouseY) && spriteWidget.isFocused()) {
return suggestionWidget.mouseClicked(event, doubleClick);
} else {
@@ -185,6 +192,7 @@ public boolean keyPressed(KeyEvent event) {
if (suggestionWidget.keyPressed(event)) {
return true;
}
+ if (keyPressedColorPicker(event)) return true;
return super.keyPressed(event);
}
@@ -194,4 +202,9 @@ public CustomPacketPayload getUpdatePayload() {
blockEntity.setChanged();
return C2SEditSpriteBlock.of(blockEntity);
}
+
+ @Override
+ public ColorPickerWidget getColorPickerWidget() {
+ return this.colorPickerWidget;
+ }
}
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 412db662..e00dca71 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
@@ -1,9 +1,11 @@
package dev.hephaestus.glowcase.client.gui.screen.ingame;
+import dev.hephaestus.glowcase.Glowcase;
import dev.hephaestus.glowcase.block.entity.TextBlockEntity;
-import dev.hephaestus.glowcase.client.gui.widget.ingame.ColorPickerWidget;
import dev.hephaestus.glowcase.client.gui.widget.ingame.GlowcaseEditBox;
-import dev.hephaestus.glowcase.client.util.ColorUtil;
+import dev.hephaestus.glowcase.client.gui.widget.ingame.IconButtonWidget;
+import dev.hephaestus.glowcase.client.gui.widget.ingame.color.HexColorEditBox;
+import dev.hephaestus.glowcase.client.gui.widget.ingame.color.picker.ColorPickerWidget;
import dev.hephaestus.glowcase.packet.C2SEditTextBlock;
import dev.hephaestus.glowcase.util.InputFilters;
import dev.hephaestus.glowcase.util.ParseUtil;
@@ -26,7 +28,6 @@
import org.jetbrains.annotations.Nullable;
import org.lwjgl.glfw.GLFW;
-import java.awt.*;
import java.util.List;
import java.util.function.Consumer;
@@ -37,14 +38,14 @@ public class TextBlockEditScreen extends TextEditorScreen implements BlockEditor
private final TextBlockEntity textBlockEntity;
private List textWidgets;
- private List colorListeners;
private TextFieldHelper selectionManager;
- private EditBox colorEntryWidget;
+ private HexColorEditBox colorEntryWidget;
+ private HexColorEditBox backgroundColorEntryWidget;
+
private int currentRow;
private long ticksSinceOpened = 0;
private ColorPickerWidget colorPickerWidget;
- private Color colorEntryPreColorPicker; //used for color picker cancel button
public TextBlockEditScreen(TextBlockEntity textBlockEntity) {
this.textBlockEntity = textBlockEntity;
@@ -66,47 +67,48 @@ public void init() {
int middle = width / 2;
- var scaleSlider = new TextScale.SliderWidget(textBlockEntity, middle - 203, innerPadding, 113, 20);
- addFormattingButtons(middle - 90 + 6, innerPadding, 0, 20, 2);
+ var scaleSlider = new TextScale.SliderWidget(textBlockEntity, middle - 203 - 5, innerPadding, 113, 20);
+ addFormattingButtons(middle - 90 + 6 - 5, innerPadding, 0, 20, 2);
- this.colorEntryWidget = new EditBox(this.minecraft.font, middle + 54, innerPadding, 64, 20, Component.empty());
- this.colorEntryWidget.setTooltip(Tooltip.create(Component.translatable("gui.glowcase.color")));
- this.colorEntryWidget.setValue(ColorUtil.toAlphaHex(this.textBlockEntity.color));
- this.colorEntryWidget.setResponder(string -> {
- ColorUtil.parse(string, this.textBlockEntity.color).ifSuccess(newColor -> {
- final int color = (Math.max(newColor >>> 24, 0x1A) << 24) | (newColor & ColorUtil.RGB_MASK);
+ this.colorPickerWidget = this.createColorPickerWidget();
- this.textBlockEntity.color = color;
- // make sure it doesn't update from the color picker updating the text
- if (this.colorEntryWidget.isFocused()) {
- this.colorPickerWidget.setColor(new Color(color));
+ this.colorEntryWidget = HexColorEditBox.builder(this.minecraft.font, middle + 54 - 5, innerPadding,
+ () -> this.textBlockEntity.color, color -> {
+ this.textBlockEntity.color = color;
+ this.textBlockEntity.rebake(true);
}
+ )
+ .setEditableAlpha(true)
+ .setColorPickerWidget(this.colorPickerWidget)
+ .build();
+ this.colorEntryWidget.setTooltip(Tooltip.create(Component.translatable("gui.glowcase.color_argb")));
+
+ this.backgroundColorEntryWidget = HexColorEditBox.builder(this.minecraft.font, middle + 54 + 64 + 6 - 5, innerPadding,
+ () -> this.textBlockEntity.backgroundColor, color -> {
+ this.textBlockEntity.backgroundColor = color;
this.textBlockEntity.rebake(true);
- });
- });
-
- this.colorPickerWidget = ColorPickerWidget.builder(this, 216, 10).size(182, 104).build();
- this.colorPickerWidget.toggle(false); //start deactivated
-
- var moreOptionsButton = Button.builder(
- Component.translatable("gui.glowcase.more"),
- button -> {
- var optionsScreen = new TextBlockOptionsScreen(this, textBlockEntity);
- Minecraft.getInstance().setScreen(optionsScreen);
- })
- .bounds(middle + 124, innerPadding, 80, 20)
+ }
+ )
+ .setEditableAlpha(true)
+ .setColorPickerWidget(this.colorPickerWidget)
.build();
+ this.backgroundColorEntryWidget.setTooltip(Tooltip.create(Component.translatable("gui.glowcase.background_color_argb")));
- this.textWidgets = List.of(
- this.colorEntryWidget
- );
+ Button moreOptionsButton = IconButtonWidget.builder(Glowcase.id("properties"), button -> {
+ TextBlockOptionsScreen optionsScreen = new TextBlockOptionsScreen(this, textBlockEntity);
+ Minecraft.getInstance().setScreen(optionsScreen);
+ })
+ .dimensions(middle + 124 + 64 + 6 - 5, innerPadding, 20, 20, 16, 16)
+ .build();
+ moreOptionsButton.setTooltip(Tooltip.create(Component.translatable("gui.glowcase.extra_properties")));
- this.colorListeners = List.of(
- this.colorEntryWidget
+ this.textWidgets = List.of(
+ this.colorEntryWidget,
+ this.backgroundColorEntryWidget
);
- this.addRenderableWidget(colorPickerWidget);
this.addRenderableWidget(this.colorEntryWidget);
+ this.addRenderableWidget(this.backgroundColorEntryWidget);
this.addRenderableWidget(scaleSlider);
this.addRenderableWidget(moreOptionsButton);
}
@@ -141,6 +143,7 @@ private void checkRow() {
}
}
+ // FIXME - Text shadow to false is not represented in editor
@Override
public void extractRenderState(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float delta) {
super.extractRenderState(graphics, mouseX, mouseY, delta);
@@ -198,7 +201,7 @@ public void extractRenderState(GuiGraphicsExtractor graphics, int mouseX, int mo
graphics.pose().popMatrix();
- colorPickerWidget.extractRenderState(graphics, mouseX, mouseY, delta);
+ this.extractColorPicker(graphics, mouseX, mouseY, delta);
}
@Override
@@ -216,31 +219,13 @@ public boolean charTyped(CharacterEvent event) {
public boolean keyPressed(KeyEvent event) {
var keyCode = event.key();
- if (this.colorPickerWidget.active) {
- switch (keyCode) {
- case GLFW.GLFW_KEY_ENTER, GLFW.GLFW_KEY_KP_ENTER -> this.colorPickerWidget.confirmColor();
- case GLFW.GLFW_KEY_ESCAPE -> this.colorPickerWidget.cancel();
- default -> {
- final GuiEventListener listener = this.colorPickerWidget.targetElement;
- if (listener != null) {
- this.setFocused(listener);
- return listener.keyPressed(event);
- }
- }
- }
-
- this.toggleColorPicker(false);
- this.setFocused(null);
-
- return true;
- }
+ if (this.keyPressedColorPicker(event)) return true;
if (this.getFocused() != null) {
if (this.getFocused().keyPressed(event)) {
return true;
}
- this.toggleColorPicker(false);
this.setFocused(null);
if (keyCode == GLFW.GLFW_KEY_ESCAPE) {
@@ -280,7 +265,7 @@ public boolean keyPressed(KeyEvent event) {
return true;
} else {
- //formatting hotkeys
+ // Formatting hotkeys
if (event.hasControlDown()) {
if (keyCode == GLFW.GLFW_KEY_B) {
insertTag(TagRegistry.SAFE.getTag("bold"), true);
@@ -292,9 +277,9 @@ public boolean keyPressed(KeyEvent event) {
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
+ // 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"), true);
return true;
} else if (keyCode == GLFW.GLFW_KEY_O) {
@@ -344,31 +329,6 @@ private void deleteLine() {
this.textBlockEntity.rebake(true);
}
- private void colorListenerClicked(EditBox textWidget) {
- this.colorPickerWidget.setPosition(Math.min(textWidget.getX(), width - colorPickerWidget.getWidth()), textWidget.getY() + textWidget.getHeight());
- this.colorPickerWidget.setTargetElement(textWidget);
- this.colorPickerWidget.setOnAccept(picker -> {
- textWidget.setValue(ColorUtil.toAlphaHex(picker.getCurrentColor().getRGB()));
- });
- this.colorPickerWidget.setOnCancel(picker -> {
- picker.setColor(this.colorEntryPreColorPicker);
- textWidget.setValue(ColorUtil.toAlphaHex(this.colorEntryPreColorPicker.getRGB()));
- });
- this.colorPickerWidget.setChangeListener(color -> {
- final int newColor = ColorUtil.transferAlpha(this.colorEntryPreColorPicker.getRGB(), color.getRGB());
- textWidget.setValue(ColorUtil.toAlphaHex(newColor));
- });
- this.colorPickerWidget.setPresetListener((color, formatting) -> {
- this.colorPickerWidget.setColor(color);
- });
- ColorUtil.parse(textWidget.getValue(), ColorUtil.WHITE).ifSuccess(color -> {
- final Color pickerColor = new Color(color);
- this.colorEntryPreColorPicker = pickerColor;
- this.colorPickerWidget.setColor(pickerColor);
- }).ifError(textColorError -> this.colorEntryPreColorPicker = this.colorPickerWidget.getCurrentColor());
- toggleColorPicker(true);
- }
-
@Override
public boolean mouseClicked(MouseButtonEvent event, boolean doubleClick) {
double mouseX = event.x();
@@ -380,27 +340,11 @@ public boolean mouseClicked(MouseButtonEvent event, boolean doubleClick) {
continue;
}
this.setFocused(text);
- if (this.colorListeners.contains(text)) {
- this.colorListenerClicked(text);
- }
- if (this.colorPickerWidget.targetElement != text) {
- text.setFocused(false);
- }
break;
}
- if (colorPickerWidget.active && colorPickerWidget.visible) {
- if (colorPickerWidget.isMouseOver(mouseX, mouseY)) {
- colorPickerWidget.mouseClicked(event, doubleClick);
- this.setFocused(colorPickerWidget);
- this.setDragging(true);
- return true;
- } else {
- if (!this.colorPickerWidget.targetElement.isMouseOver(mouseX, mouseY)) {
- toggleColorPicker(false);
- }
- }
- }
+ if (mouseClickedColorPicker(event, doubleClick)) return true;
+
if (mouseY > topOffset) {
this.currentRow = Mth.clamp((int) (mouseY - topOffset) / 12, 0, this.textBlockEntity.lines.size() - 1);
this.setFocused(null);
@@ -455,15 +399,10 @@ public boolean mouseClicked(MouseButtonEvent event, boolean doubleClick) {
}
@Override
- public ColorPickerWidget colorPickerWidget() {
+ public ColorPickerWidget getColorPickerWidget() {
return this.colorPickerWidget;
}
- @Override
- public void toggleColorPicker(boolean active) {
- this.colorPickerWidget.toggle(active);
- }
-
@Override
TextFieldHelper getSelectionManager() {
return this.selectionManager;
diff --git a/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/TextBlockOptionsScreen.java b/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/TextBlockOptionsScreen.java
index 248b7276..bdf87961 100644
--- a/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/TextBlockOptionsScreen.java
+++ b/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/TextBlockOptionsScreen.java
@@ -2,7 +2,8 @@
import dev.hephaestus.glowcase.block.entity.TextBlockEntity;
import dev.hephaestus.glowcase.client.gui.screen.ingame.TextBlockEditScreen.TextScale;
-import dev.hephaestus.glowcase.client.util.ColorUtil;
+import dev.hephaestus.glowcase.client.gui.widget.ingame.color.HexColorEditBox;
+import dev.hephaestus.glowcase.client.gui.widget.ingame.color.picker.ColorPickerWidget;
import dev.hephaestus.glowcase.packet.C2SEditTextBlock;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphicsExtractor;
@@ -10,12 +11,13 @@
import net.minecraft.client.gui.components.Button;
import net.minecraft.client.gui.components.ContainerObjectSelectionList;
import net.minecraft.client.gui.components.CycleButton;
-import net.minecraft.client.gui.components.EditBox;
import net.minecraft.client.gui.components.StringWidget;
import net.minecraft.client.gui.components.events.GuiEventListener;
import net.minecraft.client.gui.layouts.HeaderAndFooterLayout;
import net.minecraft.client.gui.narration.NarratableEntry;
import net.minecraft.client.gui.screens.Screen;
+import net.minecraft.client.input.KeyEvent;
+import net.minecraft.client.input.MouseButtonEvent;
import net.minecraft.network.chat.CommonComponents;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
@@ -23,9 +25,10 @@
import java.util.List;
-public class TextBlockOptionsScreen extends BlockEditorScreen {
+public class TextBlockOptionsScreen extends BlockEditorScreen implements ColorPickerIncludedScreen {
private final Screen returnScreen;
+ private ColorPickerWidget colorPickerWidget;
public final HeaderAndFooterLayout layout = new HeaderAndFooterLayout(this);
public TextOptionList options;
@@ -98,32 +101,30 @@ public void init() {
)
);
- this.options.addHeaders(Component.translatable("gui.glowcase.color"), Component.translatable("gui.glowcase.background_color"));
- var colorEditBox = new EditBox(
- this.font,
- Button.DEFAULT_WIDTH,
- Button.DEFAULT_HEIGHT,
- Component.translatable("gui.glowcase.color")
- );
- colorEditBox.setValue(ColorUtil.toAlphaHex(this.blockEntity.color));
- colorEditBox.setResponder(string -> ColorUtil.parse(string, blockEntity.color)
- .ifSuccess(newColor -> {
- blockEntity.color = newColor;
- blockEntity.rebake(true);
- }));
-
- var backgroundEditBox = new EditBox(
- this.font,
- Button.DEFAULT_WIDTH,
- Button.DEFAULT_HEIGHT,
- Component.translatable("gui.glowcase.background_color")
- );
- backgroundEditBox.setValue(ColorUtil.toAlphaHex(this.blockEntity.backgroundColor));
- backgroundEditBox.setResponder(string -> ColorUtil.parse(string, blockEntity.backgroundColor)
- .ifSuccess(newColor -> {
- blockEntity.backgroundColor = newColor;
- blockEntity.rebake(true);
- }));
+ // Note: Color picker position is messed up on this screen, but it still works otherwise
+ this.colorPickerWidget = createColorPickerWidget();
+ this.options.addHeaders(Component.translatable("gui.glowcase.color_argb"), Component.translatable("gui.glowcase.background_color_argb"));
+ HexColorEditBox colorEditBox = HexColorEditBox.builder(this.minecraft.font, 0, 0,
+ () -> this.blockEntity.color, color -> {
+ this.blockEntity.color = color;
+ this.blockEntity.rebake(true);
+ }
+ )
+ .setEditableAlpha(true)
+ .setWidth(Button.DEFAULT_WIDTH)
+ .setColorPickerWidget(this.colorPickerWidget)
+ .build();
+
+ HexColorEditBox backgroundEditBox = HexColorEditBox.builder(this.minecraft.font, 0, 0,
+ () -> this.blockEntity.backgroundColor, color -> {
+ this.blockEntity.backgroundColor = color;
+ this.blockEntity.rebake(true);
+ }
+ )
+ .setWidth(Button.DEFAULT_WIDTH)
+ .setEditableAlpha(true)
+ .setColorPickerWidget(this.colorPickerWidget)
+ .build();
this.options.add(colorEditBox, backgroundEditBox);
@@ -159,6 +160,29 @@ public void onClose() {
this.minecraft.setScreen(this.returnScreen);
}
+ @Override
+ public void extractRenderState(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float delta) {
+ super.extractRenderState(graphics, mouseX, mouseY, delta);
+ this.extractColorPicker(graphics, mouseX, mouseY, delta);
+ }
+
+ @Override
+ public boolean mouseClicked(MouseButtonEvent event, boolean doubleClick) {
+ if (this.mouseClickedColorPicker(event, doubleClick)) return true;
+ return super.mouseClicked(event, doubleClick);
+ }
+
+ @Override
+ public boolean keyPressed(KeyEvent event) {
+ if (this.keyPressedColorPicker(event)) return true;
+ return super.keyPressed(event);
+ }
+
+ @Override
+ public ColorPickerWidget getColorPickerWidget() {
+ return this.colorPickerWidget;
+ }
+
@NullMarked
public static class TextOptionList extends ContainerObjectSelectionList {
public TextOptionList(Minecraft minecraft, int width, int height, int y) {
diff --git a/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/TextEditorScreen.java b/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/TextEditorScreen.java
index de428bd5..3f096102 100644
--- a/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/TextEditorScreen.java
+++ b/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/TextEditorScreen.java
@@ -1,6 +1,7 @@
package dev.hephaestus.glowcase.client.gui.screen.ingame;
-import dev.hephaestus.glowcase.client.gui.widget.ingame.ColorPickerWidget;
+import dev.hephaestus.glowcase.client.gui.widget.ingame.color.picker.ColorPickerWidget;
+import dev.hephaestus.glowcase.client.util.ColorUtil;
import eu.pb4.placeholders.api.parsers.tag.TagRegistry;
import eu.pb4.placeholders.api.parsers.tag.TextTag;
import net.minecraft.ChatFormatting;
@@ -47,24 +48,31 @@ protected void addFormattingButtons(int x, int y, int innerPadding, int buttonSi
buttonX += buttonSize + buttonPadding; // + 4? (only works on padding of 2)
this.colorText = Button.builder(Component.literal("\uD83D\uDD8C"), action -> {
- ColorPickerWidget colorPickerWidget = colorPickerWidget();
- colorPickerWidget.setPosition(216, 10);
- colorPickerWidget.setTargetElement(this.colorText);
- colorPickerWidget.setOnAccept(picker -> {
- picker.insertColor(picker.color);
- picker.toggle(false);
+ ColorPickerWidget colorPickerWidget = this.getColorPickerWidget();
+ colorPickerWidget.target(this.colorText, ColorUtil.RED, false, true, pickedColor -> {});
+ colorPickerWidget.setConfirmListener(this::insertHexColor);
+ colorPickerWidget.setPresetListener(preset -> {
+ if (preset.getPresetFormatting() != null) this.insertFormattingTag(preset.getPresetFormatting());
+ colorPickerWidget.hide();
});
- colorPickerWidget.setOnCancel(picker -> picker.toggle(false));
- colorPickerWidget.setPresetListener((color, formatting) -> {
- if(formatting != null) {
- insertFormattingTag(formatting);
- } else {
- insertHexTag(ColorPickerWidget.getHexCode(color));
- }
- this.toggleColorPicker(false);
- });
- colorPickerWidget.setChangeListener(null);
- toggleColorPicker(!colorPickerWidget.active);
+// ColorPickerWidget colorPickerWidget = getColorPickerWidget();
+// colorPickerWidget.setPosition(216, 10);
+// colorPickerWidget.setTargetElement(this.colorText);
+// colorPickerWidget.setOnAccept(picker -> {
+// picker.insertColor(picker.color);
+// picker.toggle(false);
+// });
+// colorPickerWidget.setOnCancel(picker -> picker.toggle(false));
+// colorPickerWidget.setPresetListener((color, formatting) -> {
+// if(formatting != null) {
+// insertFormattingTag(formatting);
+// } else {
+// insertHexTag(ColorPickerWidget.getHexCode(color));
+// }
+// this.toggleColorPickerWidget(false);
+// });
+// colorPickerWidget.setChangeListener(null);
+// toggleColorPickerWidget(!colorPickerWidget.active);
}).bounds(buttonX, buttonY, buttonSize, buttonSize).build();
widgets = new Button[]{
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
deleted file mode 100644
index c5c38aaf..00000000
--- a/src/main/java/dev/hephaestus/glowcase/client/gui/widget/ingame/ColorPickerWidget.java
+++ /dev/null
@@ -1,607 +0,0 @@
-package dev.hephaestus.glowcase.client.gui.widget.ingame;
-
-import com.mojang.blaze3d.pipeline.RenderPipeline;
-import com.mojang.blaze3d.vertex.VertexConsumer;
-import dev.hephaestus.glowcase.client.gui.screen.ingame.ColorPickerIncludedScreen;
-import net.fabricmc.api.EnvType;
-import net.fabricmc.api.Environment;
-import net.minecraft.ChatFormatting;
-import net.minecraft.client.gui.GuiGraphicsExtractor;
-import net.minecraft.client.gui.components.AbstractButton;
-import net.minecraft.client.gui.components.events.GuiEventListener;
-import net.minecraft.client.gui.narration.NarrationElementOutput;
-import net.minecraft.client.gui.navigation.ScreenRectangle;
-import net.minecraft.client.gui.render.TextureSetup;
-import net.minecraft.client.input.InputWithModifiers;
-import net.minecraft.client.input.MouseButtonEvent;
-import net.minecraft.client.renderer.RenderPipelines;
-import net.minecraft.client.renderer.state.gui.GuiElementRenderState;
-import net.minecraft.network.chat.Component;
-import net.minecraft.resources.Identifier;
-import org.apache.commons.compress.utils.Lists;
-import org.jetbrains.annotations.Nullable;
-import org.joml.Matrix3x2fStack;
-
-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 AbstractButton {
- static final Identifier CONFIRM_TEXTURE = Identifier.withDefaultNamespace("pending_invite/accept");
- static final Identifier CONFIRM_HIGHLIGHTED_TEXTURE = Identifier.withDefaultNamespace("pending_invite/accept_highlighted");
- static final Identifier CANCEL_TEXTURE = Identifier.withDefaultNamespace("pending_invite/reject");
- static final Identifier CANCEL_HIGHLIGHTED_TEXTURE = Identifier.withDefaultNamespace("pending_invite/reject_highlighted");
-
- public final ColorPickerIncludedScreen screen;
- public GuiEventListener 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;
- 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, Component 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 setTargetElement(GuiEventListener element) {
- this.targetElement = element;
- }
-
- 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() {
- if (this.onAccept != null) {
- this.onAccept.accept(this);
- }
-
- this.toggle(false);
- }
-
- public void cancel() {
- if (this.onCancel != null) {
- this.onCancel.accept(this);
- }
-
- 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(ChatFormatting formatting) {
- this.screen.insertFormattingTag(formatting);
- }
-
- public void setColor(Color color) {
- this.color = color;
- this.updateHSL();
- this.updateThumbPositions();
- }
-
- @Override
- protected void extractContents(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float delta) {
- if (!visible) return;
- updateHSL();
-
- //graphics.setShaderColor(1f, 1f, 1f, this.alpha);
- /*RenderSystem.enableBlend();
- RenderSystem.enableDepthTest();*/
- Matrix3x2fStack matrices = graphics.pose();
- //graphics.applyBlur();
-
- graphics.nextStratum();
- matrices.pushMatrix();
-
- int x = this.getX();
- int y = this.getY();
- int z = 1;
- int width = this.getWidth();
- int height = this.getHeight();
-
- //background
- graphics.blit(RenderPipelines.GUI_TEXTURED, Identifier.withDefaultNamespace("textures/gui/inworld_menu_list_background.png"), x, y, 0, 0, width, height, 32, 32);
- if (this.isHoveredOrFocused()) {
- //outline
- drawOutline(graphics, x, y, width, height, 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(graphics, previewX, previewY, previewWidth, previewHeight);
- drawSatLight(graphics, satLightX, satLightY, satLightWidth, satLightHeight);
- drawHueBar(graphics, 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(graphics, mouseX, mouseY, delta, previewX, presetY, y + height - presetY, z + 1, presetSize, width / (presetSize + presetPadding), presetPadding);
- }
-
- this.confirmButton.extractRenderState(graphics, mouseX, mouseY, delta);
- this.cancelButton.extractRenderState(graphics, mouseX, mouseY, delta);
-
-
- matrices.popMatrix();
-
- //graphics.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(GuiGraphicsExtractor graphics, int x, int y, int width, int height) {
- graphics.fill(x, y, x + width, y + height, this.color.getRGB());
- }
-
- private void drawHueBar(GuiGraphicsExtractor graphics, 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(
- graphics,
- x + (width / maxColors * (color)), y,
- width / maxColors, height,
- colors[color], colors[color + 1]
- );
- }
-
- //thumb
- graphics.fill(hueThumbX - 3, y - 1, hueThumbX + 3, y + height + 1, getRgbFromHueThumb());
- drawOutline(graphics, hueThumbX - 3, y - 1, 6, height + 2, Color.white);
- }
-
- private void drawSatLight(GuiGraphicsExtractor graphics, int x, int y, int width, int height) {
- //white to current color's hue, left to right
- sidewaysGradient(graphics, x, y, width, height, Color.white.getRGB(), getRgbFromHueThumb());
-
- //transparent to black, top to bottom
- graphics.fillGradient(x, y, x + width, y + height, 0x00000000, Color.black.getRGB());
-
- //thumb
- graphics.fill(satLightThumbX - 4, satLightThumbY - 4, satLightThumbX + 4, satLightThumbY + 4, this.color.getRGB());
- drawOutline(graphics, satLightThumbX - 4, satLightThumbY - 4, 8, 8, Color.white);
- }
-
- private void drawOutline(GuiGraphicsExtractor graphics, int x, int y, int width, int height, Color outlineColor) {
- int color = outlineColor.getRGB();
- graphics.fill(x, y, x + width, y + 1, color);
- graphics.fill(x, y, x + 1, y + height, color);
- graphics.fill(x + width, y, x + width - 1, y + height, color);
- graphics.fill(x, y + height, x + width, y + height - 1, color);
- }
-
- private void sidewaysGradient(GuiGraphicsExtractor graphics, int x, int y, int width, int height, int startColor, int endColor) {
- graphics.guiRenderState.addGuiElement(new GuiElementRenderState() {
-
- @Override
- public ScreenRectangle bounds() {
- return new ScreenRectangle(x, y, width, height).transformMaxBounds(graphics.pose());
- }
-
- @Override
- public void buildVertices(VertexConsumer vertices) {
- Matrix3x2fStack matrix = graphics.pose();
- vertices.addVertexWith2DPose(matrix, x, y).setColor(startColor);
- vertices.addVertexWith2DPose(matrix, x, y + height).setColor(startColor);
- vertices.addVertexWith2DPose(matrix, x + width, y + height).setColor(endColor);
- vertices.addVertexWith2DPose(matrix, x + width, y).setColor(endColor);
- }
-
- @Override
- public RenderPipeline pipeline() {
- return RenderPipelines.GUI;
- }
-
- @Override
- public TextureSetup textureSetup() {
- return TextureSetup.noTexture();
- }
-
- @Override
- public @Nullable ScreenRectangle scissorArea() {
- return null;
- }
- });
- }
-
- private void drawPresets(GuiGraphicsExtractor graphics, 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.extractRenderState(graphics, 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, ChatFormatting.DARK_RED);
- ColorPresetWidget red = ColorPresetWidget.fromFormatting(this, ChatFormatting.RED);
- ColorPresetWidget gold = ColorPresetWidget.fromFormatting(this, ChatFormatting.GOLD);
- ColorPresetWidget yellow = ColorPresetWidget.fromFormatting(this, ChatFormatting.YELLOW);
- ColorPresetWidget green = ColorPresetWidget.fromFormatting(this, ChatFormatting.GREEN);
- ColorPresetWidget darkGreen = ColorPresetWidget.fromFormatting(this, ChatFormatting.DARK_GREEN);
- ColorPresetWidget aqua = ColorPresetWidget.fromFormatting(this, ChatFormatting.AQUA);
- ColorPresetWidget darkAqua = ColorPresetWidget.fromFormatting(this, ChatFormatting.DARK_AQUA);
- ColorPresetWidget blue = ColorPresetWidget.fromFormatting(this, ChatFormatting.BLUE);
- ColorPresetWidget darkBlue = ColorPresetWidget.fromFormatting(this, ChatFormatting.DARK_BLUE);
- ColorPresetWidget lightPurple = ColorPresetWidget.fromFormatting(this, ChatFormatting.LIGHT_PURPLE);
- ColorPresetWidget darkPurple = ColorPresetWidget.fromFormatting(this, ChatFormatting.DARK_PURPLE);
- ColorPresetWidget white = ColorPresetWidget.fromFormatting(this, ChatFormatting.WHITE);
- ColorPresetWidget grey = ColorPresetWidget.fromFormatting(this, ChatFormatting.GRAY);
- ColorPresetWidget darkGrey = ColorPresetWidget.fromFormatting(this, ChatFormatting.DARK_GRAY);
- ColorPresetWidget black = ColorPresetWidget.fromFormatting(this, ChatFormatting.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(MouseButtonEvent event, boolean doubleClick) {
- this.mouseDown = true;
- this.satLightDown = false;
- this.hueDown = false;
- this.presetDown = false;
- this.confirmOrCancelButtonDown = false;
- setColorFromMouse(event, doubleClick);
- }
-
- public void setColorFromMouse(MouseButtonEvent event, boolean doubleClick) {
- int colorAlpha = color.getAlpha();
-
- double mouseX = event.x();
- double mouseY = event.y();
- 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(event, doubleClick);
- confirmOrCancelButtonDown = true;
- } else if (this.cancelButton.isMouseOver(mouseX, mouseY)) {
- if (satLightDown || hueDown || presetDown || confirmOrCancelButtonDown) return;
- this.cancelButton.onClick(event, doubleClick);
- confirmOrCancelButtonDown = true;
- } else {
- //clickedPreset also sets the preset to avoid an extra calculation
- checkAndSetPreset(event, doubleClick);
- }
-
- if (this.changeListener != null) {
- this.changeListener.accept(this.color);
- }
- }
-
- 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(MouseButtonEvent event, boolean doubleClick) {
- 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(event.x(), event.y())) {
- preset.onClick(event, doubleClick);
- //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(MouseButtonEvent event, double dx, double dy) {
- if (mouseDown || isMouseOver(event.x(), event.y())) {
- setColorFromMouse(event, false);
- }
- }
-
- @Override
- public void onRelease(MouseButtonEvent event) {
- this.mouseDown = false;
- }
-
- @Override
- public boolean isMouseOver(double mouseX, double mouseY) {
- return super.isMouseOver(mouseX, mouseY);
- }
-
- @Override
- public void onPress(InputWithModifiers input) {
- }
-
-
- 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);
- }
-
- public 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 updateWidgetNarration(NarrationElementOutput builder) {
- this.defaultButtonNarrationText(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);
- }
-
- @Environment(EnvType.CLIENT)
- public static class Builder {
- private final ColorPickerIncludedScreen screen;
- private final int x;
- private final int y;
- private int width = 150;
- private int height = 200;
- private boolean includePresets = true;
- private boolean includeDefaultPresets = true;
- private final 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, Component.nullToEmpty(""));
- 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
deleted file mode 100644
index 2533b6dc..00000000
--- a/src/main/java/dev/hephaestus/glowcase/client/gui/widget/ingame/ColorPresetWidget.java
+++ /dev/null
@@ -1,84 +0,0 @@
-package dev.hephaestus.glowcase.client.gui.widget.ingame;
-
-import net.minecraft.ChatFormatting;
-import net.minecraft.client.gui.GuiGraphicsExtractor;
-import net.minecraft.client.gui.components.AbstractButton;
-import net.minecraft.client.gui.narration.NarrationElementOutput;
-import net.minecraft.client.input.InputWithModifiers;
-import net.minecraft.network.chat.Component;
-import org.jetbrains.annotations.Nullable;
-
-import java.awt.*;
-import java.util.function.BiConsumer;
-
-public class ColorPresetWidget extends AbstractButton {
- public final ColorPickerWidget colorPickerWidget;
- public final Color color;
- @Nullable
- public ChatFormatting 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, Component.nullToEmpty(""));
- this.colorPickerWidget = colorPicker;
- this.color = color;
- }
-
- public void setPosition(int x, int y, int z, int size) {
- this.setX(x);
- this.setY(y);
- this.setSize(size, size);
- this.z = z;
- }
-
- public static ColorPresetWidget fromFormatting(ColorPickerWidget colorPicker, ChatFormatting formatting) {
- if (formatting.isColor()) {
- //noinspection DataFlowIssue
- ColorPresetWidget presetWidget = new ColorPresetWidget(colorPicker, 0, 0, 0, 0, new Color(formatting.getColor()));
- 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 extractContents(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float delta) {
- graphics.fill(this.getX(), this.getY(), this.getX() + this.getWidth(), this.getY() + this.getHeight(), this.color.getRGB());
- if (isMouseOver(mouseX, mouseY)) {
- drawOutline(graphics, this.getX() - 1, this.getY() - 1, this.getWidth() + 2, this.getHeight() + 2, this.z + 1);
- }
- }
-
- private void drawOutline(GuiGraphicsExtractor graphics, int x, int y, int width, int height, int z) {
- int color = Color.white.getRGB();
- graphics.fill(x, y, x + width, y + 1, color);
- graphics.fill(x, y, x + 1, y + height, color);
- graphics.fill(x + width, y, x + width - 1, y + height, color);
- graphics.fill(x, y + height, x + width, y + height - 1, color);
- }
-
- @Override
- public void onPress(InputWithModifiers input) {
- BiConsumer presetListener = this.colorPickerWidget.getPresetListener();
- if (presetListener != null) {
- presetListener.accept(this.color, this.formatting != null && this.formatting.isColor() ? this.formatting : null);
- } else {
- if (this.formatting != null && formatting.isColor()) {
- this.colorPickerWidget.color = this.color;
- this.colorPickerWidget.toggle(false);
- } else {
- this.colorPickerWidget.setColor(this.color);
- }
- }
-
- }
-
- @Override
- protected void updateWidgetNarration(NarrationElementOutput builder) {
-
- }
-}
diff --git a/src/main/java/dev/hephaestus/glowcase/client/gui/widget/ingame/color/HexColorEditBox.java b/src/main/java/dev/hephaestus/glowcase/client/gui/widget/ingame/color/HexColorEditBox.java
new file mode 100644
index 00000000..a7ccf651
--- /dev/null
+++ b/src/main/java/dev/hephaestus/glowcase/client/gui/widget/ingame/color/HexColorEditBox.java
@@ -0,0 +1,138 @@
+package dev.hephaestus.glowcase.client.gui.widget.ingame.color;
+
+import dev.hephaestus.glowcase.client.gui.widget.ingame.GlowcaseEditBox;
+import dev.hephaestus.glowcase.client.gui.widget.ingame.color.functional.ColorGetter;
+import dev.hephaestus.glowcase.client.gui.widget.ingame.color.functional.ColorSetter;
+import dev.hephaestus.glowcase.client.gui.widget.ingame.color.picker.ColorPickerWidget;
+import dev.hephaestus.glowcase.client.util.ColorUtil;
+import net.minecraft.client.gui.Font;
+import net.minecraft.client.input.CharacterEvent;
+import net.minecraft.client.input.MouseButtonEvent;
+import net.minecraft.network.chat.Component;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * An EditBox Widget built for HEX colors, with automatic color (as an integer) getter & setter, and built-in {@link ColorPickerWidget} support.
+ * @author Superkat32
+ */
+public class HexColorEditBox extends GlowcaseEditBox {
+ @Nullable
+ public final ColorPickerWidget colorPickerWidget;
+ public final ColorGetter colorGetter;
+ public final ColorSetter colorSetter;
+ public int color;
+ public boolean editableAlpha;
+
+ public static HexColorEditBox.Builder builder(Font textRenderer, int x, int y, ColorGetter colorGetter, ColorSetter colorSetter) {
+ return new Builder(textRenderer, x, y, colorGetter, colorSetter);
+ }
+
+ public HexColorEditBox(
+ Font textRenderer, int x, int y, int width, int height,
+ boolean editableAlpha, @Nullable ColorPickerWidget colorPickerWidget,
+ ColorGetter colorGetter, ColorSetter colorSetter
+ ) {
+ super(textRenderer, x, y, width, height, Component.empty());
+ this.colorPickerWidget = colorPickerWidget;
+ this.colorGetter = colorGetter;
+ this.colorSetter = colorSetter;
+ this.color = colorGetter.get();
+ this.editableAlpha = editableAlpha;
+
+ this.setResponder(string -> {
+ ColorUtil.parse(string, this.color).ifSuccess(this.colorSetter::set);
+ });
+
+ this.setTextFromColor();
+ }
+
+ @Override
+ public void onClick(MouseButtonEvent event, boolean doubleClick) {
+ super.onClick(event, doubleClick);
+ if (this.colorPickerWidget != null) {
+ this.colorPickerWidget.target(this, this.color, this.editableAlpha, this::setColor);
+ }
+ }
+
+ @Override
+ public boolean charTyped(CharacterEvent event) {
+ if(super.charTyped(event)) {
+ if (this.colorPickerWidget != null) {
+ ColorUtil.parse(this.getValue(), this.color).ifSuccess(parsedColor -> {
+ this.color = parsedColor;
+ this.colorPickerWidget.setColor(parsedColor);
+ });
+ }
+ return true;
+ }
+ return false;
+ }
+
+ public void setTextFromColor() {
+ String hexString = this.formatColorAsHex(this.color);
+ this.setValue(hexString);
+ }
+
+ public String formatColorAsHex(int color) {
+ if (this.editableAlpha) return ColorUtil.toAlphaHex(color);
+ return ColorUtil.toHex(color);
+ }
+
+ public void setColor(int color) {
+ this.color = color;
+ this.setTextFromColor();
+ }
+
+ public static class Builder {
+ private static final int DEFAULT_WIDTH = 50;
+ private static final int DEFAULT_ALPHA_WIDTH = 64;
+
+ private final ColorGetter colorGetter;
+ private final ColorSetter colorSetter;
+ private final Font textRenderer;
+ private final int x, y;
+
+ private int width = DEFAULT_WIDTH;
+ private int height = 20;
+ private boolean editableAlpha = false;
+ private ColorPickerWidget colorPickerWidget = null;
+
+ public Builder(Font textRenderer, int x, int y, ColorGetter colorGetter, ColorSetter colorSetter) {
+ this.colorGetter = colorGetter;
+ this.colorSetter = colorSetter;
+ this.textRenderer = textRenderer;
+ this.x = x;
+ this.y = y;
+ }
+
+ public Builder setWidth(int width) {
+ this.width = width;
+ return this;
+ }
+
+ public Builder setHeight(int height) {
+ this.height = height;
+ return this;
+ }
+
+ public Builder setEditableAlpha(boolean editableAlpha) {
+ this.editableAlpha = editableAlpha;
+ if (this.width == DEFAULT_WIDTH) this.width = DEFAULT_ALPHA_WIDTH;
+ return this;
+ }
+
+ public Builder setColorPickerWidget(ColorPickerWidget colorPickerWidget) {
+ this.colorPickerWidget = colorPickerWidget;
+ return this;
+ }
+
+ public HexColorEditBox build() {
+ return new HexColorEditBox(
+ this.textRenderer, this.x, this.y, this.width, this.height,
+ this.editableAlpha, this.colorPickerWidget,
+ this.colorGetter, this.colorSetter
+ );
+ }
+ }
+
+}
diff --git a/src/main/java/dev/hephaestus/glowcase/client/gui/widget/ingame/color/functional/ColorGetter.java b/src/main/java/dev/hephaestus/glowcase/client/gui/widget/ingame/color/functional/ColorGetter.java
new file mode 100644
index 00000000..1f4602ed
--- /dev/null
+++ b/src/main/java/dev/hephaestus/glowcase/client/gui/widget/ingame/color/functional/ColorGetter.java
@@ -0,0 +1,5 @@
+package dev.hephaestus.glowcase.client.gui.widget.ingame.color.functional;
+
+public interface ColorGetter {
+ int get();
+}
diff --git a/src/main/java/dev/hephaestus/glowcase/client/gui/widget/ingame/color/functional/ColorSetter.java b/src/main/java/dev/hephaestus/glowcase/client/gui/widget/ingame/color/functional/ColorSetter.java
new file mode 100644
index 00000000..8e97df43
--- /dev/null
+++ b/src/main/java/dev/hephaestus/glowcase/client/gui/widget/ingame/color/functional/ColorSetter.java
@@ -0,0 +1,5 @@
+package dev.hephaestus.glowcase.client.gui.widget.ingame.color.functional;
+
+public interface ColorSetter {
+ void set(int color);
+}
diff --git a/src/main/java/dev/hephaestus/glowcase/client/gui/widget/ingame/color/functional/PickerPresetListener.java b/src/main/java/dev/hephaestus/glowcase/client/gui/widget/ingame/color/functional/PickerPresetListener.java
new file mode 100644
index 00000000..488e3d50
--- /dev/null
+++ b/src/main/java/dev/hephaestus/glowcase/client/gui/widget/ingame/color/functional/PickerPresetListener.java
@@ -0,0 +1,7 @@
+package dev.hephaestus.glowcase.client.gui.widget.ingame.color.functional;
+
+import dev.hephaestus.glowcase.client.gui.widget.ingame.color.picker.PickerPreset;
+
+public interface PickerPresetListener {
+ void onPresetClick(PickerPreset preset);
+}
diff --git a/src/main/java/dev/hephaestus/glowcase/client/gui/widget/ingame/color/picker/ColorPickerWidget.java b/src/main/java/dev/hephaestus/glowcase/client/gui/widget/ingame/color/picker/ColorPickerWidget.java
new file mode 100644
index 00000000..183adaa3
--- /dev/null
+++ b/src/main/java/dev/hephaestus/glowcase/client/gui/widget/ingame/color/picker/ColorPickerWidget.java
@@ -0,0 +1,490 @@
+package dev.hephaestus.glowcase.client.gui.widget.ingame.color.picker;
+
+import com.mojang.blaze3d.platform.cursor.CursorTypes;
+import dev.hephaestus.glowcase.Glowcase;
+import dev.hephaestus.glowcase.client.gui.screen.ingame.ColorPickerIncludedScreen;
+import dev.hephaestus.glowcase.client.gui.widget.ingame.color.functional.ColorSetter;
+import dev.hephaestus.glowcase.client.gui.widget.ingame.color.functional.PickerPresetListener;
+import dev.hephaestus.glowcase.client.util.ColorUtil;
+import dev.hephaestus.glowcase.client.util.GuiGraphicsUtil;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.GuiGraphicsExtractor;
+import net.minecraft.client.gui.components.AbstractButton;
+import net.minecraft.client.gui.components.AbstractWidget;
+import net.minecraft.client.gui.components.events.GuiEventListener;
+import net.minecraft.client.gui.narration.NarrationElementOutput;
+import net.minecraft.client.gui.screens.Screen;
+import net.minecraft.client.input.InputWithModifiers;
+import net.minecraft.client.input.MouseButtonEvent;
+import net.minecraft.client.renderer.RenderPipelines;
+import net.minecraft.network.chat.Component;
+import net.minecraft.resources.Identifier;
+import net.minecraft.util.ARGB;
+import org.jetbrains.annotations.Nullable;
+import org.jspecify.annotations.NonNull;
+
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * Main color picker widget, including a preview box, saturation & value box, hue slider, optional alpha slider, and color presets.
+ * Each {@link ColorPickerIncludedScreen} has *one* Color Picker, which each widget uses. This means that position, showAlpha, and color values will be updated constantly.
+ * @author Superkat32
+ */
+public class ColorPickerWidget extends AbstractButton {
+ // To keep things simple (and keep me sane), sizes are fully locked and hardcoded
+ // Width calculated by: ((presetScale + padding) * presetsPerLine) + padding -> ((16 + 2) * 10) + 2
+ // Height calculated by: Whatever my heart desired at the moment -> 116 and 146, apparently
+ // Alpha height difference: (presetScale + padding) + (alphaHeight + padding) -> (16 + 2) + (10 + 2)
+ private static final int DEFAULT_WIDTH = 182;
+ private static final int DEFAULT_HEIGHT = 116;
+ private static final int DEFAULT_ALPHA_HEIGHT = 146;
+ private static final Identifier BACKGROUND_TEXTURE = Identifier.withDefaultNamespace("textures/gui/inworld_menu_list_background.png");
+
+ // Different alpha textures have different tile sizes to better match their background
+ private static final Identifier ALPHA_PREVIEW_TEXTURE = Glowcase.id("color_picker/alpha_preview");
+ private static final Identifier ALPHA_SLIDER_TEXTURE = Glowcase.id("color_picker/alpha_slider");
+ private static final Identifier ALPHA_THUMB_TEXTURE = Glowcase.id("color_picker/alpha_thumb");
+ private static final Identifier ALPHA_PRESET_TEXTURE = Glowcase.id("color_picker/alpha_preset");
+
+ public final ColorPickerIncludedScreen screen;
+ @Nullable
+ public GuiEventListener targetElement = null;
+ @Nullable
+ public ColorSetter pickedColorListener = null;
+ @Nullable
+ public PickerPresetListener presetListener = null;
+ @Nullable
+ public Consumer confirmListener = null;
+ public boolean showAlpha = false;
+
+ public final List clickableAreas;
+ public PickerArea previewArea;
+ public PickerArea satValueArea;
+ public PickerArea hueArea;
+ public PickerArea alphaArea;
+ public PickerArea presetsArea;
+ public List formattingPresetAreas;
+ public List alphaPresetAreas;
+ public List buttonAreas;
+
+ @Nullable
+ public PickerArea currentClickedArea = null; // Used for allowing mouse drags beyond an area's boundaries
+
+ public float hue, saturation, value, alpha;
+ public float prevHue, prevSaturation, prevValue, prevAlpha; // Used for cancelling, *not* interpolation
+
+ public static ColorPickerWidget.Builder builder(ColorPickerIncludedScreen screen) {
+ return new Builder(screen);
+ }
+
+ public ColorPickerWidget(ColorPickerIncludedScreen screen, int x, int y, int width, int height, Component message) {
+ super(x, y, width, height, message);
+ this.screen = screen;
+
+ this.previewArea = new PickerArea(this, false);
+ this.satValueArea = new PickerArea(this, xLerp -> this.saturation = xLerp, yLerp -> this.value = 1f - yLerp);
+ this.hueArea = new PickerArea(this, xLerp -> this.hue = xLerp);
+ this.alphaArea = new PickerArea(this, xLerp -> this.alpha = xLerp);
+ this.presetsArea = new PickerArea(this, false);
+ this.formattingPresetAreas = PickerPreset.createFormattingPresets(this);
+ this.alphaPresetAreas = PickerPreset.createAlphaPresets(this);
+ this.buttonAreas = PickerButton.createButtons(this);
+ this.updateAreas();
+
+ this.clickableAreas = List.of(this.satValueArea, this.hueArea, this.alphaArea);
+ this.hide(); // Start hidden
+ }
+
+ // Preset & button width & height: 16
+ // Presets per row: 10 (2 rows by default, 3 rows if alpha is shown)
+ // Hue & Alpha slider heights: 10
+ // Preview & Sat/value picker: Whatever height is left
+ // Preview width: 1/3 of picker padded width
+ // Sat/value picker: Whatever width is left from preview
+ // Padding among everything: 2
+ public void updateAreas() {
+ int paddedX = this.getX() + 2;
+ int bottomY = this.getY() + this.getHeight() - 2;
+ int paddedWidth = this.getWidth() - 4;
+
+ int presetsHeight = (16) * (this.showAlpha ? 3 : 2) + (this.showAlpha ? 4 : 2);
+ this.presetsArea.set(paddedX, bottomY - presetsHeight, paddedWidth, presetsHeight);
+ this.alphaArea.set(paddedX, presetsArea.getY() - 10 - 2, paddedWidth, this.showAlpha ? 10 : 0);
+ this.hueArea.set(paddedX, alphaArea.getY() - (showAlpha ? 10 + 2 : 0), paddedWidth, 10);
+
+ int remainingHeight = this.hueArea.getY() - this.getY() - 4;
+ int remainingY = hueArea.getY() - remainingHeight - 2;
+ this.previewArea.set(paddedX, remainingY, paddedWidth / 3, remainingHeight);
+ int satValueWidth = this.getX() + this.getWidth() - previewArea.getX2() - 4;
+ this.satValueArea.set(previewArea.getX2() + 2, remainingY, satValueWidth, remainingHeight);
+
+ for (int i = 0; i < this.formattingPresetAreas.size(); i++) {
+ PickerPreset preset = this.formattingPresetAreas.get(i);
+ int x = paddedX + ((i % 10) * 18);
+ int y = presetsArea.getY() + (18 * (i / 10));
+ preset.set(x, y, 16, 16);
+ }
+
+ if (this.showAlpha) {
+ for (int i = 0; i < this.alphaPresetAreas.size(); i++) {
+ PickerPreset preset = this.alphaPresetAreas.get(i);
+ int x = paddedX + ((i % 10) * 18);
+ int y = presetsArea.getY() + (18 * ((i / 10) + 2));
+ preset.set(x, y, 16, 16);
+ }
+ }
+
+ for (int i = 0; i < this.buttonAreas.size(); i++) {
+ // First button is confirm, second is cancel, so start from far right and shift left
+ PickerButton button = this.buttonAreas.get(i);
+ int x = this.getX() + this.getWidth() - (18 * (i + 1));
+ int y = this.presetsArea.getY() + (18 * (this.showAlpha ? 2 : 1));
+ button.set(x, y, 16, 16);
+ }
+ }
+
+ // Position the thumbs based on the current color
+ public void updateAreaThumbs() {
+ this.hueArea.lerpThumbX(this.hue);
+ this.satValueArea.lerpThumbX(this.saturation);
+ this.satValueArea.lerpThumbY(1f - this.value);
+ if (this.showAlpha) this.alphaArea.lerpThumbX(this.alpha);
+ }
+
+ // region R.E.P.O - Render, Extract, and Position Operations
+ @Override
+ protected void extractContents(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float delta) {
+ // Background
+ graphics.blit(RenderPipelines.GUI_TEXTURED, BACKGROUND_TEXTURE, this.getX(), this.getY(), 0, 0, this.getWidth(), this.getHeight(), 32, 32);
+
+ // Render areas
+ this.extractPreviewArea(this.previewArea, graphics);
+ this.extractSatValueArea(this.satValueArea, graphics, mouseX, mouseY);
+ this.extractHueArea(this.hueArea, graphics, mouseX, mouseY);
+ if (showAlpha) this.extractAlphaArea(this.alphaArea, graphics, mouseX, mouseY);
+ for (PickerPreset preset : this.formattingPresetAreas) {
+ this.extractPresetArea(preset, graphics, false, mouseX, mouseY);
+ }
+ if (showAlpha) {
+ for (PickerPreset preset : this.alphaPresetAreas) {
+ this.extractPresetArea(preset, graphics, true, mouseX, mouseY);
+ }
+ }
+ for (PickerButton button : buttonAreas) {
+ this.extractButtonArea(button, graphics, mouseX, mouseY);
+ }
+
+ // Render thumbs (to ensure they are above everything despite overlaps)
+ this.extractThumb(graphics, this.hueArea.getThumbX(), this.hueArea.getY() + 5, 6, 12, this.getHueColor());
+ if (showAlpha) this.extractThumb(graphics, this.alphaArea.getThumbX(), this.alphaArea.getY() + 5, 6, 12, this.getColor(), true);
+ this.extractThumb(graphics, this.satValueArea.getThumbX(), this.satValueArea.getThumbY(), 8, 8, this.getColorNoAlpha());
+ }
+
+ public void extractPreviewArea(PickerArea area, GuiGraphicsExtractor graphics) {
+ // Alpha background texture (if needed)
+ if (this.showAlpha) GuiGraphicsUtil.drawPreciseTile(graphics, ALPHA_PREVIEW_TEXTURE, area.getX(), area.getY(), area.getX2(), area.getY2(), 8, 7);
+ // Current color
+ graphics.fill(area.getX(), area.getY(), area.getX2(), area.getY2(), this.getColor());
+ }
+
+ public void extractSatValueArea(PickerArea area, GuiGraphicsExtractor graphics, int mouseX, int mouseY) {
+ // White to current hue, left to right
+ GuiGraphicsUtil.extractHorizontalGradient(graphics, area.getX(), area.getY(), area.getX2(), area.getY2(), ColorUtil.WHITE, this.getHueColor());
+ // Transparent to black, top to bottom
+ graphics.fillGradient(area.getX(), area.getY(), area.getX2(), area.getY2(), ColorUtil.TRANSPARENT, ColorUtil.BLACK);
+ // Outline
+ if (area.shouldOutline(mouseX, mouseY)) {
+ graphics.outline(area.getX(), area.getY(), area.getWidth(), area.getHeight(), ColorUtil.WHITE);
+ graphics.requestCursor(CursorTypes.POINTING_HAND);
+ }
+ }
+
+ public void extractHueArea(PickerArea area, GuiGraphicsExtractor graphics, int mouseX, int mouseY) {
+ // Hue gradient, starting and ending on red
+ GuiGraphicsUtil.extractHueGradient(graphics, area.getX(), area.getY(), area.getX2(), area.getY2());
+ // Outline
+ if (area.shouldOutline(mouseX, mouseY)) {
+ graphics.outline(area.getX(), area.getY(), area.getWidth(), area.getHeight(), ColorUtil.WHITE);
+ graphics.requestCursor(CursorTypes.POINTING_HAND);
+ }
+ }
+
+ public void extractAlphaArea(PickerArea area, GuiGraphicsExtractor graphics, int mouseX, int mouseY) {
+ // Alpha background texture
+ graphics.blitSprite(RenderPipelines.GUI_TEXTURED, ALPHA_SLIDER_TEXTURE, area.getX(), area.getY(), area.getWidth(), area.getHeight());
+ // Transparent to current color, left to right
+ GuiGraphicsUtil.extractHorizontalGradient(graphics, area.getX(), area.getY(), area.getX2(), area.getY2(), ColorUtil.TRANSPARENT, this.getColorNoAlpha());
+ // Outline
+ if (area.shouldOutline(mouseX, mouseY)) {
+ graphics.outline(area.getX(), area.getY(), area.getWidth(), area.getHeight(), ColorUtil.WHITE);
+ graphics.requestCursor(CursorTypes.POINTING_HAND);
+ }
+ }
+
+ public void extractPresetArea(PickerPreset preset, GuiGraphicsExtractor graphics, boolean alphaPreset, int mouseX, int mouseY) {
+ // Alpha background texture (if needed)
+ if (alphaPreset) graphics.blitSprite(RenderPipelines.GUI_TEXTURED, ALPHA_PRESET_TEXTURE, preset.getX(), preset.getY(), preset.getWidth(), preset.getHeight());
+ graphics.fill(preset.getX(), preset.getY(), preset.getX2(), preset.getY2(), preset.getPresetColor());
+ // Alpha preset tooltip
+ if (alphaPreset) {
+ // I'll be honest I have no clue how this works, I just got lucky
+ String hex = String.format("%1$02X", ARGB.alpha(preset.getPresetColor()));
+ graphics.text(Minecraft.getInstance().font, hex, preset.getX() + 1, preset.getY() + 7, ColorUtil.WHITE);
+ }
+ // Outline
+ if (preset.shouldOutline(mouseX, mouseY)) {
+ graphics.outline(preset.getX(), preset.getY(), preset.getWidth(), preset.getHeight(), ColorUtil.WHITE);
+ graphics.requestCursor(CursorTypes.POINTING_HAND);
+ }
+ }
+
+ public void extractButtonArea(PickerButton button, GuiGraphicsExtractor graphics, int mouseX, int mouseY) {
+ graphics.blitSprite(RenderPipelines.GUI_TEXTURED, button.getTexture(mouseX, mouseY), button.getX() - button.getPadX(), button.getY() - button.getPadY(), button.getWidth() + button.getPadScale(), button.getHeight() + button.getPadScale());
+ if (button.isMouseOver(mouseX, mouseY)) graphics.requestCursor(CursorTypes.POINTING_HAND);
+ }
+
+ public void extractThumb(GuiGraphicsExtractor graphics, int thumbX, int thumbY, int thumbWidth, int thumbHeight, int thumbColor) {
+ this.extractThumb(graphics, thumbX, thumbY, thumbWidth, thumbHeight, thumbColor, false);
+ }
+
+ public void extractThumb(GuiGraphicsExtractor graphics, int thumbX, int thumbY, int thumbWidth, int thumbHeight, int thumbColor, boolean alphaBackground) {
+ int halfWidth = thumbWidth / 2;
+ int halfHeight = thumbHeight / 2;
+
+ int x = thumbX - halfWidth;
+ int y = thumbY - halfHeight;
+ int x2 = thumbX + halfWidth;
+ int y2 = thumbY + halfHeight;
+
+ if (alphaBackground) graphics.blitSprite(RenderPipelines.GUI_TEXTURED, ALPHA_THUMB_TEXTURE, x, y, thumbWidth, thumbHeight);
+ graphics.fill(x, y, x2, y2, thumbColor);
+ graphics.outline(x, y, thumbWidth, thumbHeight, ColorUtil.WHITE);
+ }
+
+ @Override
+ protected void handleCursor(GuiGraphicsExtractor graphics) {
+ // NO-OP
+ }
+
+ // endregion
+
+ public void target(AbstractWidget widget, int initColor, boolean showAlpha, ColorSetter pickedColorListener) {
+ this.target(widget, initColor, showAlpha, false, pickedColorListener);
+ }
+
+ /**
+ * Position & set up the Color Picker based on a widget and its needs. Positioning accounts for screen size, ensuring the color picker never goes off-screen.
+ * @param widget The Widget to position the Color Picker too.
+ * @param initColor The initial color the Color Picker should be, and should revert to if canceled/undone.
+ * @param showAlpha Whether the alpha should be editable, and the alpha slider visible.
+ * @param rightAligned If the Color Picker Widget should align itself to the right side of the widget instead of the left side .
+ * @param pickedColorListener What to do with the picked color, as an integer.
+ */
+ public void target(AbstractWidget widget, int initColor, boolean showAlpha, boolean rightAligned, ColorSetter pickedColorListener) {
+ Screen screen = Minecraft.getInstance().screen;
+ this.visible = true;
+ this.active = true;
+ this.targetElement = widget;
+
+ // Ensure at least 2 pixels of padding between screen edges
+ int x = Math.min(screen.width - this.getWidth() - 2, widget.getX());
+ if (rightAligned) x = Math.max(2, widget.getX() + widget.getWidth() - this.getWidth() + 2);
+ int y = widget.getY() + widget.getHeight(); // Beneath widget
+ if (y + this.getHeight() > screen.height - 2) y = widget.getY() - this.getHeight(); // Above widget
+ this.setX(x);
+ this.setY(y);
+
+ this.setPrevColor(initColor);
+ this.setColor(initColor);
+
+ this.showAlpha = showAlpha;
+ this.setHeight(this.showAlpha ? DEFAULT_ALPHA_HEIGHT : DEFAULT_HEIGHT);
+
+ this.pickedColorListener = pickedColorListener;
+ this.presetListener = preset -> { // Default preset behaviour, just set the picker color
+ if (preset.isAlphaPreset()) {
+ this.alpha = preset.getAlpha();
+ this.updateAreaThumbs();
+ } else {
+ this.setColor(preset.getPresetColor(), false);
+ }
+ };
+ this.confirmListener = null;
+
+ this.updateAreas();
+ this.updateAreaThumbs();
+ }
+
+ public void setPresetListener(PickerPresetListener presetListener) {
+ this.presetListener = presetListener;
+ }
+
+ public void setConfirmListener(Consumer confirmListener) {
+ this.confirmListener = confirmListener;
+ }
+
+ public void setColor(int color) {
+ this.setColor(color, true);
+ }
+
+ /**
+ * Set the Color Picker's color from an integer. Intended for something like the {@link dev.hephaestus.glowcase.client.gui.widget.ingame.color.HexColorEditBox} when a user edits the HEX string, and the color picker needs to reflect that update.
+ */
+ public void setColor(int color, boolean setAlpha) {
+ float[] HSBA = ColorUtil.ARGBToHSBA(color);
+ this.hue = HSBA[0];
+ this.saturation = HSBA[1];
+ this.value = HSBA[2];
+ if (setAlpha) this.alpha = HSBA[3];
+
+ this.updateAreaThumbs();
+ }
+
+ /**
+ * The color to revert/undo to in case the user presses cancel.
+ */
+ public void setPrevColor(int color) {
+ float[] HSBA = ColorUtil.ARGBToHSBA(color);
+ this.prevHue = HSBA[0];
+ this.prevSaturation = HSBA[1];
+ this.prevValue = HSBA[2];
+ this.prevAlpha = HSBA[3];
+ }
+
+ public void confirm() {
+ if (this.confirmListener != null) this.confirmListener.accept(this.getColor());
+ this.hide();
+ }
+
+ public void cancel() {
+ int prevColor = ColorUtil.HSBAtoARGB(this.prevHue, this.prevSaturation, this.prevValue, this.showAlpha ? this.prevAlpha : 1f);
+ this.setColor(prevColor);
+ this.onColorPicked();
+ this.hide();
+ }
+
+ public void hide() {
+ this.active = false;
+ this.visible = false;
+ this.setFocused(false);
+
+ this.pickedColorListener = null;
+ this.presetListener = null;
+ this.confirmListener = null;
+ }
+
+ @Override
+ public void onClick(@NonNull MouseButtonEvent event, boolean doubleClick) {
+ this.tryClickAreas(event);
+ }
+
+ @Override
+ protected void onDrag(@NonNull MouseButtonEvent event, double dx, double dy) {
+ this.tryClickAreas(event);
+ }
+
+ @Override
+ public void onRelease(@NonNull MouseButtonEvent event) {
+ if (this.currentClickedArea != null) {
+ this.currentClickedArea.mouseReleased();
+ this.currentClickedArea = null;
+ }
+ }
+
+ public void tryClickAreas(MouseButtonEvent event) {
+ double x = event.x();
+ double y = event.y();
+
+ // Current clicked area gets full click priority
+ if (this.currentClickedArea != null) {
+ if (this.currentClickedArea.mouseClicked(x, y)) {
+ this.onColorPicked();
+ }
+ return;
+ }
+
+ // No area is currently clicked, so search for one to click, and return if found
+ for (PickerArea area : clickableAreas) {
+ if (area.mouseClicked(x, y)) {
+ this.currentClickedArea = area;
+ this.onColorPicked();
+ return;
+ }
+ }
+
+ for (PickerPreset preset : formattingPresetAreas) {
+ if (preset.mouseClicked(x, y)) {
+ this.currentClickedArea = preset;
+ this.onColorPicked();
+ return;
+ }
+ }
+
+ for (PickerPreset preset : alphaPresetAreas) {
+ if (preset.mouseClicked(x, y)) {
+ this.currentClickedArea = preset;
+ this.onColorPicked();
+ return;
+ }
+ }
+
+ for (PickerButton button : buttonAreas) {
+ if (button.mouseClicked(x, y)) {
+ this.currentClickedArea = button;
+ return;
+ }
+ }
+ }
+
+ public void onPresetClick(PickerPreset preset) {
+ if (this.presetListener != null) {
+ this.presetListener.onPresetClick(preset);
+ }
+ }
+
+ public void onColorPicked() {
+ if (this.pickedColorListener != null) {
+ int pickedColor = this.showAlpha ? this.getColor() : this.getColorNoAlpha();
+ this.pickedColorListener.set(pickedColor);
+ }
+ }
+
+ /**
+ * @return The Color Picker's picked color, as an integer
+ */
+ public int getColor() {
+ float returnedAlpha = this.showAlpha ? this.alpha : 1f;
+ return ColorUtil.HSBAtoARGB(this.hue, this.saturation, this.value, returnedAlpha);
+ }
+
+ public int getColorNoAlpha() {
+ return ColorUtil.HSBtoRGB(this.hue, this.saturation, this.value);
+ }
+
+ public int getHueColor() {
+ return ColorUtil.HSBtoRGB(this.hue, 1f, 1f);
+ }
+
+ @Override
+ public void onPress(@NonNull InputWithModifiers input) {
+ // NO-OP
+ }
+
+ @Override
+ protected void updateWidgetNarration(@NonNull NarrationElementOutput output) {
+ // NO-OP
+ }
+
+ public static class Builder {
+ private final ColorPickerIncludedScreen screen;
+
+ public Builder(ColorPickerIncludedScreen screen) {
+ this.screen = screen;
+ }
+
+ public ColorPickerWidget build() {
+ return new ColorPickerWidget(screen, 0, 0, DEFAULT_WIDTH, DEFAULT_HEIGHT, Component.nullToEmpty(""));
+ }
+ }
+}
diff --git a/src/main/java/dev/hephaestus/glowcase/client/gui/widget/ingame/color/picker/PickerArea.java b/src/main/java/dev/hephaestus/glowcase/client/gui/widget/ingame/color/picker/PickerArea.java
new file mode 100644
index 00000000..6ebc1289
--- /dev/null
+++ b/src/main/java/dev/hephaestus/glowcase/client/gui/widget/ingame/color/picker/PickerArea.java
@@ -0,0 +1,160 @@
+package dev.hephaestus.glowcase.client.gui.widget.ingame.color.picker;
+
+import net.minecraft.util.Mth;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Clickable area within a {@link ColorPickerWidget}, such as the hue slider, saturation/light picker, alpha slider, and presets.
+ * Allows for definable position & dimensions for a clickable area, and a listener for what to do when clicked (e.g. set ColorPicker hue/sat./light/alpha).
+ *
+ * @see ColorPickerWidget
+ */
+public class PickerArea {
+ private final ColorPickerWidget colorPicker;
+ @Nullable
+ private final AreaSetter xLerpSetter;
+ @Nullable
+ private final AreaSetter yLerpSetter;
+ private final boolean clickable;
+ private int x, y, width, height;
+ private int thumbX, thumbY;
+ private boolean hasMouseDown = false;
+
+ public PickerArea(ColorPickerWidget colorPicker, boolean clickable) {
+ this(colorPicker, false, null, null);
+ }
+
+ public PickerArea(ColorPickerWidget colorPicker, @Nullable AreaSetter xLerp) {
+ this(colorPicker, true, xLerp, null);
+ }
+
+ public PickerArea(ColorPickerWidget colorPicker, @Nullable AreaSetter xLerp, @Nullable AreaSetter yLerp) {
+ this(colorPicker, true, xLerp, yLerp);
+ }
+
+ public PickerArea(ColorPickerWidget colorPicker, boolean clickable, @Nullable AreaSetter xLerp, @Nullable AreaSetter yLerp) {
+ this.colorPicker = colorPicker;
+ this.clickable = clickable;
+ this.xLerpSetter = xLerp;
+ this.yLerpSetter = yLerp;
+ }
+
+ public boolean mouseClicked(double mouseX, double mouseY) {
+ if (!this.clickable) return false;
+
+ if (isMouseOver(mouseX, mouseY)) {
+ this.hasMouseDown = true;
+ }
+
+ if (this.hasMouseDown) {
+ // Assume horizontal / x movement is possible
+ if (this.xLerpSetter != null) {
+ this.thumbX = (int) Mth.clamp(mouseX, this.getX(), this.getX2());
+
+ float xLerp = calcLerp(mouseX, this.getX(), this.getX2());
+ this.xLerpSetter.set(xLerp);
+ }
+
+ // Assume vertical / y movement is possible
+ if (this.yLerpSetter != null) {
+ this.thumbY = (int) Mth.clamp(mouseY, this.getY(), this.getY2());
+
+ float yLerp = calcLerp(mouseY, this.getY(), this.getY2());
+ this.yLerpSetter.set(yLerp);
+ }
+ }
+
+ return this.hasMouseDown;
+ }
+
+ public void mouseReleased() {
+ this.hasMouseDown = false;
+ }
+
+ public boolean isMouseOver(double mouseX, double mouseY) {
+ return (mouseY > this.getY() && mouseY < this.getY2())
+ && (mouseX > this.getX() && mouseX < this.getX2());
+ }
+
+ public boolean shouldOutline(int mouseX, int mouseY) {
+ // Outline if mouse down, or if the mouse over AND no other area has the mouse down
+ return this.hasMouseDown() || (this.isMouseOver(mouseX, mouseY) && this.getColorPicker().currentClickedArea == null);
+ }
+
+ private float calcLerp(double mousePos, int minPos, int maxPos) {
+ if (mousePos < minPos) return 0f;
+ if (mousePos > maxPos) return 1f;
+ float width = maxPos - minPos;
+ return Mth.clamp(((float) mousePos - minPos) / width, 0f, 1f);
+ }
+
+ public void lerpThumbX(float lerp) {
+ this.thumbX = (int) Mth.clamp(this.getX() + (this.getWidth() * lerp), this.getX(), this.getX2());
+ }
+
+ public void lerpThumbY(float lerp) {
+ this.thumbY = (int) Mth.clamp(this.getY() + (this.getHeight() * lerp), this.getY(), this.getY2());
+ }
+
+ // region Getters & Setters
+ public void set(int x, int y, int width, int height) {
+ this.setPos(x, y);
+ this.setDimensions(width, height);
+ }
+
+ public void setPos(int x, int y) {
+ this.x = x;
+ this.y = y;
+ }
+
+ public void setDimensions(int width, int height) {
+ this.width = width;
+ this.height = height;
+ }
+
+ public int getX() {
+ return x;
+ }
+
+ public int getY() {
+ return y;
+ }
+
+ public int getX2() {
+ return this.getX() + this.getWidth();
+ }
+
+ public int getY2() {
+ return this.getY() + this.getHeight();
+ }
+
+ public int getWidth() {
+ return width;
+ }
+
+ public int getHeight() {
+ return height;
+ }
+
+ public int getThumbX() {
+ return thumbX;
+ }
+
+ public int getThumbY() {
+ return thumbY;
+ }
+
+ public boolean hasMouseDown() {
+ return this.hasMouseDown;
+ }
+
+ public ColorPickerWidget getColorPicker() {
+ return colorPicker;
+ }
+
+ // endregion
+
+ public interface AreaSetter {
+ void set(float value);
+ }
+}
diff --git a/src/main/java/dev/hephaestus/glowcase/client/gui/widget/ingame/color/picker/PickerButton.java b/src/main/java/dev/hephaestus/glowcase/client/gui/widget/ingame/color/picker/PickerButton.java
new file mode 100644
index 00000000..9b467aed
--- /dev/null
+++ b/src/main/java/dev/hephaestus/glowcase/client/gui/widget/ingame/color/picker/PickerButton.java
@@ -0,0 +1,60 @@
+package dev.hephaestus.glowcase.client.gui.widget.ingame.color.picker;
+
+import net.minecraft.resources.Identifier;
+
+import java.util.List;
+
+public class PickerButton extends PickerArea {
+ private static final Identifier CONFIRM_TEXTURE = Identifier.withDefaultNamespace("pending_invite/accept");
+ private static final Identifier CONFIRM_HIGHLIGHTED_TEXTURE = Identifier.withDefaultNamespace("pending_invite/accept_highlighted");
+ private static final Identifier CANCEL_TEXTURE = Identifier.withDefaultNamespace("pending_invite/reject");
+ private static final Identifier CANCEL_HIGHLIGHTED_TEXTURE = Identifier.withDefaultNamespace("pending_invite/reject_highlighted");
+
+ private final Identifier texture;
+ private final Identifier hoverTexture;
+ private final Runnable clickListener;
+ // Padding on the texture rendering because the icons aren't (and physically can't be) centered on the pixel grid
+ private final int padX, padY, padScale;
+
+ public static List createButtons(ColorPickerWidget colorPickerWidget) {
+ return List.of(
+ new PickerButton(colorPickerWidget, CONFIRM_TEXTURE, CONFIRM_HIGHLIGHTED_TEXTURE, 1, 1, 2, colorPickerWidget::confirm),
+ new PickerButton(colorPickerWidget, CANCEL_TEXTURE, CANCEL_HIGHLIGHTED_TEXTURE, 2, 1, 3, colorPickerWidget::cancel)
+ );
+ }
+
+ public PickerButton(ColorPickerWidget colorPicker, Identifier texture, Identifier hoverTexture, int padX, int padY, int padScale, Runnable clickListener) {
+ super(colorPicker, true, null, null);
+ this.texture = texture;
+ this.hoverTexture = hoverTexture;
+ this.padX = padX;
+ this.padY = padY;
+ this.padScale = padScale;
+ this.clickListener = clickListener;
+ }
+
+ @Override
+ public boolean mouseClicked(double mouseX, double mouseY) {
+ if (this.isMouseOver(mouseX, mouseY)) {
+ this.clickListener.run();
+ return true;
+ }
+ return super.mouseClicked(mouseX, mouseY);
+ }
+
+ public Identifier getTexture(int mouseX, int mouseY) {
+ return this.isMouseOver(mouseX, mouseY) ? this.hoverTexture : this.texture;
+ }
+
+ public int getPadX() {
+ return padX;
+ }
+
+ public int getPadY() {
+ return padY;
+ }
+
+ public int getPadScale() {
+ return padScale;
+ }
+}
diff --git a/src/main/java/dev/hephaestus/glowcase/client/gui/widget/ingame/color/picker/PickerPreset.java b/src/main/java/dev/hephaestus/glowcase/client/gui/widget/ingame/color/picker/PickerPreset.java
new file mode 100644
index 00000000..e76312aa
--- /dev/null
+++ b/src/main/java/dev/hephaestus/glowcase/client/gui/widget/ingame/color/picker/PickerPreset.java
@@ -0,0 +1,79 @@
+package dev.hephaestus.glowcase.client.gui.widget.ingame.color.picker;
+
+import net.minecraft.ChatFormatting;
+import net.minecraft.util.ARGB;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class PickerPreset extends PickerArea {
+ public static final ChatFormatting[] FORMATTING_PRESETS = new ChatFormatting[]{
+ ChatFormatting.DARK_RED, ChatFormatting.RED, ChatFormatting.GOLD, ChatFormatting.YELLOW,
+ ChatFormatting.GREEN, ChatFormatting.DARK_GREEN, ChatFormatting.AQUA, ChatFormatting.DARK_AQUA,
+ ChatFormatting.BLUE, ChatFormatting.DARK_BLUE, ChatFormatting.LIGHT_PURPLE, ChatFormatting.DARK_PURPLE,
+ ChatFormatting.WHITE, ChatFormatting.GRAY, ChatFormatting.DARK_GRAY, ChatFormatting.BLACK
+ };
+
+ public static final Integer[] ALPHA_PRESETS = new Integer[]{
+ 0x00000000, 0x40000000, 0x55000000, 0x77000000, 0x99000000, 0xAA000000, 0xCC000000, 0xFF000000
+ };
+
+ public static List createFormattingPresets(ColorPickerWidget colorPicker) {
+ List presets = new ArrayList<>();
+ for (ChatFormatting formattingPreset : FORMATTING_PRESETS) {
+ //noinspection DataFlowIssue - We can assume that the presets in this array have their color ints
+ presets.add(new PickerPreset(colorPicker, ARGB.color(1f, formattingPreset.getColor()), formattingPreset, false));
+ }
+ return presets;
+ }
+
+ public static List createAlphaPresets(ColorPickerWidget colorPicker) {
+ List presets = new ArrayList<>();
+ for (Integer alphaColor : ALPHA_PRESETS) {
+ presets.add(new PickerPreset(colorPicker, alphaColor, null, true));
+ }
+ return presets;
+ }
+
+ private final int presetColor;
+ @Nullable
+ private final ChatFormatting presetFormatting;
+ private final boolean isAlphaPreset;
+
+ public PickerPreset(ColorPickerWidget colorPicker, int presetColor, @Nullable ChatFormatting presetFormatting, boolean isAlphaPreset) {
+ super(colorPicker, true, null, null);
+ this.presetColor = presetColor;
+ this.presetFormatting = presetFormatting;
+ this.isAlphaPreset = isAlphaPreset;
+ }
+
+ @Override
+ public boolean mouseClicked(double mouseX, double mouseY) {
+ if(super.mouseClicked(mouseX, mouseY)) {
+ this.getColorPicker().onPresetClick(this);
+ return true;
+ }
+ return false;
+ }
+
+ public int getPresetColor() {
+ if (this.presetFormatting != null && this.presetFormatting.isColor() && this.presetFormatting.getColor() != null) {
+ // Needed otherwise it's transparent for some reason
+ return ARGB.color(1f, this.presetFormatting.getColor());
+ }
+ return this.presetColor;
+ }
+
+ public @Nullable ChatFormatting getPresetFormatting() {
+ return presetFormatting;
+ }
+
+ public boolean isAlphaPreset() {
+ return isAlphaPreset;
+ }
+
+ public float getAlpha() {
+ return ARGB.alphaFloat(this.presetColor);
+ }
+}
diff --git a/src/main/java/dev/hephaestus/glowcase/client/util/ColorUtil.java b/src/main/java/dev/hephaestus/glowcase/client/util/ColorUtil.java
index 2649758e..570b7d2f 100644
--- a/src/main/java/dev/hephaestus/glowcase/client/util/ColorUtil.java
+++ b/src/main/java/dev/hephaestus/glowcase/client/util/ColorUtil.java
@@ -2,6 +2,7 @@
import com.mojang.serialization.DataResult;
import net.minecraft.ChatFormatting;
+import net.minecraft.util.ARGB;
/**
* @author Ampflower
@@ -16,7 +17,16 @@ public final class ColorUtil {
public static final int RGB_MASK = (1 << RGB_BITS) - 1;
public static final int ALPHA_MASK = CHANNEL_MASK << RGB_BITS;
+
+ public static final int RED = 0xFFFF0000;
+ public static final int YELLOW = 0xFFFFFF00;
+ public static final int GREEN = 0xFF00FF00;
+ public static final int CYAN = 0xFF00FFFF;
+ public static final int BLUE = 0xFF0000FF;
+ public static final int MAGENTA = 0xFFFF00FF;
+
public static final int WHITE = ALPHA_MASK | RGB_MASK;
+ public static final int BLACK = 0xFF000000;
public static final int TRANSPARENT = 0;
public static int transferAlpha(int oldColor, int newColor) {
@@ -80,6 +90,10 @@ public static String toAlphaHex(int color) {
return String.format("#%1$08X", color);
}
+ public static String toHex(int color) {
+ return "#" + String.format("%1$06X", color & 0x00FFFFFF);
+ }
+
private static int upcast(int color) {
int r = (color & 0x000F) * 0x00000011;
int g = (color & 0x00F0) * 0x00000110;
@@ -87,4 +101,120 @@ private static int upcast(int color) {
int a = (color & 0xF000) * 0x00011000;
return r | g | b | a;
}
+
+ /**
+ * Convert an integer color to hue, saturation, brightness/value, and alpha.
+ */
+ public static float[] ARGBToHSBA(int color) {
+ int red = ARGB.red(color);
+ int green = ARGB.green(color);
+ int blue = ARGB.blue(color);
+ float[] HSBA = new float[4];
+ RGBtoHSB(red, green, blue, HSBA);
+
+ HSBA[3] = ARGB.alphaFloat(color);
+ return HSBA;
+ }
+
+ /**
+ * Converts RGB to HSB (hue, saturation, brightness/value).
+ *
+ * Custom method here used in favor of java.awt.Color's to prevent possible crashes on Mac.
+ * @see java.awt.Color#RGBtoHSB(int, int, int, float[])
+ */
+ public static float[] RGBtoHSB(int r, int g, int b, float[] hsbvals) {
+ float hue, saturation, brightness;
+ if (hsbvals == null) {
+ hsbvals = new float[3];
+ }
+ int cmax = (r > g) ? r : g;
+ if (b > cmax) cmax = b;
+ int cmin = (r < g) ? r : g;
+ if (b < cmin) cmin = b;
+
+ brightness = ((float) cmax) / 255.0f;
+ if (cmax != 0)
+ saturation = ((float) (cmax - cmin)) / ((float) cmax);
+ else
+ saturation = 0;
+ if (saturation == 0)
+ hue = 0;
+ else {
+ float redc = ((float) (cmax - r)) / ((float) (cmax - cmin));
+ float greenc = ((float) (cmax - g)) / ((float) (cmax - cmin));
+ float bluec = ((float) (cmax - b)) / ((float) (cmax - cmin));
+ if (r == cmax)
+ hue = bluec - greenc;
+ else if (g == cmax)
+ hue = 2.0f + redc - bluec;
+ else
+ hue = 4.0f + greenc - redc;
+ hue = hue / 6.0f;
+ if (hue < 0)
+ hue = hue + 1.0f;
+ }
+ hsbvals[0] = hue;
+ hsbvals[1] = saturation;
+ hsbvals[2] = brightness;
+ return hsbvals;
+ }
+
+ /**
+ * Convert hue, saturation, brightness/value, and alpha to an integer of ARGB.
+ */
+ public static int HSBAtoARGB(float hue, float saturation, float brightness, float alpha) {
+ return ARGB.color(alpha, HSBtoRGB(hue, saturation,brightness));
+ }
+
+ /**
+ * Converts HSB (hue, saturation, brightness/value) to RGB.
+ *
+ * Custom method here used in favor of java.awt.Color's to prevent possible crashes on Mac.
+ * @see java.awt.Color#HSBtoRGB(float, float, float)
+ */
+ public static int HSBtoRGB(float hue, float saturation, float brightness) {
+ int r = 0, g = 0, b = 0;
+ if (saturation == 0) {
+ r = g = b = (int) (brightness * 255.0f + 0.5f);
+ } else {
+ float h = (hue - (float)Math.floor(hue)) * 6.0f;
+ float f = h - (float)java.lang.Math.floor(h);
+ float p = brightness * (1.0f - saturation);
+ float q = brightness * (1.0f - saturation * f);
+ float t = brightness * (1.0f - (saturation * (1.0f - f)));
+ switch ((int) h) {
+ case 0:
+ r = (int) (brightness * 255.0f + 0.5f);
+ g = (int) (t * 255.0f + 0.5f);
+ b = (int) (p * 255.0f + 0.5f);
+ break;
+ case 1:
+ r = (int) (q * 255.0f + 0.5f);
+ g = (int) (brightness * 255.0f + 0.5f);
+ b = (int) (p * 255.0f + 0.5f);
+ break;
+ case 2:
+ r = (int) (p * 255.0f + 0.5f);
+ g = (int) (brightness * 255.0f + 0.5f);
+ b = (int) (t * 255.0f + 0.5f);
+ break;
+ case 3:
+ r = (int) (p * 255.0f + 0.5f);
+ g = (int) (q * 255.0f + 0.5f);
+ b = (int) (brightness * 255.0f + 0.5f);
+ break;
+ case 4:
+ r = (int) (t * 255.0f + 0.5f);
+ g = (int) (p * 255.0f + 0.5f);
+ b = (int) (brightness * 255.0f + 0.5f);
+ break;
+ case 5:
+ r = (int) (brightness * 255.0f + 0.5f);
+ g = (int) (p * 255.0f + 0.5f);
+ b = (int) (q * 255.0f + 0.5f);
+ break;
+ }
+ }
+ return 0xff000000 | (r << 16) | (g << 8) | (b << 0);
+ }
}
diff --git a/src/main/java/dev/hephaestus/glowcase/client/util/GuiGraphicsUtil.java b/src/main/java/dev/hephaestus/glowcase/client/util/GuiGraphicsUtil.java
new file mode 100644
index 00000000..ecfd4ac9
--- /dev/null
+++ b/src/main/java/dev/hephaestus/glowcase/client/util/GuiGraphicsUtil.java
@@ -0,0 +1,143 @@
+package dev.hephaestus.glowcase.client.util;
+
+import com.mojang.blaze3d.pipeline.RenderPipeline;
+import com.mojang.blaze3d.vertex.VertexConsumer;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.GuiGraphicsExtractor;
+import net.minecraft.client.gui.navigation.ScreenRectangle;
+import net.minecraft.client.gui.render.TextureSetup;
+import net.minecraft.client.renderer.RenderPipelines;
+import net.minecraft.client.renderer.state.gui.GuiElementRenderState;
+import net.minecraft.client.renderer.texture.AbstractTexture;
+import net.minecraft.client.renderer.texture.TextureAtlasSprite;
+import net.minecraft.data.AtlasIds;
+import net.minecraft.resources.Identifier;
+import org.joml.Matrix3x2fStack;
+import org.jspecify.annotations.NonNull;
+import org.jspecify.annotations.Nullable;
+
+public class GuiGraphicsUtil {
+ public static final int[] HUE_GRADIENT_COLORS = new int[]{
+ ColorUtil.RED, ColorUtil.YELLOW, ColorUtil.GREEN, ColorUtil.CYAN, ColorUtil.BLUE, ColorUtil.MAGENTA, ColorUtil.RED
+ };
+
+ /**
+ * Fill a horizontal gradient between two colors.
+ * @apiNote Do not replace the position floats with integers! {@link GuiGraphicsUtil#extractHueGradient(GuiGraphicsExtractor, int, int, int, int)} relies on float position to ensure its gradients can be fully rendered across any specified integer length, instead of requiring the length to be perfectly divisible by a specific number (7 I think) or else it leaves an empty gap.
+ * @see GuiGraphicsExtractor#fillGradient
+ */
+ public static void extractHorizontalGradient(GuiGraphicsExtractor graphics, float x0, float y0, float x1, float y1, int startColor, int endColor) {
+ graphics.guiRenderState.addGuiElement(
+ new GuiElementRenderState() {
+ @Override
+ public void buildVertices(@NonNull VertexConsumer vertexConsumer) {
+ Matrix3x2fStack matrix = graphics.pose();
+ vertexConsumer.addVertexWith2DPose(matrix, x0, y0).setColor(startColor);
+ vertexConsumer.addVertexWith2DPose(matrix, x0, y1).setColor(startColor);
+ vertexConsumer.addVertexWith2DPose(matrix, x1, y1).setColor(endColor);
+ vertexConsumer.addVertexWith2DPose(matrix, x1, y0).setColor(endColor);
+ }
+
+ @Override
+ public @NonNull RenderPipeline pipeline() {
+ return RenderPipelines.GUI;
+ }
+
+ @Override
+ public @NonNull TextureSetup textureSetup() {
+ return TextureSetup.noTexture();
+ }
+
+ @Override
+ public @Nullable ScreenRectangle scissorArea() {
+ return null;
+ }
+
+ @Override
+ public @NonNull ScreenRectangle bounds() {
+ return new ScreenRectangle((int) x0, (int) y0, (int) (x1 - x0), (int) (y1 - y0)).transformMaxBounds(graphics.pose());
+ }
+ }
+ );
+ }
+
+ /**
+ * Render a hue gradient, starting and ending on red.
+ * @apiNote Do not replace the width float with an integer! See {@link GuiGraphicsUtil#extractHorizontalGradient(GuiGraphicsExtractor, float, float, float, float, int, int)}'s API Note for details.
+ */
+ public static void extractHueGradient(GuiGraphicsExtractor graphics, int x0, int y0, int x1, int y1) {
+ float width = x1 - x0;
+ int colorCount = HUE_GRADIENT_COLORS.length - 1;
+ for (int i = 0; i < colorCount; i++) {
+ extractHorizontalGradient(
+ graphics,
+ x0 + (width / colorCount * i), y0,
+ x0 + (width / colorCount * (i + 1)), y1,
+ HUE_GRADIENT_COLORS[i], HUE_GRADIENT_COLORS[i + 1]
+ );
+ }
+ }
+
+ /**
+ * Tile a texture across an area with precise texture placement. Allows for better tiling of textures within an integer bounds than otherwise possible.
+ */
+ public static void drawPreciseTile(GuiGraphicsExtractor graphics, Identifier spriteId, int x0, int y0, int x1, int y1, float rows, float columns) {
+ TextureAtlasSprite sprite = Minecraft.getInstance().getAtlasManager().getAtlasOrThrow(AtlasIds.GUI).getSprite(spriteId);
+ AbstractTexture spriteTexture = Minecraft.getInstance().getTextureManager().getTexture(sprite.atlasLocation());
+ float width = x1 - x0;
+ float height = y1 - y0;
+ float tileWidth = width / columns;
+ float tileHeight = height / rows;
+ for (int iX = 0; iX < columns; iX++) {
+ float tileX = x0 + (tileWidth * iX);
+ for (int iY = 0; iY < rows; iY++) {
+ float tileY = y0 + (tileHeight * iY);
+ drawPreciseTexture(
+ graphics, spriteTexture,
+ tileX, tileY, tileX + tileWidth, tileY + tileHeight,
+ sprite.getU0(), sprite.getU1(), sprite.getV0(), sprite.getV1()
+ );
+ }
+ }
+ }
+
+ /**
+ * Draw a texture with precise placement beyond the normal integer grid of GUI rendering.
+ */
+ public static void drawPreciseTexture(GuiGraphicsExtractor graphics, AbstractTexture texture, float x0, float y0, float x1, float y1, float u0, float u1, float v0, float v1) {
+ graphics.guiRenderState.addGuiElement(
+ new GuiElementRenderState() {
+ @Override
+ public void buildVertices(@NonNull VertexConsumer vertexConsumer) {
+ Matrix3x2fStack matrix = graphics.pose();
+ int color = -1;
+ vertexConsumer.addVertexWith2DPose(matrix, x0, y0).setUv(u0, v0).setColor(color);
+ vertexConsumer.addVertexWith2DPose(matrix, x0, y1).setUv(u0, v1).setColor(color);
+ vertexConsumer.addVertexWith2DPose(matrix, x1, y1).setUv(u1, v1).setColor(color);
+ vertexConsumer.addVertexWith2DPose(matrix, x1, y0).setUv(u1, v0).setColor(color);
+ }
+
+ @Override
+ public @NonNull RenderPipeline pipeline() {
+ return RenderPipelines.GUI_TEXTURED;
+ }
+
+ @Override
+ public @NonNull TextureSetup textureSetup() {
+ return TextureSetup.singleTexture(texture.getTextureView(), texture.getSampler());
+ }
+
+ @Override
+ public @Nullable ScreenRectangle scissorArea() {
+ return null;
+ }
+
+ @Override
+ public @NonNull ScreenRectangle bounds() {
+ return new ScreenRectangle((int) x0, (int) y0, (int) (x1 - x0), (int) (y1 - y0)).transformMaxBounds(graphics.pose());
+ }
+ }
+ );
+ }
+
+}
diff --git a/src/main/resources/assets/glowcase/lang/en_us.json b/src/main/resources/assets/glowcase/lang/en_us.json
index cd13ddd9..007fc95a 100644
--- a/src/main/resources/assets/glowcase/lang/en_us.json
+++ b/src/main/resources/assets/glowcase/lang/en_us.json
@@ -101,8 +101,10 @@
"gui.glowcase.volume": "Volume",
"gui.glowcase.pitch": "Pitch",
"gui.glowcase.yaw": "Yaw",
- "gui.glowcase.color": "Color",
- "gui.glowcase.background_color": "Background Color",
+ "gui.glowcase.color_argb": "Color (ARGB)",
+ "gui.glowcase.color": "Color (RGB)",
+ "gui.glowcase.background_color_argb": "Background Color (ARGB)",
+ "gui.glowcase.background_color": "Background Color (RGB)",
"gui.glowcase.repeat_delay": "Repeat Delay",
"gui.glowcase.distance": "Distance",
"gui.glowcase.sound_category": "%s",
@@ -179,5 +181,6 @@
"gui.glowcase.render_distance_value.infinite": "Render Distance: Infinite",
"gui.glowcase.text_alignment": "Text Alignment",
"gui.glowcase.text_shadow": "Text Shadow",
- "gui.glowcase.more": "More..."
+ "gui.glowcase.more": "More...",
+ "gui.glowcase.extra_properties": "Extra Properties"
}
diff --git a/src/main/resources/assets/glowcase/textures/gui/sprites/color_picker/alpha_preset.png b/src/main/resources/assets/glowcase/textures/gui/sprites/color_picker/alpha_preset.png
new file mode 100644
index 00000000..137d044f
Binary files /dev/null and b/src/main/resources/assets/glowcase/textures/gui/sprites/color_picker/alpha_preset.png differ
diff --git a/src/main/resources/assets/glowcase/textures/gui/sprites/color_picker/alpha_preset.png.mcmeta b/src/main/resources/assets/glowcase/textures/gui/sprites/color_picker/alpha_preset.png.mcmeta
new file mode 100644
index 00000000..920b97f9
--- /dev/null
+++ b/src/main/resources/assets/glowcase/textures/gui/sprites/color_picker/alpha_preset.png.mcmeta
@@ -0,0 +1,10 @@
+{
+ "gui": {
+ "scaling": {
+ "type": "tile",
+ "width": 16,
+ "height": 16,
+ "border": 2
+ }
+ }
+}
diff --git a/src/main/resources/assets/glowcase/textures/gui/sprites/color_picker/alpha_preview.png b/src/main/resources/assets/glowcase/textures/gui/sprites/color_picker/alpha_preview.png
new file mode 100644
index 00000000..137d044f
Binary files /dev/null and b/src/main/resources/assets/glowcase/textures/gui/sprites/color_picker/alpha_preview.png differ
diff --git a/src/main/resources/assets/glowcase/textures/gui/sprites/color_picker/alpha_preview.png.mcmeta b/src/main/resources/assets/glowcase/textures/gui/sprites/color_picker/alpha_preview.png.mcmeta
new file mode 100644
index 00000000..fdc0c74a
--- /dev/null
+++ b/src/main/resources/assets/glowcase/textures/gui/sprites/color_picker/alpha_preview.png.mcmeta
@@ -0,0 +1,10 @@
+{
+ "gui": {
+ "scaling": {
+ "type": "tile",
+ "width": 20,
+ "height": 20,
+ "border": 2
+ }
+ }
+}
diff --git a/src/main/resources/assets/glowcase/textures/gui/sprites/color_picker/alpha_slider.png b/src/main/resources/assets/glowcase/textures/gui/sprites/color_picker/alpha_slider.png
new file mode 100644
index 00000000..137d044f
Binary files /dev/null and b/src/main/resources/assets/glowcase/textures/gui/sprites/color_picker/alpha_slider.png differ
diff --git a/src/main/resources/assets/glowcase/textures/gui/sprites/color_picker/alpha_slider.png.mcmeta b/src/main/resources/assets/glowcase/textures/gui/sprites/color_picker/alpha_slider.png.mcmeta
new file mode 100644
index 00000000..3fdfad94
--- /dev/null
+++ b/src/main/resources/assets/glowcase/textures/gui/sprites/color_picker/alpha_slider.png.mcmeta
@@ -0,0 +1,10 @@
+{
+ "gui": {
+ "scaling": {
+ "type": "tile",
+ "width": 10,
+ "height": 10,
+ "border": 2
+ }
+ }
+}
diff --git a/src/main/resources/assets/glowcase/textures/gui/sprites/color_picker/alpha_thumb.png b/src/main/resources/assets/glowcase/textures/gui/sprites/color_picker/alpha_thumb.png
new file mode 100644
index 00000000..137d044f
Binary files /dev/null and b/src/main/resources/assets/glowcase/textures/gui/sprites/color_picker/alpha_thumb.png differ
diff --git a/src/main/resources/assets/glowcase/textures/gui/sprites/color_picker/alpha_thumb.png.mcmeta b/src/main/resources/assets/glowcase/textures/gui/sprites/color_picker/alpha_thumb.png.mcmeta
new file mode 100644
index 00000000..0382014c
--- /dev/null
+++ b/src/main/resources/assets/glowcase/textures/gui/sprites/color_picker/alpha_thumb.png.mcmeta
@@ -0,0 +1,10 @@
+{
+ "gui": {
+ "scaling": {
+ "type": "tile",
+ "width": 12,
+ "height": 12,
+ "border": 2
+ }
+ }
+}
diff --git a/src/main/resources/assets/glowcase/textures/gui/sprites/properties.png b/src/main/resources/assets/glowcase/textures/gui/sprites/properties.png
new file mode 100644
index 00000000..46df3000
Binary files /dev/null and b/src/main/resources/assets/glowcase/textures/gui/sprites/properties.png differ