aboutsummaryrefslogtreecommitdiff
path: root/common/src/main/java/dev/isxander/yacl/gui
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 /common/src/main/java/dev/isxander/yacl/gui
parent590e69f4bf445a39737b0b1552cf116ff780d75e (diff)
downloadYetAnotherConfigLib-ead0b794ac57e9ab2558338f7f3da7545d2e12ff.tar.gz
YetAnotherConfigLib-ead0b794ac57e9ab2558338f7f3da7545d2e12ff.tar.bz2
YetAnotherConfigLib-ead0b794ac57e9ab2558338f7f3da7545d2e12ff.zip
scrollable navbar, group descriptions, auto-scroll descriptions
Diffstat (limited to 'common/src/main/java/dev/isxander/yacl/gui')
-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
5 files changed, 183 insertions, 30 deletions
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() {