aboutsummaryrefslogtreecommitdiff
path: root/src/client/java/dev/isxander/yacl/gui/controllers
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/java/dev/isxander/yacl/gui/controllers')
-rw-r--r--src/client/java/dev/isxander/yacl/gui/controllers/ActionController.java120
-rw-r--r--src/client/java/dev/isxander/yacl/gui/controllers/BooleanController.java158
-rw-r--r--src/client/java/dev/isxander/yacl/gui/controllers/ColorController.java206
-rw-r--r--src/client/java/dev/isxander/yacl/gui/controllers/ControllerWidget.java169
-rw-r--r--src/client/java/dev/isxander/yacl/gui/controllers/LabelController.java144
-rw-r--r--src/client/java/dev/isxander/yacl/gui/controllers/TickBoxController.java120
-rw-r--r--src/client/java/dev/isxander/yacl/gui/controllers/cycling/CyclingControllerElement.java60
-rw-r--r--src/client/java/dev/isxander/yacl/gui/controllers/cycling/CyclingListController.java79
-rw-r--r--src/client/java/dev/isxander/yacl/gui/controllers/cycling/EnumController.java60
-rw-r--r--src/client/java/dev/isxander/yacl/gui/controllers/cycling/ICyclingController.java38
-rw-r--r--src/client/java/dev/isxander/yacl/gui/controllers/package-info.java12
-rw-r--r--src/client/java/dev/isxander/yacl/gui/controllers/slider/DoubleSliderController.java114
-rw-r--r--src/client/java/dev/isxander/yacl/gui/controllers/slider/FloatSliderController.java114
-rw-r--r--src/client/java/dev/isxander/yacl/gui/controllers/slider/ISliderController.java54
-rw-r--r--src/client/java/dev/isxander/yacl/gui/controllers/slider/IntegerSliderController.java111
-rw-r--r--src/client/java/dev/isxander/yacl/gui/controllers/slider/LongSliderController.java111
-rw-r--r--src/client/java/dev/isxander/yacl/gui/controllers/slider/SliderControllerElement.java160
-rw-r--r--src/client/java/dev/isxander/yacl/gui/controllers/slider/package-info.java10
-rw-r--r--src/client/java/dev/isxander/yacl/gui/controllers/string/IStringController.java44
-rw-r--r--src/client/java/dev/isxander/yacl/gui/controllers/string/StringController.java40
-rw-r--r--src/client/java/dev/isxander/yacl/gui/controllers/string/StringControllerElement.java326
-rw-r--r--src/client/java/dev/isxander/yacl/gui/controllers/string/number/DoubleFieldController.java105
-rw-r--r--src/client/java/dev/isxander/yacl/gui/controllers/string/number/FloatFieldController.java105
-rw-r--r--src/client/java/dev/isxander/yacl/gui/controllers/string/number/IntegerFieldController.java109
-rw-r--r--src/client/java/dev/isxander/yacl/gui/controllers/string/number/LongFieldController.java109
-rw-r--r--src/client/java/dev/isxander/yacl/gui/controllers/string/number/NumberFieldController.java69
-rw-r--r--src/client/java/dev/isxander/yacl/gui/controllers/string/number/package-info.java10
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(),