diff options
19 files changed, 362 insertions, 107 deletions
diff --git a/src/main/java/dev/isxander/yacl/api/ConfigCategory.java b/src/main/java/dev/isxander/yacl/api/ConfigCategory.java index 5aafc62..9f2f954 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(), ImmutableList.copyOf(rootOptions), true)); + combinedGroups.add(new OptionGroupImpl(Text.empty(), Text.empty(), ImmutableList.copyOf(rootOptions), 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/OptionGroup.java b/src/main/java/dev/isxander/yacl/api/OptionGroup.java index 6a302c4..9376b8e 100644 --- a/src/main/java/dev/isxander/yacl/api/OptionGroup.java +++ b/src/main/java/dev/isxander/yacl/api/OptionGroup.java @@ -2,6 +2,7 @@ package dev.isxander.yacl.api; import com.google.common.collect.ImmutableList; import dev.isxander.yacl.impl.OptionGroupImpl; +import net.minecraft.text.MutableText; import net.minecraft.text.Text; import org.apache.commons.lang3.Validate; import org.jetbrains.annotations.NotNull; @@ -23,6 +24,11 @@ public interface OptionGroup { Text name(); /** + * Tooltip displayed on hover. + */ + Text tooltip(); + + /** * List of all options in the group */ @NotNull ImmutableList<Option<?>> options(); @@ -42,6 +48,7 @@ public interface OptionGroup { class Builder { private Text name = Text.empty(); + private final List<Text> tooltipLines = new ArrayList<>(); private final List<Option<?>> options = new ArrayList<>(); private Builder() { @@ -61,6 +68,20 @@ public interface OptionGroup { } /** + * Sets the tooltip to be used by the option group. + * Can be invoked twice to append more lines. + * No need to wrap the text yourself, the gui does this itself. + * + * @param tooltips text lines - merged with a new-line on {@link Builder#build()}. + */ + public Builder tooltip(@NotNull Text... tooltips) { + Validate.notEmpty(tooltips, "`tooltips` cannot be empty"); + + tooltipLines.addAll(List.of(tooltips)); + return this; + } + + /** * Adds an option to group. * To construct an option, use {@link Option#createBuilder(Class)} * @@ -89,7 +110,16 @@ public interface OptionGroup { public OptionGroup build() { Validate.notEmpty(options, "`options` must not be empty to build `OptionGroup`"); - return new OptionGroupImpl(name, ImmutableList.copyOf(options), false); + MutableText concatenatedTooltip = Text.empty(); + boolean first = true; + for (Text line : tooltipLines) { + if (!first) concatenatedTooltip.append("\n"); + first = false; + + concatenatedTooltip.append(line); + } + + return new OptionGroupImpl(name, concatenatedTooltip, ImmutableList.copyOf(options), false); } } } diff --git a/src/main/java/dev/isxander/yacl/gui/CategoryWidget.java b/src/main/java/dev/isxander/yacl/gui/CategoryWidget.java index f4a50e8..04c4c22 100644 --- a/src/main/java/dev/isxander/yacl/gui/CategoryWidget.java +++ b/src/main/java/dev/isxander/yacl/gui/CategoryWidget.java @@ -1,6 +1,7 @@ package dev.isxander.yacl.gui; import dev.isxander.yacl.api.ConfigCategory; +import dev.isxander.yacl.impl.YACLConstants; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.widget.ButtonWidget; import net.minecraft.client.util.math.MatrixStack; @@ -22,7 +23,7 @@ public class CategoryWidget extends ButtonWidget { public void render(MatrixStack matrices, int mouseX, int mouseY, float delta) { super.render(matrices, mouseX, mouseY, delta); - if (isHovered() && prevMouseX == mouseX && prevMouseY == mouseY) { + if (isHovered() && (!YACLConstants.HOVER_MOUSE_RESET || (prevMouseX == mouseX && prevMouseY == mouseY))) { hoveredTicks += delta; } else { hoveredTicks = 0; diff --git a/src/main/java/dev/isxander/yacl/gui/OptionListWidget.java b/src/main/java/dev/isxander/yacl/gui/OptionListWidget.java index 8cb1160..1f118cc 100644 --- a/src/main/java/dev/isxander/yacl/gui/OptionListWidget.java +++ b/src/main/java/dev/isxander/yacl/gui/OptionListWidget.java @@ -1,17 +1,24 @@ package dev.isxander.yacl.gui; +import com.google.common.collect.ImmutableList; 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; import net.minecraft.client.gui.Element; 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.ElementListWidget; import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.text.OrderedText; +import java.util.Collections; import java.util.List; public class OptionListWidget extends ElementListWidget<OptionListWidget.Entry> { @@ -23,7 +30,7 @@ public class OptionListWidget extends ElementListWidget<OptionListWidget.Entry> for (OptionGroup group : category.groups()) { if (!group.isRoot()) - addEntry(new GroupSeparatorEntry(group)); + addEntry(new GroupSeparatorEntry(group, screen)); for (Option<?> option : group.options()) { addEntry(new OptionEntry(option.controller().provideWidget(screen, null))); } @@ -45,6 +52,13 @@ public class OptionListWidget extends ElementListWidget<OptionListWidget.Entry> return left + super.getScrollbarPositionX(); } + @Override + protected void renderBackground(MatrixStack matrices) { + setRenderBackground(client.world == null); + if (client.world != null) + fill(matrices, left, top, right, bottom, 0x6B000000); + } + public static abstract class Entry extends ElementListWidget.Entry<Entry> { } @@ -70,36 +84,68 @@ public class OptionListWidget extends ElementListWidget<OptionListWidget.Entry> @Override public List<? extends Selectable> selectableChildren() { - return List.of(widget); + return ImmutableList.of(widget); } @Override public List<? extends Element> children() { - return List.of(widget); + return ImmutableList.of(widget); } } private static class GroupSeparatorEntry extends Entry { private final OptionGroup group; + private final List<OrderedText> wrappedTooltip; - public GroupSeparatorEntry(OptionGroup group) { + private float hoveredTicks = 0; + private int prevMouseX, prevMouseY; + + private final Screen screen; + private final TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer; + + public GroupSeparatorEntry(OptionGroup group, Screen screen) { this.group = group; + this.screen = screen; + this.wrappedTooltip = textRenderer.wrapLines(group.tooltip(), screen.width / 2); } @Override public void render(MatrixStack matrices, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) { - TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer; + if (hovered && (!YACLConstants.HOVER_MOUSE_RESET || (mouseX == prevMouseX && mouseY == prevMouseY))) + hoveredTicks += tickDelta; + else + hoveredTicks = 0; + drawCenteredText(matrices, textRenderer, group.name(), x + entryWidth / 2, y + entryHeight / 2 - textRenderer.fontHeight / 2, -1); + + if (hoveredTicks >= YACLConstants.HOVER_TICKS) { + screen.renderOrderedTooltip(matrices, wrappedTooltip, x - 6, y + entryHeight); + } + + prevMouseX = mouseX; + prevMouseY = mouseY; } @Override public List<? extends Selectable> selectableChildren() { - return List.of(); + return ImmutableList.of(new Selectable() { + @Override + public Selectable.SelectionType getType() { + return Selectable.SelectionType.HOVERED; + } + + @Override + public void appendNarrations(NarrationMessageBuilder builder) { + builder.put(NarrationPart.TITLE, group.name()); + } + }); } @Override public List<? extends Element> children() { - return List.of(); + return Collections.emptyList(); } + + } } diff --git a/src/main/java/dev/isxander/yacl/gui/YACLScreen.java b/src/main/java/dev/isxander/yacl/gui/YACLScreen.java index 6ecbcc7..6da1c29 100644 --- a/src/main/java/dev/isxander/yacl/gui/YACLScreen.java +++ b/src/main/java/dev/isxander/yacl/gui/YACLScreen.java @@ -5,10 +5,12 @@ import dev.isxander.yacl.api.Option; import dev.isxander.yacl.api.YetAnotherConfigLib; import dev.isxander.yacl.api.utils.Dimension; import dev.isxander.yacl.api.utils.OptionUtils; +import dev.isxander.yacl.impl.YACLConstants; import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.gui.widget.ButtonWidget; import net.minecraft.client.util.math.MatrixStack; import net.minecraft.text.Text; +import net.minecraft.util.Formatting; import java.util.ArrayList; import java.util.List; @@ -24,6 +26,10 @@ public class YACLScreen extends Screen { public final List<CategoryWidget> categoryButtons; public ButtonWidget finishedSaveButton, cancelResetButton, undoButton; + public Text saveButtonMessage; + private int saveButtonMessageTime; + + public YACLScreen(YetAnotherConfigLib config, Screen parent) { super(config.title()); this.config = config; @@ -57,8 +63,16 @@ public class YACLScreen extends Screen { Dimension<Integer> actionDim = Dimension.ofInt(padding, height - padding - 20, columnWidth - padding * 2, 20); finishedSaveButton = new ButtonWidget(actionDim.x(), actionDim.y(), actionDim.width(), actionDim.height(), Text.empty(), (btn) -> { + saveButtonMessage = null; + if (pendingChanges()) { OptionUtils.forEachOptions(config, Option::applyValue); + OptionUtils.forEachOptions(config, option -> { + if (option.changed()) { + YACLConstants.LOGGER.error("Option '{}' was saved as '{}' but the changes don't seem to have applied.", option.name().getString(), option.pendingValue()); + setSaveButtonMessage(Text.translatable("yocl.gui.fail_apply").formatted(Formatting.RED)); + } + }); config.saveFunction().run(); } else close(); }); @@ -78,9 +92,9 @@ public class YACLScreen extends Screen { }); updateActionAvailability(); - addDrawableChild(finishedSaveButton); addDrawableChild(cancelResetButton); addDrawableChild(undoButton); + addDrawableChild(finishedSaveButton); ConfigCategory currentCategory = config.categories().get(currentCategoryIdx); optionList = new OptionListWidget(currentCategory, this, client, width, height); @@ -98,7 +112,7 @@ public class YACLScreen extends Screen { optionList.render(matrices, mouseX, mouseY, delta); for (CategoryWidget categoryWidget : categoryButtons) { - if (categoryWidget.hoveredTicks > 30) { + if (categoryWidget.hoveredTicks > YACLConstants.HOVER_TICKS) { renderOrderedTooltip(matrices, categoryWidget.wrappedDescription, mouseX, mouseY); } } @@ -107,6 +121,21 @@ public class YACLScreen extends Screen { @Override public void tick() { updateActionAvailability(); + + if (saveButtonMessage != null) { + if (saveButtonMessageTime > 140) { + saveButtonMessage = null; + saveButtonMessageTime = 0; + } else { + saveButtonMessageTime++; + finishedSaveButton.setMessage(saveButtonMessage); + } + } + } + + private void setSaveButtonMessage(Text message) { + saveButtonMessage = message; + saveButtonMessageTime = 0; } public void changeCategory(int idx) { @@ -146,7 +175,11 @@ public class YACLScreen extends Screen { @Override public boolean shouldCloseOnEsc() { - return !undoButton.active; + if (pendingChanges()) { + setSaveButtonMessage(finishedSaveButton.getMessage().copy().formatted(Formatting.GREEN, Formatting.BOLD)); + return false; + } + return true; } @Override 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 92ef3d5..d198c96 100644 --- a/src/main/java/dev/isxander/yacl/gui/controllers/ActionController.java +++ b/src/main/java/dev/isxander/yacl/gui/controllers/ActionController.java @@ -6,6 +6,7 @@ import dev.isxander.yacl.api.utils.Dimension; import dev.isxander.yacl.gui.YACLScreen; import net.minecraft.text.Text; import org.jetbrains.annotations.ApiStatus; +import org.lwjgl.glfw.GLFW; import java.util.function.Consumer; @@ -71,17 +72,32 @@ public class ActionController implements Controller<Consumer<YACLScreen>> { super(control, screen, dim); } + public void executeAction() { + playDownSound(); + control.option().action().accept(screen); + } + @Override public boolean mouseClicked(double mouseX, double mouseY, int button) { if (isMouseOver(mouseX, mouseY)) { - playDownSound(); - control.option().action().accept(screen); + executeAction(); return true; } return false; } @Override + public boolean keyPressed(int keyCode, int scanCode, int modifiers) { + if (keyCode != GLFW.GLFW_KEY_ENTER && keyCode != GLFW.GLFW_KEY_SPACE && keyCode != GLFW.GLFW_KEY_KP_ENTER) { + return false; + } + + executeAction(); + + return true; + } + + @Override protected int getHoveredControlWidth() { return getUnhoveredControlWidth(); } 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 6bf0b80..ac8754f 100644 --- a/src/main/java/dev/isxander/yacl/gui/controllers/BooleanController.java +++ b/src/main/java/dev/isxander/yacl/gui/controllers/BooleanController.java @@ -9,6 +9,7 @@ import net.minecraft.client.util.math.MatrixStack; import net.minecraft.text.Text; import net.minecraft.util.Formatting; import org.jetbrains.annotations.ApiStatus; +import org.lwjgl.glfw.GLFW; import java.util.function.Function; @@ -117,8 +118,7 @@ public class BooleanController implements Controller<Boolean> { if (!isMouseOver(mouseX, mouseY)) return false; - control.option().requestSet(!control.option().pendingValue()); - playDownSound(); + toggleSetting(); return true; } @@ -127,6 +127,11 @@ public class BooleanController implements Controller<Boolean> { return getUnhoveredControlWidth(); } + public void toggleSetting() { + control.option().requestSet(!control.option().pendingValue()); + playDownSound(); + } + @Override protected Text getValueText() { if (control.coloured()) { @@ -135,5 +140,16 @@ public class BooleanController implements Controller<Boolean> { return super.getValueText(); } + + @Override + public boolean keyPressed(int keyCode, int scanCode, int modifiers) { + if (keyCode != GLFW.GLFW_KEY_ENTER && keyCode != GLFW.GLFW_KEY_SPACE && keyCode != GLFW.GLFW_KEY_KP_ENTER) { + return false; + } + + toggleSetting(); + + return true; + } } } 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 77ac9ca..d712e56 100644 --- a/src/main/java/dev/isxander/yacl/gui/controllers/ControllerWidget.java +++ b/src/main/java/dev/isxander/yacl/gui/controllers/ControllerWidget.java @@ -4,6 +4,7 @@ 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.YACLConstants; import net.minecraft.client.gui.DrawableHelper; import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder; import net.minecraft.client.util.math.MatrixStack; @@ -19,6 +20,7 @@ public abstract class ControllerWidget<T extends Controller<?>> extends Abstract protected Dimension<Integer> dim; protected final YACLScreen screen; + protected boolean focused = false; protected boolean hovered = false; protected float hoveredTicks = 0; @@ -34,7 +36,7 @@ public abstract class ControllerWidget<T extends Controller<?>> extends Abstract @Override public void render(MatrixStack matrices, int mouseX, int mouseY, float delta) { hovered = isMouseOver(mouseX, mouseY); - if (hovered && mouseX == prevMouseX && mouseY == prevMouseY) { + if (hovered && (!YACLConstants.HOVER_MOUSE_RESET || (mouseX == prevMouseX && mouseY == prevMouseY))) { hoveredTicks += delta; } else { hoveredTicks = 0; @@ -53,18 +55,18 @@ 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); + drawButtonRect(matrices, dim.x(), dim.y(), dim.xLimit(), dim.yLimit(), hovered || focused); 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) { + if (hovered || focused) { drawHoveredControl(matrices, mouseX, mouseY, delta); } - if (hoveredTicks > 30) { + if (hoveredTicks > YACLConstants.HOVER_TICKS) { screen.renderOrderedTooltip(matrices, wrappedTooltip, mouseX, mouseY); } @@ -90,7 +92,7 @@ public abstract class ControllerWidget<T extends Controller<?>> extends Abstract } protected int getControlWidth() { - return hovered ? getHoveredControlWidth() : getUnhoveredControlWidth(); + return hovered || focused ? getHoveredControlWidth() : getUnhoveredControlWidth(); } protected abstract int getHoveredControlWidth(); @@ -127,8 +129,14 @@ public abstract class ControllerWidget<T extends Controller<?>> extends Abstract } @Override + public boolean changeFocus(boolean lookForwards) { + this.focused = !this.focused; + return this.focused; + } + + @Override public SelectionType getType() { - return hovered ? SelectionType.HOVERED : SelectionType.NONE; + return focused ? SelectionType.FOCUSED : hovered ? 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 f4c4006..af52255 100644 --- a/src/main/java/dev/isxander/yacl/gui/controllers/EnumController.java +++ b/src/main/java/dev/isxander/yacl/gui/controllers/EnumController.java @@ -8,6 +8,7 @@ import dev.isxander.yacl.gui.YACLScreen; import net.minecraft.client.gui.screen.Screen; import net.minecraft.text.Text; import org.jetbrains.annotations.ApiStatus; +import org.lwjgl.glfw.GLFW; import java.util.function.Function; @@ -85,21 +86,41 @@ public class EnumController<T extends Enum<T>> implements Controller<T> { this.values = values; } + public void cycleValue(int increment) { + int targetIdx = control.option().pendingValue().ordinal() + increment; + if (targetIdx >= values.length) { + targetIdx -= values.length; + } else if (targetIdx < 0) { + targetIdx += values.length; + } + control.option().requestSet(values[targetIdx]); + } + @Override public boolean mouseClicked(double mouseX, double mouseY, int button) { if (!isMouseOver(mouseX, mouseY) || (button != 0 && button != 1)) return false; playDownSound(); + cycleValue(button == 1 || Screen.hasShiftDown() || Screen.hasControlDown() ? -1 : 1); - int change = button == 1 || Screen.hasShiftDown() ? -1 : 1; - int targetIdx = control.option().pendingValue().ordinal() + change; - if (targetIdx >= values.length) { - targetIdx -= values.length; - } else if (targetIdx < 0) { - targetIdx += values.length; + return true; + } + + @Override + public boolean keyPressed(int keyCode, int scanCode, int modifiers) { + switch (keyCode) { + case GLFW.GLFW_KEY_LEFT, GLFW.GLFW_KEY_DOWN -> + cycleValue(-1); + case GLFW.GLFW_KEY_RIGHT, GLFW.GLFW_KEY_UP -> + cycleValue(1); + case GLFW.GLFW_KEY_ENTER, GLFW.GLFW_KEY_SPACE, GLFW.GLFW_KEY_KP_ENTER -> + cycleValue(Screen.hasControlDown() || Screen.hasShiftDown() ? -1 : 1); + default -> { + return false; + } } - control.option().requestSet(values[targetIdx]); + return true; } 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 1d78c78..a1c0095 100644 --- a/src/main/java/dev/isxander/yacl/gui/controllers/TickBoxController.java +++ b/src/main/java/dev/isxander/yacl/gui/controllers/TickBoxController.java @@ -5,10 +5,10 @@ import dev.isxander.yacl.api.Option; import dev.isxander.yacl.api.utils.Dimension; import dev.isxander.yacl.gui.YACLScreen; 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 org.jetbrains.annotations.ApiStatus; +import org.lwjgl.glfw.GLFW; /** * This controller renders a tickbox @@ -81,8 +81,7 @@ public class TickBoxController implements Controller<Boolean> { if (!isMouseOver(mouseX, mouseY)) return false; - control.option().requestSet(!control.option().pendingValue()); - playDownSound(); + toggleSetting(); return true; } @@ -90,5 +89,21 @@ public class TickBoxController implements Controller<Boolean> { protected int getHoveredControlWidth() { return 10; } + + public void toggleSetting() { + control.option().requestSet(!control.option().pendingValue()); + playDownSound(); + } + + @Override + public boolean keyPressed(int keyCode, int scanCode, int modifiers) { + if (keyCode != GLFW.GLFW_KEY_ENTER && keyCode != GLFW.GLFW_KEY_SPACE && keyCode != GLFW.GLFW_KEY_KP_ENTER) { + return false; + } + + toggleSetting(); + + return true; + } } } 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 8af2433..a6f046a 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 @@ -8,6 +8,7 @@ import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.util.math.MatrixStack; import net.minecraft.util.math.MathHelper; import org.jetbrains.annotations.ApiStatus; +import org.lwjgl.glfw.GLFW; @ApiStatus.Internal public class SliderControllerElement extends ControllerWidget<ISliderController<?>> { @@ -49,7 +50,7 @@ public class SliderControllerElement extends ControllerWidget<ISliderController< @Override protected void drawValueText(MatrixStack matrices, int mouseX, int mouseY, float delta) { matrices.push(); - if (hovered) + if (hovered || focused) matrices.translate(-(sliderBounds.width() + 6 + getThumbWidth() / 2f), 0, 0); super.drawValueText(matrices, mouseX, mouseY, delta); matrices.pop(); @@ -75,13 +76,17 @@ public class SliderControllerElement extends ControllerWidget<ISliderController< return true; } + public void incrementValue(double amount) { + control.setPendingValue(MathHelper.clamp(control.pendingValue() + interval * amount, min, max)); + calculateInterpolation(); + } + @Override public boolean mouseScrolled(double mouseX, double mouseY, double amount) { - if (!isMouseOver(mouseX, mouseY) || !Screen.hasShiftDown()) + if ((!isMouseOver(mouseX, mouseY)) || (!Screen.hasShiftDown() && !Screen.hasControlDown())) return false; - control.setPendingValue(MathHelper.clamp(control.pendingValue() + interval * amount, min, max)); - calculateInterpolation(); + incrementValue(amount); return true; } @@ -95,6 +100,19 @@ public class SliderControllerElement extends ControllerWidget<ISliderController< } @Override + public boolean keyPressed(int keyCode, int scanCode, int modifiers) { + switch (keyCode) { + case GLFW.GLFW_KEY_LEFT, GLFW.GLFW_KEY_DOWN -> incrementValue(-1); + case GLFW.GLFW_KEY_RIGHT, GLFW.GLFW_KEY_UP -> incrementValue(1); + default -> { + return false; + } + } + + return true; + } + + @Override public boolean isMouseOver(double mouseX, double mouseY) { return super.isMouseOver(mouseX, mouseY) || mouseDown; } diff --git a/src/main/java/dev/isxander/yacl/impl/OptionGroupImpl.java b/src/main/java/dev/isxander/yacl/impl/OptionGroupImpl.java index 73bff07..1f2d4e2 100644 --- a/src/main/java/dev/isxander/yacl/impl/OptionGroupImpl.java +++ b/src/main/java/dev/isxander/yacl/impl/OptionGroupImpl.java @@ -4,7 +4,7 @@ import com.google.common.collect.ImmutableList; import dev.isxander.yacl.api.Option; import dev.isxander.yacl.api.OptionGroup; import net.minecraft.text.Text; -import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.NotNull; -public record OptionGroupImpl(@Nullable Text name, ImmutableList<Option<?>> options, boolean isRoot) implements OptionGroup { +public record OptionGroupImpl(@NotNull Text name, @NotNull Text tooltip, ImmutableList<Option<?>> options, boolean isRoot) implements OptionGroup { } diff --git a/src/main/java/dev/isxander/yacl/impl/OptionImpl.java b/src/main/java/dev/isxander/yacl/impl/OptionImpl.java index c61eaa1..176bf93 100644 --- a/src/main/java/dev/isxander/yacl/impl/OptionImpl.java +++ b/src/main/java/dev/isxander/yacl/impl/OptionImpl.java @@ -69,7 +69,9 @@ public class OptionImpl<T> implements Option<T> { @Override public void applyValue() { - binding().setValue(pendingValue); + if (changed()) { + binding().setValue(pendingValue); + } } @Override diff --git a/src/main/java/dev/isxander/yacl/impl/YACLConstants.java b/src/main/java/dev/isxander/yacl/impl/YACLConstants.java new file mode 100644 index 0000000..28d6a65 --- /dev/null +++ b/src/main/java/dev/isxander/yacl/impl/YACLConstants.java @@ -0,0 +1,23 @@ +package dev.isxander.yacl.impl; + +import org.jetbrains.annotations.ApiStatus; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class YACLConstants { + /** + * Logger used by YACL + */ + @ApiStatus.Internal + public static final Logger LOGGER = LoggerFactory.getLogger("YetAnotherConfigLib"); + + /** + * Amount of ticks to hover before showing tooltips. + */ + public static final int HOVER_TICKS = 20; + + /** + * Reset hover ticks back to 0 when the mouse is moved. + */ + public static final boolean HOVER_MOUSE_RESET = true; +} diff --git a/src/main/resources/assets/yet-another-config-lib/lang/en_us.json b/src/main/resources/assets/yet-another-config-lib/lang/en_us.json index 7f609d3..78945de 100644 --- a/src/main/resources/assets/yet-another-config-lib/lang/en_us.json +++ b/src/main/resources/assets/yet-another-config-lib/lang/en_us.json @@ -12,5 +12,6 @@ "yacl.gui.finished": "Finished", "yacl.gui.cancel": "Cancel", "yacl.gui.reset": "Reset", - "yacl.gui.undo": "Undo" + "yacl.gui.undo": "Undo", + "yocl.gui.fail_apply": "Failed to apply" } diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 6c28f3c..b40f7eb 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -12,7 +12,7 @@ "issues": "https://github.com/${github}/issues", "sources": "https://github.com/${github}" }, - "icon": "icon.png", + "icon": "yacl-128x.png", "license": "LGPL-3.0-or-later", "environment": "client", "entrypoints": { diff --git a/src/main/resources/icon.png b/src/main/resources/icon.png Binary files differdeleted file mode 100644 index 3f6dae4..0000000 --- a/src/main/resources/icon.png +++ /dev/null diff --git a/src/main/resources/yacl-128x.png b/src/main/resources/yacl-128x.png Binary files differnew file mode 100644 index 0000000..c86981c --- /dev/null +++ b/src/main/resources/yacl-128x.png diff --git a/src/testmod/java/dev/isxander/yacl/test/ModMenuIntegration.java b/src/testmod/java/dev/isxander/yacl/test/ModMenuIntegration.java index d1fad67..57d809b 100644 --- a/src/testmod/java/dev/isxander/yacl/test/ModMenuIntegration.java +++ b/src/testmod/java/dev/isxander/yacl/test/ModMenuIntegration.java @@ -27,74 +27,98 @@ public class ModMenuIntegration implements ModMenuApi { .category(ConfigCategory.createBuilder() .name(Text.of("Control Examples")) .tooltip(Text.of("Example Category Description")) - .option(Option.createBuilder(boolean.class) - .name(Text.of("Boolean Toggle")) - .binding( - false, - () -> TestSettings.booleanToggle, - (value) -> TestSettings.booleanToggle = value - ) - .controller(BooleanController::new) - .build()) - .option(Option.createBuilder(boolean.class) - .name(Text.of("Tick Box")) - .tooltip(Text.of("Super long tooltip that is very descriptive to show off the text wrapping features of the thingy yes whwowwoow")) - .binding( - false, - () -> TestSettings.tickbox, - (value) -> TestSettings.tickbox = value - ) - .controller(TickBoxController::new) - .build()) - .option(Option.createBuilder(int.class) - .name(Text.of("Int Slider that is cut off because the slider")) - .binding( - 0, - () -> TestSettings.intSlider, - (value) -> TestSettings.intSlider = value - ) - .controller(opt -> new IntegerSliderController(opt, 0, 3, 1)) - .build()) - .option(Option.createBuilder(double.class) - .name(Text.of("Double Slider")) - .binding( - 0.0, - () -> TestSettings.doubleSlider, - (value) -> TestSettings.doubleSlider = value - ) - .controller(opt -> new DoubleSliderController(opt, 0, 3, 0.05)) - .build()) - .option(Option.createBuilder(float.class) - .name(Text.of("Float Slider")) - .binding( - 0f, - () -> TestSettings.floatSlider, - (value) -> TestSettings.floatSlider = value - ) - .controller(opt -> new FloatSliderController(opt, 0, 3, 0.1f)) + .group(OptionGroup.createBuilder() + .name(Text.of("Boolean Controllers")) + .tooltip(Text.of("Test!")) + .option(Option.createBuilder(boolean.class) + .name(Text.of("Boolean Toggle")) + .tooltip(Text.of("A simple toggle button.")) + .binding( + false, + () -> TestSettings.booleanToggle, + (value) -> TestSettings.booleanToggle = value + ) + .controller(BooleanController::new) + .build()) + .option(Option.createBuilder(boolean.class) + .name(Text.of("Custom Boolean Toggle")) + .tooltip(Text.of("You can customize these controllers like this!")) + .binding( + false, + () -> TestSettings.customBooleanToggle, + (value) -> TestSettings.customBooleanToggle = value + ) + .controller(opt -> new BooleanController(opt, state -> state ? Text.of("Amazing") : Text.of("Not Amazing"), true)) + .build()) + .option(Option.createBuilder(boolean.class) + .name(Text.of("Tick Box")) + .tooltip(Text.of("There are even alternate methods of displaying the same data type!")) + .binding( + false, + () -> TestSettings.tickbox, + (value) -> TestSettings.tickbox = value + ) + .controller(TickBoxController::new) + .build()) .build()) - .option(Option.createBuilder(long.class) - .name(Text.of("Long Slider")) - .binding( - 0L, - () -> TestSettings.longSlider, - (value) -> TestSettings.longSlider = value - ) - .controller(opt -> new LongSliderController(opt, 0, 1_000_000, 100)) + .group(OptionGroup.createBuilder() + .name(Text.of("Slider Controllers")) + .option(Option.createBuilder(int.class) + .name(Text.of("Int Slider that is cut off because the slider")) + .binding( + 0, + () -> TestSettings.intSlider, + (value) -> TestSettings.intSlider = value + ) + .controller(opt -> new IntegerSliderController(opt, 0, 3, 1)) + .build()) + .option(Option.createBuilder(double.class) + .name(Text.of("Double Slider")) + .binding( + 0.0, + () -> TestSettings.doubleSlider, + (value) -> TestSettings.doubleSlider = value + ) + .controller(opt -> new DoubleSliderController(opt, 0, 3, 0.05)) + .build()) + .option(Option.createBuilder(float.class) + .name(Text.of("Float Slider")) + .binding( + 0f, + () -> TestSettings.floatSlider, + (value) -> TestSettings.floatSlider = value + ) + .controller(opt -> new FloatSliderController(opt, 0, 3, 0.1f)) + .build()) + .option(Option.createBuilder(long.class) + .name(Text.of("Long Slider")) + .binding( + 0L, + () -> TestSettings.longSlider, + (value) -> TestSettings.longSlider = value + ) + .controller(opt -> new LongSliderController(opt, 0, 1_000_000, 100)) + .build()) .build()) - .option(Option.createBuilder(TestSettings.Alphabet.class) - .name(Text.of("Enum Cycler")) - .binding( - TestSettings.Alphabet.A, - () -> TestSettings.enumOption, - (value) -> TestSettings.enumOption = value - ) - .controller(opt -> new EnumController<>(opt, TestSettings.Alphabet.class)) + .group(OptionGroup.createBuilder() + .name(Text.of("Enum Controllers")) + .option(Option.createBuilder(TestSettings.Alphabet.class) + .name(Text.of("Enum Cycler")) + .binding( + TestSettings.Alphabet.A, + () -> TestSettings.enumOption, + (value) -> TestSettings.enumOption = value + ) + .controller(opt -> new EnumController<>(opt, TestSettings.Alphabet.class)) + .build()) .build()) - .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) + .group(OptionGroup.createBuilder() + .name(Text.of("Buttons!")) + .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()) .build()) .build()) .category(ConfigCategory.createBuilder() @@ -289,6 +313,7 @@ public class ModMenuIntegration implements ModMenuApi { private static class TestSettings { private static boolean booleanToggle = false; + private static boolean customBooleanToggle = false; private static boolean tickbox = false; private static int intSlider = 0; private static double doubleSlider = 0; |