aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main/java/dev/isxander/yacl/api/Binding.java15
-rw-r--r--src/main/java/dev/isxander/yacl/api/ConfigCategory.java2
-rw-r--r--src/main/java/dev/isxander/yacl/api/Controller.java4
-rw-r--r--src/main/java/dev/isxander/yacl/api/Option.java4
-rw-r--r--src/main/java/dev/isxander/yacl/api/OptionGroup.java18
-rw-r--r--src/main/java/dev/isxander/yacl/gui/AbstractWidget.java24
-rw-r--r--src/main/java/dev/isxander/yacl/gui/OptionListWidget.java105
-rw-r--r--src/main/java/dev/isxander/yacl/gui/YACLScreen.java18
-rw-r--r--src/main/java/dev/isxander/yacl/gui/controllers/ActionController.java5
-rw-r--r--src/main/java/dev/isxander/yacl/gui/controllers/BooleanController.java6
-rw-r--r--src/main/java/dev/isxander/yacl/gui/controllers/ControllerWidget.java28
-rw-r--r--src/main/java/dev/isxander/yacl/gui/controllers/EnumController.java5
-rw-r--r--src/main/java/dev/isxander/yacl/gui/controllers/LabelController.java70
-rw-r--r--src/main/java/dev/isxander/yacl/gui/controllers/TickBoxController.java5
-rw-r--r--src/main/java/dev/isxander/yacl/gui/controllers/slider/ISliderController.java4
-rw-r--r--src/main/java/dev/isxander/yacl/gui/controllers/slider/SliderControllerElement.java2
-rw-r--r--src/main/java/dev/isxander/yacl/gui/controllers/string/BasicStringController.java42
-rw-r--r--src/main/java/dev/isxander/yacl/gui/controllers/string/IStringController.java14
-rw-r--r--src/main/java/dev/isxander/yacl/gui/controllers/string/StringControllerElement.java257
-rw-r--r--src/main/java/dev/isxander/yacl/impl/OptionGroupImpl.java2
-rw-r--r--src/main/resources/fabric.mod.json1
-rw-r--r--src/main/resources/yacl.accesswidener3
-rw-r--r--src/testmod/java/dev/isxander/yacl/test/ModMenuIntegration.java26
23 files changed, 613 insertions, 47 deletions
diff --git a/src/main/java/dev/isxander/yacl/api/Binding.java b/src/main/java/dev/isxander/yacl/api/Binding.java
index 37514ca..395beb2 100644
--- a/src/main/java/dev/isxander/yacl/api/Binding.java
+++ b/src/main/java/dev/isxander/yacl/api/Binding.java
@@ -46,4 +46,19 @@ public interface Binding<T> {
minecraftOption::setValue
);
}
+
+ /**
+ * Creates an immutable binding that has no default and cannot be modified.
+ *
+ * @param value the value for the binding
+ */
+ static <T> Binding<T> immutable(T value) {
+ Validate.notNull(value, "`value` must not be null");
+
+ return new GenericBindingImpl<>(
+ value,
+ () -> value,
+ changed -> {}
+ );
+ }
}
diff --git a/src/main/java/dev/isxander/yacl/api/ConfigCategory.java b/src/main/java/dev/isxander/yacl/api/ConfigCategory.java
index 9f2f954..1b2a2bc 100644
--- a/src/main/java/dev/isxander/yacl/api/ConfigCategory.java
+++ b/src/main/java/dev/isxander/yacl/api/ConfigCategory.java
@@ -138,7 +138,7 @@ public interface ConfigCategory {
Validate.notNull(name, "`name` must not be null to build `ConfigCategory`");
List<OptionGroup> combinedGroups = new ArrayList<>();
- combinedGroups.add(new OptionGroupImpl(Text.empty(), Text.empty(), ImmutableList.copyOf(rootOptions), true));
+ combinedGroups.add(new OptionGroupImpl(Text.empty(), Text.empty(), ImmutableList.copyOf(rootOptions), false, true));
combinedGroups.addAll(groups);
Validate.notEmpty(combinedGroups, "at least one option must be added to build `ConfigCategory`");
diff --git a/src/main/java/dev/isxander/yacl/api/Controller.java b/src/main/java/dev/isxander/yacl/api/Controller.java
index 198e5df..1a00920 100644
--- a/src/main/java/dev/isxander/yacl/api/Controller.java
+++ b/src/main/java/dev/isxander/yacl/api/Controller.java
@@ -1,8 +1,8 @@
package dev.isxander.yacl.api;
import dev.isxander.yacl.api.utils.Dimension;
+import dev.isxander.yacl.gui.AbstractWidget;
import dev.isxander.yacl.gui.YACLScreen;
-import dev.isxander.yacl.gui.controllers.ControllerWidget;
import net.minecraft.text.Text;
import org.jetbrains.annotations.ApiStatus;
@@ -26,5 +26,5 @@ public interface Controller<T> {
* @param screen parent screen
*/
@ApiStatus.Internal
- ControllerWidget<?> provideWidget(YACLScreen screen, Dimension<Integer> widgetDimension);
+ AbstractWidget provideWidget(YACLScreen screen, Dimension<Integer> widgetDimension);
}
diff --git a/src/main/java/dev/isxander/yacl/api/Option.java b/src/main/java/dev/isxander/yacl/api/Option.java
index 9d6ebe2..a353ae4 100644
--- a/src/main/java/dev/isxander/yacl/api/Option.java
+++ b/src/main/java/dev/isxander/yacl/api/Option.java
@@ -3,6 +3,7 @@ package dev.isxander.yacl.api;
import dev.isxander.yacl.impl.OptionImpl;
import net.minecraft.text.MutableText;
import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
import org.apache.commons.lang3.Validate;
import org.jetbrains.annotations.NotNull;
@@ -87,7 +88,7 @@ public interface Option<T> {
}
class Builder<T> {
- private Text name;
+ private Text name = Text.literal("Name not specified!").formatted(Formatting.RED);
private final List<Text> tooltipLines = new ArrayList<>();
@@ -172,7 +173,6 @@ public interface Option<T> {
}
public Option<T> build() {
- Validate.notNull(name, "`name` must not be null when building `Option`");
Validate.notNull(controlGetter, "`control` must not be null when building `Option`");
Validate.notNull(binding, "`binding` must not be null when building `Option`");
diff --git a/src/main/java/dev/isxander/yacl/api/OptionGroup.java b/src/main/java/dev/isxander/yacl/api/OptionGroup.java
index 9376b8e..f8c346b 100644
--- a/src/main/java/dev/isxander/yacl/api/OptionGroup.java
+++ b/src/main/java/dev/isxander/yacl/api/OptionGroup.java
@@ -34,6 +34,11 @@ public interface OptionGroup {
@NotNull ImmutableList<Option<?>> options();
/**
+ * Dictates if the group should be collapsed by default.
+ */
+ boolean collapsed();
+
+ /**
* Always false when using the {@link Builder}
* used to not render the separator if true
*/
@@ -50,6 +55,7 @@ public interface OptionGroup {
private Text name = Text.empty();
private final List<Text> tooltipLines = new ArrayList<>();
private final List<Option<?>> options = new ArrayList<>();
+ private boolean collapsed = false;
private Builder() {
@@ -107,6 +113,16 @@ public interface OptionGroup {
return this;
}
+ /**
+ * Dictates if the group should be collapsed by default
+ *
+ * @see OptionGroup#collapsed()
+ */
+ public Builder collapsed(boolean collapsible) {
+ this.collapsed = collapsible;
+ return this;
+ }
+
public OptionGroup build() {
Validate.notEmpty(options, "`options` must not be empty to build `OptionGroup`");
@@ -119,7 +135,7 @@ public interface OptionGroup {
concatenatedTooltip.append(line);
}
- return new OptionGroupImpl(name, concatenatedTooltip, ImmutableList.copyOf(options), false);
+ return new OptionGroupImpl(name, concatenatedTooltip, ImmutableList.copyOf(options), collapsed, false);
}
}
}
diff --git a/src/main/java/dev/isxander/yacl/gui/AbstractWidget.java b/src/main/java/dev/isxander/yacl/gui/AbstractWidget.java
index 7affbd4..a6e75f9 100644
--- a/src/main/java/dev/isxander/yacl/gui/AbstractWidget.java
+++ b/src/main/java/dev/isxander/yacl/gui/AbstractWidget.java
@@ -1,12 +1,14 @@
package dev.isxander.yacl.gui;
import com.mojang.blaze3d.systems.RenderSystem;
+import dev.isxander.yacl.api.utils.Dimension;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.font.TextRenderer;
import net.minecraft.client.gui.Drawable;
import net.minecraft.client.gui.DrawableHelper;
import net.minecraft.client.gui.Element;
import net.minecraft.client.gui.Selectable;
+import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder;
import net.minecraft.client.gui.widget.ClickableWidget;
import net.minecraft.client.render.GameRenderer;
import net.minecraft.client.sound.PositionedSoundInstance;
@@ -18,7 +20,27 @@ public abstract class AbstractWidget implements Element, Drawable, Selectable {
protected final MinecraftClient client = MinecraftClient.getInstance();
protected final TextRenderer textRenderer = client.textRenderer;
- public void tick() {
+ protected Dimension<Integer> dim;
+
+ public AbstractWidget(Dimension<Integer> dim) {
+ this.dim = dim;
+ }
+
+ public void setDimension(Dimension<Integer> dim) {
+ this.dim = dim;
+ }
+
+ @Override
+ public SelectionType getType() {
+ return SelectionType.NONE;
+ }
+
+ public void unfocus() {
+
+ }
+
+ @Override
+ public void appendNarrations(NarrationMessageBuilder builder) {
}
diff --git a/src/main/java/dev/isxander/yacl/gui/OptionListWidget.java b/src/main/java/dev/isxander/yacl/gui/OptionListWidget.java
index 1f118cc..11c9fa6 100644
--- a/src/main/java/dev/isxander/yacl/gui/OptionListWidget.java
+++ b/src/main/java/dev/isxander/yacl/gui/OptionListWidget.java
@@ -5,7 +5,6 @@ import dev.isxander.yacl.api.ConfigCategory;
import dev.isxander.yacl.api.Option;
import dev.isxander.yacl.api.OptionGroup;
import dev.isxander.yacl.api.utils.Dimension;
-import dev.isxander.yacl.gui.controllers.ControllerWidget;
import dev.isxander.yacl.impl.YACLConstants;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.font.TextRenderer;
@@ -14,12 +13,15 @@ import net.minecraft.client.gui.Selectable;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder;
import net.minecraft.client.gui.screen.narration.NarrationPart;
+import net.minecraft.client.gui.widget.ButtonWidget;
import net.minecraft.client.gui.widget.ElementListWidget;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.text.OrderedText;
+import net.minecraft.text.Text;
import java.util.Collections;
import java.util.List;
+import java.util.function.Supplier;
public class OptionListWidget extends ElementListWidget<OptionListWidget.Entry> {
@@ -29,15 +31,32 @@ public class OptionListWidget extends ElementListWidget<OptionListWidget.Entry>
right = width;
for (OptionGroup group : category.groups()) {
- if (!group.isRoot())
- addEntry(new GroupSeparatorEntry(group, screen));
+ Supplier<Boolean> viewableSupplier;
+ if (!group.isRoot()) {
+ GroupSeparatorEntry groupSeparatorEntry = new GroupSeparatorEntry(group, screen);
+ viewableSupplier = groupSeparatorEntry::isExpanded;
+ addEntry(groupSeparatorEntry);
+ } else {
+ viewableSupplier = () -> true;
+ }
+
for (Option<?> option : group.options()) {
- addEntry(new OptionEntry(option.controller().provideWidget(screen, null)));
+ addEntry(new OptionEntry(option.controller().provideWidget(screen, null), viewableSupplier));
}
}
}
@Override
+ public boolean mouseClicked(double mouseX, double mouseY, int button) {
+ for (Entry child : children()) {
+ if (child != getEntryAtPosition(mouseX, mouseY) && child instanceof OptionEntry optionEntry)
+ optionEntry.widget.unfocus();
+ }
+
+ return super.mouseClicked(mouseX, mouseY, button);
+ }
+
+ @Override
public boolean mouseScrolled(double mouseX, double mouseY, double amount) {
for (Entry child : children()) {
if (child.mouseScrolled(mouseX, mouseY, amount))
@@ -48,6 +67,26 @@ public class OptionListWidget extends ElementListWidget<OptionListWidget.Entry>
}
@Override
+ public boolean keyPressed(int keyCode, int scanCode, int modifiers) {
+ for (Entry child : children()) {
+ if (child.keyPressed(keyCode, scanCode, modifiers))
+ return true;
+ }
+
+ return super.keyPressed(keyCode, scanCode, modifiers);
+ }
+
+ @Override
+ public boolean charTyped(char chr, int modifiers) {
+ for (Entry child : children()) {
+ if (child.charTyped(chr, modifiers))
+ return true;
+ }
+
+ return super.charTyped(chr, modifiers);
+ }
+
+ @Override
protected int getScrollbarPositionX() {
return left + super.getScrollbarPositionX();
}
@@ -59,15 +98,24 @@ public class OptionListWidget extends ElementListWidget<OptionListWidget.Entry>
fill(matrices, left, top, right, bottom, 0x6B000000);
}
- public static abstract class Entry extends ElementListWidget.Entry<Entry> {
+ @Override
+ public List<Entry> children() {
+ return super.children().stream().filter(Entry::isViewable).toList();
+ }
+ public static abstract class Entry extends ElementListWidget.Entry<Entry> {
+ public boolean isViewable() {
+ return true;
+ }
}
private static class OptionEntry extends Entry {
- private final ControllerWidget<?> widget;
+ public final AbstractWidget widget;
+ private final Supplier<Boolean> viewableSupplier;
- public OptionEntry(ControllerWidget<?> widget) {
+ public OptionEntry(AbstractWidget widget, Supplier<Boolean> viewableSupplier) {
this.widget = widget;
+ this.viewableSupplier = viewableSupplier;
}
@Override
@@ -83,6 +131,21 @@ public class OptionListWidget extends ElementListWidget<OptionListWidget.Entry>
}
@Override
+ public boolean keyPressed(int keyCode, int scanCode, int modifiers) {
+ return widget.keyPressed(keyCode, scanCode, modifiers);
+ }
+
+ @Override
+ public boolean charTyped(char chr, int modifiers) {
+ return widget.charTyped(chr, modifiers);
+ }
+
+ @Override
+ public boolean isViewable() {
+ return viewableSupplier.get();
+ }
+
+ @Override
public List<? extends Selectable> selectableChildren() {
return ImmutableList.of(widget);
}
@@ -97,20 +160,36 @@ public class OptionListWidget extends ElementListWidget<OptionListWidget.Entry>
private final OptionGroup group;
private final List<OrderedText> wrappedTooltip;
+ private final ButtonWidget expandMinimizeButton;
+
private float hoveredTicks = 0;
private int prevMouseX, prevMouseY;
private final Screen screen;
private final TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer;
+ private boolean groupExpanded;
+
public GroupSeparatorEntry(OptionGroup group, Screen screen) {
this.group = group;
this.screen = screen;
this.wrappedTooltip = textRenderer.wrapLines(group.tooltip(), screen.width / 2);
+ this.groupExpanded = !group.collapsed();
+ this.expandMinimizeButton = new ButtonWidget(0, 0, 20, 20, Text.empty(), btn -> {
+ groupExpanded = !groupExpanded;
+ updateExpandMinimizeText();
+ });
+ updateExpandMinimizeText();
}
@Override
public void render(MatrixStack matrices, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) {
+ expandMinimizeButton.x = x + entryWidth - expandMinimizeButton.getWidth();
+ expandMinimizeButton.y = y + entryHeight / 2 - expandMinimizeButton.getHeight() / 2;
+ if (hovered)
+ expandMinimizeButton.render(matrices, mouseX, mouseY, tickDelta);
+
+ hovered &= !expandMinimizeButton.isMouseOver(mouseX, mouseY);
if (hovered && (!YACLConstants.HOVER_MOUSE_RESET || (mouseX == prevMouseX && mouseY == prevMouseY)))
hoveredTicks += tickDelta;
else
@@ -126,6 +205,14 @@ public class OptionListWidget extends ElementListWidget<OptionListWidget.Entry>
prevMouseY = mouseY;
}
+ public boolean isExpanded() {
+ return groupExpanded;
+ }
+
+ private void updateExpandMinimizeText() {
+ expandMinimizeButton.setMessage(Text.of(isExpanded() ? "\u25BC" : "\u25C0"));
+ }
+
@Override
public List<? extends Selectable> selectableChildren() {
return ImmutableList.of(new Selectable() {
@@ -143,9 +230,7 @@ public class OptionListWidget extends ElementListWidget<OptionListWidget.Entry>
@Override
public List<? extends Element> children() {
- return Collections.emptyList();
+ return ImmutableList.of(expandMinimizeButton);
}
-
-
}
}
diff --git a/src/main/java/dev/isxander/yacl/gui/YACLScreen.java b/src/main/java/dev/isxander/yacl/gui/YACLScreen.java
index 235364f..ccd3929 100644
--- a/src/main/java/dev/isxander/yacl/gui/YACLScreen.java
+++ b/src/main/java/dev/isxander/yacl/gui/YACLScreen.java
@@ -121,6 +121,24 @@ public class YACLScreen extends Screen {
}
}
+ @Override
+ public boolean keyPressed(int keyCode, int scanCode, int modifiers) {
+ if (optionList.keyPressed(keyCode, scanCode, modifiers)) {
+ return true;
+ }
+
+ return super.keyPressed(keyCode, scanCode, modifiers);
+ }
+
+ @Override
+ public boolean charTyped(char chr, int modifiers) {
+ if (optionList.charTyped(chr, modifiers)) {
+ return true;
+ }
+
+ return super.charTyped(chr, modifiers);
+ }
+
public void changeCategory(int idx) {
currentCategoryIdx = idx;
refreshGUI();
diff --git a/src/main/java/dev/isxander/yacl/gui/controllers/ActionController.java b/src/main/java/dev/isxander/yacl/gui/controllers/ActionController.java
index e0d0230..e337ee3 100644
--- a/src/main/java/dev/isxander/yacl/gui/controllers/ActionController.java
+++ b/src/main/java/dev/isxander/yacl/gui/controllers/ActionController.java
@@ -3,6 +3,7 @@ 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.text.Text;
import org.jetbrains.annotations.ApiStatus;
@@ -62,7 +63,7 @@ public class ActionController implements Controller<Consumer<YACLScreen>> {
* {@inheritDoc}
*/
@Override
- public ControllerWidget<ActionController> provideWidget(YACLScreen screen, Dimension<Integer> widgetDimension) {
+ public AbstractWidget provideWidget(YACLScreen screen, Dimension<Integer> widgetDimension) {
return new ActionControllerElement(this, screen, widgetDimension);
}
@@ -88,7 +89,7 @@ public class ActionController implements Controller<Consumer<YACLScreen>> {
@Override
public boolean keyPressed(int keyCode, int scanCode, int modifiers) {
- if (!focused && !hovered) {
+ if (!focused) {
return false;
}
diff --git a/src/main/java/dev/isxander/yacl/gui/controllers/BooleanController.java b/src/main/java/dev/isxander/yacl/gui/controllers/BooleanController.java
index a338f5f..38be6db 100644
--- a/src/main/java/dev/isxander/yacl/gui/controllers/BooleanController.java
+++ b/src/main/java/dev/isxander/yacl/gui/controllers/BooleanController.java
@@ -3,8 +3,8 @@ 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.gui.screen.Screen;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
@@ -98,7 +98,7 @@ public class BooleanController implements Controller<Boolean> {
* {@inheritDoc}
*/
@Override
- public ControllerWidget<BooleanController> provideWidget(YACLScreen screen, Dimension<Integer> widgetDimension) {
+ public AbstractWidget provideWidget(YACLScreen screen, Dimension<Integer> widgetDimension) {
return new BooleanControllerElement(this, screen, widgetDimension);
}
@@ -143,7 +143,7 @@ public class BooleanController implements Controller<Boolean> {
@Override
public boolean keyPressed(int keyCode, int scanCode, int modifiers) {
- if (!focused && !hovered) {
+ if (!focused) {
return false;
}
diff --git a/src/main/java/dev/isxander/yacl/gui/controllers/ControllerWidget.java b/src/main/java/dev/isxander/yacl/gui/controllers/ControllerWidget.java
index d712e56..29722e1 100644
--- a/src/main/java/dev/isxander/yacl/gui/controllers/ControllerWidget.java
+++ b/src/main/java/dev/isxander/yacl/gui/controllers/ControllerWidget.java
@@ -16,8 +16,6 @@ import java.util.List;
public abstract class ControllerWidget<T extends Controller<?>> extends AbstractWidget {
protected final T control;
protected final List<OrderedText> wrappedTooltip;
-
- protected Dimension<Integer> dim;
protected final YACLScreen screen;
protected boolean focused = false;
@@ -27,8 +25,8 @@ public abstract class ControllerWidget<T extends Controller<?>> extends Abstract
private int prevMouseX, prevMouseY;
public ControllerWidget(T control, YACLScreen screen, Dimension<Integer> dim) {
+ super(dim);
this.control = control;
- this.dim = dim;
this.screen = screen;
this.wrappedTooltip = textRenderer.wrapLines(control.option().tooltip(), screen.width / 2);
}
@@ -47,7 +45,7 @@ public abstract class ControllerWidget<T extends Controller<?>> extends Abstract
boolean firstIter = true;
while (textRenderer.getWidth(nameString) > dim.width() - getControlWidth() - getXPadding() - 7) {
- nameString = nameString.substring(0, nameString.length() - (firstIter ? 2 : 5)).trim();
+ nameString = nameString.substring(0, Math.max(nameString.length() - (firstIter ? 2 : 5), 0)).trim();
nameString += "...";
firstIter = false;
@@ -55,14 +53,14 @@ public abstract class ControllerWidget<T extends Controller<?>> extends Abstract
Text shortenedName = Text.literal(nameString).fillStyle(name.getStyle());
- drawButtonRect(matrices, dim.x(), dim.y(), dim.xLimit(), dim.yLimit(), hovered || focused);
+ drawButtonRect(matrices, dim.x(), dim.y(), dim.xLimit(), dim.yLimit(), isHovered());
matrices.push();
matrices.translate(dim.x() + getXPadding(), getTextY(), 0);
textRenderer.drawWithShadow(matrices, shortenedName, 0, 0, -1);
matrices.pop();
drawValueText(matrices, mouseX, mouseY, delta);
- if (hovered || focused) {
+ if (isHovered()) {
drawHoveredControl(matrices, mouseX, mouseY, delta);
}
@@ -88,11 +86,16 @@ public abstract class ControllerWidget<T extends Controller<?>> extends Abstract
@Override
public boolean isMouseOver(double mouseX, double mouseY) {
+ if (dim == null) return false;
return this.dim.isPointInside((int) mouseX, (int) mouseY);
}
protected int getControlWidth() {
- return hovered || focused ? getHoveredControlWidth() : getUnhoveredControlWidth();
+ return isHovered() ? getHoveredControlWidth() : getUnhoveredControlWidth();
+ }
+
+ public boolean isHovered() {
+ return hovered || focused;
}
protected abstract int getHoveredControlWidth();
@@ -124,10 +127,6 @@ public abstract class ControllerWidget<T extends Controller<?>> extends Abstract
return dim.y() + dim.height() / 2f - textRenderer.fontHeight / 2f;
}
- public void setDimension(Dimension<Integer> dim) {
- this.dim = dim;
- }
-
@Override
public boolean changeFocus(boolean lookForwards) {
this.focused = !this.focused;
@@ -135,8 +134,13 @@ public abstract class ControllerWidget<T extends Controller<?>> extends Abstract
}
@Override
+ public void unfocus() {
+ this.focused = false;
+ }
+
+ @Override
public SelectionType getType() {
- return focused ? SelectionType.FOCUSED : hovered ? SelectionType.HOVERED : SelectionType.NONE;
+ return focused ? SelectionType.FOCUSED : isHovered() ? SelectionType.HOVERED : SelectionType.NONE;
}
@Override
diff --git a/src/main/java/dev/isxander/yacl/gui/controllers/EnumController.java b/src/main/java/dev/isxander/yacl/gui/controllers/EnumController.java
index cb25963..9d8e59c 100644
--- a/src/main/java/dev/isxander/yacl/gui/controllers/EnumController.java
+++ b/src/main/java/dev/isxander/yacl/gui/controllers/EnumController.java
@@ -4,6 +4,7 @@ import dev.isxander.yacl.api.Controller;
import dev.isxander.yacl.api.NameableEnum;
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.gui.screen.Screen;
import net.minecraft.text.Text;
@@ -72,7 +73,7 @@ public class EnumController<T extends Enum<T>> implements Controller<T> {
* {@inheritDoc}
*/
@Override
- public ControllerWidget<EnumController<T>> provideWidget(YACLScreen screen, Dimension<Integer> widgetDimension) {
+ public AbstractWidget provideWidget(YACLScreen screen, Dimension<Integer> widgetDimension) {
return new EnumControllerElement<>(this, screen, widgetDimension, option().typeClass().getEnumConstants());
}
@@ -108,7 +109,7 @@ public class EnumController<T extends Enum<T>> implements Controller<T> {
@Override
public boolean keyPressed(int keyCode, int scanCode, int modifiers) {
- if (!focused && !hovered)
+ if (!focused)
return false;
switch (keyCode) {
diff --git a/src/main/java/dev/isxander/yacl/gui/controllers/LabelController.java b/src/main/java/dev/isxander/yacl/gui/controllers/LabelController.java
new file mode 100644
index 0000000..63b1b42
--- /dev/null
+++ b/src/main/java/dev/isxander/yacl/gui/controllers/LabelController.java
@@ -0,0 +1,70 @@
+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.math.MatrixStack;
+import net.minecraft.text.Text;
+import org.jetbrains.annotations.ApiStatus;
+
+public class LabelController implements Controller<Text> {
+ private final Option<Text> option;
+ private final int color;
+
+ /**
+ * Constructs a label controller
+ *
+ * @param option bound option
+ */
+ public LabelController(Option<Text> option) {
+ this(option, -1);
+ }
+
+ /**
+ * Constructs a label controller
+ *
+ * @param option bound option
+ * @param color color of the label
+ */
+ public LabelController(Option<Text> option, int color) {
+ this.option = option;
+ this.color = color;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Option<Text> option() {
+ return option;
+ }
+
+ public int color() {
+ return color;
+ }
+
+ @Override
+ public Text formatValue() {
+ return option().pendingValue();
+ }
+
+ @Override
+ public AbstractWidget provideWidget(YACLScreen screen, Dimension<Integer> widgetDimension) {
+ return new LabelControllerElement(widgetDimension);
+ }
+
+ @ApiStatus.Internal
+ public class LabelControllerElement extends AbstractWidget {
+
+ public LabelControllerElement(Dimension<Integer> dim) {
+ super(dim);
+ }
+
+ @Override
+ public void render(MatrixStack matrices, int mouseX, int mouseY, float delta) {
+ textRenderer.drawWithShadow(matrices, formatValue(), dim.x(), dim.centerY() - textRenderer.fontHeight / 2f, color());
+ }
+ }
+}
diff --git a/src/main/java/dev/isxander/yacl/gui/controllers/TickBoxController.java b/src/main/java/dev/isxander/yacl/gui/controllers/TickBoxController.java
index 106f76a..df0ae83 100644
--- a/src/main/java/dev/isxander/yacl/gui/controllers/TickBoxController.java
+++ b/src/main/java/dev/isxander/yacl/gui/controllers/TickBoxController.java
@@ -3,6 +3,7 @@ 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.gui.DrawableHelper;
import net.minecraft.client.util.math.MatrixStack;
@@ -45,7 +46,7 @@ public class TickBoxController implements Controller<Boolean> {
* {@inheritDoc}
*/
@Override
- public ControllerWidget<TickBoxController> provideWidget(YACLScreen screen, Dimension<Integer> widgetDimension) {
+ public AbstractWidget provideWidget(YACLScreen screen, Dimension<Integer> widgetDimension) {
return new TickBoxControllerElement(this, screen, widgetDimension);
}
@@ -97,7 +98,7 @@ public class TickBoxController implements Controller<Boolean> {
@Override
public boolean keyPressed(int keyCode, int scanCode, int modifiers) {
- if (!focused && !hovered) {
+ if (!focused) {
return false;
}
diff --git a/src/main/java/dev/isxander/yacl/gui/controllers/slider/ISliderController.java b/src/main/java/dev/isxander/yacl/gui/controllers/slider/ISliderController.java
index ede3456..aa3c18f 100644
--- a/src/main/java/dev/isxander/yacl/gui/controllers/slider/ISliderController.java
+++ b/src/main/java/dev/isxander/yacl/gui/controllers/slider/ISliderController.java
@@ -2,8 +2,8 @@ package dev.isxander.yacl.gui.controllers.slider;
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.gui.controllers.ControllerWidget;
/**
* Simple custom slider implementation that shifts the current value across when shown.
@@ -48,7 +48,7 @@ public interface ISliderController<T extends Number> extends Controller<T> {
* {@inheritDoc}
*/
@Override
- default ControllerWidget<?> provideWidget(YACLScreen screen, Dimension<Integer> widgetDimension) {
+ default AbstractWidget provideWidget(YACLScreen screen, Dimension<Integer> widgetDimension) {
return new SliderControllerElement(this, screen, widgetDimension, min(), max(), interval());
}
}
diff --git a/src/main/java/dev/isxander/yacl/gui/controllers/slider/SliderControllerElement.java b/src/main/java/dev/isxander/yacl/gui/controllers/slider/SliderControllerElement.java
index b587258..7eb7310 100644
--- a/src/main/java/dev/isxander/yacl/gui/controllers/slider/SliderControllerElement.java
+++ b/src/main/java/dev/isxander/yacl/gui/controllers/slider/SliderControllerElement.java
@@ -101,7 +101,7 @@ public class SliderControllerElement extends ControllerWidget<ISliderController<
@Override
public boolean keyPressed(int keyCode, int scanCode, int modifiers) {
- if (!focused && !hovered)
+ if (!focused)
return false;
switch (keyCode) {
diff --git a/src/main/java/dev/isxander/yacl/gui/controllers/string/BasicStringController.java b/src/main/java/dev/isxander/yacl/gui/controllers/string/BasicStringController.java
new file mode 100644
index 0000000..edda506
--- /dev/null
+++ b/src/main/java/dev/isxander/yacl/gui/controllers/string/BasicStringController.java
@@ -0,0 +1,42 @@
+package dev.isxander.yacl.gui.controllers.string;
+
+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;
+
+public class BasicStringController implements IStringController<String> {
+ private final Option<String> option;
+
+ /**
+ * Constructs a tickbox controller
+ *
+ * @param option bound option
+ */
+ public BasicStringController(Option<String> option) {
+ this.option = option;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Option<String> option() {
+ return option;
+ }
+
+ @Override
+ public String getString() {
+ return option().pendingValue();
+ }
+
+ @Override
+ public void setFromString(String value) {
+ option().requestSet(value);
+ }
+
+ @Override
+ public AbstractWidget provideWidget(YACLScreen screen, Dimension<Integer> widgetDimension) {
+ return new StringControllerElement(this, screen, widgetDimension);
+ }
+}
diff --git a/src/main/java/dev/isxander/yacl/gui/controllers/string/IStringController.java b/src/main/java/dev/isxander/yacl/gui/controllers/string/IStringController.java
new file mode 100644
index 0000000..ae66433
--- /dev/null
+++ b/src/main/java/dev/isxander/yacl/gui/controllers/string/IStringController.java
@@ -0,0 +1,14 @@
+package dev.isxander.yacl.gui.controllers.string;
+
+import dev.isxander.yacl.api.Controller;
+import net.minecraft.text.Text;
+
+public interface IStringController<T> extends Controller<T> {
+ String getString();
+ void setFromString(String value);
+
+ @Override
+ default Text formatValue() {
+ return Text.of(getString());
+ }
+}
diff --git a/src/main/java/dev/isxander/yacl/gui/controllers/string/StringControllerElement.java b/src/main/java/dev/isxander/yacl/gui/controllers/string/StringControllerElement.java
new file mode 100644
index 0000000..ed066f2
--- /dev/null
+++ b/src/main/java/dev/isxander/yacl/gui/controllers/string/StringControllerElement.java
@@ -0,0 +1,257 @@
+package dev.isxander.yacl.gui.controllers.string;
+
+import dev.isxander.yacl.api.utils.Dimension;
+import dev.isxander.yacl.gui.YACLScreen;
+import dev.isxander.yacl.gui.controllers.ControllerWidget;
+import net.minecraft.client.gui.DrawableHelper;
+import net.minecraft.client.gui.screen.Screen;
+import net.minecraft.client.util.math.MatrixStack;
+import net.minecraft.text.Text;
+import net.minecraft.util.Formatting;
+import net.minecraft.util.Pair;
+import org.lwjgl.glfw.GLFW;
+
+public class StringControllerElement extends ControllerWidget<IStringController<?>> {
+ protected StringBuilder inputField;
+ protected Dimension<Integer> inputFieldBounds;
+ protected boolean inputFieldFocused;
+
+ protected int caretPos;
+ protected int selectionLength;
+
+ protected float ticks;
+
+ private final Text emptyText;
+
+ public StringControllerElement(IStringController<?> control, YACLScreen screen, Dimension<Integer> dim) {
+ super(control, screen, dim);
+ inputField = new StringBuilder(control.getString());
+ inputFieldFocused = false;
+ caretPos = inputField.length();
+ selectionLength = 0;
+ emptyText = Text.literal("Click to type...").formatted(Formatting.GRAY);
+ }
+
+ @Override
+ protected void drawHoveredControl(MatrixStack matrices, int mouseX, int mouseY, float delta) {
+ ticks += delta;
+
+ DrawableHelper.fill(matrices, inputFieldBounds.x(), inputFieldBounds.yLimit(), inputFieldBounds.xLimit(), inputFieldBounds.yLimit() + 1, -1);
+ DrawableHelper.fill(matrices, inputFieldBounds.x() + 1, inputFieldBounds.yLimit() + 1, inputFieldBounds.xLimit() + 1, inputFieldBounds.yLimit() + 2, 0xFF404040);
+
+ if (inputFieldFocused || focused) {
+ int caretX = inputFieldBounds.x() + textRenderer.getWidth(inputField.substring(0, caretPos)) - 1;
+ if (inputField.isEmpty())
+ caretX += inputFieldBounds.width() / 2;
+
+ if (ticks % 20 <= 10) {
+ DrawableHelper.fill(matrices, caretX, inputFieldBounds.y(), caretX + 1, inputFieldBounds.yLimit(), -1);
+ }
+
+ if (selectionLength != 0) {
+ int selectionX = inputFieldBounds.x() + textRenderer.getWidth(inputField.substring(0, caretPos + selectionLength));
+ DrawableHelper.fill(matrices, caretX, inputFieldBounds.y() - 1, selectionX, inputFieldBounds.yLimit(), 0x803030FF);
+ }
+ }
+ }
+
+ @Override
+ public boolean mouseClicked(double mouseX, double mouseY, int button) {
+ if (inputFieldBounds.isPointInside((int) mouseX, (int) mouseY)) {
+ if (!inputFieldFocused)
+ inputFieldFocused = true;
+ else {
+ int textWidth = (int) mouseX - inputFieldBounds.x();
+ caretPos = textRenderer.trimToWidth(control.getString(), textWidth).length();
+ selectionLength = 0;
+ }
+ } else {
+ inputFieldFocused = false;
+ }
+
+ return super.mouseClicked(mouseX, mouseY, button);
+ }
+
+ @Override
+ public boolean keyPressed(int keyCode, int scanCode, int modifiers) {
+ if (!inputFieldFocused)
+ return false;
+
+ switch (keyCode) {
+ case GLFW.GLFW_KEY_LEFT -> {
+ if (Screen.hasShiftDown()) {
+ if (Screen.hasControlDown()) {
+ int spaceChar = findSpaceIndex(true);
+ selectionLength += caretPos - spaceChar;
+ caretPos = spaceChar;
+ } else if (caretPos > 0) {
+ caretPos--;
+ selectionLength += 1;
+ }
+ } else {
+ if (caretPos > 0)
+ caretPos--;
+ selectionLength = 0;
+ }
+
+ return true;
+ }
+ case GLFW.GLFW_KEY_RIGHT -> {
+ if (Screen.hasShiftDown()) {
+ if (Screen.hasControlDown()) {
+ int spaceChar = findSpaceIndex(false);
+ selectionLength -= spaceChar - caretPos;
+ caretPos = spaceChar;
+ } else if (caretPos < inputField.length()) {
+ caretPos++;
+ selectionLength -= 1;
+ }
+ } else {
+ if (caretPos < inputField.length())
+ caretPos++;
+ selectionLength = 0;
+ }
+
+ return true;
+ }
+ case GLFW.GLFW_KEY_BACKSPACE -> {
+ if (selectionLength != 0) {
+ write("");
+ } else if (caretPos > 0) {
+ inputField.deleteCharAt(caretPos - 1);
+ updateControl();
+ caretPos--;
+ }
+ return true;
+ }
+ case GLFW.GLFW_KEY_DELETE -> {
+ if (caretPos < inputField.length()) {
+ inputField.deleteCharAt(caretPos);
+ updateControl();
+ }
+ return true;
+ }
+ }
+
+ if (Screen.isPaste(keyCode)) {
+ this.write(client.keyboard.getClipboard());
+ } else if (Screen.isCopy(keyCode) && selectionLength != 0) {
+ client.keyboard.setClipboard(getSelection());
+ } else if (Screen.isCut(keyCode) && selectionLength != 0) {
+ client.keyboard.setClipboard(getSelection());
+ this.write("");
+ } else if (Screen.isSelectAll(keyCode)) {
+ caretPos = inputField.length();
+ selectionLength = -caretPos;
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean charTyped(char chr, int modifiers) {
+ if (!inputFieldFocused)
+ return false;
+
+ write(Character.toString(chr));
+
+ return true;
+ }
+
+ public void write(String string) {
+ if (selectionLength == 0) {
+ string = textRenderer.trimToWidth(string, getMaxLength() - textRenderer.getWidth(inputField.toString()));
+
+ inputField.insert(caretPos, string);
+ caretPos += string.length();
+ } else {
+ int start = getSelectionStart();
+ int end = getSelectionEnd();
+
+ string = textRenderer.trimToWidth(string, getMaxLength() - textRenderer.getWidth(inputField.toString()) + textRenderer.getWidth(inputField.substring(start, end)));
+
+ inputField.replace(start, end, string);
+ caretPos = start + string.length();
+ selectionLength = 0;
+ }
+ updateControl();
+ }
+
+ public int getMaxLength() {
+ return dim.width() / 8 * 7;
+ }
+
+ public int getSelectionStart() {
+ return Math.min(caretPos, caretPos + selectionLength);
+ }
+
+ public int getSelectionEnd() {
+ return Math.max(caretPos, caretPos + selectionLength);
+ }
+
+ protected String getSelection() {
+ return inputField.substring(getSelectionStart(), getSelectionEnd());
+ }
+
+ protected int findSpaceIndex(boolean reverse) {
+ int i;
+ int fromIndex = caretPos;
+ if (reverse) {
+ if (caretPos > 0)
+ fromIndex -= 1;
+ i = this.inputField.lastIndexOf(" ", fromIndex);
+
+ if (i == -1) i = 0;
+ } else {
+ if (caretPos < inputField.length())
+ fromIndex += 1;
+ i = this.inputField.indexOf(" ", fromIndex);
+
+ if (i == -1) i = inputField.length();
+ }
+
+ System.out.println(i);
+ return i;
+ }
+
+ @Override
+ public boolean changeFocus(boolean lookForwards) {
+ return inputFieldFocused = super.changeFocus(lookForwards);
+ }
+
+ @Override
+ public void unfocus() {
+ super.unfocus();
+ inputFieldFocused = false;
+ }
+
+ @Override
+ public void setDimension(Dimension<Integer> dim) {
+ super.setDimension(dim);
+
+ int width = Math.max(6, textRenderer.getWidth(getValueText()));
+ inputFieldBounds = Dimension.ofInt(dim.xLimit() - getXPadding() - width, dim.centerY() - textRenderer.fontHeight / 2, width, textRenderer.fontHeight);
+ }
+
+ @Override
+ public boolean isHovered() {
+ return super.isHovered() || inputFieldFocused;
+ }
+
+ protected void updateControl() {
+ control.setFromString(inputField.toString());
+ }
+
+ @Override
+ protected int getHoveredControlWidth() {
+ return getUnhoveredControlWidth();
+ }
+
+ @Override
+ protected Text getValueText() {
+ if (!inputFieldFocused && inputField.isEmpty())
+ return emptyText;
+
+ return super.getValueText();
+ }
+}
diff --git a/src/main/java/dev/isxander/yacl/impl/OptionGroupImpl.java b/src/main/java/dev/isxander/yacl/impl/OptionGroupImpl.java
index 1f2d4e2..58bc96b 100644
--- a/src/main/java/dev/isxander/yacl/impl/OptionGroupImpl.java
+++ b/src/main/java/dev/isxander/yacl/impl/OptionGroupImpl.java
@@ -6,5 +6,5 @@ import dev.isxander.yacl.api.OptionGroup;
import net.minecraft.text.Text;
import org.jetbrains.annotations.NotNull;
-public record OptionGroupImpl(@NotNull Text name, @NotNull Text tooltip, ImmutableList<Option<?>> options, boolean isRoot) implements OptionGroup {
+public record OptionGroupImpl(@NotNull Text name, @NotNull Text tooltip, ImmutableList<Option<?>> options, boolean collapsed, boolean isRoot) implements OptionGroup {
}
diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json
index dba6e5a..fc5a14f 100644
--- a/src/main/resources/fabric.mod.json
+++ b/src/main/resources/fabric.mod.json
@@ -26,6 +26,7 @@
"mixins": [
"yet-another-config-lib.mixins.json"
],
+ "accessWidener": "yacl.accesswidener",
"custom": {
"modmenu": {
"badges": ["library"]
diff --git a/src/main/resources/yacl.accesswidener b/src/main/resources/yacl.accesswidener
new file mode 100644
index 0000000..b43aaf3
--- /dev/null
+++ b/src/main/resources/yacl.accesswidener
@@ -0,0 +1,3 @@
+accessWidener v1 named
+
+extendable method net/minecraft/client/gui/widget/EntryListWidget children ()Ljava/util/List;
diff --git a/src/testmod/java/dev/isxander/yacl/test/ModMenuIntegration.java b/src/testmod/java/dev/isxander/yacl/test/ModMenuIntegration.java
index ac313e2..b966ae8 100644
--- a/src/testmod/java/dev/isxander/yacl/test/ModMenuIntegration.java
+++ b/src/testmod/java/dev/isxander/yacl/test/ModMenuIntegration.java
@@ -3,14 +3,12 @@ package dev.isxander.yacl.test;
import com.terraformersmc.modmenu.api.ConfigScreenFactory;
import com.terraformersmc.modmenu.api.ModMenuApi;
import dev.isxander.yacl.api.*;
-import dev.isxander.yacl.gui.controllers.ActionController;
-import dev.isxander.yacl.gui.controllers.BooleanController;
-import dev.isxander.yacl.gui.controllers.EnumController;
-import dev.isxander.yacl.gui.controllers.TickBoxController;
+import dev.isxander.yacl.gui.controllers.*;
import dev.isxander.yacl.gui.controllers.slider.DoubleSliderController;
import dev.isxander.yacl.gui.controllers.slider.FloatSliderController;
import dev.isxander.yacl.gui.controllers.slider.IntegerSliderController;
import dev.isxander.yacl.gui.controllers.slider.LongSliderController;
+import dev.isxander.yacl.gui.controllers.string.BasicStringController;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.option.GraphicsMode;
@@ -52,6 +50,7 @@ public class ModMenuIntegration implements ModMenuApi {
.group(OptionGroup.createBuilder()
.name(Text.of("Boolean Controllers"))
.tooltip(Text.of("Test!"))
+ .collapsed(true)
.option(Option.createBuilder(boolean.class)
.name(Text.of("Boolean Toggle"))
.tooltip(Text.of("A simple toggle button."))
@@ -123,6 +122,18 @@ public class ModMenuIntegration implements ModMenuApi {
.build())
.build())
.group(OptionGroup.createBuilder()
+ .name(Text.of("Input Field Controllers"))
+ .option(Option.createBuilder(String.class)
+ .name(Text.of("Text Option"))
+ .binding(
+ "Hello",
+ () -> TestSettings.textField,
+ value -> TestSettings.textField = value
+ )
+ .controller(BasicStringController::new)
+ .build())
+ .build())
+ .group(OptionGroup.createBuilder()
.name(Text.of("Enum Controllers"))
.option(Option.createBuilder(TestSettings.Alphabet.class)
.name(Text.of("Enum Cycler"))
@@ -135,12 +146,16 @@ public class ModMenuIntegration implements ModMenuApi {
.build())
.build())
.group(OptionGroup.createBuilder()
- .name(Text.of("Buttons!"))
+ .name(Text.of("Options that aren't really options"))
.option(ButtonOption.createBuilder()
.name(Text.of("Button \"Option\""))
.action(screen -> SystemToast.add(MinecraftClient.getInstance().getToastManager(), SystemToast.Type.TUTORIAL_HINT, Text.of("Button Pressed"), Text.of("Button option was invoked!")))
.controller(ActionController::new)
.build())
+ .option(Option.createBuilder(Text.class)
+ .binding(Binding.immutable(Text.of("To center, or not to center?!")))
+ .controller(LabelController::new)
+ .build())
.build())
.group(OptionGroup.createBuilder()
.name(Text.of("Minecraft Bindings"))
@@ -357,6 +372,7 @@ public class ModMenuIntegration implements ModMenuApi {
private static double doubleSlider = 0;
private static float floatSlider = 0;
private static long longSlider = 0;
+ private static String textField = "Hello";
private static Alphabet enumOption = Alphabet.A;
private static boolean groupTestRoot = false;