aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorisXander <xandersmith2008@gmail.com>2023-05-22 19:33:00 +0100
committerisXander <xandersmith2008@gmail.com>2023-05-22 19:33:00 +0100
commitead0b794ac57e9ab2558338f7f3da7545d2e12ff (patch)
treed1c92f8d3283a8989923e038a377af4a00c24124
parent590e69f4bf445a39737b0b1552cf116ff780d75e (diff)
downloadYetAnotherConfigLib-ead0b794ac57e9ab2558338f7f3da7545d2e12ff.tar.gz
YetAnotherConfigLib-ead0b794ac57e9ab2558338f7f3da7545d2e12ff.tar.bz2
YetAnotherConfigLib-ead0b794ac57e9ab2558338f7f3da7545d2e12ff.zip
scrollable navbar, group descriptions, auto-scroll descriptions
-rw-r--r--build.gradle.kts7
-rw-r--r--common/src/main/java/dev/isxander/yacl/api/OptionDescription.java4
-rw-r--r--common/src/main/java/dev/isxander/yacl/api/OptionGroup.java6
-rw-r--r--common/src/main/java/dev/isxander/yacl/gui/OptionDescriptionWidget.java70
-rw-r--r--common/src/main/java/dev/isxander/yacl/gui/OptionListWidget.java27
-rw-r--r--common/src/main/java/dev/isxander/yacl/gui/ScrollableNavigationBar.java79
-rw-r--r--common/src/main/java/dev/isxander/yacl/gui/TabListWidget.java14
-rw-r--r--common/src/main/java/dev/isxander/yacl/gui/YACLScreen.java23
-rw-r--r--common/src/main/java/dev/isxander/yacl/impl/ConfigCategoryImpl.java7
-rw-r--r--common/src/main/java/dev/isxander/yacl/impl/OptionDescriptionImpl.java25
-rw-r--r--common/src/main/java/dev/isxander/yacl/impl/OptionGroupImpl.java51
-rw-r--r--common/src/main/resources/yacl.accesswidener8
-rw-r--r--fabric/build.gradle.kts9
-rw-r--r--forge/build.gradle.kts10
-rw-r--r--test-common/src/main/java/dev/isxander/yacl/test/GuiTest.java38
15 files changed, 303 insertions, 75 deletions
diff --git a/build.gradle.kts b/build.gradle.kts
index d85c3fb..b9fb2df 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -12,7 +12,7 @@ architectury {
minecraft = libs.versions.minecraft.get()
}
-version = "2.5.1-beta.1+1.20"
+version = "3.0.0-beta.1+1.20"
val isBeta = "beta" in version.toString()
val changelogText = rootProject.file("changelogs/${project.version}.md").takeIf { it.exists() }?.readText() ?: "No changelog provided."
@@ -92,6 +92,7 @@ githubRelease {
tagName("${project.version}")
targetCommitish(grgit.branch.current().name)
body(changelogText)
+ prerelease(isBeta)
releaseAssets(
{ findProject(":fabric")?.tasks?.get("remapJar")?.outputs?.files },
{ findProject(":fabric")?.tasks?.get("remapSourcesJar")?.outputs?.files },
@@ -109,6 +110,6 @@ tasks.register("releaseMod") {
tasks.register("buildAll") {
group = "mod"
- dependsOn(project(":fabric").tasks["build"])
- dependsOn(project(":forge").tasks["build"])
+ findProject(":fabric")?.let { dependsOn(it.tasks["build"]) }
+ findProject(":forge")?.let { dependsOn(it.tasks["build"]) }
}
diff --git a/common/src/main/java/dev/isxander/yacl/api/OptionDescription.java b/common/src/main/java/dev/isxander/yacl/api/OptionDescription.java
index 22eebc9..e5ed0b6 100644
--- a/common/src/main/java/dev/isxander/yacl/api/OptionDescription.java
+++ b/common/src/main/java/dev/isxander/yacl/api/OptionDescription.java
@@ -6,6 +6,7 @@ import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import java.nio.file.Path;
+import java.util.Collection;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
@@ -23,7 +24,8 @@ public interface OptionDescription {
interface Builder {
Builder name(Component name);
- Builder description(Component description);
+ Builder description(Component... description);
+ Builder description(Collection<? extends Component> lines);
Builder image(ResourceLocation image, int width, int height);
Builder image(Path path, ResourceLocation uniqueLocation);
diff --git a/common/src/main/java/dev/isxander/yacl/api/OptionGroup.java b/common/src/main/java/dev/isxander/yacl/api/OptionGroup.java
index 4fe43c7..f1b2b5a 100644
--- a/common/src/main/java/dev/isxander/yacl/api/OptionGroup.java
+++ b/common/src/main/java/dev/isxander/yacl/api/OptionGroup.java
@@ -19,9 +19,12 @@ public interface OptionGroup {
*/
Component name();
+ OptionDescription description();
+
/**
* Tooltip displayed on hover.
*/
+ @Deprecated
Component tooltip();
/**
@@ -55,6 +58,8 @@ public interface OptionGroup {
*/
Builder name(@NotNull Component name);
+ Builder description(@NotNull OptionDescription description);
+
/**
* Sets the tooltip to be used by the option group.
* Can be invoked twice to append more lines.
@@ -62,6 +67,7 @@ public interface OptionGroup {
*
* @param tooltips Component lines - merged with a new-line on {@link Builder#build()}.
*/
+ @Deprecated
Builder tooltip(@NotNull Component... tooltips);
/**
diff --git a/common/src/main/java/dev/isxander/yacl/gui/OptionDescriptionWidget.java b/common/src/main/java/dev/isxander/yacl/gui/OptionDescriptionWidget.java
index 882d75b..5c346d0 100644
--- a/common/src/main/java/dev/isxander/yacl/gui/OptionDescriptionWidget.java
+++ b/common/src/main/java/dev/isxander/yacl/gui/OptionDescriptionWidget.java
@@ -1,5 +1,7 @@
package dev.isxander.yacl.gui;
+import com.mojang.blaze3d.Blaze3D;
+import com.mojang.blaze3d.platform.InputConstants;
import dev.isxander.yacl.api.OptionDescription;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.Font;
@@ -17,6 +19,9 @@ import java.util.List;
import java.util.function.Supplier;
public class OptionDescriptionWidget extends AbstractWidget {
+ private static final int AUTO_SCROLL_TIMER = 3000;
+ private static final float AUTO_SCROLL_SPEED = 1;
+
private @Nullable OptionDescription description;
private List<FormattedCharSequence> wrappedText;
@@ -25,10 +30,13 @@ public class OptionDescriptionWidget extends AbstractWidget {
private Supplier<ScreenRectangle> dimensions;
- private int scrollAmount;
+ private float targetScrollAmount, currentScrollAmount;
private int maxScrollAmount;
private int descriptionY;
+ private int lastInteractionTime;
+ private boolean scrollingBackward;
+
public OptionDescriptionWidget(Supplier<ScreenRectangle> dimensions, @Nullable OptionDescription description) {
super(0, 0, 0, 0, description == null ? Component.empty() : description.descriptiveName());
this.dimensions = dimensions;
@@ -39,6 +47,8 @@ public class OptionDescriptionWidget extends AbstractWidget {
public void renderWidget(GuiGraphics graphics, int mouseX, int mouseY, float delta) {
if (description == null) return;
+ currentScrollAmount = Mth.lerp(delta * 0.5f, currentScrollAmount, targetScrollAmount);
+
ScreenRectangle dimensions = this.dimensions.get();
this.setX(dimensions.left());
this.setY(dimensions.top());
@@ -58,7 +68,7 @@ public class OptionDescriptionWidget extends AbstractWidget {
graphics.enableScissor(getX(), y, getX() + getWidth(), getY() + getHeight());
- y -= scrollAmount;
+ y -= (int)currentScrollAmount;
if (description.image().isDone()) {
var image = description.image().join();
@@ -79,12 +89,19 @@ public class OptionDescriptionWidget extends AbstractWidget {
graphics.disableScissor();
- maxScrollAmount = Math.max(0, y + scrollAmount - getY() - getHeight());
+ maxScrollAmount = Math.max(0, y + (int)currentScrollAmount - getY() - getHeight());
+ if (isHoveredOrFocused()) {
+ lastInteractionTime = currentTimeMS();
+ }
Style hoveredStyle = getDescStyle(mouseX, mouseY);
if (hoveredStyle != null && hoveredStyle.getHoverEvent() != null) {
graphics.renderComponentHoverEffect(font, hoveredStyle, mouseX, mouseY);
}
+
+ if (isFocused()) {
+ graphics.renderOutline(getX(), getY(), getWidth(), getHeight(), -1);
+ }
}
@Override
@@ -104,12 +121,50 @@ public class OptionDescriptionWidget extends AbstractWidget {
@Override
public boolean mouseScrolled(double mouseX, double mouseY, double amount) {
if (isMouseOver(mouseX, mouseY)) {
- scrollAmount = Mth.clamp(scrollAmount - (int) amount * 10, 0, maxScrollAmount);
+ targetScrollAmount = Mth.clamp(targetScrollAmount - (int) amount * 10, 0, maxScrollAmount);
+ lastInteractionTime = currentTimeMS();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean keyPressed(int keyCode, int scanCode, int modifiers) {
+ if (isFocused()) {
+ switch (keyCode) {
+ case InputConstants.KEY_UP ->
+ targetScrollAmount = Mth.clamp(targetScrollAmount - 10, 0, maxScrollAmount);
+ case InputConstants.KEY_DOWN ->
+ targetScrollAmount = Mth.clamp(targetScrollAmount + 10, 0, maxScrollAmount);
+ default -> {
+ return false;
+ }
+ }
return true;
}
return false;
}
+ public void tick() {
+ float pxPerTick = AUTO_SCROLL_SPEED / 20f * font.lineHeight;
+ if (maxScrollAmount > 0 && currentTimeMS() - lastInteractionTime > AUTO_SCROLL_TIMER) {
+ if (scrollingBackward) {
+ pxPerTick *= -1;
+ if (targetScrollAmount + pxPerTick < 0) {
+ scrollingBackward = false;
+ lastInteractionTime = currentTimeMS();
+ }
+ } else {
+ if (targetScrollAmount + pxPerTick > maxScrollAmount) {
+ scrollingBackward = true;
+ lastInteractionTime = currentTimeMS();
+ }
+ }
+
+ targetScrollAmount = Mth.clamp(targetScrollAmount + pxPerTick, 0, maxScrollAmount);
+ }
+ }
+
private Style getDescStyle(int mouseX, int mouseY) {
if (!clicked(mouseX, mouseY))
return null;
@@ -135,5 +190,12 @@ public class OptionDescriptionWidget extends AbstractWidget {
public void setOptionDescription(OptionDescription description) {
this.description = description;
this.wrappedText = null;
+ this.targetScrollAmount = 0;
+ this.currentScrollAmount = 0;
+ this.lastInteractionTime = currentTimeMS();
+ }
+
+ private int currentTimeMS() {
+ return (int)(Blaze3D.getTime() * 1000);
}
}
diff --git a/common/src/main/java/dev/isxander/yacl/gui/OptionListWidget.java b/common/src/main/java/dev/isxander/yacl/gui/OptionListWidget.java
index 390e6c0..98272d1 100644
--- a/common/src/main/java/dev/isxander/yacl/gui/OptionListWidget.java
+++ b/common/src/main/java/dev/isxander/yacl/gui/OptionListWidget.java
@@ -27,10 +27,10 @@ public class OptionListWidget extends ElementListWidgetExt<OptionListWidget.Entr
private final ConfigCategory category;
private ImmutableList<Entry> viewableChildren;
private String searchQuery = "";
- private final Consumer<Option<?>> hoverEvent;
- private Option<?> lastHoveredOption;
+ private final Consumer<OptionDescription> hoverEvent;
+ private OptionDescription lastHoveredOption;
- public OptionListWidget(YACLScreen screen, ConfigCategory category, Minecraft client, int x, int y, int width, int height, Consumer<Option<?>> hoverEvent) {
+ public OptionListWidget(YACLScreen screen, ConfigCategory category, Minecraft client, int x, int y, int width, int height, Consumer<OptionDescription> hoverEvent) {
super(client, x, y, width, height, true);
this.yaclScreen = screen;
this.category = category;
@@ -237,6 +237,13 @@ public class OptionListWidget extends ElementListWidgetExt<OptionListWidget.Entr
return ret;
}
+ private void setHoverDescription(OptionDescription description) {
+ if (description != lastHoveredOption) {
+ lastHoveredOption = description;
+ hoverEvent.accept(description);
+ }
+ }
+
public abstract class Entry extends ElementListWidgetExt.Entry<Entry> {
public boolean isViewable() {
return true;
@@ -292,11 +299,8 @@ public class OptionListWidget extends ElementListWidgetExt<OptionListWidget.Entr
resetButton.render(graphics, mouseX, mouseY, tickDelta);
}
- if (isHovered()) {
- if (lastHoveredOption != option) {
- lastHoveredOption = option;
- hoverEvent.accept(option);
- }
+ if (isHovered() || isFocused()) {
+ setHoverDescription(option.description());
}
}
@@ -392,12 +396,9 @@ public class OptionListWidget extends ElementListWidgetExt<OptionListWidget.Entr
expandMinimizeButton.render(graphics, mouseX, mouseY, tickDelta);
wrappedName.renderCentered(graphics, x + entryWidth / 2, y + getYPadding());
- }
- @Override
- public void postRender(GuiGraphics graphics, int mouseX, int mouseY, float delta) {
- if ((isHovered() && !expandMinimizeButton.isMouseOver(mouseX, mouseY)) || expandMinimizeButton.isFocused()) {
- YACLScreen.renderMultilineTooltip(graphics, font, wrappedTooltip, getRowLeft() + getRowWidth() / 2, y - 3, y + getItemHeight() + 3, screen.width, screen.height);
+ if (isHovered() || isFocused()) {
+ setHoverDescription(group.description());
}
}
diff --git a/common/src/main/java/dev/isxander/yacl/gui/ScrollableNavigationBar.java b/common/src/main/java/dev/isxander/yacl/gui/ScrollableNavigationBar.java
new file mode 100644
index 0000000..e2d9ab8
--- /dev/null
+++ b/common/src/main/java/dev/isxander/yacl/gui/ScrollableNavigationBar.java
@@ -0,0 +1,79 @@
+package dev.isxander.yacl.gui;
+
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.Font;
+import net.minecraft.client.gui.components.AbstractWidget;
+import net.minecraft.client.gui.components.TabButton;
+import net.minecraft.client.gui.components.events.GuiEventListener;
+import net.minecraft.client.gui.components.tabs.Tab;
+import net.minecraft.client.gui.components.tabs.TabManager;
+import net.minecraft.client.gui.components.tabs.TabNavigationBar;
+import net.minecraft.util.Mth;
+import org.jetbrains.annotations.Nullable;
+
+public class ScrollableNavigationBar extends TabNavigationBar {
+ private static final int NAVBAR_MARGIN = 28;
+
+ private static final Font font = Minecraft.getInstance().font;
+
+ private int scrollOffset;
+ private int maxScrollOffset;
+
+ public ScrollableNavigationBar(int i, TabManager tabManager, Iterable<Tab> iterable) {
+ super(i, tabManager, iterable);
+ }
+
+ @Override
+ public void arrangeElements() {
+ int noScrollWidth = this.width - NAVBAR_MARGIN*2;
+ int minimumSize = tabButtons.stream()
+ .map(AbstractWidget::getMessage)
+ .mapToInt(label -> font.width(label) + 3)
+ .min().orElse(0);
+ int singleTabWidth = Math.max(noScrollWidth / Math.min(this.tabButtons.size(), 3), minimumSize);
+ for (TabButton tabButton : this.tabButtons) {
+ tabButton.setWidth(singleTabWidth);
+ }
+
+ this.layout.arrangeElements();
+ this.layout.setY(0);
+ this.scrollOffset = 0;
+
+ int allTabsWidth = singleTabWidth * this.tabButtons.size();
+ this.layout.setX(Math.max((this.width - allTabsWidth) / 2, NAVBAR_MARGIN));
+ this.maxScrollOffset = Math.max(0, allTabsWidth - noScrollWidth);
+ }
+
+ @Override
+ public boolean mouseScrolled(double mouseX, double mouseY, double amount) {
+ this.setScrollOffset(this.scrollOffset - (int)(amount*10));
+ return true;
+ }
+
+ @Override
+ public boolean isMouseOver(double mouseX, double mouseY) {
+ return mouseY <= 24;
+ }
+
+ public void setScrollOffset(int scrollOffset) {
+ layout.setX(layout.getX() + this.scrollOffset);
+ this.scrollOffset = Mth.clamp(scrollOffset, 0, maxScrollOffset);
+ layout.setX(layout.getX() - this.scrollOffset);
+ }
+
+ @Override
+ public void setFocused(@Nullable GuiEventListener child) {
+ super.setFocused(child);
+ if (child instanceof TabButton tabButton) {
+ this.ensureVisible(tabButton);
+ }
+ }
+
+ protected void ensureVisible(TabButton tabButton) {
+ if (tabButton.getX() < NAVBAR_MARGIN) {
+ this.setScrollOffset(this.scrollOffset - (NAVBAR_MARGIN - tabButton.getX()));
+ } else if (tabButton.getX() + tabButton.getWidth() > this.width - NAVBAR_MARGIN) {
+ this.setScrollOffset(this.scrollOffset + (tabButton.getX() + tabButton.getWidth() - (this.width - NAVBAR_MARGIN)));
+ }
+ }
+}
diff --git a/common/src/main/java/dev/isxander/yacl/gui/TabListWidget.java b/common/src/main/java/dev/isxander/yacl/gui/TabListWidget.java
index b9e756c..041c06a 100644
--- a/common/src/main/java/dev/isxander/yacl/gui/TabListWidget.java
+++ b/common/src/main/java/dev/isxander/yacl/gui/TabListWidget.java
@@ -1,11 +1,13 @@
package dev.isxander.yacl.gui;
import com.google.common.collect.ImmutableList;
+import net.minecraft.client.gui.ComponentPath;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.components.AbstractWidget;
import net.minecraft.client.gui.components.events.ContainerEventHandler;
import net.minecraft.client.gui.components.events.GuiEventListener;
import net.minecraft.client.gui.narration.NarrationElementOutput;
+import net.minecraft.client.gui.navigation.FocusNavigationEvent;
import net.minecraft.client.gui.navigation.ScreenRectangle;
import net.minecraft.network.chat.CommonComponents;
import org.jetbrains.annotations.Nullable;
@@ -102,4 +104,16 @@ public class TabListWidget<T extends ElementListWidgetExt<?>> extends AbstractWi
public void setFocused(@Nullable GuiEventListener listener) {
this.list.setFocused(listener);
}
+
+ @Nullable
+ @Override
+ public ComponentPath nextFocusPath(FocusNavigationEvent event) {
+ return this.list.nextFocusPath(event);
+ }
+
+ @Nullable
+ @Override
+ public ComponentPath getCurrentFocusPath() {
+ return this.list.getCurrentFocusPath();
+ }
}
diff --git a/common/src/main/java/dev/isxander/yacl/gui/YACLScreen.java b/common/src/main/java/dev/isxander/yacl/gui/YACLScreen.java
index 0ce98ba..a72640e 100644
--- a/common/src/main/java/dev/isxander/yacl/gui/YACLScreen.java
+++ b/common/src/main/java/dev/isxander/yacl/gui/YACLScreen.java
@@ -56,17 +56,13 @@ public class YACLScreen extends Screen {
@Override
protected void init() {
- tabNavigationBar = TabNavigationBar.builder(tabManager, this.width)
- .addTabs(tabs = config.categories()
- .stream()
- .map(category -> {
- if (category instanceof PlaceholderCategory placeholder)
- return new PlaceholderTab(placeholder);
- return new CategoryTab(category);
- })
- .toArray(Tab[]::new)
- )
- .build();
+ tabNavigationBar = new ScrollableNavigationBar(this.width, tabManager, config.categories()
+ .stream()
+ .map(category -> {
+ if (category instanceof PlaceholderCategory placeholder)
+ return new PlaceholderTab(placeholder);
+ return new CategoryTab(category);
+ }).toList());
tabNavigationBar.selectTab(0, false);
tabNavigationBar.arrangeElements();
ScreenRectangle navBarArea = tabNavigationBar.getRectangle();
@@ -239,8 +235,8 @@ public class YACLScreen extends Screen {
this.optionList = new TabListWidget<>(
() -> new ScreenRectangle(tabArea.position(), tabArea.width() / 3 * 2 + 1, tabArea.height()),
- new OptionListWidget(YACLScreen.this, category, minecraft, 0, 0, width / 3 * 2 + 1, height, hoveredOption -> {
- descriptionWidget.setOptionDescription(hoveredOption.description());
+ new OptionListWidget(YACLScreen.this, category, minecraft, 0, 0, width / 3 * 2 + 1, height, desc -> {
+ descriptionWidget.setOptionDescription(desc);
})
);
@@ -316,6 +312,7 @@ public class YACLScreen extends Screen {
public void tick() {
updateButtons();
searchField.tick();
+ descriptionWidget.tick();
}
private void updateButtons() {
diff --git a/common/src/main/java/dev/isxander/yacl/impl/ConfigCategoryImpl.java b/common/src/main/java/dev/isxander/yacl/impl/ConfigCategoryImpl.java
index 2d39eb9..ed73174 100644
--- a/common/src/main/java/dev/isxander/yacl/impl/ConfigCategoryImpl.java
+++ b/common/src/main/java/dev/isxander/yacl/impl/ConfigCategoryImpl.java
@@ -1,10 +1,7 @@
package dev.isxander.yacl.impl;
import com.google.common.collect.ImmutableList;
-import dev.isxander.yacl.api.ConfigCategory;
-import dev.isxander.yacl.api.ListOption;
-import dev.isxander.yacl.api.Option;
-import dev.isxander.yacl.api.OptionGroup;
+import dev.isxander.yacl.api.*;
import dev.isxander.yacl.impl.utils.YACLConstants;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.ComponentContents;
@@ -114,7 +111,7 @@ public final class ConfigCategoryImpl implements ConfigCategory {
Validate.notNull(name, "`name` must not be null to build `ConfigCategory`");
List<OptionGroup> combinedGroups = new ArrayList<>();
- combinedGroups.add(new OptionGroupImpl(Component.empty(), Component.empty(), ImmutableList.copyOf(rootOptions), false, true));
+ combinedGroups.add(new OptionGroupImpl(Component.empty(), OptionDescription.createBuilder().name(Component.literal("Root")).build(), ImmutableList.copyOf(rootOptions), false, true));
combinedGroups.addAll(groups);
Validate.notEmpty(combinedGroups, "at least one option must be added to build `ConfigCategory`");
diff --git a/common/src/main/java/dev/isxander/yacl/impl/OptionDescriptionImpl.java b/common/src/main/java/dev/isxander/yacl/impl/OptionDescriptionImpl.java
index c866b43..5d09828 100644
--- a/common/src/main/java/dev/isxander/yacl/impl/OptionDescriptionImpl.java
+++ b/common/src/main/java/dev/isxander/yacl/impl/OptionDescriptionImpl.java
@@ -4,19 +4,20 @@ import dev.isxander.yacl.api.OptionDescription;
import dev.isxander.yacl.gui.ImageRenderer;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.Component;
+import net.minecraft.network.chat.MutableComponent;
import net.minecraft.resources.ResourceLocation;
import org.apache.commons.lang3.Validate;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Path;
-import java.util.Optional;
+import java.util.*;
import java.util.concurrent.CompletableFuture;
public record OptionDescriptionImpl(Component descriptiveName, Component description, CompletableFuture<Optional<ImageRenderer>> image) implements OptionDescription {
public static class BuilderImpl implements Builder {
private Component name;
- private Component description;
+ private final List<Component> descriptionLines = new ArrayList<>();
private CompletableFuture<Optional<ImageRenderer>> image = CompletableFuture.completedFuture(Optional.empty());
private boolean imageUnset = true;
@@ -27,8 +28,14 @@ public record OptionDescriptionImpl(Component descriptiveName, Component descrip
}
@Override
- public Builder description(Component description) {
- this.description = description;
+ public Builder description(Component... description) {
+ this.descriptionLines.addAll(Arrays.asList(description));
+ return this;
+ }
+
+ @Override
+ public Builder description(Collection<? extends Component> lines) {
+ this.descriptionLines.addAll(lines);
return this;
}
@@ -115,10 +122,14 @@ public record OptionDescriptionImpl(Component descriptiveName, Component descrip
public OptionDescription build() {
Validate.notNull(name, "Name must be set!");
- if (description == null)
- description = Component.empty();
+ MutableComponent concatenatedDescription = Component.empty();
+ Iterator<Component> iter = descriptionLines.iterator();
+ while (iter.hasNext()) {
+ concatenatedDescription.append(iter.next());
+ if (iter.hasNext()) concatenatedDescription.append("\n");
+ }
- return new OptionDescriptionImpl(name.copy().withStyle(ChatFormatting.BOLD), description, image);
+ return new OptionDescriptionImpl(name.copy().withStyle(ChatFormatting.BOLD), concatenatedDescription, image);
}
}
}
diff --git a/common/src/main/java/dev/isxander/yacl/impl/OptionGroupImpl.java b/common/src/main/java/dev/isxander/yacl/impl/OptionGroupImpl.java
index 113aefc..a72aa71 100644
--- a/common/src/main/java/dev/isxander/yacl/impl/OptionGroupImpl.java
+++ b/common/src/main/java/dev/isxander/yacl/impl/OptionGroupImpl.java
@@ -3,6 +3,7 @@ package dev.isxander.yacl.impl;
import com.google.common.collect.ImmutableList;
import dev.isxander.yacl.api.ListOption;
import dev.isxander.yacl.api.Option;
+import dev.isxander.yacl.api.OptionDescription;
import dev.isxander.yacl.api.OptionGroup;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.ComponentContents;
@@ -10,6 +11,7 @@ import net.minecraft.network.chat.MutableComponent;
import org.apache.commons.lang3.Validate;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
@@ -18,14 +20,14 @@ import java.util.List;
@ApiStatus.Internal
public final class OptionGroupImpl implements OptionGroup {
private final @NotNull Component name;
- private final @NotNull Component tooltip;
+ private final @NotNull OptionDescription description;
private final ImmutableList<? extends Option<?>> options;
private final boolean collapsed;
private final boolean isRoot;
- public OptionGroupImpl(@NotNull Component name, @NotNull Component tooltip, ImmutableList<? extends Option<?>> options, boolean collapsed, boolean isRoot) {
+ public OptionGroupImpl(@NotNull Component name, @NotNull OptionDescription description, ImmutableList<? extends Option<?>> options, boolean collapsed, boolean isRoot) {
this.name = name;
- this.tooltip = tooltip;
+ this.description = description;
this.options = options;
this.collapsed = collapsed;
this.isRoot = isRoot;
@@ -37,8 +39,13 @@ public final class OptionGroupImpl implements OptionGroup {
}
@Override
+ public OptionDescription description() {
+ return description;
+ }
+
+ @Override
public @NotNull Component tooltip() {
- return tooltip;
+ return description.description();
}
@Override
@@ -59,7 +66,8 @@ public final class OptionGroupImpl implements OptionGroup {
@ApiStatus.Internal
public static final class BuilderImpl implements Builder {
private Component name = Component.empty();
- private final List<Component> tooltipLines = new ArrayList<>();
+ private OptionDescription description = null;
+ private OptionDescription.Builder legacyBuilder = null;
private final List<Option<?>> options = new ArrayList<>();
private boolean collapsed = false;
@@ -72,10 +80,22 @@ public final class OptionGroupImpl implements OptionGroup {
}
@Override
+ public Builder description(@NotNull OptionDescription description) {
+ Validate.isTrue(legacyBuilder == null, "Cannot set description when deprecated `tooltip` method is used");
+ Validate.notNull(description, "`description` must not be null");
+
+ this.description = description;
+ return this;
+ }
+
+ @Override
public Builder tooltip(@NotNull Component... tooltips) {
+ Validate.isTrue(description == null, "Cannot use deprecated `tooltip` method when `description` in use.");
Validate.notEmpty(tooltips, "`tooltips` cannot be empty");
- tooltipLines.addAll(List.of(tooltips));
+ ensureLegacyDescriptionBuilder();
+
+ legacyBuilder.description(tooltips);
return this;
}
@@ -111,19 +131,18 @@ public final class OptionGroupImpl implements OptionGroup {
public OptionGroup build() {
Validate.notEmpty(options, "`options` must not be empty to build `OptionGroup`");
- MutableComponent concatenatedTooltip = Component.empty();
- boolean first = true;
- for (Component line : tooltipLines) {
- if (line.getContents() == ComponentContents.EMPTY)
- continue;
+ if (description == null) {
+ ensureLegacyDescriptionBuilder();
+ description = legacyBuilder.name(name).build();
+ }
- if (!first) concatenatedTooltip.append("\n");
- first = false;
+ return new OptionGroupImpl(name, description, ImmutableList.copyOf(options), collapsed, false);
+ }
- concatenatedTooltip.append(line);
+ private void ensureLegacyDescriptionBuilder() {
+ if (legacyBuilder == null) {
+ legacyBuilder = OptionDescription.createBuilder();
}
-
- return new OptionGroupImpl(name, concatenatedTooltip, ImmutableList.copyOf(options), collapsed, false);
}
}
}
diff --git a/common/src/main/resources/yacl.accesswidener b/common/src/main/resources/yacl.accesswidener
index 2188ea5..303b57a 100644
--- a/common/src/main/resources/yacl.accesswidener
+++ b/common/src/main/resources/yacl.accesswidener
@@ -3,4 +3,10 @@ accessWidener v2 named
extendable method net/minecraft/client/gui/components/AbstractSelectionList children ()Ljava/util/List;
extendable method net/minecraft/client/gui/components/AbstractSelectionList getEntryAtPosition (DD)Lnet/minecraft/client/gui/components/AbstractSelectionList$Entry;
accessible class net/minecraft/client/gui/components/AbstractSelectionList$Entry
-extendable method net/minecraft/client/gui/components/AbstractButton getTextureY ()I \ No newline at end of file
+extendable method net/minecraft/client/gui/components/AbstractButton getTextureY ()I
+accessible method net/minecraft/client/gui/components/tabs/TabNavigationBar <init> (ILnet/minecraft/client/gui/components/tabs/TabManager;Ljava/lang/Iterable;)V
+accessible field net/minecraft/client/gui/components/tabs/TabNavigationBar layout Lnet/minecraft/client/gui/layouts/GridLayout;
+accessible field net/minecraft/client/gui/components/tabs/TabNavigationBar width I
+accessible field net/minecraft/client/gui/components/tabs/TabNavigationBar tabManager Lnet/minecraft/client/gui/components/tabs/TabManager;
+accessible field net/minecraft/client/gui/components/tabs/TabNavigationBar tabs Lcom/google/common/collect/ImmutableList;
+accessible field net/minecraft/client/gui/components/tabs/TabNavigationBar tabButtons Lcom/google/common/collect/ImmutableList;
diff --git a/fabric/build.gradle.kts b/fabric/build.gradle.kts
index 6d527d6..690fe30 100644
--- a/fabric/build.gradle.kts
+++ b/fabric/build.gradle.kts
@@ -109,12 +109,13 @@ tasks {
}
}
-components["java"].withGroovyBuilder {
- "withVariantsFromConfiguration"(configurations["shadowRuntimeElements"]) {
- "skip"()
+components["java"].run {
+ if (this is AdhocComponentWithVariants) {
+ withVariantsFromConfiguration(configurations["shadowRuntimeElements"]) {
+ skip()
+ }
}
}
-
val changelogText: String by ext
val isBeta: Boolean by ext
diff --git a/forge/build.gradle.kts b/forge/build.gradle.kts
index 88e1b5f..4c822d3 100644
--- a/forge/build.gradle.kts
+++ b/forge/build.gradle.kts
@@ -1,4 +1,6 @@
+import org.gradle.api.component.AdhocComponentWithVariants
import org.gradle.jvm.tasks.Jar
+import org.gradle.kotlin.dsl.get
import org.gradle.kotlin.dsl.libs
plugins {
@@ -113,9 +115,11 @@ tasks {
}
}
-components["java"].withGroovyBuilder {
- "withVariantsFromConfiguration"(configurations["shadowRuntimeElements"]) {
- "skip"()
+components["java"].run {
+ if (this is AdhocComponentWithVariants) {
+ withVariantsFromConfiguration(configurations["shadowRuntimeElements"]) {
+ skip()
+ }
}
}
diff --git a/test-common/src/main/java/dev/isxander/yacl/test/GuiTest.java b/test-common/src/main/java/dev/isxander/yacl/test/GuiTest.java
index f274fe2..7c9cf9d 100644
--- a/test-common/src/main/java/dev/isxander/yacl/test/GuiTest.java
+++ b/test-common/src/main/java/dev/isxander/yacl/test/GuiTest.java
@@ -84,7 +84,7 @@ public class GuiTest {
.name(Component.literal("Custom Boolean Toggle"))
.description(OptionDescription.createBuilder()
.name(Component.literal("Custom Boolean Toggle"))
- .description(Component.literal("You can customize controllers like so! YACL is truly infinitely customizable!"))
+ .description(Component.literal("You can customize controllers like so! YACL is truly infinitely customizable! This tooltip is long in order to demonstrate the cool, smooth scrolling of these descriptions. Did you know, they are also super clickable?! I know, cool right, YACL 3.x really is amazing."))
.image(Path.of("D:\\Xander\\Downloads\\_MG_0860-Enhanced-NR.png"), new ResourceLocation("yacl", "f.webp"))
.build())
.tooltip(Component.literal("You can customize these controllers like this!"))
@@ -283,10 +283,6 @@ public class GuiTest {
.initial(Component.literal("Initial label"))
.build())
.build())
- .category(PlaceholderCategory.createBuilder()
- .name(Component.literal("Placeholder Category"))
- .screen((client, yaclScreen) -> new RequireRestartScreen(yaclScreen))
- .build())
.category(ConfigCategory.createBuilder()
.name(Component.literal("Group Test"))
.option(Option.createBuilder(boolean.class)
@@ -332,6 +328,38 @@ public class GuiTest {
.build())
.build())
.build())
+ .category(ConfigCategory.createBuilder()
+ .name(Component.literal("Category Test"))
+ .option(LabelOption.create(Component.literal("This is a test category!")))
+ .build())
+ .category(ConfigCategory.createBuilder()
+ .name(Component.literal("Category Test"))
+ .option(LabelOption.create(Component.literal("This is a test category!")))
+ .build())
+ .category(ConfigCategory.createBuilder()
+ .name(Component.literal("Category Test"))
+ .option(LabelOption.create(Component.literal("This is a test category!")))
+ .build())
+ .category(ConfigCategory.createBuilder()
+ .name(Component.literal("Category Test"))
+ .option(LabelOption.create(Component.literal("This is a test category!")))
+ .build())
+ .category(ConfigCategory.createBuilder()
+ .name(Component.literal("Category Test"))
+ .option(LabelOption.create(Component.literal("This is a test category!")))
+ .build())
+ .category(ConfigCategory.createBuilder()
+ .name(Component.literal("Category Test"))
+ .option(LabelOption.create(Component.literal("This is a test category!")))
+ .build())
+ .category(ConfigCategory.createBuilder()
+ .name(Component.literal("Category Test"))
+ .option(LabelOption.create(Component.literal("This is a test category!")))
+ .build())
+ .category(PlaceholderCategory.createBuilder()
+ .name(Component.literal("Placeholder Category"))
+ .screen((client, yaclScreen) -> new RequireRestartScreen(yaclScreen))
+ .build())
.save(() -> {
Minecraft.getInstance().options.save();
ConfigTest.GSON.save();