diff options
Diffstat (limited to 'src/client/java/dev/isxander/yacl/gui/controllers')
27 files changed, 2757 insertions, 0 deletions
diff --git a/src/client/java/dev/isxander/yacl/gui/controllers/ActionController.java b/src/client/java/dev/isxander/yacl/gui/controllers/ActionController.java new file mode 100644 index 0000000..7666dff --- /dev/null +++ b/src/client/java/dev/isxander/yacl/gui/controllers/ActionController.java @@ -0,0 +1,120 @@ +package dev.isxander.yacl.gui.controllers; + +import dev.isxander.yacl.api.ButtonOption; +import dev.isxander.yacl.api.Controller; +import dev.isxander.yacl.api.utils.Dimension; +import dev.isxander.yacl.gui.AbstractWidget; +import dev.isxander.yacl.gui.YACLScreen; +import net.minecraft.client.util.InputUtil; +import net.minecraft.text.Text; + +import java.util.function.BiConsumer; + +/** + * Simple controller that simply runs the button action on press + * and renders a {@link} Text on the right. + */ +public class ActionController implements Controller<BiConsumer<YACLScreen, ButtonOption>> { + public static final Text DEFAULT_TEXT = Text.translatable("yacl.control.action.execute"); + + private final ButtonOption option; + private final Text text; + + /** + * Constructs an action controller + * with the default formatter of {@link ActionController#DEFAULT_TEXT} + * + * @param option bound option + */ + public ActionController(ButtonOption option) { + this(option, DEFAULT_TEXT); + } + + /** + * Constructs an action controller + * + * @param option bound option + * @param text text to display + */ + public ActionController(ButtonOption option, Text text) { + this.option = option; + this.text = text; + + } + + /** + * {@inheritDoc} + */ + @Override + public ButtonOption option() { + return option; + } + + /** + * {@inheritDoc} + */ + @Override + public Text formatValue() { + return text; + } + + /** + * {@inheritDoc} + */ + @Override + public AbstractWidget provideWidget(YACLScreen screen, Dimension<Integer> widgetDimension) { + return new ActionControllerElement(this, screen, widgetDimension); + } + + public static class ActionControllerElement extends ControllerWidget<ActionController> { + private final String buttonString; + + public ActionControllerElement(ActionController control, YACLScreen screen, Dimension<Integer> dim) { + super(control, screen, dim); + buttonString = control.formatValue().getString().toLowerCase(); + } + + public void executeAction() { + playDownSound(); + control.option().action().accept(screen, control.option()); + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + if (isMouseOver(mouseX, mouseY) && isAvailable()) { + executeAction(); + return true; + } + return false; + } + + @Override + public boolean keyPressed(int keyCode, int scanCode, int modifiers) { + if (!focused) { + return false; + } + + if (keyCode == InputUtil.GLFW_KEY_ENTER || keyCode == InputUtil.GLFW_KEY_SPACE || keyCode == InputUtil.GLFW_KEY_KP_ENTER) { + executeAction(); + return true; + } + + return false; + } + + @Override + protected int getHoveredControlWidth() { + return getUnhoveredControlWidth(); + } + + @Override + public boolean canReset() { + return false; + } + + @Override + public boolean matchesSearch(String query) { + return super.matchesSearch(query) || buttonString.contains(query); + } + } +} diff --git a/src/client/java/dev/isxander/yacl/gui/controllers/BooleanController.java b/src/client/java/dev/isxander/yacl/gui/controllers/BooleanController.java new file mode 100644 index 0000000..3a8e5c3 --- /dev/null +++ b/src/client/java/dev/isxander/yacl/gui/controllers/BooleanController.java @@ -0,0 +1,158 @@ +package dev.isxander.yacl.gui.controllers; + +import dev.isxander.yacl.api.Controller; +import dev.isxander.yacl.api.Option; +import dev.isxander.yacl.api.utils.Dimension; +import dev.isxander.yacl.gui.AbstractWidget; +import dev.isxander.yacl.gui.YACLScreen; +import net.minecraft.client.util.InputUtil; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.screen.ScreenTexts; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import org.lwjgl.glfw.GLFW; + +import java.util.function.Function; + +/** + * This controller renders a simple formatted {@link Text} + */ +public class BooleanController implements Controller<Boolean> { + + public static final Function<Boolean, Text> ON_OFF_FORMATTER = (state) -> + state + ? ScreenTexts.ON + : ScreenTexts.OFF; + + public static final Function<Boolean, Text> TRUE_FALSE_FORMATTER = (state) -> + state + ? Text.translatable("yacl.control.boolean.true") + : Text.translatable("yacl.control.boolean.false"); + + public static final Function<Boolean, Text> YES_NO_FORMATTER = (state) -> + state + ? ScreenTexts.YES + : ScreenTexts.NO; + + private final Option<Boolean> option; + private final Function<Boolean, Text> valueFormatter; + private final boolean coloured; + + /** + * Constructs a tickbox controller + * with the default value formatter of {@link BooleanController#ON_OFF_FORMATTER} + * + * @param option bound option + */ + public BooleanController(Option<Boolean> option) { + this(option, ON_OFF_FORMATTER, false); + } + + /** + * Constructs a tickbox controller + * with the default value formatter of {@link BooleanController#ON_OFF_FORMATTER} + * + * @param option bound option + * @param coloured value format is green or red depending on the state + */ + public BooleanController(Option<Boolean> option, boolean coloured) { + this(option, ON_OFF_FORMATTER, coloured); + } + + /** + * Constructs a tickbox controller + * + * @param option bound option + * @param valueFormatter format value into any {@link Text} + * @param coloured value format is green or red depending on the state + */ + public BooleanController(Option<Boolean> option, Function<Boolean, Text> valueFormatter, boolean coloured) { + this.option = option; + this.valueFormatter = valueFormatter; + this.coloured = coloured; + } + + /** + * {@inheritDoc} + */ + @Override + public Option<Boolean> option() { + return option; + } + + /** + * {@inheritDoc} + */ + @Override + public Text formatValue() { + return valueFormatter.apply(option().pendingValue()); + } + + /** + * Value format is green or red depending on the state + */ + public boolean coloured() { + return coloured; + } + + /** + * {@inheritDoc} + */ + @Override + public AbstractWidget provideWidget(YACLScreen screen, Dimension<Integer> widgetDimension) { + return new BooleanControllerElement(this, screen, widgetDimension); + } + + public static class BooleanControllerElement extends ControllerWidget<BooleanController> { + public BooleanControllerElement(BooleanController control, YACLScreen screen, Dimension<Integer> dim) { + super(control, screen, dim); + } + + @Override + protected void drawHoveredControl(MatrixStack matrices, int mouseX, int mouseY, float delta) { + + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + if (!isMouseOver(mouseX, mouseY) || !isAvailable()) + return false; + + toggleSetting(); + return true; + } + + @Override + protected int getHoveredControlWidth() { + return getUnhoveredControlWidth(); + } + + public void toggleSetting() { + control.option().requestSet(!control.option().pendingValue()); + playDownSound(); + } + + @Override + protected Text getValueText() { + if (control.coloured()) { + return super.getValueText().copy().formatted(control.option().pendingValue() ? Formatting.GREEN : Formatting.RED); + } + + return super.getValueText(); + } + + @Override + public boolean keyPressed(int keyCode, int scanCode, int modifiers) { + if (!focused) { + return false; + } + + if (keyCode == InputUtil.GLFW_KEY_ENTER || keyCode == InputUtil.GLFW_KEY_SPACE || keyCode == InputUtil.GLFW_KEY_KP_ENTER) { + toggleSetting(); + return true; + } + + return false; + } + } +} diff --git a/src/client/java/dev/isxander/yacl/gui/controllers/ColorController.java b/src/client/java/dev/isxander/yacl/gui/controllers/ColorController.java new file mode 100644 index 0000000..b8e1942 --- /dev/null +++ b/src/client/java/dev/isxander/yacl/gui/controllers/ColorController.java @@ -0,0 +1,206 @@ +package dev.isxander.yacl.gui.controllers; + +import com.google.common.collect.ImmutableList; +import dev.isxander.yacl.api.Option; +import dev.isxander.yacl.api.utils.Dimension; +import dev.isxander.yacl.api.utils.MutableDimension; +import dev.isxander.yacl.gui.AbstractWidget; +import dev.isxander.yacl.gui.YACLScreen; +import dev.isxander.yacl.gui.controllers.string.IStringController; +import dev.isxander.yacl.gui.controllers.string.StringControllerElement; +import net.minecraft.client.gui.DrawableHelper; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.text.MutableText; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; + +import java.awt.Color; +import java.util.List; + +/** + * A color controller that uses a hex color field as input. + */ +public class ColorController implements IStringController<Color> { + private final Option<Color> option; + private final boolean allowAlpha; + + /** + * Constructs a color controller with {@link ColorController#allowAlpha()} defaulting to false + * + * @param option bound option + */ + public ColorController(Option<Color> option) { + this(option, false); + } + + /** + * Constructs a color controller + * + * @param option bound option + * @param allowAlpha allows the color input to accept alpha values + */ + public ColorController(Option<Color> option, boolean allowAlpha) { + this.option = option; + this.allowAlpha = allowAlpha; + } + + /** + * {@inheritDoc} + */ + @Override + public Option<Color> option() { + return option; + } + + public boolean allowAlpha() { + return allowAlpha; + } + + @Override + public String getString() { + return formatValue().getString(); + } + + @Override + public Text formatValue() { + MutableText text = Text.literal("#"); + text.append(Text.literal(toHex(option().pendingValue().getRed())).formatted(Formatting.RED)); + text.append(Text.literal(toHex(option().pendingValue().getGreen())).formatted(Formatting.GREEN)); + text.append(Text.literal(toHex(option().pendingValue().getBlue())).formatted(Formatting.BLUE)); + if (allowAlpha()) text.append(toHex(option().pendingValue().getAlpha())); + return text; + } + + private String toHex(int value) { + String hex = Integer.toString(value, 16).toUpperCase(); + if (hex.length() == 1) + hex = "0" + hex; + return hex; + } + + @Override + public void setFromString(String value) { + if (value.startsWith("#")) + value = value.substring(1); + + int red = Integer.parseInt(value.substring(0, 2), 16); + int green = Integer.parseInt(value.substring(2, 4), 16); + int blue = Integer.parseInt(value.substring(4, 6), 16); + + if (allowAlpha()) { + int alpha = Integer.parseInt(value.substring(6, 8), 16); + option().requestSet(new Color(red, green, blue, alpha)); + } else { + option().requestSet(new Color(red, green, blue)); + } + } + + @Override + public AbstractWidget provideWidget(YACLScreen screen, Dimension<Integer> widgetDimension) { + return new ColorControllerElement(this, screen, widgetDimension); + } + + public static class ColorControllerElement extends StringControllerElement { + private final ColorController colorController; + + protected MutableDimension<Integer> colorPreviewDim; + + private final List<Character> allowedChars; + + public ColorControllerElement(ColorController control, YACLScreen screen, Dimension<Integer> dim) { + super(control, screen, dim, true); + this.colorController = control; + this.allowedChars = ImmutableList.of('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'); + } + + @Override + protected void drawValueText(MatrixStack matrices, int mouseX, int mouseY, float delta) { + if (isHovered()) { + colorPreviewDim.move(-inputFieldBounds.width() - 5, 0); + super.drawValueText(matrices, mouseX, mouseY, delta); + } + + DrawableHelper.fill(matrices, colorPreviewDim.x(), colorPreviewDim.y(), colorPreviewDim.xLimit(), colorPreviewDim.yLimit(), colorController.option().pendingValue().getRGB()); + drawOutline(matrices, colorPreviewDim.x(), colorPreviewDim.y(), colorPreviewDim.xLimit(), colorPreviewDim.yLimit(), 1, 0xFF000000); + } + + @Override + public void write(String string) { + for (char chr : string.toCharArray()) { + if (!allowedChars.contains(Character.toLowerCase(chr))) { + return; + } + } + + if (caretPos == 0) + return; + + String trimmed = string.substring(0, Math.min(inputField.length() - caretPos, string.length())); + + if (modifyInput(builder -> builder.replace(caretPos, caretPos + trimmed.length(), trimmed))) { + caretPos += trimmed.length(); + setSelectionLength(); + updateControl(); + } + } + + @Override + protected void doBackspace() { + if (caretPos > 1) { + if (modifyInput(builder -> builder.setCharAt(caretPos - 1, '0'))) { + caretPos--; + updateControl(); + } + } + } + + @Override + protected void doDelete() { + + } + + @Override + protected boolean canUseShortcuts() { + return false; + } + + protected void setSelectionLength() { + selectionLength = caretPos < inputField.length() && caretPos > 0 ? 1 : 0; + } + + @Override + protected int getDefaultCarotPos() { + return colorController.allowAlpha() ? 3 : 1; + } + + @Override + public void setDimension(Dimension<Integer> dim) { + super.setDimension(dim); + + int previewSize = (dim.height() - getYPadding() * 2) / 2; + colorPreviewDim = Dimension.ofInt(dim.xLimit() - getXPadding() - previewSize, dim.centerY() - previewSize / 2, previewSize, previewSize); + } + + @Override + public boolean keyPressed(int keyCode, int scanCode, int modifiers) { + int prevSelectionLength = selectionLength; + selectionLength = 0; + if (super.keyPressed(keyCode, scanCode, modifiers)) { + caretPos = Math.max(1, caretPos); + setSelectionLength(); + return true; + } else selectionLength = prevSelectionLength; + return false; + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + if (super.mouseClicked(mouseX, mouseY, button)) { + caretPos = Math.max(1, caretPos); + setSelectionLength(); + return true; + } + return false; + } + } +} diff --git a/src/client/java/dev/isxander/yacl/gui/controllers/ControllerWidget.java b/src/client/java/dev/isxander/yacl/gui/controllers/ControllerWidget.java new file mode 100644 index 0000000..cebaba7 --- /dev/null +++ b/src/client/java/dev/isxander/yacl/gui/controllers/ControllerWidget.java @@ -0,0 +1,169 @@ +package dev.isxander.yacl.gui.controllers; + +import dev.isxander.yacl.api.Controller; +import dev.isxander.yacl.api.utils.Dimension; +import dev.isxander.yacl.gui.AbstractWidget; +import dev.isxander.yacl.gui.YACLScreen; +import dev.isxander.yacl.impl.utils.YACLConstants; +import net.minecraft.client.font.MultilineText; +import net.minecraft.client.gui.DrawableHelper; +import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; + +public abstract class ControllerWidget<T extends Controller<?>> extends AbstractWidget { + protected final T control; + protected MultilineText wrappedTooltip; + protected final YACLScreen screen; + + protected boolean focused = false; + protected boolean hovered = false; + + protected final Text modifiedOptionName; + protected final String optionNameString; + + public ControllerWidget(T control, YACLScreen screen, Dimension<Integer> dim) { + super(dim); + this.control = control; + this.screen = screen; + control.option().addListener((opt, pending) -> updateTooltip()); + updateTooltip(); + this.modifiedOptionName = control.option().name().copy().formatted(Formatting.ITALIC); + this.optionNameString = control.option().name().getString().toLowerCase(); + } + + @Override + public void render(MatrixStack matrices, int mouseX, int mouseY, float delta) { + hovered = isMouseOver(mouseX, mouseY); + + Text name = control.option().changed() ? modifiedOptionName : control.option().name(); + String nameString = name.getString(); + + boolean firstIter = true; + while (textRenderer.getWidth(nameString) > getDimension().width() - getControlWidth() - getXPadding() - 7) { + nameString = nameString.substring(0, Math.max(nameString.length() - (firstIter ? 2 : 5), 0)).trim(); + nameString += "..."; + + if (nameString.equals("...")) + break; + + firstIter = false; + } + + Text shortenedName = Text.literal(nameString).fillStyle(name.getStyle()); + + drawButtonRect(matrices, getDimension().x(), getDimension().y(), getDimension().xLimit(), getDimension().yLimit(), isHovered(), isAvailable()); + matrices.push(); + matrices.translate(getDimension().x() + getXPadding(), getTextY(), 0); + textRenderer.drawWithShadow(matrices, shortenedName, 0, 0, getValueColor()); + matrices.pop(); + + drawValueText(matrices, mouseX, mouseY, delta); + if (isHovered()) { + drawHoveredControl(matrices, mouseX, mouseY, delta); + } + } + + @Override + public void postRender(MatrixStack matrices, int mouseX, int mouseY, float delta) { + if (hovered) { + YACLScreen.renderMultilineTooltip(matrices, textRenderer, wrappedTooltip, getDimension().centerX(), getDimension().y() - 5, getDimension().yLimit() + 5, screen.width, screen.height); + } + } + + protected void drawHoveredControl(MatrixStack matrices, int mouseX, int mouseY, float delta) { + + } + + protected void drawValueText(MatrixStack matrices, int mouseX, int mouseY, float delta) { + Text valueText = getValueText(); + matrices.push(); + matrices.translate(getDimension().xLimit() - textRenderer.getWidth(valueText) - getXPadding(), getTextY(), 0); + textRenderer.drawWithShadow(matrices, valueText, 0, 0, getValueColor()); + matrices.pop(); + } + + private void updateTooltip() { + this.wrappedTooltip = MultilineText.create(textRenderer, control.option().tooltip(), screen.width / 3 * 2 - 10); + } + + protected int getControlWidth() { + return isHovered() ? getHoveredControlWidth() : getUnhoveredControlWidth(); + } + + public boolean isHovered() { + return isAvailable() && (hovered || focused); + } + + protected abstract int getHoveredControlWidth(); + + protected int getUnhoveredControlWidth() { + return textRenderer.getWidth(getValueText()); + } + + protected int getXPadding() { + return 5; + } + + protected int getYPadding() { + return 2; + } + + protected Text getValueText() { + return control.formatValue(); + } + + protected boolean isAvailable() { + return control.option().available(); + } + + protected int getValueColor() { + return isAvailable() ? -1 : inactiveColor; + } + + @Override + public boolean canReset() { + return true; + } + + protected void drawOutline(MatrixStack matrices, int x1, int y1, int x2, int y2, int width, int color) { + DrawableHelper.fill(matrices, x1, y1, x2, y1 + width, color); + DrawableHelper.fill(matrices, x2, y1, x2 - width, y2, color); + DrawableHelper.fill(matrices, x1, y2, x2, y2 - width, color); + DrawableHelper.fill(matrices, x1, y1, x1 + width, y2, color); + } + + protected float getTextY() { + return getDimension().y() + getDimension().height() / 2f - textRenderer.fontHeight / 2f; + } + + @Override + public boolean changeFocus(boolean lookForwards) { + if (!isAvailable()) + return false; + + this.focused = !this.focused; + return this.focused; + } + + @Override + public void unfocus() { + this.focused = false; + } + + @Override + public boolean matchesSearch(String query) { + return optionNameString.contains(query.toLowerCase()); + } + + @Override + public SelectionType getType() { + return focused ? SelectionType.FOCUSED : isHovered() ? SelectionType.HOVERED : SelectionType.NONE; + } + + @Override + public void appendNarrations(NarrationMessageBuilder builder) { + + } +} diff --git a/src/client/java/dev/isxander/yacl/gui/controllers/LabelController.java b/src/client/java/dev/isxander/yacl/gui/controllers/LabelController.java new file mode 100644 index 0000000..8369680 --- /dev/null +++ b/src/client/java/dev/isxander/yacl/gui/controllers/LabelController.java @@ -0,0 +1,144 @@ +package dev.isxander.yacl.gui.controllers; + +import dev.isxander.yacl.api.Controller; +import dev.isxander.yacl.api.Option; +import dev.isxander.yacl.api.utils.Dimension; +import dev.isxander.yacl.gui.AbstractWidget; +import dev.isxander.yacl.gui.YACLScreen; +import net.minecraft.client.font.MultilineText; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.item.ItemStack; +import net.minecraft.text.*; + +import java.util.List; + +/** + * Simply renders some text as a label. + */ +public class LabelController implements Controller<Text> { + private final Option<Text> option; + /** + * Constructs a label controller + * + * @param option bound option + */ + public LabelController(Option<Text> option) { + this.option = option; + } + + /** + * {@inheritDoc} + */ + @Override + public Option<Text> option() { + return option; + } + + @Override + public Text formatValue() { + return option().pendingValue(); + } + + @Override + public AbstractWidget provideWidget(YACLScreen screen, Dimension<Integer> widgetDimension) { + return new LabelControllerElement(screen, widgetDimension); + } + + public class LabelControllerElement extends AbstractWidget { + private List<OrderedText> wrappedText; + protected MultilineText wrappedTooltip; + + protected final YACLScreen screen; + + public LabelControllerElement(YACLScreen screen, Dimension<Integer> dim) { + super(dim); + this.screen = screen; + option().addListener((opt, pending) -> updateTooltip()); + updateTooltip(); + updateText(); + } + + @Override + public void render(MatrixStack matrices, int mouseX, int mouseY, float delta) { + updateText(); + + float y = getDimension().y(); + for (OrderedText text : wrappedText) { + textRenderer.drawWithShadow(matrices, text, getDimension().x(), y + getYPadding(), option().available() ? -1 : 0xFFA0A0A0); + y += textRenderer.fontHeight; + } + } + + @Override + public void postRender(MatrixStack matrices, int mouseX, int mouseY, float delta) { + if (isMouseOver(mouseX, mouseY)) { + YACLScreen.renderMultilineTooltip(matrices, textRenderer, wrappedTooltip, getDimension().centerX(), getDimension().y() - 5, getDimension().yLimit() + 5, screen.width, screen.height); + + Style style = getStyle(mouseX, mouseY); + if (style != null && style.getHoverEvent() != null) { + HoverEvent hoverEvent = style.getHoverEvent(); + HoverEvent.ItemStackContent itemStackContent = hoverEvent.getValue(HoverEvent.Action.SHOW_ITEM); + if (itemStackContent != null) { + ItemStack stack = itemStackContent.asStack(); + screen.renderTooltip(matrices, screen.getTooltipFromItem(stack), stack.getTooltipData(), mouseX, mouseY); + } else { + HoverEvent.EntityContent entityContent = hoverEvent.getValue(HoverEvent.Action.SHOW_ENTITY); + if (entityContent != null) { + if (this.client.options.advancedItemTooltips) { + screen.renderTooltip(matrices, entityContent.asTooltip(), mouseX, mouseY); + } + } else { + Text text = hoverEvent.getValue(HoverEvent.Action.SHOW_TEXT); + if (text != null) { + MultilineText multilineText = MultilineText.create(textRenderer, text, getDimension().width()); + YACLScreen.renderMultilineTooltip(matrices, textRenderer, multilineText, getDimension().centerX(), getDimension().y(), getDimension().yLimit(), |
