diff --git a/src/main/java/dev/hephaestus/glowcase/block/entity/PopupBlockEntity.java b/src/main/java/dev/hephaestus/glowcase/block/entity/PopupBlockEntity.java index acb2d4ea..45887b97 100644 --- a/src/main/java/dev/hephaestus/glowcase/block/entity/PopupBlockEntity.java +++ b/src/main/java/dev/hephaestus/glowcase/block/entity/PopupBlockEntity.java @@ -15,12 +15,12 @@ import net.minecraft.world.level.storage.ValueOutput; public class PopupBlockEntity extends GlowcaseBlockEntity { - public static final NodeParser PARSER = TagParser.DEFAULT; public String title = ""; public List lines = new ArrayList<>(); public TextBlockEntity.TextAlignment textAlignment = TextBlockEntity.TextAlignment.CENTER; public int color = 0xFFFFFFFF; public boolean renderDirty = true; + public boolean viewScreenTitle = true; public PopupBlockEntity(BlockPos pos, BlockState state) { super(Glowcase.POPUP_BLOCK_ENTITY.get(), pos, state); @@ -32,6 +32,7 @@ protected void saveAdditional(ValueOutput view) { super.saveAdditional(view); view.putString("title", this.title); + view.putBoolean("view_screen_title", this.viewScreenTitle); view.putInt("color", this.color); view.store("text_alignment", TextBlockEntity.TextAlignment.CODEC, this.textAlignment); @@ -44,6 +45,7 @@ protected void loadAdditional(ValueInput view) { super.loadAdditional(view); this.title = view.getStringOr("title", ""); + this.viewScreenTitle = view.getBooleanOr("view_screen_title", true); this.color = view.getIntOr("color", 0xFFFFFF); this.textAlignment = view.read("text_alignment", TextBlockEntity.TextAlignment.CODEC).orElse(TextBlockEntity.TextAlignment.CENTER); @@ -52,39 +54,4 @@ protected void loadAdditional(ValueInput view) { this.renderDirty = true; } - - public String getRawLine(int i) { - var line = this.lines.get(i); - - if (line.getStyle() == null) { - return line.getString(); - } - - var insert = line.getStyle().getInsertion(); - - if (insert == null) { - return line.getString(); - } - return insert; - } - - public void addRawLine(int i, String string) { - var parsed = PARSER.parseComponent(string, ParserContext.of()); - - if (parsed.getString().equals(string)) { - this.lines.add(i, Component.literal(string)); - } else { - this.lines.add(i, Component.empty().append(parsed).setStyle(Style.EMPTY.withInsertion(string))); - } - } - - public void setRawLine(int i, String string) { - var parsed = PARSER.parseComponent(string, ParserContext.of()); - - if (parsed.getString().equals(string)) { - this.lines.set(i, Component.literal(string)); - } else { - this.lines.set(i, Component.empty().append(parsed).setStyle(Style.EMPTY.withInsertion(string))); - } - } } diff --git a/src/main/java/dev/hephaestus/glowcase/block/entity/TextBlockEntity.java b/src/main/java/dev/hephaestus/glowcase/block/entity/TextBlockEntity.java index fb6966af..2c74163b 100644 --- a/src/main/java/dev/hephaestus/glowcase/block/entity/TextBlockEntity.java +++ b/src/main/java/dev/hephaestus/glowcase/block/entity/TextBlockEntity.java @@ -18,16 +18,16 @@ import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.storage.ValueInput; import net.minecraft.world.level.storage.ValueOutput; +import net.minecraft.world.phys.Vec3; import java.util.ArrayList; import java.util.List; public class TextBlockEntity extends GlowcaseBlockEntity { - public static final NodeParser PARSER = TagParser.DEFAULT; - public static final int PLATE_BACKGROUND = 0x44000000; public List lines = new ArrayList<>(); + // TODO (AC) - Anchor related field changes/additions (don't forget to save & load them!) public TextAlignment textAlignment = TextAlignment.CENTER; public HorizontalAlignment horizontalAlignment = HorizontalAlignment.CENTER; public ZOffset zOffset = ZOffset.CENTER; @@ -35,6 +35,8 @@ public class TextBlockEntity extends GlowcaseBlockEntity { public float scale = 1F; public int color = ColorUtil.WHITE; public int backgroundColor = 0; + public Vec3 offset = Vec3.ZERO; + public Vec3 rotation = Vec3.ZERO; // Yaw, pitch, roll public TextBlockEntity(BlockPos pos, BlockState state) { super(Glowcase.TEXT_BLOCK_ENTITY.get(), pos, state); @@ -54,7 +56,10 @@ protected void saveAdditional(ValueOutput view) { view.store("z_offset", ZOffset.CODEC, this.zOffset); view.putBoolean("shadow", this.shadow); - view.store("lines", ComponentSerialization.CODEC.listOf(), lines); + view.store("lines", ComponentSerialization.CODEC.listOf(), this.lines); + + view.store("offset", Vec3.CODEC, this.offset); + view.store("rotation", Vec3.CODEC, this.rotation); } @Override @@ -69,46 +74,16 @@ protected void loadAdditional(ValueInput view) { this.backgroundColor = view.getIntOr("background_color", 0); this.shadow = view.getBooleanOr("shadow", true); + // TODO (AC) - Handle loading old data alignment and converting it into new justify/anchor/whatever data this.textAlignment = view.read("text_alignment", TextAlignment.CODEC).orElse(TextAlignment.CENTER); this.horizontalAlignment = view.read("horizontal_alignment", HorizontalAlignment.CODEC).orElse(HorizontalAlignment.CENTER); this.zOffset = view.read("z_offset", ZOffset.CODEC).orElse(ZOffset.CENTER); this.lines = new ArrayList<>(view.read("lines", ComponentSerialization.CODEC.listOf()).orElseGet(List::of)); - this.rebake(false); - } - - public String getRawLine(int i) { - var line = this.lines.get(i); - - if (line.getStyle() == null) { - return line.getString(); - } - - var insert = line.getStyle().getInsertion(); - if (insert == null) { - return line.getString(); - } - return insert; - } - - public void addRawLine(int i, String string) { - var parsed = PARSER.parseComponent(string, ParserContext.of()); - - if (parsed.getString().equals(string)) { - this.lines.add(i, Component.literal(string)); - } else { - this.lines.add(i, Component.empty().append(parsed).setStyle(Style.EMPTY.withInsertion(string))); - } - } + this.offset = view.read("offset", Vec3.CODEC).orElse(Vec3.ZERO); + this.rotation = view.read("rotation", Vec3.CODEC).orElse(Vec3.ZERO); - public void setRawLine(int i, String string) { - var parsed = PARSER.parseComponent(string, ParserContext.of()); - - if (parsed.getString().equals(string)) { - this.lines.set(i, Component.literal(string)); - } else { - this.lines.set(i, Component.empty().append(parsed).setStyle(Style.EMPTY.withInsertion(string))); - } + this.rebake(false); } public void rebake(boolean immediate) { @@ -116,6 +91,7 @@ public void rebake(boolean immediate) { this.getLevel().sendBlockUpdated(this.getBlockPos(), this.getBlockState(), this.getBlockState(), immediate ? Block.UPDATE_IMMEDIATE : 0); } + // TODO (AC) - Rename into TextJustify? public enum TextAlignment implements StringRepresentable { LEFT, CENTER, @@ -133,6 +109,8 @@ public String getSerializedName() { } } + // TODO (AC Idea) - Rename to "ZAnchor" to provide a clear distinction between xyz offset and this? + // If done, fields/variables and translations for the edit screen should be updated public enum ZOffset implements StringRepresentable { FRONT, CENTER, BACK; @@ -144,6 +122,7 @@ public String getSerializedName() { } } + @Deprecated public enum HorizontalAlignment implements StringRepresentable { LEFT, CENTER, RIGHT; @@ -155,4 +134,41 @@ public String getSerializedName() { return name().toLowerCase(); } } + + public enum Anchor implements StringRepresentable { + TOP_LEFT(-1, 1), TOP(0, 1), TOP_RIGHT(1, 1), + MIDDLE_LEFT(-1, 0), MIDDLE(0, 0), MIDDLE_RIGHT(1, 0), + BOTTOM_LEFT(-1, -1), BOTTOM(0, -1), BOTTOM_RIGHT(1, -1); + + public static final Codec CODEC = StringRepresentable.fromEnum(Anchor::values); + public static final StreamCodec STREAM_CODEC = ByteBufCodecs.BYTE.map(index -> Anchor.values()[index], anchor -> (byte) anchor.ordinal()); + + public static Anchor fromHorizontalAlignment(HorizontalAlignment horizontalAlignment) { + return switch (horizontalAlignment) { + case LEFT -> MIDDLE_LEFT; + case RIGHT -> MIDDLE_RIGHT; + default -> MIDDLE; + }; + } + + private final int x; + private final int y; + Anchor(int x, int y) { + this.x = x; + this.y = y; + } + + public int getX() { + return this.x; + } + + public int getY() { + return this.y; + } + + @Override + public String getSerializedName() { + return this.name().toLowerCase(); + } + } } 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..063d4c70 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,81 @@ package dev.hephaestus.glowcase.client.gui.screen.ingame; -import dev.hephaestus.glowcase.client.gui.widget.ingame.ColorPickerWidget; -import net.minecraft.ChatFormatting; +import dev.hephaestus.glowcase.client.gui.widget.ingame.color.picker.ColorPickerWidget; +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: + *
    + *
  1. Implement this interface, and return a private ColorPickerWidget in {@link ColorPickerIncludedScreen#getColorPickerWidget()}.
  2. + *
  3. Initialize your ColorPickerWidget in {@link Screen#init()} via {@link ColorPickerWidget#builder(ColorPickerIncludedScreen)}. You do *not* need to add it as a renderableWidget.
  4. + *
  5. 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.
  6. + *
  7. 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.
  8. + *
  9. 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.
  10. + *
  11. Booyah!
  12. + *
+ * @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(); + } } diff --git a/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/ItemAcceptorBlockEditScreen.java b/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/ItemAcceptorBlockEditScreen.java index 815cd4d5..3b5cd0de 100644 --- a/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/ItemAcceptorBlockEditScreen.java +++ b/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/ItemAcceptorBlockEditScreen.java @@ -34,8 +34,8 @@ public void init() { this.itemWidget.setValue((this.blockEntity.isItemTag ? "#" : "") + item); } this.itemWidget.setHint(TextUtils.placeholder("gui.glowcase.item_or_tag")); - this.itemWidget.setFilter((currentValue, newChar, cursorPos) -> { - if (!InputFilters.assertOptionalPrefix('#', currentValue, newChar, cursorPos)) return false; + this.itemWidget.setFilter((currentValue, newChar, cursorPos, highlightPos) -> { + if (!InputFilters.assertOptionalPrefix('#', currentValue, newChar, cursorPos, highlightPos)) return false; if (newChar == '#' && cursorPos == 0) return true; return this.isValidCharacterForName(currentValue, newChar, cursorPos); 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..3f8cc59b 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 @@ -1,46 +1,37 @@ package dev.hephaestus.glowcase.client.gui.screen.ingame; -import com.mojang.datafixers.util.Pair; import dev.hephaestus.glowcase.Glowcase; -import dev.hephaestus.glowcase.client.gui.widget.ingame.ColorPickerWidget; +import dev.hephaestus.glowcase.block.entity.TextBlockEntity; +import dev.hephaestus.glowcase.client.gui.widget.ingame.GlowcaseEditBox; +import dev.hephaestus.glowcase.client.gui.widget.ingame.color.picker.ColorPickerWidget; +import dev.hephaestus.glowcase.client.gui.widget.ingame.text.FormattableMultilineTextField; +import dev.hephaestus.glowcase.client.gui.widget.ingame.text.GlowcaseMultilineEditBox; import dev.hephaestus.glowcase.client.util.NoteTextColorResource; import dev.hephaestus.glowcase.item.component.NoteComponent; import dev.hephaestus.glowcase.packet.C2SEditNoteItem; import eu.pb4.placeholders.api.ParserContext; -import eu.pb4.placeholders.api.parsers.NodeParser; -import eu.pb4.placeholders.api.parsers.TagParser; import net.minecraft.ChatFormatting; import net.minecraft.client.gui.Font; import net.minecraft.client.gui.GuiGraphicsExtractor; +import net.minecraft.client.gui.components.AbstractWidget; import net.minecraft.client.gui.components.Button; -import net.minecraft.client.gui.font.TextFieldHelper; -import net.minecraft.client.input.CharacterEvent; +import net.minecraft.client.gui.components.Whence; import net.minecraft.client.input.KeyEvent; import net.minecraft.client.input.MouseButtonEvent; import net.minecraft.client.renderer.RenderPipelines; -import net.minecraft.locale.Language; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.FormattedText; -import net.minecraft.network.chat.Style; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; import net.minecraft.resources.Identifier; -import net.minecraft.util.Mth; import net.minecraft.world.item.ItemStack; -import org.lwjgl.glfw.GLFW; import java.util.ArrayList; import java.util.List; import java.util.Optional; -//TODO: multi-character selection at some point? it may be a bit complex but it'd be nice public class NoteEditScreen extends TextEditorScreen { private static final Identifier TEXTURE = Glowcase.id("textures/gui/note.png"); - private static final int SCREEN_X1 = 3; - private static final int SCREEN_Y1 = 5; - private static final int SCREEN_X2 = -3; - private static final int SCREEN_Y2 = -5; - private static final int BG_SIZE = 256; private static final int BG_WIDTH = 244; @@ -48,29 +39,26 @@ public class NoteEditScreen extends TextEditorScreen { private static final int TXT_OFF_Y = 12; private static final int TXT_X_PADDING = 15 * 2; - private static final Component ARROW_LEFT_SYMBOL = Component.literal("«"); - private static final Component ARROW_RIGHT_SYMBOL = Component.literal("»"); - - private int editing_line_offset = 0; - private final List lines; + private List lines; private String title = ""; private String author = ""; private NoteComponent.Alignment textAlignment; - public static final NodeParser PARSER = TagParser.DEFAULT; - private TextFieldHelper selectionManager; - private int currentRow; - private long ticksSinceOpened = 0; - private boolean signing = false; private boolean finalizing = false; - private List signing_text; - private ColorPickerWidget colorPickerWidget; - private Button doneButton; - private Button signButton; + private List editingWidgets; + private GlowcaseMultilineEditBox glowcaseEditBox; private Button changeAlignment; + private Button doneAndCancelButton; + + private List signingWidgets; + private GlowcaseEditBox titleEditBox; + private GlowcaseEditBox authorEditBox; + private Button signAndFinalizeButton; + + private ColorPickerWidget colorPickerWidget; public NoteEditScreen(ItemStack stack) { if (stack.has(Glowcase.NOTE_COMPONENT.get())) { @@ -78,18 +66,23 @@ public NoteEditScreen(ItemStack stack) { NoteComponent note = stack.get(Glowcase.NOTE_COMPONENT.get()); assert note != null; - lines = new ArrayList<>(); - lines.addAll(note.lines()); - for (int i = 0; i < (NoteComponent.LINES_LIMIT - note.lines().size()); i++) - lines.add(Component.literal("")); + this.lines = new ArrayList<>( + note.lines().stream() + .filter(component -> !component.getString().isBlank()) + .toList() + ); +// lines = new ArrayList<>(); +// lines.addAll(note.lines()); +// for (int i = 0; i < (NoteComponent.LINES_LIMIT - note.lines().size()); i++) +// lines.add(Component.literal("")); textAlignment = note.alignment(); } else { // Default data lines = new ArrayList<>(); - for (int i = 0; i < NoteComponent.LINES_LIMIT; i++) - lines.add(Component.literal("")); +// for (int i = 0; i < NoteComponent.LINES_LIMIT; i++) +// lines.add(Component.literal("")); textAlignment = NoteComponent.Alignment.LEFT; } @@ -99,54 +92,27 @@ public NoteEditScreen(ItemStack stack) { protected void init() { super.init(); - selectionManager = new TextFieldHelper( - () -> signing ? (currentRow == 6 ? title : author) : getRawLine(currentRow), - (string) -> { - if (signing) { - if (currentRow == 6) - title = string; - else - author = string; - } else - setRawLine(currentRow, string); - }, - TextFieldHelper.createClipboardGetter(minecraft), - TextFieldHelper.createClipboardSetter(minecraft), - (string) -> true); - - // Setup Signing Screen - - signing_text = new ArrayList<>(); - //noinspection unchecked - Pair[] lines = new Pair[]{ - new Pair<>(2, Component.translatable("gui.glowcase.note.signing")), - new Pair<>(3, Component.translatable("gui.glowcase.note.warning")), - new Pair<>(1, Component.literal("")), - new Pair<>(1, Component.translatable("gui.glowcase.note.title")), - new Pair<>(1, Component.translatable("gui.glowcase.note.author")), - new Pair<>(1, Component.literal("")), - new Pair<>(1, Component.translatable("gui.glowcase.note.required").setStyle(Style.EMPTY.withColor(ChatFormatting.RED))), - }; - for (Pair section : lines) { - int height = section.getFirst(); - List texts = font.getSplitter().splitLines(section.getSecond(), BG_WIDTH - TXT_X_PADDING, Style.EMPTY); - - for (int i = 0; i < height; i++) { - if (i + 1 <= texts.size()) { - FormattedText text = texts.get(i); - if (i == (height - 1) && texts.size() > height) - text = ensureBounds(font, text); - - signing_text.add(text); - } else { - signing_text.add(Component.empty()); - } - } - } - // Widgets int offset = 7; + this.glowcaseEditBox = GlowcaseMultilineEditBox.builder( + this.font, this.lines, + this.width / 2 - BG_WIDTH / 2 + TXT_X_PADDING / 2, this.height / 2 - BG_HEIGHT / 2 + TXT_OFF_Y, + BG_WIDTH - TXT_X_PADDING, BG_HEIGHT - TXT_OFF_Y * 2 + 5, // Adding 5 to prevent scroll + parsedLines -> { + this.lines = parsedLines; + }) + .setMaxLines(10) + .setLineHeight(9) + .setSideAlignmentPadding(0) + .setTruncateText(true) + .setOverflowArrowColor(NoteTextColorResource.TXT_COLOR) + .setInsetCursorColor(0xCC000000) + .setAppendCursorColor(NoteTextColorResource.TXT_COLOR) + .build(); + this.glowcaseEditBox.updateSettings(NoteTextColorResource.TXT_COLOR, false, getTextBlockAlignment(this.textAlignment)); + this.glowcaseEditBox.textField.seekCursor(Whence.ABSOLUTE, 0); + this.changeAlignment = Button.builder(Component.translatableEscape("gui.glowcase.alignment", textAlignment), action -> { switch (textAlignment) { case LEFT -> textAlignment = NoteComponent.Alignment.CENTER; @@ -155,185 +121,120 @@ protected void init() { } this.changeAlignment.setMessage(Component.translatableEscape("gui.glowcase.alignment", textAlignment)); + this.glowcaseEditBox.setTextAlignment(getTextBlockAlignment(this.textAlignment)); }).bounds(width / 2 - BG_WIDTH / 2, height / 2 - BG_HEIGHT / 2 - offset - 20, BG_WIDTH / 12 * 6 - 3 - 7, 20).build(); - signButton = Button.builder(Component.translatable("book.signButton"), action -> { - if (!signing) { - signing = true; - doneButton.setMessage(Component.translatable("gui.cancel")); - signButton.setMessage(Component.translatable("book.finalizeButton")); - signButton.active = false; - changeAlignment.active = false; - toggleWidgets(false); - - title = ""; - author = ""; - currentRow = 6; + this.colorPickerWidget = this.createColorPickerWidget(); + this.initFormattingButtons(width / 2 - BG_WIDTH / 2 + BG_WIDTH / 12 * 6 - 5 - 7, height / 2 - BG_HEIGHT / 2 - offset - 20 - 4, width / 100); + + this.signAndFinalizeButton = Button.builder(Component.translatable("book.signButton"), action -> { + if (!this.signing) { + this.switchToSigning(); } else { - finalizing = true; - onClose(); + this.finalizing = true; + this.onClose(); } }).bounds(width / 2 - BG_WIDTH / 2, height / 2 + BG_HEIGHT / 2 + offset, BG_WIDTH / 2 - 3, 20).build(); - doneButton = Button.builder(Component.translatable("gui.done"), action -> { - if (signing) { - signing = false; - doneButton.setMessage(Component.translatable("gui.done")); - signButton.setMessage(Component.translatable("book.signButton")); - signButton.active = true; - changeAlignment.active = true; - toggleWidgets(true); - } else - onClose(); + + this.doneAndCancelButton = Button.builder(Component.translatable("gui.done"), action -> { + if (this.signing) { + this.switchToEditing(); + } else { + this.onClose(); + } }).bounds(width / 2 + BG_WIDTH / 2 - (BG_WIDTH / 2 - 3), height / 2 + BG_HEIGHT / 2 + offset, BG_WIDTH / 2 - 3, 20).build(); + int titleTextWidth = this.font.width(Component.translatable("gui.glowcase.note.title")); + this.titleEditBox = new GlowcaseEditBox( + this.font, + this.glowcaseEditBox.getX() + titleTextWidth, + this.glowcaseEditBox.getY() + 9 * 6, + this.glowcaseEditBox.getWidth() - titleTextWidth, 10, Component.empty() + ); + this.titleEditBox.setResponder(value -> { + this.title = value; + this.signAndFinalizeButton.active = !this.title.isBlank(); + }); + this.titleEditBox.setBordered(false); + this.titleEditBox.setTextColor(NoteTextColorResource.TXT_COLOR); + this.titleEditBox.setTextShadow(false); + + int authorTextWidth = this.font.width(Component.translatable("gui.glowcase.note.author")); + this.authorEditBox = new GlowcaseEditBox( + this.font, + this.glowcaseEditBox.getX() + authorTextWidth, + this.glowcaseEditBox.getY() + 9 * 7, + this.glowcaseEditBox.getWidth() - authorTextWidth, 10, Component.empty() + ); + this.authorEditBox.setResponder(value -> this.author = value); + this.authorEditBox.setBordered(false); + this.authorEditBox.setTextColor(NoteTextColorResource.TXT_COLOR); + this.authorEditBox.setTextShadow(false); + + this.addRenderableWidget(this.glowcaseEditBox); + this.addRenderableWidget(this.changeAlignment); + this.addRenderableWidget(this.doneAndCancelButton); + this.addRenderableWidget(this.signAndFinalizeButton); + this.editingWidgets = new ArrayList<>(); + this.editingWidgets.add(this.glowcaseEditBox); + this.editingWidgets.add(this.changeAlignment); + this.editingWidgets.addAll(this.formattingButtons); + + this.addRenderableWidget(this.titleEditBox); + this.addRenderableWidget(this.authorEditBox); + this.signingWidgets = List.of(this.titleEditBox, this.authorEditBox); + + this.switchToEditing(); + } - this.colorPickerWidget = ColorPickerWidget.builder(this, 216, 10).size(182, 104).build(); - this.colorPickerWidget.toggle(false); //start deactivated + public void switchToSigning() { + this.signing = true; + for (AbstractWidget editingWidget : this.editingWidgets) { + editingWidget.visible = false; + } + for (AbstractWidget signingWidget : this.signingWidgets) { + signingWidget.visible = true; + } - this.addRenderableWidget(colorPickerWidget); + this.doneAndCancelButton.setMessage(Component.translatable("gui.cancel")); + this.signAndFinalizeButton.setMessage(Component.translatable("book.finalizeButton")); + this.title = ""; + this.author = ""; + this.setFocused(this.titleEditBox); + } - addRenderableWidget(changeAlignment); - addRenderableWidget(doneButton); - addRenderableWidget(signButton); + public void switchToEditing() { + this.signing = false; + for (AbstractWidget signingWidget : this.signingWidgets) { + signingWidget.visible = false; + } + for (AbstractWidget editingWidget : this.editingWidgets) { + editingWidget.visible = true; + } - addFormattingButtons(width / 2 - BG_WIDTH / 2 + BG_WIDTH / 12 * 6 - 5 - 7, height / 2 - BG_HEIGHT / 2 - offset - 20 - 4, width / 100, 20, 2); + this.doneAndCancelButton.setMessage(Component.translatable("gui.done")); + this.signAndFinalizeButton.setMessage(Component.translatable("book.signButton")); + this.signAndFinalizeButton.active = true; + this.setFocused(this.glowcaseEditBox); } @Override public void extractRenderState(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float delta) { super.extractRenderState(graphics, mouseX, mouseY, delta); - List screen = signing ? signing_text : lines; - NoteComponent.Alignment alignment = signing ? NoteComponent.Alignment.LEFT : textAlignment; - - // Ensure no overflow is happening - graphics.enableScissor( - width / 2 - BG_WIDTH / 2 + SCREEN_X1, - height / 2 - BG_HEIGHT / 2 + SCREEN_Y1, - width / 2 + BG_WIDTH / 2 + SCREEN_X2, - height / 2 + BG_HEIGHT / 2 + SCREEN_Y2 - ); - - // Text rendering - boolean overflow = false; - for (int i = 0; i < screen.size(); i++) { - FormattedText text = screen.get(i); - if (signing && i >= 6 && i <= 7) - text = FormattedText.composite(text, Component.nullToEmpty((i == 6) ? title : author)); - - if (outOfBounds(font, text)) - text = ensureBounds(font, text); - - int line_width = font.width(text); - float x = 0; - - if (i == currentRow && !signing) { - text = Component.literal(getRawLine(currentRow)); - line_width = font.width(text); - if (outOfBounds(font, text)) { - x += width / 2f + BG_WIDTH / 2f - TXT_X_PADDING / 2f - line_width + editing_line_offset; - overflow = true; - } - } - - if (!overflow || i != currentRow) { - x += switch (alignment) { - case LEFT -> width / 2f - BG_WIDTH / 2f + TXT_X_PADDING / 2f; - case CENTER -> width / 2f - line_width / 2f; - case RIGHT -> width / 2f + BG_WIDTH / 2f - TXT_X_PADDING / 2f - line_width; - }; - } - - graphics.text(font, Language.getInstance().getVisualOrder(text), (int) x, (height / 2 - BG_HEIGHT / 2 + TXT_OFF_Y) + (font.lineHeight * i), NoteTextColorResource.TXT_COLOR, false); - - if (overflow && i == currentRow) { - //RenderSystem.enableBlend(); - for (int j = 0; j < font.lineHeight; j++) { - graphics.blit(RenderPipelines.GUI_TEXTURED, TEXTURE, - width / 2 - BG_WIDTH / 2 + SCREEN_X1, - height / 2 - BG_HEIGHT / 2 + TXT_OFF_Y + (font.lineHeight * currentRow) + j, - 0, BG_SIZE - 1, 32, 1, BG_SIZE, BG_SIZE - ); - - graphics.blit(RenderPipelines.GUI_TEXTURED, TEXTURE, - width / 2 + BG_WIDTH / 2 + SCREEN_X2 - 32, - height / 2 - BG_HEIGHT / 2 + TXT_OFF_Y + (font.lineHeight * currentRow) + j, - 0, BG_SIZE - 2, 32, 1, BG_SIZE, BG_SIZE - ); - } - - if (x < (width / 2f - BG_WIDTH / 2f + SCREEN_X1)) { - graphics.text(font, ARROW_LEFT_SYMBOL, width / 2 - BG_WIDTH / 2 + SCREEN_X1 + 1, height / 2 - BG_HEIGHT / 2 + TXT_OFF_Y + (font.lineHeight * currentRow), NoteTextColorResource.TXT_COLOR, false); - } - - if (editing_line_offset > 0) { - graphics.text(font, ARROW_RIGHT_SYMBOL, width / 2 + BG_WIDTH / 2 + SCREEN_X2 - font.width(ARROW_RIGHT_SYMBOL) - 1, height / 2 - BG_HEIGHT / 2 + TXT_OFF_Y + (font.lineHeight * currentRow), NoteTextColorResource.TXT_COLOR, false); - } - - //RenderSystem.disableBlend(); - } - } - - // Cursor / Selection - // I literally copied this from TextBlockEditScreen, we might want to abstract this further more down too - int caretStart = selectionManager.getCursorPos(); - int caretEnd = selectionManager.getSelectionPos(); - - if (caretStart >= 0) { - String line = signing - ? (currentRow == 6 ? title : author) - : getRawLine(currentRow); - - int selectionStart = Mth.clamp(Math.min(caretStart, caretEnd), 0, line.length()); - int selectionEnd = Mth.clamp(Math.max(caretStart, caretEnd), 0, line.length()); - - String preSelection = line.substring(0, Mth.clamp(line.length(), 0, selectionStart)); - int startX = minecraft.font.width(preSelection); - int caretStartY = (height / 2 - BG_HEIGHT / 2 + TXT_OFF_Y) + (font.lineHeight * currentRow); - - float push = switch (overflow ? NoteComponent.Alignment.RIGHT : alignment) { - case LEFT -> width / 2f - BG_WIDTH / 2f + TXT_X_PADDING / 2f; - case CENTER -> width / 2f - font.width(line) / 2f; - case RIGHT -> width / 2f + BG_WIDTH / 2f - TXT_X_PADDING / 2f - font.width(line); - }; - - startX += (int) push; - if (signing) - startX += font.width(screen.get(currentRow)); - - if (overflow) { - int apply = 0; - - while ((startX + editing_line_offset + apply) < (width / 2 - BG_WIDTH / 2 + SCREEN_X1 + 32)) - apply++; - while ((startX + editing_line_offset + apply) > (width / 2 + BG_WIDTH / 2 + SCREEN_X2 - 32)) - apply--; - - editing_line_offset += apply; - startX += editing_line_offset; - } - - int caretLength = 9; - if (this.ticksSinceOpened / 6 % 2 == 0) { - if (selectionStart < line.length()) { - graphics.fill(startX, caretStartY, startX + 1, caretStartY + caretLength, 0xCC000000); - } else { - graphics.text(font, "_", startX, caretStartY, NoteTextColorResource.TXT_COLOR, false); - } - } - - if (caretStart != caretEnd) { - int endX = startX + font.width(line.substring(selectionStart, selectionEnd)); - graphics.textHighlight(startX, caretStartY, endX, caretStartY + 9, false); - } + if (this.signing) { + int x = this.glowcaseEditBox.getX(); + int y = this.glowcaseEditBox.getY(); + int width = this.glowcaseEditBox.getWidth(); + int color = NoteTextColorResource.TXT_COLOR; + + graphics.textWithWordWrap(this.font, Component.translatable("gui.glowcase.note.signing"), x, y, width, color, false); + graphics.textWithWordWrap(this.font, Component.translatable("gui.glowcase.note.warning"), x, y + 18, width, color, false); + graphics.textWithWordWrap(this.font, Component.translatable("gui.glowcase.note.title"), x, y + 54, width, color, false); + graphics.textWithWordWrap(this.font, Component.translatable("gui.glowcase.note.author"), x, y + 63, width, color, false); + graphics.textWithWordWrap(this.font, Component.translatable("gui.glowcase.note.required").withStyle(ChatFormatting.RED), x, y + 81, width, color, false); } - - graphics.disableScissor(); - } - - @Override - public void tick() { - ++this.ticksSinceOpened; + this.extractColorPicker(graphics, mouseX, mouseY, delta); } @Override @@ -344,211 +245,14 @@ public void extractBackground(GuiGraphicsExtractor context, int mouseX, int mous @Override 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(); - } - result = true; - } else { - setFocused(null); - result = true; - if (keyCode == GLFW.GLFW_KEY_UP || (keyCode == GLFW.GLFW_KEY_LEFT && selectionManager.getCursorPos() <= 0 && currentRow > 0)) { - // Move cursor up - currentRow = Math.max(currentRow - 1, signing ? 6 : 0); - editing_line_offset = 0; - selectionManager.setCursorToEnd(); - } else if (keyCode == GLFW.GLFW_KEY_DOWN || (keyCode == GLFW.GLFW_KEY_RIGHT && selectionManager.getCursorPos() >= getRawLine(currentRow).length() && currentRow < NoteComponent.LINES_LIMIT - 1)) { - // Move cursor down - currentRow = Math.min(currentRow + 1, signing ? 7 : NoteComponent.LINES_LIMIT - 1); - editing_line_offset = 0; - - if (keyCode == GLFW.GLFW_KEY_DOWN) - selectionManager.setCursorToEnd(); - else - selectionManager.setCursorToStart(); - } else if (!signing && (currentRow < NoteComponent.LINES_LIMIT - 1) && (keyCode == GLFW.GLFW_KEY_ENTER || keyCode == GLFW.GLFW_KEY_KP_ENTER)) { - // Split lines (enter) - if (hasSpaceLeft()) { - int cursor = selectionManager.getCursorPos(); - if (cursor <= 0) { - lines.add(currentRow, Component.nullToEmpty("")); - currentRow++; - selectionManager.setCursorToStart(); - } else if (cursor >= getRawLine(currentRow).length()) { - lines.add(currentRow + 1, Component.nullToEmpty("")); - currentRow++; - selectionManager.setCursorToStart(); - } else { - String curLine = getRawLine(currentRow); - String newLine = curLine.substring(cursor); - curLine = curLine.substring(0, cursor); - - setRawLine(currentRow, curLine); - lines.add(currentRow + 1, Component.nullToEmpty("")); - setRawLine(currentRow + 1, newLine); - - currentRow++; - selectionManager.setCursorToStart(); - } - } - } else if (!signing && (currentRow > 0 && selectionManager.getCursorPos() <= 0) && (keyCode == GLFW.GLFW_KEY_BACKSPACE)) { - // Delete before cursor (backspace) - String curLine = getRawLine(currentRow); - String before = getRawLine(currentRow - 1); - setRawLine(currentRow - 1, before + curLine); - - lines.remove(currentRow); - lines.add(Component.nullToEmpty("")); - - currentRow--; - selectionManager.setCursorToStart(); - selectionManager.moveByChars(before.length()); - } else if (!signing && (currentRow < NoteComponent.LINES_LIMIT - 1 && selectionManager.getCursorPos() >= getRawLine(currentRow).length()) && (keyCode == GLFW.GLFW_KEY_DELETE)) { - // Delete after cursor (delete key) - String curLine = getRawLine(currentRow); - String after = getRawLine(currentRow + 1); - setRawLine(currentRow, curLine + after); - - lines.remove(currentRow + 1); - lines.add(Component.nullToEmpty("")); - } else if (signing && keyCode == GLFW.GLFW_KEY_TAB) { - // Tab - currentRow = (currentRow == 6 ? 7 : 6); - } else { - // Rest - result = selectionManager.keyPressed(event) || super.keyPressed(event); - } - } - - if (signing) - signButton.active = !title.isEmpty(); - - return result; - } - - @Override - public boolean charTyped(CharacterEvent event) { - if (!signing || (currentRow == 6 ? title : author).length() < NoteComponent.TITLE_LIMIT) { - this.selectionManager.charTyped(event); - return true; - } - return false; + if (this.keyPressedColorPicker(event)) return true; + return super.keyPressed(event); } @Override 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); - } - } - } - - 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); - - if (withinX && withinY) { - this.setFocused(null); - - double linePos = mouseY - (height / 2f - BG_HEIGHT / 2f + TXT_OFF_Y); - double totalHeight = NoteComponent.LINES_LIMIT * font.lineHeight; - - int clickedLine = Math.clamp( - (int) (NoteComponent.LINES_LIMIT / totalHeight * linePos), - 0, - NoteComponent.LINES_LIMIT - 1 - ); - if (signing) - clickedLine = Math.clamp(clickedLine, 6, 7); - - if (clickedLine == currentRow && !signing) { - // Click on current line, get more precise in-row positioning - String line = getRawLine(currentRow); - int chars = line.length(); - Component text = Component.nullToEmpty(line); - int length = font.width(text); - - int charPos = (int) mouseX; - - if (outOfBounds(font, text)) { - // Scrolling line - charPos -= (int) (width / 2f + BG_WIDTH / 2f - TXT_X_PADDING / 2f - length + editing_line_offset); - } else { - // Non-scrolling line - float offset = switch (textAlignment) { - case LEFT -> width / 2f - BG_WIDTH / 2f + TXT_X_PADDING / 2f; - case CENTER -> width / 2f - font.width(line) / 2f; - case RIGHT -> width / 2f + BG_WIDTH / 2f - TXT_X_PADDING / 2f - font.width(line); - }; - charPos -= (int) offset; - } - - // Find spot to move the cursor to - - if (charPos >= length) { - selectionManager.setCursorToEnd(); - } else if (charPos <= 0) { - selectionManager.setCursorToStart(); - } else { - // Clicking mid-text - for (int i = 1; i < chars; i++) { - String testContents = line.substring(0, i); - int sub_width = font.width(testContents); - if (charPos <= sub_width) { - selectionManager.setCursorToStart(); - selectionManager.moveByChars(i); - break; - } - } - } - } else { - // Apply new line selection - currentRow = clickedLine; - selectionManager.setCursorToEnd(); - editing_line_offset = 0; - } - - return true; - } else { - return super.mouseClicked(event, doubleClick); - } - } - - private boolean hasSpaceLeft() { - Component last = lines.getLast(); - if (last.getString().isEmpty()) { - lines.removeLast(); - return true; - } - return false; - } - - public String getRawLine(int i) { - var line = this.lines.get(i); - return extractRaw(line); - } - - public void setRawLine(int i, String string) { - var parsed = PARSER.parseComponent(string, ParserContext.of()); - - if (parsed.getString().equals(string)) { - this.lines.set(i, Component.literal(string)); - } else { - this.lines.set(i, Component.empty().append(parsed).setStyle(Style.EMPTY.withInsertion(string))); - } + if (this.mouseClickedColorPicker(event, doubleClick)) return true; + return super.mouseClicked(event, doubleClick); } public static boolean outOfBounds(Font textRenderer, T text) { @@ -564,17 +268,12 @@ public static FormattedText ensureBounds(Font textRenderer, FormattedText text) ); } - public static String extractRaw(Component text) { - if (text.getStyle() == null) { - return text.getString(); - } - - var insert = text.getStyle().getInsertion(); - - if (insert == null) { - return text.getString(); - } - return insert; + public static TextBlockEntity.TextAlignment getTextBlockAlignment(NoteComponent.Alignment noteAlignment) { + return switch (noteAlignment) { + case LEFT -> TextBlockEntity.TextAlignment.LEFT; + case RIGHT -> TextBlockEntity.TextAlignment.RIGHT; + default -> TextBlockEntity.TextAlignment.CENTER; + }; } @Override @@ -582,8 +281,8 @@ public CustomPacketPayload getUpdatePayload() { if (finalizing) { // Remove insertion for optimization as it is not needed anymore for (int i = 0; i < lines.size(); i++) { - String rawLine = getRawLine(i); - Component text = PARSER.parseComponent(rawLine, ParserContext.of()); + String rawLine = this.glowcaseEditBox.formatTextField.getRawLineFromParsed(i); + Component text = FormattableMultilineTextField.PARSER.parseComponent(rawLine, ParserContext.of()); lines.set(i, text); } } @@ -597,17 +296,12 @@ 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 - TextFieldHelper getSelectionManager() { - return selectionManager; + GlowcaseMultilineEditBox getGlowcaseMultilineEditBox() { + return this.glowcaseEditBox; } } 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/ParticleDisplayEditScreen.java b/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/ParticleDisplayEditScreen.java index 0cb6370b..10a6ea27 100644 --- a/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/ParticleDisplayEditScreen.java +++ b/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/ParticleDisplayEditScreen.java @@ -7,7 +7,7 @@ import dev.hephaestus.glowcase.block.entity.ParticleDisplayBlockEntity; 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.Vec3FieldsWidget; +import dev.hephaestus.glowcase.client.gui.widget.ingame.number.Vec3FieldsWidget; import dev.hephaestus.glowcase.packet.C2SEditParticleDisplayBlock; import dev.hephaestus.glowcase.util.DeviatedInteger; import dev.hephaestus.glowcase.util.DeviatedVec3d; @@ -95,42 +95,30 @@ protected void init() { // endregion // region Position - positionMean = new Vec3FieldsWidget( - width / 10, height / 2 - 60, - (4 * width / 10) - 6, 20, - this.minecraft, - blockEntity.position.mean() - ); - + positionMean = Vec3FieldsWidget.builder(this.font, blockEntity.position.mean()) + .setPos(width / 10, height / 2 - 60) + .setWidth((4 * width / 10) - 6) + .build(); this.addRenderableWidget(positionMean); - positionStdDev = new Vec3FieldsWidget( - width / 10 + (4 * width / 10) + 6, height / 2 - 60, - (4 * width / 10) - 6, 20, - this.minecraft, - blockEntity.position.stdDev() - ); - + this.positionStdDev = Vec3FieldsWidget.builder(this.font, blockEntity.position.stdDev()) + .setPos(width / 10 + (4 * width / 10) + 6, height / 2 - 60) + .setWidth((4 * width / 10) - 6) + .build(); this.addRenderableWidget(positionStdDev); // endregion // region Velocity - velocityMean = new Vec3FieldsWidget( - width / 10, (height / 2) - 10, - (4 * width / 10) - 6, 20, - this.minecraft, - blockEntity.velocity.mean() - ); - + this.velocityMean = Vec3FieldsWidget.builder(this.font, blockEntity.velocity.mean()) + .setPos(width / 10, (height / 2) - 10) + .setWidth((4 * width / 10) - 6) + .build(); this.addRenderableWidget(velocityMean); - velocityStdDev = new Vec3FieldsWidget( - width / 10 + (4 * width / 10) + 6, (height / 2) - 10, - (4 * width / 10) - 6, 20, - this.minecraft, - blockEntity.velocity.stdDev() - ); - + this.velocityStdDev = Vec3FieldsWidget.builder(this.font, blockEntity.velocity.stdDev()) + .setPos(width / 10 + (4 * width / 10) + 6, (height / 2) - 10) + .setWidth((4 * width / 10) - 6) + .build(); this.addRenderableWidget(velocityStdDev); // endregion 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..6ab834d6 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,355 +3,134 @@ 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.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.client.gui.widget.ingame.text.GlowcaseMultilineEditBox; import dev.hephaestus.glowcase.packet.C2SEditPopupBlock; -import dev.hephaestus.glowcase.util.TextUtils; -import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphicsExtractor; import net.minecraft.client.gui.components.Button; -import net.minecraft.client.gui.components.EditBox; -import net.minecraft.client.gui.font.TextFieldHelper; -import net.minecraft.client.input.CharacterEvent; 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 { - private TextFieldHelper selectionManager; - private int currentRow; - private long ticksSinceOpened = 0; - private EditBox titleEntryWidget; +public class PopupBlockEditScreen extends TextEditorScreen implements BlockEditor { + private final PopupBlockEntity popupBlockEntity; + + private GlowcaseMultilineEditBox glowcaseEditBox; + private GlowcaseEditBox titleEntryWidget; private Button changeAlignment; - private EditBox colorEntryWidget; + private HexColorEditBox colorEntryWidget; + + private ColorPickerWidget colorPickerWidget; public PopupBlockEditScreen(PopupBlockEntity blockEntity) { - super(blockEntity); + this.popupBlockEntity = blockEntity; } @Override public void init() { super.init(); - int innerPadding = width / 100; + int innerPadding = this.width / 100; + int middle = this.width / 2; - this.selectionManager = new TextFieldHelper( - () -> this.blockEntity.getRawLine(this.currentRow), - (string) -> { - blockEntity.setRawLine(this.currentRow, string); - this.blockEntity.renderDirty = true; - }, - TextFieldHelper.createClipboardGetter(this.minecraft), - TextFieldHelper.createClipboardSetter(this.minecraft), - (string) -> true); + this.glowcaseEditBox = GlowcaseMultilineEditBox.builder( + this.font, this.popupBlockEntity.lines, + 2, 40 + innerPadding, this.width - 4, this.height - 40 - innerPadding, + parsedLines -> { + this.popupBlockEntity.lines = parsedLines; + this.popupBlockEntity.renderDirty = true; + } + ).build(); + this.glowcaseEditBox.updateSettings(this.popupBlockEntity.color, true, this.popupBlockEntity.textAlignment); - this.titleEntryWidget = new EditBox(this.minecraft.font, width / 10, 0, 8 * width / 10, 20, Component.empty()); + this.titleEntryWidget = new GlowcaseEditBox(this.font, this.width / 10, 2, 8 * this.width / 10, 20, Component.empty()); this.titleEntryWidget.setMaxLength(HyperlinkBlockEntity.TITLE_MAX_LENGTH); - this.titleEntryWidget.setValue(this.blockEntity.title); - this.titleEntryWidget.setHint(TextUtils.placeholder("gui.glowcase.title")); - this.titleEntryWidget.setResponder(string -> { - this.blockEntity.title = this.titleEntryWidget.getValue(); - this.blockEntity.renderDirty = true; + this.titleEntryWidget.setValue(this.popupBlockEntity.title); + this.titleEntryWidget.setHint(Component.translatable("gui.glowcase.title")); + this.titleEntryWidget.setResponder(value -> { + this.popupBlockEntity.title = value; + this.popupBlockEntity.renderDirty = true; }); + // startX = middle - (160 + 2 + 50 + 4 + (20 + 2) * 6) / 2 = middle - 174; this.changeAlignment = Button.builder(Component.translatableEscape( "gui.glowcase.alignment", - this.blockEntity.textAlignment + this.popupBlockEntity.textAlignment ), action -> { - switch (blockEntity.textAlignment) { - case LEFT -> blockEntity.textAlignment = TextBlockEntity.TextAlignment.CENTER; + switch (popupBlockEntity.textAlignment) { + case LEFT -> popupBlockEntity.textAlignment = TextBlockEntity.TextAlignment.CENTER; case CENTER, CENTER_LEFT, CENTER_RIGHT -> - blockEntity.textAlignment = TextBlockEntity.TextAlignment.RIGHT; - case RIGHT -> blockEntity.textAlignment = TextBlockEntity.TextAlignment.LEFT; + popupBlockEntity.textAlignment = TextBlockEntity.TextAlignment.RIGHT; + case RIGHT -> popupBlockEntity.textAlignment = TextBlockEntity.TextAlignment.LEFT; } - this.blockEntity.renderDirty = true; + this.popupBlockEntity.renderDirty = true; this.changeAlignment.setMessage(Component.translatableEscape( "gui.glowcase.alignment", - this.blockEntity.textAlignment + this.popupBlockEntity.textAlignment )); - }).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.glowcaseEditBox.setTextAlignment(this.popupBlockEntity.textAlignment); + }).bounds(middle - 174, 20 + innerPadding, 160, 20).build(); + + this.colorPickerWidget = createColorPickerWidget(); + this.colorEntryWidget = HexColorEditBox.builder(this.minecraft.font, middle - 174 + 162, 20 + innerPadding, + () -> this.popupBlockEntity.color, color -> { + this.popupBlockEntity.color = color; + this.popupBlockEntity.renderDirty = true; + this.glowcaseEditBox.setTextColor(this.popupBlockEntity.color); + }) + .setWidth(50) + .setColorPickerWidget(this.colorPickerWidget) + .build(); + + this.initFormattingButtons(middle - 174 + 162 + 54, 20 + innerPadding, 0); + this.addRenderableWidget(this.glowcaseEditBox); this.addRenderableWidget(this.titleEntryWidget); this.addRenderableWidget(this.changeAlignment); this.addRenderableWidget(this.colorEntryWidget); } @Override - public void tick() { - ++this.ticksSinceOpened; + 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 @Nullable CustomPacketPayload getUpdatePayload() { - return C2SEditPopupBlock.of(blockEntity); - } - - private void checkRow() { - final int size = this.blockEntity.lines.size(); - if (this.currentRow >= size) { - this.currentRow = size - 1; - } + public boolean keyPressed(KeyEvent event) { + if (this.keyPressedColorPicker(event)) return true; + return super.keyPressed(event); } @Override - public void extractRenderState(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float delta) { - super.extractRenderState(graphics, mouseX, mouseY, delta); - - graphics.pose().pushMatrix(); - graphics.pose().translate(0, 40 + 2 * this.width / 100F); - for (int i = 0; i < this.blockEntity.lines.size(); ++i) { - var text = this.currentRow == i ? - Component.literal(this.blockEntity.getRawLine(i)) : - this.blockEntity.lines.get(i); - - int lineWidth = this.font.width(text); - switch (this.blockEntity.textAlignment) { - case LEFT -> graphics.text(minecraft.font, text, this.width / 10, i * 12, this.blockEntity.color); - case CENTER, CENTER_LEFT, CENTER_RIGHT -> - graphics.text(minecraft.font, text, this.width / 2 - lineWidth / 2, i * 12, this.blockEntity.color); - case RIGHT -> graphics.text(minecraft.font, - text, - this.width - this.width / 10 - lineWidth, - i * 12, - this.blockEntity.color - ); - } - } - - int caretStart = this.selectionManager.getCursorPos(); - int caretEnd = this.selectionManager.getSelectionPos(); - - if (caretStart >= 0) { - this.checkRow(); - String line = this.blockEntity.getRawLine(this.currentRow); - int selectionStart = Mth.clamp(Math.min(caretStart, caretEnd), 0, line.length()); - int selectionEnd = Mth.clamp(Math.max(caretStart, caretEnd), 0, line.length()); - - String preSelection = line.substring(0, Mth.clamp(line.length(), 0, selectionStart)); - int startX = this.minecraft.font.width(preSelection); - - float push = switch (this.blockEntity.textAlignment) { - case LEFT -> this.width / 10F; - case CENTER, CENTER_LEFT, CENTER_RIGHT -> this.width / 2F - this.font.width(line) / 2F; - case RIGHT -> this.width - this.width / 10F - this.font.width(line); - }; - - startX += (int) push; - - - int caretStartY = this.currentRow * 12; - if (this.ticksSinceOpened / 6 % 2 == 0 && !this.titleEntryWidget.canConsumeInput() && !this.colorEntryWidget.canConsumeInput()) { - if (selectionStart < line.length()) { - graphics.fill(startX, caretStartY, startX + 1, caretStartY + 9, 0xCCFFFFFF); - } else { - graphics.text(minecraft.font, "_", startX, this.currentRow * 12, 0xFFFFFFFF, false); - } - } - - if (caretStart != caretEnd) { - int endX = startX + this.minecraft.font.width(line.substring(selectionStart, selectionEnd)); - graphics.textHighlight(startX, caretStartY, endX, caretStartY + 9, false); - } - } - - graphics.pose().popMatrix(); + public boolean mouseClicked(MouseButtonEvent event, boolean doubleClick) { + if (this.mouseClickedColorPicker(event, doubleClick)) return true; + return super.mouseClicked(event, doubleClick); } @Override - public boolean charTyped(CharacterEvent event) { - if (this.titleEntryWidget.canConsumeInput()) { - return this.titleEntryWidget.charTyped(event); - } else if (this.colorEntryWidget.canConsumeInput()) { - return this.colorEntryWidget.charTyped(event); - } else { - this.selectionManager.charTyped(event); - return true; - } + public @Nullable CustomPacketPayload getUpdatePayload() { + return C2SEditPopupBlock.of(popupBlockEntity); } @Override - public boolean keyPressed(KeyEvent event) { - int keyCode = event.key(); - if (this.titleEntryWidget.canConsumeInput()) { - if (keyCode == GLFW.GLFW_KEY_ESCAPE) { - this.onClose(); - return true; - } else { - return this.titleEntryWidget.keyPressed(event); - } - } else if (this.colorEntryWidget.canConsumeInput()) { - if (keyCode == GLFW.GLFW_KEY_ESCAPE) { - this.onClose(); - return true; - } else { - return this.colorEntryWidget.keyPressed(event); - } - } else { - setFocused(null); - if (keyCode == GLFW.GLFW_KEY_ENTER || keyCode == GLFW.GLFW_KEY_KP_ENTER) { - this.blockEntity.addRawLine( - this.currentRow + 1, - this.blockEntity.getRawLine(this.currentRow).substring( - Mth.clamp( - this.selectionManager.getCursorPos(), - 0, - this.blockEntity.getRawLine(this.currentRow).length() - ) - )); - this.blockEntity.setRawLine( - this.currentRow, - this.blockEntity.getRawLine(this.currentRow) - .substring( - 0, - Mth.clamp( - this.selectionManager.getCursorPos(), - 0, - this.blockEntity.getRawLine(this.currentRow).length() - ) - )); - this.blockEntity.renderDirty = true; - ++this.currentRow; - this.selectionManager.setCursorToStart(); - return true; - } else if (keyCode == GLFW.GLFW_KEY_UP) { - this.currentRow = Math.max(this.currentRow - 1, 0); - this.selectionManager.setCursorToEnd(); - return true; - } else if (keyCode == GLFW.GLFW_KEY_DOWN) { - this.currentRow = Math.min(this.currentRow + 1, (this.blockEntity.lines.size() - 1)); - this.selectionManager.setCursorToEnd(); - return true; - } else if (keyCode == GLFW.GLFW_KEY_BACKSPACE && this.currentRow > 0 && this.blockEntity.lines.size() > 1 && this.selectionManager.getCursorPos() == 0 && this.selectionManager.getSelectionPos() == this.selectionManager.getCursorPos()) { - --this.currentRow; - this.selectionManager.setCursorToEnd(); - deleteLine(); - return true; - } else if (keyCode == GLFW.GLFW_KEY_DELETE && this.currentRow < this.blockEntity.lines.size() - 1 && this.selectionManager.getSelectionPos() == this.blockEntity.getRawLine( - this.currentRow).length()) { - deleteLine(); - return true; - } else { - try { - boolean val = this.selectionManager.keyPressed(event) || super.keyPressed(event); - int selectionOffset = this.blockEntity.getRawLine(this.currentRow) - .length() - this.selectionManager.getCursorPos(); - - // Find line feed characters and create proper newlines - for (int i = 0; i < this.blockEntity.lines.size(); ++i) { - int lineFeedIndex = this.blockEntity.getRawLine(i).indexOf("\n"); - - if (lineFeedIndex >= 0) { - this.blockEntity.addRawLine( - i + 1, - this.blockEntity.getRawLine(i).substring( - Mth.clamp(lineFeedIndex + 1, 0, this.blockEntity.getRawLine(i).length()) - )); - this.blockEntity.setRawLine( - i, - this.blockEntity.getRawLine(i) - .substring(0, Mth.clamp(lineFeedIndex, 0, this.blockEntity.getRawLine(i).length()) - )); - this.blockEntity.renderDirty = true; - ++this.currentRow; - this.selectionManager.setCursorToEnd(); - this.selectionManager.moveByChars(-selectionOffset); - } - } - return val; - } catch (StringIndexOutOfBoundsException e) { - e.printStackTrace(); - Minecraft.getInstance().setScreen(null); - return false; - } - } - } + public PopupBlockEntity getBlockEntity() { + return this.popupBlockEntity; } - private void deleteLine() { - this.blockEntity.setRawLine( - this.currentRow, - this.blockEntity.getRawLine(this.currentRow) + this.blockEntity.getRawLine(this.currentRow + 1) - ); - - this.blockEntity.lines.remove(this.currentRow + 1); - this.blockEntity.renderDirty = true; + @Override + public ColorPickerWidget getColorPickerWidget() { + return this.colorPickerWidget; } @Override - 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.titleEntryWidget.mouseClicked(event, doubleClick)) { - this.titleEntryWidget.setFocused(false); - } - if (!this.colorEntryWidget.mouseClicked(event, doubleClick)) { - this.colorEntryWidget.setFocused(false); - } - if (mouseY > topOffset) { - this.currentRow = Mth.clamp((int) (mouseY - topOffset) / 12, 0, this.blockEntity.lines.size() - 1); - this.setFocused(null); - String baseContents = this.blockEntity.getRawLine(currentRow); - int baseContentsWidth = this.font.width(baseContents); - int contentsStart; - int contentsEnd; - switch (this.blockEntity.textAlignment) { - case LEFT -> { - contentsStart = this.width / 10; - contentsEnd = contentsStart + baseContentsWidth; - } - case CENTER, CENTER_LEFT, CENTER_RIGHT -> { - int midpoint = this.width / 2; - int textMidpoint = baseContentsWidth / 2; - contentsStart = midpoint - textMidpoint; - contentsEnd = midpoint + textMidpoint; - } - case RIGHT -> { - contentsEnd = this.width - this.width / 10; - contentsStart = contentsEnd - baseContentsWidth; - } - //even though this is exhaustive, javac won't treat contentsStart and contentsEnd as initialized - //why? who knows! just throw bc this should be impossible - default -> throw new IllegalStateException(":HOW:"); - } - - if (mouseX <= contentsStart) { - this.selectionManager.setCursorToStart(); - } else if (mouseX >= contentsEnd) { - this.selectionManager.setCursorToEnd(); - } else { - int lastWidth = 0; - for (int i = 1; i < baseContents.length(); i++) { - String testContents = baseContents.substring(0, i); - int width = this.font.width(testContents); - int midpointWidth = (width + lastWidth) / 2; - if (mouseX < contentsStart + midpointWidth) { - this.selectionManager.setCursorPos(i - 1, false); - break; - } else if (mouseX <= contentsStart + width) { - this.selectionManager.setCursorPos(i, false); - break; - } - lastWidth = width; - } - } - return true; - } else { - return super.mouseClicked(event, doubleClick); - } + GlowcaseMultilineEditBox getGlowcaseMultilineEditBox() { + return this.glowcaseEditBox; } } diff --git a/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/PopupBlockViewScreen.java b/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/PopupBlockViewScreen.java index eea15216..1eb367a3 100644 --- a/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/PopupBlockViewScreen.java +++ b/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/PopupBlockViewScreen.java @@ -1,33 +1,43 @@ package dev.hephaestus.glowcase.client.gui.screen.ingame; import dev.hephaestus.glowcase.block.entity.PopupBlockEntity; +import dev.hephaestus.glowcase.client.gui.widget.ingame.text.MultilineTextViewArea; import net.minecraft.client.gui.GuiGraphicsExtractor; -//TODO: multi-character selection at some point? it may be a bit complex but it'd be nice public class PopupBlockViewScreen extends GlowcaseScreen { private final PopupBlockEntity popupBlockEntity; + private MultilineTextViewArea textViewArea; public PopupBlockViewScreen(PopupBlockEntity popupBlockEntity) { this.popupBlockEntity = popupBlockEntity; } + @Override + protected void init() { + int innerPadding = this.width / 100; + this.textViewArea = new MultilineTextViewArea( + this.font, this.popupBlockEntity.lines, + 2, 40 + innerPadding, + this.width - 4, this.height - 40 - innerPadding, + this.popupBlockEntity.color, this.popupBlockEntity.textAlignment + ); + + this.addRenderableWidget(this.textViewArea); + } + @Override public void extractRenderState(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float delta) { super.extractRenderState(graphics, mouseX, mouseY, delta); - graphics.pose().pushMatrix(); - graphics.pose().translate(0, 40 + 2 * this.width / 100F); - for (int i = 0; i < this.popupBlockEntity.lines.size(); ++i) { - var text = this.popupBlockEntity.lines.get(i); - - int lineWidth = this.font.width(text); - switch (this.popupBlockEntity.textAlignment) { - case LEFT -> graphics.text(minecraft.font, text, this.width / 10, i * 12, this.popupBlockEntity.color); - case CENTER -> graphics.text(minecraft.font, text, this.width / 2 - lineWidth / 2, i * 12, this.popupBlockEntity.color); - case RIGHT -> graphics.text(minecraft.font, text, this.width - this.width / 10 - lineWidth, i * 12, this.popupBlockEntity.color); - } - } + if (!this.popupBlockEntity.viewScreenTitle || this.popupBlockEntity.title.isBlank()) return; + int titleWidth = this.font.width(this.popupBlockEntity.title); + graphics.text( + this.font, this.popupBlockEntity.title, + this.width / 2 - titleWidth / 2, 16, + this.popupBlockEntity.color + ); - graphics.pose().popMatrix(); + int breakWidth = Math.min(titleWidth + 16, this.width / 2 - 16); + graphics.fill(this.width / 2 - breakWidth, 27, this.width / 2 + breakWidth, 28, this.popupBlockEntity.color); } } diff --git a/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/SoundPlayerBlockEditScreen.java b/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/SoundPlayerBlockEditScreen.java index 9828a083..ac0ceef0 100644 --- a/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/SoundPlayerBlockEditScreen.java +++ b/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/SoundPlayerBlockEditScreen.java @@ -3,7 +3,7 @@ import dev.hephaestus.glowcase.block.entity.SoundPlayerBlockEntity; 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.Vec3FieldsWidget; +import dev.hephaestus.glowcase.client.gui.widget.ingame.number.Vec3FieldsWidget; import dev.hephaestus.glowcase.packet.C2SEditSoundBlock; import dev.hephaestus.glowcase.util.InputFilters; import dev.hephaestus.glowcase.util.ParseUtil; @@ -133,12 +133,10 @@ protected void init() { }).bounds(width / 10, height / 2 + 90, (4 * width / 10) - 6, 20).build(); this.addRenderableWidget(this.relativeButton); - this.offset = new Vec3FieldsWidget( - width / 10 + (4 * width / 10) + 6, height / 2 + 90, - (4 * width / 10) - 6, 20, - this.minecraft, - blockEntity.offset - ); + this.offset = Vec3FieldsWidget.builder(this.font, blockEntity.offset) + .setPos(width / 10 + (4 * width / 10) + 6, height / 2 + 90) + .setWidth((4 * width / 10) - 6) + .build(); this.addRenderableWidget(this.offset); validSounds = BuiltInRegistries.SOUND_EVENT.stream() 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/TagFormatIncludedScreen.java b/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/TagFormatIncludedScreen.java new file mode 100644 index 00000000..9bf4c61a --- /dev/null +++ b/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/TagFormatIncludedScreen.java @@ -0,0 +1,65 @@ +package dev.hephaestus.glowcase.client.gui.screen.ingame; + +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; + +import java.util.Arrays; +import java.util.Comparator; + +/** + * Main interface for any Screen wishing for niceties related to QuickText tag formatting.

+ * Includes some default methods for various tag insertions, and also prevents the narrator from being enabled when pressing "ctrl+b" + * @see TextEditorScreen + * @see dev.hephaestus.glowcase.client.gui.widget.ingame.text.GlowcaseMultilineEditBox#insertTag(String) + * @see dev.hephaestus.glowcase.mixin.client.KeyboardHandlerMixin + * @author Superkat32 + */ +public interface TagFormatIncludedScreen { + + void insertTag(String tagName); + + default void insertTextTag(TextTag tag) { + this.insertTextTag(tag, true); + } + + default void insertTextTag(TextTag tag, boolean findShortestAlias) { + if (tag == null) return; + + String tagName = tag.name(); + if (findShortestAlias && tag.aliases().length > 1) { // Find an alias with the least amount of characters + tagName = Arrays.stream(tag.aliases()).min(Comparator.comparing(String::length)).get(); + } + this.insertTag(tagName); + } + + default void insertFormattingTag(ChatFormatting formatting) { + this.insertTextTag(TagRegistry.SAFE.getTag(formatting.getName()), false); + } + + default void insertColorHexTag(int color) { + String hex = ColorUtil.toHex(color); + this.insertTag(hex); + } + + default void insertBoldTag() { + this.insertTextTag(TagRegistry.SAFE.getTag("bold")); + } + + default void insertItalicTag() { + this.insertTextTag(TagRegistry.SAFE.getTag("italic")); + } + + default void insertStrikethroughTag() { + this.insertTextTag(TagRegistry.SAFE.getTag("strikethrough")); + } + + default void insertUnderlineTag() { + this.insertTextTag(TagRegistry.SAFE.getTag("underline")); + } + + default void insertObfuscatedTag() { + this.insertTextTag(TagRegistry.SAFE.getTag("obfuscated")); + } +} 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..a3ebc3b6 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,50 +1,60 @@ 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.AnchorPositionGridWidget; +import dev.hephaestus.glowcase.client.gui.widget.ingame.IconButtonWidget; +import dev.hephaestus.glowcase.client.gui.widget.ingame.SuggestionListWidget; +import dev.hephaestus.glowcase.client.gui.widget.ingame.number.Vec3FieldsWidget; +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.client.gui.widget.ingame.slider.EditableSliderWidget; +import dev.hephaestus.glowcase.client.gui.widget.ingame.tab.GlowcaseTab; +import dev.hephaestus.glowcase.client.gui.widget.ingame.tab.GlowcaseTabNavBar; +import dev.hephaestus.glowcase.client.gui.widget.ingame.text.GlowcaseMultilineEditBox; import dev.hephaestus.glowcase.packet.C2SEditTextBlock; -import dev.hephaestus.glowcase.util.InputFilters; -import dev.hephaestus.glowcase.util.ParseUtil; -import eu.pb4.placeholders.api.parsers.tag.TagRegistry; import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.Font; import net.minecraft.client.gui.GuiGraphicsExtractor; -import net.minecraft.client.gui.components.AbstractSliderButton; +import net.minecraft.client.gui.components.AbstractWidget; import net.minecraft.client.gui.components.Button; -import net.minecraft.client.gui.components.EditBox; +import net.minecraft.client.gui.components.CycleButton; import net.minecraft.client.gui.components.Tooltip; -import net.minecraft.client.gui.components.events.GuiEventListener; -import net.minecraft.client.gui.font.TextFieldHelper; -import net.minecraft.client.input.CharacterEvent; +import net.minecraft.client.gui.components.Whence; +import net.minecraft.client.gui.layouts.FrameLayout; +import net.minecraft.client.gui.layouts.LayoutSettings; +import net.minecraft.client.gui.layouts.LinearLayout; import net.minecraft.client.input.KeyEvent; import net.minecraft.client.input.MouseButtonEvent; import net.minecraft.network.chat.Component; import net.minecraft.network.protocol.common.custom.CustomPacketPayload; -import net.minecraft.util.Mth; +import net.minecraft.resources.FileToIdConverter; +import net.minecraft.resources.Identifier; +import net.minecraft.server.packs.resources.Resource; +import net.minecraft.server.packs.resources.ResourceManager; import org.jetbrains.annotations.Nullable; -import org.lwjgl.glfw.GLFW; -import java.awt.*; +import java.util.ArrayList; +import java.util.Iterator; import java.util.List; -import java.util.function.Consumer; +import java.util.Map; -//TODO: multi-character selection at some point? it may be a bit complex but it'd be nice public class TextBlockEditScreen extends TextEditorScreen implements BlockEditor { - private static final int innerPadding = 4; - private static final int editorOffset = 20; private final TextBlockEntity textBlockEntity; - private List textWidgets; - private List colorListeners; + private GlowcaseMultilineEditBox glowcaseEditBox; + private HexColorEditBox colorEntryWidget; + private HexColorEditBox backgroundColorEntryWidget; - private TextFieldHelper selectionManager; - private EditBox colorEntryWidget; - private int currentRow; - private long ticksSinceOpened = 0; private ColorPickerWidget colorPickerWidget; - private Color colorEntryPreColorPicker; //used for color picker cancel button + private SuggestionListWidget fontSuggestionWidget; + + private Button zFrontButton; + private Button zCenterButton; + private Button zBackButton; + private IconButtonWidget justifyLeftButton; + private IconButtonWidget justifyCenterButton; + private IconButtonWidget justifyRightButton; + private Button insertFontButton; public TextBlockEditScreen(TextBlockEntity textBlockEntity) { this.textBlockEntity = textBlockEntity; @@ -54,478 +64,370 @@ public TextBlockEditScreen(TextBlockEntity textBlockEntity) { public void init() { super.init(); - this.selectionManager = new TextFieldHelper( - () -> this.textBlockEntity.getRawLine(this.currentRow), - (string) -> { - textBlockEntity.setRawLine(this.currentRow, string); + this.glowcaseEditBox = GlowcaseMultilineEditBox.builder( + this.font, this.textBlockEntity.lines, 2, 25, this.width - 4, this.height - 28, + parsedLines -> { + this.textBlockEntity.lines = new ArrayList<>(parsedLines); this.textBlockEntity.rebake(true); - }, - TextFieldHelper.createClipboardGetter(this.minecraft), - TextFieldHelper.createClipboardSetter(this.minecraft), - (_) -> true); - - int middle = width / 2; - - var scaleSlider = new TextScale.SliderWidget(textBlockEntity, middle - 203, innerPadding, 113, 20); - addFormattingButtons(middle - 90 + 6, 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.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.textBlockEntity.rebake(true); - }); - }); + } + ).build(); + this.glowcaseEditBox.updateSettings(this.textBlockEntity.color, this.textBlockEntity.shadow, this.textBlockEntity.textAlignment); + this.glowcaseEditBox.textField.seekCursor(Whence.ABSOLUTE, 0); + this.addRenderableWidget(this.glowcaseEditBox); + + this.colorPickerWidget = this.createColorPickerWidget(); + + List editTabWidgets = this.initEditTabWidgets(); + List viewTabWidgets = this.initViewTabWidgets(); + + GlowcaseTabNavBar tabNavBar = GlowcaseTabNavBar.builder( + this.width, height -> { + this.glowcaseEditBox.setY(height + 3); + }) + .setY(2) + .setWidgetAreaPadding(2) + .addTabs( + new GlowcaseTab( + Component.literal("Edit"), 20, + editTabWidgets + ), + new GlowcaseTab( + Component.literal("View"), 42, + viewTabWidgets + ) + ).build(); + this.addRenderableWidget(tabNavBar); + } - this.colorPickerWidget = ColorPickerWidget.builder(this, 216, 10).size(182, 104).build(); - this.colorPickerWidget.toggle(false); //start deactivated + public List initEditTabWidgets() { + // nav bar y padding (2) + tab button height (20) + widget area padding (1) = 23; + int firstRowY = 23; - 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) + EditableSliderWidget scaleSlider = EditableSliderWidget.builder( + this.font, this.textBlockEntity.scale, 0.125f, 16, + aFloat -> Component.translatable("gui.glowcase.scale_value", aFloat), + aFloat -> { + this.textBlockEntity.scale = aFloat; + this.textBlockEntity.rebake(true); + }) + .setStep(0.125f) + .setWidth(113) .build(); + scaleSlider.setTooltip(Tooltip.create(Component.translatable("gui.glowcase.text_scale_slider"))); - this.textWidgets = List.of( - this.colorEntryWidget - ); + this.colorEntryWidget = HexColorEditBox.builder(this.minecraft.font, 0, 0, + () -> this.textBlockEntity.color, color -> { + this.textBlockEntity.color = color; + this.textBlockEntity.rebake(true); + this.glowcaseEditBox.setTextColor(color); + } + ) + .setEditableAlpha(true) + .setColorPickerWidget(this.colorPickerWidget) + .build(); + this.colorEntryWidget.setTooltip(Tooltip.create(Component.translatable("gui.glowcase.text_color"))); - this.colorListeners = List.of( - this.colorEntryWidget - ); + this.backgroundColorEntryWidget = HexColorEditBox.builder(this.minecraft.font, 0, 0, + () -> this.textBlockEntity.backgroundColor, color -> { + this.textBlockEntity.backgroundColor = color; + this.textBlockEntity.rebake(true); + } + ) + .setEditableAlpha(true) + .setColorPickerWidget(this.colorPickerWidget) + .build(); + this.backgroundColorEntryWidget.setTooltip(Tooltip.create(Component.translatable("gui.glowcase.background_color_argb"))); - this.addRenderableWidget(colorPickerWidget); - this.addRenderableWidget(this.colorEntryWidget); this.addRenderableWidget(scaleSlider); - this.addRenderableWidget(moreOptionsButton); - } - - @Override - public void tick() { - ++this.ticksSinceOpened; - } - - @Override - public TextBlockEntity getBlockEntity() { - return this.textBlockEntity; - } - - @Override - public @Nullable CustomPacketPayload getUpdatePayload() { - return C2SEditTextBlock.of(textBlockEntity); - } - - private boolean isFocusedTextActive() { - final GuiEventListener focused = this.getFocused(); - if (focused instanceof EditBox text) { - return text.canConsumeInput(); - } - return false; - } - - private void checkRow() { - final int size = this.textBlockEntity.lines.size(); - if (this.currentRow >= size) { - this.currentRow = size - 1; - } - } - - @Override - public void extractRenderState(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float delta) { - super.extractRenderState(graphics, mouseX, mouseY, delta); - - graphics.pose().pushMatrix(); - graphics.pose().translate(0, editorOffset + 2 * this.width / 100F); - for (int i = 0; i < this.textBlockEntity.lines.size(); ++i) { - var text = this.currentRow == i ? Component.literal(this.textBlockEntity.getRawLine(i)) : this.textBlockEntity.lines.get(i); - - int lineWidth = this.font.width(text); - switch (this.textBlockEntity.textAlignment) { - case LEFT -> graphics.text(minecraft.font, text, this.width / 10, i * 12, this.textBlockEntity.color); - case CENTER, CENTER_LEFT, CENTER_RIGHT -> - graphics.text(minecraft.font, text, this.width / 2 - lineWidth / 2, i * 12, this.textBlockEntity.color); - case RIGHT -> - graphics.text(minecraft.font, text, this.width - this.width / 10 - lineWidth, i * 12, this.textBlockEntity.color); - } - } - - int caretStart = this.selectionManager.getCursorPos(); - int caretEnd = this.selectionManager.getSelectionPos(); - - if (caretStart >= 0) { - this.checkRow(); - String line = this.textBlockEntity.getRawLine(this.currentRow); - int selectionStart = Mth.clamp(Math.min(caretStart, caretEnd), 0, line.length()); - int selectionEnd = Mth.clamp(Math.max(caretStart, caretEnd), 0, line.length()); - - String preSelection = line.substring(0, Mth.clamp(line.length(), 0, selectionStart)); - int startX = this.minecraft.font.width(preSelection); - - float push = switch (this.textBlockEntity.textAlignment) { - case LEFT -> this.width / 10F; - case CENTER, CENTER_LEFT, CENTER_RIGHT -> this.width / 2F - this.font.width(line) / 2F; - case RIGHT -> this.width - this.width / 10F - this.font.width(line); - }; - - startX += (int) push; - - - int caretStartY = this.currentRow * 12; - if (this.ticksSinceOpened / 6 % 2 == 0 && !this.isFocusedTextActive()) { - if (selectionStart < line.length()) { - graphics.fill(startX, caretStartY, startX + 1, caretStartY + 9, 0xCCFFFFFF); - } else { - graphics.text(minecraft.font, "_", startX, this.currentRow * 12, 0xFFFFFFFF, false); - } - } + this.initFormattingButtons(0, 0, 0); + this.addRenderableWidget(this.colorEntryWidget); + this.addRenderableWidget(this.backgroundColorEntryWidget); - if (caretStart != caretEnd) { - int endX = startX + this.minecraft.font.width(line.substring(selectionStart, selectionEnd)); - graphics.textHighlight(startX, caretStartY, endX, caretStartY + 9, false); - } + LinearLayout editTabLayout = LinearLayout.horizontal().spacing(2); + editTabLayout.addChild(scaleSlider, LayoutSettings.defaults().paddingRight(6)); + for (Button formattingButton : this.formattingButtons) { + editTabLayout.addChild(formattingButton); } + editTabLayout.addChild(this.colorEntryWidget, LayoutSettings.defaults().paddingLeft(6)); + editTabLayout.addChild(this.backgroundColorEntryWidget, LayoutSettings.defaults().paddingLeft(4)); - graphics.pose().popMatrix(); + editTabLayout.arrangeElements(); // Setup initial positioning + FrameLayout.centerInRectangle(editTabLayout, 0, firstRowY, this.width, firstRowY); // Finish positioning - colorPickerWidget.extractRenderState(graphics, mouseX, mouseY, delta); + List editTabWidgets = new ArrayList<>(this.formattingButtons); + editTabWidgets.add(scaleSlider); + editTabWidgets.add(this.colorEntryWidget); + editTabWidgets.add(this.backgroundColorEntryWidget); + return editTabWidgets; } - @Override - public boolean charTyped(CharacterEvent event) { - for (final var element : this.textWidgets) { - if (element.charTyped(event)) { - return true; - } - } - - return this.selectionManager.charTyped(event); - } + public List initViewTabWidgets() { + int firstRowY = 24; + int secondRowY = firstRowY + 22; - @Override - 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.justifyLeftButton = IconButtonWidget.builder( + Glowcase.id("text_alignment/left"), button -> { + this.textBlockEntity.textAlignment = TextBlockEntity.TextAlignment.LEFT; + this.textBlockEntity.rebake(true); + this.glowcaseEditBox.setTextAlignment(TextBlockEntity.TextAlignment.LEFT); + this.updateSelectedJustifyButton(); + }) + .dimensions(0, 0, 20, 20, 16, 16) + .build(); + this.justifyLeftButton.setTooltip(Tooltip.create(Component.translatable("gui.glowcase.justify_left"))); + this.justifyCenterButton = IconButtonWidget.builder( + Glowcase.id("text_alignment/center"), button -> { + this.textBlockEntity.textAlignment = TextBlockEntity.TextAlignment.CENTER; + this.textBlockEntity.rebake(true); + this.glowcaseEditBox.setTextAlignment(TextBlockEntity.TextAlignment.CENTER); + this.updateSelectedJustifyButton(); + }) + .dimensions(0, 0, 20, 20, 16, 16) + .build(); + this.justifyCenterButton.setTooltip(Tooltip.create(Component.translatable("gui.glowcase.justify_center"))); + this.justifyRightButton = IconButtonWidget.builder( + Glowcase.id("text_alignment/right"), button -> { + this.textBlockEntity.textAlignment = TextBlockEntity.TextAlignment.RIGHT; + this.textBlockEntity.rebake(true); + this.glowcaseEditBox.setTextAlignment(TextBlockEntity.TextAlignment.RIGHT); + this.updateSelectedJustifyButton(); + }) + .dimensions(0, 0, 20, 20, 16, 16) + .build(); + this.justifyRightButton.setTooltip(Tooltip.create(Component.translatable("gui.glowcase.justify_right"))); + this.updateSelectedJustifyButton(); + + this.zFrontButton = Button.builder(Component.translatable("gui.glowcase.front"), button -> { + this.textBlockEntity.zOffset = TextBlockEntity.ZOffset.FRONT; + this.textBlockEntity.rebake(true); + this.updateSelectedZButton(); + }).size(50, 20).build(); + this.zCenterButton = Button.builder(Component.translatable("gui.glowcase.center"), button -> { + this.textBlockEntity.zOffset = TextBlockEntity.ZOffset.CENTER; + this.textBlockEntity.rebake(true); + this.updateSelectedZButton(); + }).size(50, 20).build(); + this.zBackButton = Button.builder(Component.translatable("gui.glowcase.back"), button -> { + this.textBlockEntity.zOffset = TextBlockEntity.ZOffset.BACK; + this.textBlockEntity.rebake(true); + this.updateSelectedZButton(); + }).size(50, 20).build(); + this.updateSelectedZButton(); + + // TODO (AC) - Use block entity anchor value instead of horizontal alignment + TextBlockEntity.Anchor fakeAnchor = TextBlockEntity.Anchor.fromHorizontalAlignment(this.textBlockEntity.horizontalAlignment); + AnchorPositionGridWidget anchorGrid = new AnchorPositionGridWidget(0, 0, fakeAnchor, anchor -> { + // TODO (AC) - Set block anchor variables here + if (anchor.getY() == 0) { + this.textBlockEntity.horizontalAlignment = TextBlockEntity.HorizontalAlignment.values()[anchor.getX() + 1]; + this.textBlockEntity.rebake(true); } + }); - this.toggleColorPicker(false); - this.setFocused(null); - - return true; - } - - if (this.getFocused() != null) { - if (this.getFocused().keyPressed(event)) { - return true; + CycleButton textShadowButton = CycleButton.onOffBuilder(this.textBlockEntity.shadow).create( + Component.translatable("gui.glowcase.text_shadow"), + (button, shadow) -> { + this.textBlockEntity.shadow = shadow; + this.textBlockEntity.rebake(true); + this.glowcaseEditBox.setTextShadow(shadow); } + ); + textShadowButton.setWidth(100); - this.toggleColorPicker(false); - this.setFocused(null); - - if (keyCode == GLFW.GLFW_KEY_ESCAPE) { - return true; + this.insertFontButton = IconButtonWidget.builder(Component.literal("Aa"), button -> { + this.fontSuggestionWidget.setPosition(this.insertFontButton.getRight() - 200, this.insertFontButton.getBottom()); + if (this.fontSuggestionWidget.hasSuggestions()) { + this.fontSuggestionWidget.updateSuggestions(new ArrayList<>(), "", this); + } else { + this.fontSuggestionWidget.updateSuggestions(getAvailableFontIds(), "", this); } - } + }).bounds(0, 0, 20, 20) + .tooltip(Tooltip.create(Component.translatable("gui.glowcase.insert_font"))) + .build(); - { - setFocused(null); - if (keyCode == GLFW.GLFW_KEY_ENTER || keyCode == GLFW.GLFW_KEY_KP_ENTER) { - this.textBlockEntity.addRawLine(this.currentRow + 1, - this.textBlockEntity.getRawLine(this.currentRow).substring( - Mth.clamp(this.selectionManager.getCursorPos(), 0, this.textBlockEntity.getRawLine(this.currentRow).length()) - )); - this.textBlockEntity.setRawLine(this.currentRow, - this.textBlockEntity.getRawLine(this.currentRow).substring(0, Mth.clamp(this.selectionManager.getCursorPos(), 0, this.textBlockEntity.getRawLine(this.currentRow).length()) - )); + this.fontSuggestionWidget = new SuggestionListWidget<>( + this.insertFontButton, this.font, + 0, 0, 200, 200, + 10, 4, 10, + identifier -> { + // TODO - The logic behind this will need some updating if a Block Font button is ever added + this.insertTag("font '" + identifier.toString() + "'"); + this.fontSuggestionWidget.updateSuggestions(new ArrayList<>(), "", this); + }, + Identifier::toString + ); + this.fontSuggestionWidget.updateSuggestions(new ArrayList<>(), "", this); + + Vec3FieldsWidget offsetWidgets = Vec3FieldsWidget.builder(this.font, this.textBlockEntity.offset) + .setWidth(106) + .setEditBoxCharacterLimit(5) + .setTooltips( + Tooltip.create(Component.translatable("gui.glowcase.x_offset_label")), + Tooltip.create(Component.translatable("gui.glowcase.y_offset_label")), + Tooltip.create(Component.translatable("gui.glowcase.z_offset_label")) + ) + .setOnValueChange(vec3 -> { + this.textBlockEntity.offset = vec3; this.textBlockEntity.rebake(true); - ++this.currentRow; - this.selectionManager.setCursorToStart(); - return true; - } else if (keyCode == GLFW.GLFW_KEY_UP) { - this.currentRow = Math.max(this.currentRow - 1, 0); - this.selectionManager.setCursorToEnd(); - return true; - } else if (keyCode == GLFW.GLFW_KEY_DOWN) { - this.currentRow = Math.min(this.currentRow + 1, (this.textBlockEntity.lines.size() - 1)); - this.selectionManager.setCursorToEnd(); - return true; - } else if (keyCode == GLFW.GLFW_KEY_BACKSPACE && this.currentRow > 0 && this.textBlockEntity.lines.size() > 1 && this.selectionManager.getCursorPos() == 0 && this.selectionManager.getSelectionPos() == this.selectionManager.getCursorPos()) { - --this.currentRow; - this.selectionManager.setCursorToEnd(); - deleteLine(); - return true; - } else if (keyCode == GLFW.GLFW_KEY_DELETE && this.currentRow < this.textBlockEntity.lines.size() - 1 && this.selectionManager.getSelectionPos() == this.textBlockEntity.getRawLine(this.currentRow).length()) { - deleteLine(); - return true; - } else { + }) + .build(); + Vec3FieldsWidget rotationWidgets = Vec3FieldsWidget.builder(this.font, this.textBlockEntity.rotation) + .setWidth(106) + .setRotation(true) + .setEditBoxCharacterLimit(5) + .setTooltips( + Tooltip.create(Component.translatable("gui.glowcase.yaw")), + Tooltip.create(Component.translatable("gui.glowcase.pitch")), + Tooltip.create(Component.translatable("gui.glowcase.roll")) + ) + .setOnValueChange(vec3 -> { + this.textBlockEntity.rotation = vec3; + this.textBlockEntity.rebake(true); + }) + .build(); - //formatting hotkeys - if (event.hasControlDown()) { - if (keyCode == GLFW.GLFW_KEY_B) { - insertTag(TagRegistry.SAFE.getTag("bold"), true); - return true; - } else if (keyCode == GLFW.GLFW_KEY_I) { - insertTag(TagRegistry.SAFE.getTag("italic"), true); - return true; - } else if (keyCode == GLFW.GLFW_KEY_U) { - insertTag(TagRegistry.SAFE.getTag("underline"), true); - return true; - } else if (keyCode == GLFW.GLFW_KEY_5 || keyCode == GLFW.GLFW_KEY_S) { - //There isn't a commonly agreed upon hotkey for strikethrough unlike the rest above - //apparently 5 is commonly used for strikethrough ¯\_(ツ)_/¯ - //Google Docs and Microsoft Word have 5 in their hotkeys, while Discord has S in its hotkey - insertTag(TagRegistry.SAFE.getTag("strikethrough"), true); - return true; - } else if (keyCode == GLFW.GLFW_KEY_O) { - insertTag(TagRegistry.SAFE.getTag("obfuscated"), true); - return true; - } - } + this.addRenderableWidget(this.justifyLeftButton); + this.addRenderableWidget(this.justifyCenterButton); + this.addRenderableWidget(this.justifyRightButton); + this.addRenderableWidget(zFrontButton); + this.addRenderableWidget(zCenterButton); + this.addRenderableWidget(zBackButton); + this.addRenderableWidget(anchorGrid); + this.addRenderableWidget(textShadowButton); + + this.addRenderableWidget(offsetWidgets); + this.addRenderableWidget(rotationWidgets); + this.addRenderableWidget(insertFontButton); + + LinearLayout viewTabFirstRowLayout = LinearLayout.horizontal().spacing(2); + viewTabFirstRowLayout.addChild(this.justifyLeftButton); + viewTabFirstRowLayout.addChild(this.justifyCenterButton, LayoutSettings.defaults().paddingLeft(-2)); + viewTabFirstRowLayout.addChild(this.justifyRightButton, LayoutSettings.defaults().paddingLeft(-2).paddingRight(4)); + viewTabFirstRowLayout.addChild(zFrontButton); + viewTabFirstRowLayout.addChild(zCenterButton, LayoutSettings.defaults().paddingLeft(-2)); + viewTabFirstRowLayout.addChild(zBackButton, LayoutSettings.defaults().paddingLeft(-2).paddingRight(2)); + viewTabFirstRowLayout.addChild(anchorGrid, LayoutSettings.defaults().paddingRight(4)); + viewTabFirstRowLayout.addChild(textShadowButton); + + viewTabFirstRowLayout.arrangeElements(); // Setup initial positioning + FrameLayout.centerInRectangle(viewTabFirstRowLayout, 0, firstRowY, this.width, 42); // Finish positioning + + rotationWidgets.setPosition(anchorGrid.getX() - 4 - rotationWidgets.getWidth(), secondRowY); + offsetWidgets.setPosition(rotationWidgets.getX() - 4 - offsetWidgets.getWidth(), secondRowY); + insertFontButton.setPosition(anchorGrid.getRight() + 6, secondRowY); + + return List.of( + justifyRightButton, justifyCenterButton, justifyLeftButton, + zFrontButton, zCenterButton, zBackButton, + anchorGrid, textShadowButton, + offsetWidgets, rotationWidgets, insertFontButton + ); + } - try { - boolean val = this.selectionManager.keyPressed(event) || super.keyPressed(event); - int selectionOffset = this.textBlockEntity.getRawLine(this.currentRow).length() - this.selectionManager.getCursorPos(); - - // Find line feed characters and create proper newlines - for (int i = 0; i < this.textBlockEntity.lines.size(); ++i) { - int lineFeedIndex = this.textBlockEntity.getRawLine(i).indexOf("\n"); - - if (lineFeedIndex >= 0) { - this.textBlockEntity.addRawLine(i + 1, - this.textBlockEntity.getRawLine(i).substring( - Mth.clamp(lineFeedIndex + 1, 0, this.textBlockEntity.getRawLine(i).length()) - )); - this.textBlockEntity.setRawLine(i, - this.textBlockEntity.getRawLine(i).substring(0, Mth.clamp(lineFeedIndex, 0, this.textBlockEntity.getRawLine(i).length()) - )); - this.textBlockEntity.rebake(true); - ++this.currentRow; - this.selectionManager.setCursorToEnd(); - this.selectionManager.moveByChars(-selectionOffset); - } - } - return val; - } catch (StringIndexOutOfBoundsException e) { - e.printStackTrace(); - Minecraft.getInstance().setScreen(null); - return false; - } - } - } + public void updateSelectedJustifyButton() { + TextBlockEntity.TextAlignment justify = this.textBlockEntity.textAlignment; + this.justifyLeftButton.active = justify != TextBlockEntity.TextAlignment.LEFT; + this.justifyCenterButton.active = justify != TextBlockEntity.TextAlignment.CENTER; + this.justifyRightButton.active = justify != TextBlockEntity.TextAlignment.RIGHT; } - private void deleteLine() { - this.textBlockEntity.setRawLine(this.currentRow, - this.textBlockEntity.getRawLine(this.currentRow) + this.textBlockEntity.getRawLine(this.currentRow + 1) - ); + public void updateSelectedZButton() { + TextBlockEntity.ZOffset zOffset = this.textBlockEntity.zOffset; + this.zFrontButton.active = zOffset != TextBlockEntity.ZOffset.FRONT; + this.zCenterButton.active = zOffset != TextBlockEntity.ZOffset.CENTER; + this.zBackButton.active = zOffset != TextBlockEntity.ZOffset.BACK; + } - this.textBlockEntity.lines.remove(this.currentRow + 1); - this.textBlockEntity.rebake(true); + @Override + public void extractRenderState(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float delta) { + super.extractRenderState(graphics, mouseX, mouseY, delta); + this.fontSuggestionWidget.extractRenderState(graphics, mouseX, mouseY, delta); + this.extractColorPicker(graphics, mouseX, mouseY, delta); } - 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 keyPressed(KeyEvent event) { + if (this.keyPressedColorPicker(event)) return true; + return super.keyPressed(event); } @Override public boolean mouseClicked(MouseButtonEvent event, boolean doubleClick) { - double mouseX = event.x(); - double mouseY = event.y(); - int topOffset = (int) (editorOffset + 2 * this.width / 100F); - - for (final var text : textWidgets) { - if (!text.mouseClicked(event, doubleClick)) { - continue; - } - this.setFocused(text); - if (this.colorListeners.contains(text)) { - this.colorListenerClicked(text); - } - if (this.colorPickerWidget.targetElement != text) { - text.setFocused(false); - } - break; - } + if (mouseClickedColorPicker(event, doubleClick)) return true; + if (this.fontSuggestionWidget.mouseClicked(event, doubleClick)) return true; + return super.mouseClicked(event, doubleClick); + } - 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 (mouseY > topOffset) { - this.currentRow = Mth.clamp((int) (mouseY - topOffset) / 12, 0, this.textBlockEntity.lines.size() - 1); - this.setFocused(null); - String baseContents = this.textBlockEntity.getRawLine(currentRow); - int baseContentsWidth = this.font.width(baseContents); - int contentsStart; - int contentsEnd; - switch (this.textBlockEntity.textAlignment) { - case LEFT -> { - contentsStart = this.width / 10; - contentsEnd = contentsStart + baseContentsWidth; - } - case CENTER, CENTER_LEFT, CENTER_RIGHT -> { - int midpoint = this.width / 2; - int textMidpoint = baseContentsWidth / 2; - contentsStart = midpoint - textMidpoint; - contentsEnd = midpoint + textMidpoint; - } - case RIGHT -> { - contentsEnd = this.width - this.width / 10; - contentsStart = contentsEnd - baseContentsWidth; - } - //even though this is exhaustive, javac won't treat contentsStart and contentsEnd as initialized - //why? who knows! just throw bc this should be impossible - default -> throw new IllegalStateException(":HOW:"); - } + @Override + public boolean mouseDragged(MouseButtonEvent event, double dx, double dy) { + if (this.fontSuggestionWidget.draggingScrollbar && this.fontSuggestionWidget.mouseDragged(event, dx, dy)) return true; + return super.mouseDragged(event, dx, dy); + } - if (mouseX <= contentsStart) { - this.selectionManager.setCursorToStart(); - } else if (mouseX >= contentsEnd) { - this.selectionManager.setCursorToEnd(); - } else { - int lastWidth = 0; - for (int i = 1; i < baseContents.length(); i++) { - String testContents = baseContents.substring(0, i); - int width = this.font.width(testContents); - int midpointWidth = (width + lastWidth) / 2; - if (mouseX < contentsStart + midpointWidth) { - this.selectionManager.setCursorPos(i - 1, false); - break; - } else if (mouseX <= contentsStart + width) { - this.selectionManager.setCursorPos(i, false); - break; - } - lastWidth = width; - } - } - return true; - } else { - return super.mouseClicked(event, doubleClick); - } + @Override + public boolean mouseScrolled(double x, double y, double scrollX, double scrollY) { + if (this.fontSuggestionWidget.isFocused() && this.fontSuggestionWidget.isMouseOver(x, y) + && this.fontSuggestionWidget.mouseScrolled(x, y, scrollX, scrollY)) return true; + return super.mouseScrolled(x, y, scrollX, scrollY); } @Override - public ColorPickerWidget colorPickerWidget() { + public ColorPickerWidget getColorPickerWidget() { return this.colorPickerWidget; } @Override - public void toggleColorPicker(boolean active) { - this.colorPickerWidget.toggle(active); + GlowcaseMultilineEditBox getGlowcaseMultilineEditBox() { + return this.glowcaseEditBox; } @Override - TextFieldHelper getSelectionManager() { - return this.selectionManager; + public TextBlockEntity getBlockEntity() { + return this.textBlockEntity; } - public static class TextScale { - public static final float MIN_SCALE = 0.125F; - public static final float MAX_SCALE = 16; - public static final float SCALE_DELTA = MAX_SCALE - MIN_SCALE; - - public static class SliderWidget extends AbstractSliderButton { - private final TextBlockEntity entity; - private Consumer scaleResponder; - - public SliderWidget(TextBlockEntity entity, int x, int y, int width, int height) { - var initialValue = (entity.scale - MIN_SCALE) / SCALE_DELTA; - super(x, y, width, height, Component.translatable("gui.glowcase.scale_value", entity.scale), initialValue); - this.entity = entity; - } - - @Override - protected void updateMessage() { - this.setMessage(Component.translatable("gui.glowcase.scale_value", entity.scale)); - } - - @Override - protected void applyValue() { - entity.scale = (float) Math.round(Mth.lerp(this.value, MIN_SCALE, MAX_SCALE) * 8F) / 8F; - if (scaleResponder != null) scaleResponder.accept(entity.scale); - - entity.rebake(true); - } - - public void setScaleResponder(Consumer responder) { - this.scaleResponder = responder; - } + @Override + public @Nullable CustomPacketPayload getUpdatePayload() { + return C2SEditTextBlock.of(textBlockEntity); + } - public void updateValue(double newValue) { - this.value = Mth.clamp(newValue, 0.0, 1.0); - updateMessage(); - } + // This is so cursed but it gives the best order of fonts given the amount of repetitive entries + // Some number of fonts have duplicate entries within an "include" folder, which may or may not work with QuickText + // Minecraft fonts are listed first (predefined order), while remaining fonts come afterward + // Any fonts within the "include" folder, Minecraft or external, are omitted + public static List getAvailableFontIds() { + ResourceManager manager = Minecraft.getInstance().getResourceManager(); + FileToIdConverter converter = FileToIdConverter.json("font"); + List availableFonts = new ArrayList<>(); // All available fonts, including "include" folder entries + List fontsInOrder = new ArrayList<>(); // List of fonts to return + fontsInOrder.add(Identifier.withDefaultNamespace("default")); // Set proper order of Vanilla's builtin fonts + fontsInOrder.add(Identifier.withDefaultNamespace("alt")); + fontsInOrder.add(Identifier.withDefaultNamespace("illageralt")); + fontsInOrder.add(Identifier.withDefaultNamespace("uniform")); + + for (Map.Entry> fontEntry : converter.listMatchingResourceStacks(manager).entrySet()) { + Identifier fontName = converter.fileToId(fontEntry.getKey()); + availableFonts.add(fontName); } - public static class InputWidget extends GlowcaseEditBox { - private Consumer scaleResponder; - - public InputWidget(TextBlockEntity entity, Font font, int x, int y, int width, int height) { - super(font, x, y, width, height, Component.empty()); - this.setValue(String.valueOf(entity.scale)); - this.setTooltip(Tooltip.create(Component.translatable("gui.glowcase.scale"))); - this.setFilter(InputFilters::realNumber); - this.setResponder(input -> { - entity.scale = (float) Math.clamp(ParseUtil.parseOrDefault(input, 1d), MIN_SCALE, MAX_SCALE); - if (scaleResponder != null) scaleResponder.accept(entity.scale); - - entity.rebake(true); - }); + for (Iterator iterator = availableFonts.iterator(); iterator.hasNext(); ) { + Identifier id = iterator.next(); + if (id.getNamespace().equals("minecraft") && !id.getPath().contains("include/")) { + iterator.remove(); + if (fontsInOrder.contains(id)) continue; // Likely because we added the builtin fonts already + fontsInOrder.add(id); } + } - public void setScaleResponder(Consumer scaleResponder) { - this.scaleResponder = scaleResponder; + for (Iterator iterator = availableFonts.iterator(); iterator.hasNext(); ) { + Identifier id = iterator.next(); + if (!id.getPath().contains("include/")) { + iterator.remove(); + if (fontsInOrder.contains(id)) continue; // Shouldn't happen but just in case + fontsInOrder.add(id); } } + + return fontsInOrder; } } 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 deleted file mode 100644 index 248b7276..00000000 --- a/src/main/java/dev/hephaestus/glowcase/client/gui/screen/ingame/TextBlockOptionsScreen.java +++ /dev/null @@ -1,308 +0,0 @@ -package dev.hephaestus.glowcase.client.gui.screen.ingame; - -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.packet.C2SEditTextBlock; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.GuiGraphicsExtractor; -import net.minecraft.client.gui.components.AbstractWidget; -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.network.chat.CommonComponents; -import net.minecraft.network.chat.Component; -import net.minecraft.network.protocol.common.custom.CustomPacketPayload; -import org.jspecify.annotations.NullMarked; - -import java.util.List; - -public class TextBlockOptionsScreen extends BlockEditorScreen { - private final Screen returnScreen; - - public final HeaderAndFooterLayout layout = new HeaderAndFooterLayout(this); - public TextOptionList options; - - public TextBlockOptionsScreen(Screen returnScreen, TextBlockEntity blockEntity) { - super(blockEntity, Component.translatable("gui.glowcase.text_options")); - this.returnScreen = returnScreen; - } - - @Override - public void init() { - super.init(); - - this.layout.addTitleHeader(this.title, this.font); - this.layout.addToFooter(Button.builder(CommonComponents.GUI_DONE, _ -> this.onClose()).width(200).build()); - - this.options = this.layout.addToContents(new TextOptionList(this.minecraft, this.width, this.layout.getContentHeight(), this.layout.getHeaderHeight())); - addScaleWidgetRow(this.options); - - this.options.add( - CycleButton.builder( - alignment -> Component.literal(alignment.toString()), - blockEntity.horizontalAlignment - ) - .withValues(TextBlockEntity.HorizontalAlignment.values()) - .create( - Component.translatable("gui.glowcase.x_offset_label"), - (_, alignment) -> { - blockEntity.horizontalAlignment = alignment; - blockEntity.rebake(true); - } - ), - CycleButton.builder( - offset -> Component.literal(offset.toString()), - blockEntity.zOffset - ) - .withValues(TextBlockEntity.ZOffset.values()) - .create( - Component.translatable("gui.glowcase.z_offset_label"), - (_, offset) -> { - blockEntity.zOffset = offset; - blockEntity.rebake(true); - } - ) - ); - - this.options.add( - CycleButton.builder( - alignment -> Component.literal(alignment.toString()), - blockEntity.textAlignment - ) - .withValues( - // not `.values()` to not have CENTER_LEFT or CENTER_RIGHT, unless they get removed - TextBlockEntity.TextAlignment.CENTER, - TextBlockEntity.TextAlignment.LEFT, - TextBlockEntity.TextAlignment.RIGHT - ) - .create( - Component.translatable("gui.glowcase.text_alignment"), - (_, alignment) -> { - blockEntity.textAlignment = alignment; - blockEntity.rebake(true); - } - ), - CycleButton.onOffBuilder(blockEntity.shadow).create( - Component.translatable("gui.glowcase.text_shadow"), - (_, shadow) -> { - blockEntity.shadow = shadow; - blockEntity.rebake(true); - } - ) - ); - - 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); - })); - - this.options.add(colorEditBox, backgroundEditBox); - - this.layout.visitWidgets(this::addRenderableWidget); - this.layout.arrangeElements(); - } - - private void addScaleWidgetRow(TextOptionList options) { - var slider = new TextScale.SliderWidget(this.blockEntity, -1, -1, Button.DEFAULT_WIDTH, Button.DEFAULT_HEIGHT); - var input = new TextScale.InputWidget( - this.blockEntity, - this.font, - -1, - -1, - Button.DEFAULT_WIDTH, - Button.DEFAULT_HEIGHT - ); - - slider.setScaleResponder(scale -> input.setValue(String.valueOf(scale))); - input.setScaleResponder(scale -> slider.updateValue((scale - TextScale.MIN_SCALE) / TextScale.SCALE_DELTA)); - - options.add(slider, input); - } - - @Override - public CustomPacketPayload getUpdatePayload() { - return C2SEditTextBlock.of(this.blockEntity); - } - - @Override - public void onClose() { - super.onClose(); - this.minecraft.setScreen(this.returnScreen); - } - - @NullMarked - public static class TextOptionList extends ContainerObjectSelectionList { - public TextOptionList(Minecraft minecraft, int width, int height, int y) { - super(minecraft, width, height, y, Button.DEFAULT_HEIGHT + 5); - } - - public void addHeader(Component text) { - int lineHeight = this.minecraft.font.lineHeight; - int paddingTop = this.children().isEmpty() ? 0 : lineHeight * 2; - this.addEntry(new HeaderEntry(new StringWidget(text, this.minecraft.font), paddingTop), paddingTop + lineHeight + 4); - } - - public void addHeaders(Component leftHeader, Component rightHeader) { - int lineHeight = this.minecraft.font.lineHeight; - int paddingTop = this.children().isEmpty() ? 0 : lineHeight; - this.addEntry( - new DualHeaderEntry( - new StringWidget(leftHeader, this.minecraft.font), - new StringWidget(rightHeader, this.minecraft.font), paddingTop), - paddingTop + lineHeight + 4 - ); - } - - public void add(AbstractWidget widget) { - this.addEntry(new WidgetEntry(widget)); - } - - public void add(AbstractWidget leftWidget, AbstractWidget rightWidget) { - this.addEntry(new TwoWidgetsEntry(leftWidget, rightWidget)); - } - - @Override - public int getRowWidth() { - return 310; - } - - public static abstract class Entry extends ContainerObjectSelectionList.Entry {} - - public static class HeaderEntry extends Entry { - protected final StringWidget widget; - protected final int paddingTop; - - public HeaderEntry(StringWidget widget, int paddingTop) { - this.widget = widget; - this.paddingTop = paddingTop; - } - - @Override - public void extractContent(GuiGraphicsExtractor graphics, int mouseX, int mouseY, boolean hovered, float a) { - this.widget.setPosition(this.getContentX(), this.getContentY() + this.paddingTop); - this.widget.extractRenderState(graphics, mouseX, mouseY, a); - } - - @Override - public List narratables() { - return List.of(this.widget); - } - - @Override - public List children() { - return List.of(this.widget); - } - } - - public static class DualHeaderEntry extends Entry { - protected final StringWidget leftWidget; - protected final StringWidget rightWidget; - protected final int paddingTop; - - public DualHeaderEntry(StringWidget leftWidget, StringWidget rightWidget, int paddingTop) { - this.leftWidget = leftWidget; - this.rightWidget = rightWidget; - this.paddingTop = paddingTop; - } - - @Override - public void extractContent(GuiGraphicsExtractor graphics, int mouseX, int mouseY, boolean hovered, float a) { - this.leftWidget.setPosition(this.getContentX(), this.getContentY() + this.paddingTop); - this.leftWidget.extractRenderState(graphics, mouseX, mouseY, a); - this.rightWidget.setPosition(this.getContentX() + 160, this.getContentY() + this.paddingTop); - this.rightWidget.extractRenderState(graphics, mouseX, mouseY, a); - } - - @Override - public List narratables() { - return List.of(this.leftWidget, this.rightWidget); - } - - @Override - public List children() { - return List.of(this.leftWidget, this.rightWidget); - } - } - - public static class WidgetEntry extends Entry { - protected final AbstractWidget widget; - - public WidgetEntry(AbstractWidget widget) { - this.widget = widget; - } - - @Override - public void extractContent(GuiGraphicsExtractor graphics, int mouseX, int mouseY, boolean hovered, float a) { - this.widget.setPosition(this.getContentX(), this.getContentY()); - this.widget.extractRenderState(graphics, mouseX, mouseY, a); - } - - @Override - public List narratables() { - return List.of(this.widget); - } - - @Override - public List children() { - return List.of(this.widget); - } - } - - public static class TwoWidgetsEntry extends Entry { - protected final AbstractWidget leftWidget; - protected final AbstractWidget rightWidget; - - public TwoWidgetsEntry(AbstractWidget leftWidget, AbstractWidget rightWidget) { - this.leftWidget = leftWidget; - this.rightWidget = rightWidget; - } - - @Override - public void extractContent(GuiGraphicsExtractor graphics, int mouseX, int mouseY, boolean hovered, float a) { - this.leftWidget.setPosition(this.getContentX(), this.getContentY()); - this.leftWidget.extractRenderState(graphics, mouseX, mouseY, a); - this.rightWidget.setPosition(this.getContentX() + 160, this.getContentY()); - this.rightWidget.extractRenderState(graphics, mouseX, mouseY, a); - } - - @Override - public List narratables() { - return List.of(this.leftWidget, this.rightWidget); - } - - @Override - public List children() { - return List.of(this.leftWidget, this.rightWidget); - } - } - } -} 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..ff1b07dc 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,139 +1,118 @@ package dev.hephaestus.glowcase.client.gui.screen.ingame; -import dev.hephaestus.glowcase.client.gui.widget.ingame.ColorPickerWidget; -import eu.pb4.placeholders.api.parsers.tag.TagRegistry; -import eu.pb4.placeholders.api.parsers.tag.TextTag; +import dev.hephaestus.glowcase.Glowcase; +import dev.hephaestus.glowcase.client.gui.widget.ingame.IconButtonWidget; +import dev.hephaestus.glowcase.client.gui.widget.ingame.color.picker.ColorPickerWidget; +import dev.hephaestus.glowcase.client.gui.widget.ingame.text.GlowcaseMultilineEditBox; +import dev.hephaestus.glowcase.client.util.ColorUtil; import net.minecraft.ChatFormatting; import net.minecraft.client.gui.components.Button; -import net.minecraft.client.gui.font.TextFieldHelper; +import net.minecraft.client.gui.components.Tooltip; +import net.minecraft.client.input.MouseButtonEvent; import net.minecraft.network.chat.Component; +import net.minecraft.resources.Identifier; -import java.util.Arrays; -import java.util.Comparator; - -public abstract class TextEditorScreen extends EditorScreen implements ColorPickerIncludedScreen { - private Button colorText; - private Button[] widgets = new Button[0]; - - abstract TextFieldHelper getSelectionManager(); - - protected void addFormattingButtons(int x, int y, int innerPadding, int buttonSize, int buttonPadding) { - int buttonX = x + innerPadding * 2; //adding numbers to this variable because I personally find that more readable, that's all - int buttonY = y + innerPadding; //reduce the times this is calculated - Button boldText = Button.builder(Component.literal("B").withStyle(ChatFormatting.BOLD), action -> { - insertTag(TagRegistry.SAFE.getTag("bold"), true); - }).bounds(buttonX, buttonY, buttonSize, buttonSize).build(); - - buttonX += buttonSize + buttonPadding; - Button italicizeText = Button.builder(Component.literal("I").withStyle(ChatFormatting.ITALIC), action -> { - insertTag(TagRegistry.SAFE.getTag("italic"), true); - }).bounds(buttonX, buttonY, buttonSize, buttonSize).build(); - - buttonX += buttonSize + buttonPadding; - Button strikeText = Button.builder(Component.literal("S").withStyle(ChatFormatting.STRIKETHROUGH), action -> { - insertTag(TagRegistry.SAFE.getTag("strikethrough"), true); - }).bounds(buttonX, buttonY, buttonSize, buttonSize).build(); - - buttonX += buttonSize + buttonPadding; - Button underlineText = Button.builder(Component.literal("U").withStyle(ChatFormatting.UNDERLINE), action -> { - insertTag(TagRegistry.SAFE.getTag("underline"), true); - }).bounds(buttonX, buttonY, buttonSize, buttonSize).build(); - - buttonX += buttonSize + buttonPadding; - //not using the actual obfuscated formatting here because the movement can be annoying - Button obfuscateText = Button.builder(Component.literal("@"), action -> { - insertTag(TagRegistry.SAFE.getTag("obfuscated"), true); - }).bounds(buttonX, buttonY, buttonSize, buttonSize).build(); - - 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.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); - }).bounds(buttonX, buttonY, buttonSize, buttonSize).build(); - - widgets = new Button[]{ - boldText, italicizeText, strikeText, underlineText, obfuscateText, colorText - }; - - this.addRenderableWidget(boldText); - this.addRenderableWidget(italicizeText); - this.addRenderableWidget(strikeText); - this.addRenderableWidget(underlineText); - this.addRenderableWidget(obfuscateText); - this.addRenderableWidget(colorText); +import java.util.List; + +public abstract class TextEditorScreen extends EditorScreen implements ColorPickerIncludedScreen, TagFormatIncludedScreen { + public static final Identifier COLOR_TEXT_ICON = Glowcase.id("color_text"); + + protected Button colorTextButton; + protected List