diff options
author | isXander <xandersmith2008@gmail.com> | 2023-05-22 19:33:00 +0100 |
---|---|---|
committer | isXander <xandersmith2008@gmail.com> | 2023-05-22 19:33:00 +0100 |
commit | ead0b794ac57e9ab2558338f7f3da7545d2e12ff (patch) | |
tree | d1c92f8d3283a8989923e038a377af4a00c24124 /common/src/main/java/dev/isxander/yacl/gui | |
parent | 590e69f4bf445a39737b0b1552cf116ff780d75e (diff) | |
download | YetAnotherConfigLib-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')
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() { |