diff options
| author | Xander <xander@isxander.dev> | 2023-04-25 16:28:41 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-04-25 16:28:41 +0100 |
| commit | 13c7ba45ff201423eb8dba8a40cfb66ebb531439 (patch) | |
| tree | 1e799aa9da11fbc0833bc6b7c6e6799633c2ec50 /common/src/main/java/dev/isxander/yacl/gui | |
| parent | 8ba7196ae990fe9aa98680aba1b387e385fff99c (diff) | |
| download | YetAnotherConfigLib-13c7ba45ff201423eb8dba8a40cfb66ebb531439.tar.gz YetAnotherConfigLib-13c7ba45ff201423eb8dba8a40cfb66ebb531439.tar.bz2 YetAnotherConfigLib-13c7ba45ff201423eb8dba8a40cfb66ebb531439.zip | |
Architectury! (#61)
Diffstat (limited to 'common/src/main/java/dev/isxander/yacl/gui')
40 files changed, 4570 insertions, 0 deletions
diff --git a/common/src/main/java/dev/isxander/yacl/gui/AbstractWidget.java b/common/src/main/java/dev/isxander/yacl/gui/AbstractWidget.java new file mode 100644 index 0000000..ae3c83b --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl/gui/AbstractWidget.java @@ -0,0 +1,107 @@ +package dev.isxander.yacl.gui; + +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.PoseStack; +import dev.isxander.yacl.api.utils.Dimension; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.GuiComponent; +import net.minecraft.client.gui.components.Renderable; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.client.gui.narration.NarratableEntry; +import net.minecraft.client.gui.narration.NarrationElementOutput; +import net.minecraft.client.renderer.GameRenderer; +import net.minecraft.client.resources.sounds.SimpleSoundInstance; +import net.minecraft.sounds.SoundEvents; + +import java.awt.*; + +public abstract class AbstractWidget implements GuiEventListener, Renderable, NarratableEntry { + protected final Minecraft client = Minecraft.getInstance(); + protected final Font textRenderer = client.font; + protected final int inactiveColor = 0xFFA0A0A0; + + private Dimension<Integer> dim; + + public AbstractWidget(Dimension<Integer> dim) { + this.dim = dim; + } + + public void postRender(PoseStack matrices, int mouseX, int mouseY, float delta) { + + } + + public boolean canReset() { + return false; + } + + @Override + public boolean isMouseOver(double mouseX, double mouseY) { + if (dim == null) return false; + return this.dim.isPointInside((int) mouseX, (int) mouseY); + } + + public void setDimension(Dimension<Integer> dim) { + this.dim = dim; + } + + public Dimension<Integer> getDimension() { + return dim; + } + + @Override + public NarrationPriority narrationPriority() { + return NarrationPriority.NONE; + } + + public void unfocus() { + + } + + public boolean matchesSearch(String query) { + return true; + } + + @Override + public void updateNarration(NarrationElementOutput builder) { + + } + + protected void drawButtonRect(PoseStack matrices, int x1, int y1, int x2, int y2, boolean hovered, boolean enabled) { + if (x1 > x2) { + int xx1 = x1; + x1 = x2; + x2 = xx1; + } + if (y1 > y2) { + int yy1 = y1; + y1 = y2; + y2 = yy1; + } + int width = x2 - x1; + int height = y2 - y1; + + RenderSystem.setShader(GameRenderer::getPositionTexShader); + RenderSystem.setShaderTexture(0, net.minecraft.client.gui.components.AbstractWidget.WIDGETS_LOCATION); + RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); + int i = !enabled ? 0 : hovered ? 2 : 1; + RenderSystem.enableBlend(); + RenderSystem.defaultBlendFunc(); + RenderSystem.enableDepthTest(); + GuiComponent.blit(matrices, x1, y1, 0, 0, 46 + i * 20, width / 2, height, 256, 256); + GuiComponent.blit(matrices, x1 + width / 2, y1, 0, 200 - width / 2f, 46 + i * 20, width / 2, height, 256, 256); + } + + protected int multiplyColor(int hex, float amount) { + Color color = new Color(hex, true); + + return new Color(Math.max((int)(color.getRed() * amount), 0), + Math.max((int)(color.getGreen() * amount), 0), + Math.max((int)(color.getBlue() * amount), 0), + color.getAlpha()).getRGB(); + } + + public void playDownSound() { + Minecraft.getInstance().getSoundManager().play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK, 1.0F)); + } +} diff --git a/common/src/main/java/dev/isxander/yacl/gui/CategoryListWidget.java b/common/src/main/java/dev/isxander/yacl/gui/CategoryListWidget.java new file mode 100644 index 0000000..41286ff --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl/gui/CategoryListWidget.java @@ -0,0 +1,99 @@ +package dev.isxander.yacl.gui; + +import com.google.common.collect.ImmutableList; +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.PoseStack; +import dev.isxander.yacl.api.ConfigCategory; +import dev.isxander.yacl.gui.utils.GuiUtils; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.client.gui.narration.NarratableEntry; + +import java.util.List; + +public class CategoryListWidget extends ElementListWidgetExt<CategoryListWidget.CategoryEntry> { + private final YACLScreen yaclScreen; + + public CategoryListWidget(Minecraft client, YACLScreen yaclScreen, int screenWidth, int screenHeight) { + super(client, 0, 0, screenWidth / 3, yaclScreen.searchFieldWidget.getY() - 5, true); + this.yaclScreen = yaclScreen; + setRenderBackground(false); + setRenderTopAndBottom(false); + + for (ConfigCategory category : yaclScreen.config.categories()) { + addEntry(new CategoryEntry(category)); + } + } + + @Override + public void render(PoseStack matrices, int mouseX, int mouseY, float delta) { + GuiUtils.enableScissor(0, 0, width, height); + super.render(matrices, mouseX, mouseY, delta); + RenderSystem.disableScissor(); + } + + @Override + public int getRowWidth() { + return Math.min(width - width / 10, 396); + } + + @Override + public int getRowLeft() { + return super.getRowLeft() - 2; + } + + @Override + protected int getScrollbarPosition() { + return width - 2; + } + + @Override + protected void renderBackground(PoseStack matrices) { + + } + + public class CategoryEntry extends Entry<CategoryEntry> { + private final CategoryWidget categoryButton; + public final int categoryIndex; + + public CategoryEntry(ConfigCategory category) { + this.categoryIndex = yaclScreen.config.categories().indexOf(category); + categoryButton = new CategoryWidget( + yaclScreen, + category, + categoryIndex, + getRowLeft(), 0, + getRowWidth(), 20 + ); + } + + @Override + public void render(PoseStack matrices, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) { + if (mouseY > y1) { + mouseY = -20; + } + + categoryButton.setY(y); + categoryButton.render(matrices, mouseX, mouseY, tickDelta); + } + + public void postRender(PoseStack matrices, int mouseX, int mouseY, float tickDelta) { + categoryButton.renderHoveredTooltip(matrices); + } + + @Override + public int getItemHeight() { + return 21; + } + + @Override + public List<? extends GuiEventListener> children() { + return ImmutableList.of(categoryButton); + } + + @Override + public List<? extends NarratableEntry> narratables() { + return ImmutableList.of(categoryButton); + } + } +} diff --git a/common/src/main/java/dev/isxander/yacl/gui/CategoryWidget.java b/common/src/main/java/dev/isxander/yacl/gui/CategoryWidget.java new file mode 100644 index 0000000..60817a2 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl/gui/CategoryWidget.java @@ -0,0 +1,38 @@ +package dev.isxander.yacl.gui; + +import dev.isxander.yacl.api.ConfigCategory; +import net.minecraft.client.sounds.SoundManager; + +public class CategoryWidget extends TooltipButtonWidget { + private final int categoryIndex; + + public CategoryWidget(YACLScreen screen, ConfigCategory category, int categoryIndex, int x, int y, int width, int height) { + super(screen, x, y, width, height, category.name(), category.tooltip(), btn -> { + screen.searchFieldWidget.setValue(""); + screen.changeCategory(categoryIndex); + }); + this.categoryIndex = categoryIndex; + } + + private boolean isCurrentCategory() { + return ((YACLScreen) screen).getCurrentCategoryIdx() == categoryIndex; + } + + @Override + protected int getTextureY() { + int i = 1; + if (!this.active) { + i = 0; + } else if (this.isHoveredOrFocused() || isCurrentCategory()) { + i = 2; + } + + return 46 + i * 20; + } + + @Override + public void playDownSound(SoundManager soundManager) { + if (!isCurrentCategory()) + super.playDownSound(soundManager); + } +} diff --git a/common/src/main/java/dev/isxander/yacl/gui/ElementListWidgetExt.java b/common/src/main/java/dev/isxander/yacl/gui/ElementListWidgetExt.java new file mode 100644 index 0000000..b177236 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl/gui/ElementListWidgetExt.java @@ -0,0 +1,177 @@ +package dev.isxander.yacl.gui; + +import com.mojang.blaze3d.platform.InputConstants; +import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.components.ContainerObjectSelectionList; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.util.Mth; +import org.jetbrains.annotations.Nullable; + +public class ElementListWidgetExt<E extends ElementListWidgetExt.Entry<E>> extends ContainerObjectSelectionList<E> { + protected final int x, y; + + private double smoothScrollAmount = getScrollAmount(); + private boolean returnSmoothAmount = false; + private final boolean doSmoothScrolling; + + public ElementListWidgetExt(Minecraft client, int x, int y, int width, int height, boolean smoothScrolling) { + super(client, width, height, y, y + height, 22); + this.x = this.x0 = x; + this.y = y; + this.x1 = this.x0 + width; + this.doSmoothScrolling = smoothScrolling; + } + + @Override + public boolean mouseScrolled(double mouseX, double mouseY, double amount) { + // default implementation bases scroll step from total height of entries, this is constant + this.setScrollAmount(this.getScrollAmount() - amount * 20); + return true; + } + + @Override + protected void renderBackground(PoseStack matrices) { + // render transparent background if in-game. + setRenderBackground(minecraft.level == null); + if (minecraft.level != null) + fill(matrices, x0, y0, x1, y1, 0x6B000000); + } + + @Override + protected int getScrollbarPosition() { + // default implementation does not respect left/right + return this.x1 - 2; + } + + @Override + public void render(PoseStack matrices, int mouseX, int mouseY, float delta) { + smoothScrollAmount = Mth.lerp(Minecraft.getInstance().getDeltaFrameTime() * 0.5, smoothScrollAmount, getScrollAmount()); + returnSmoothAmount = true; + super.render(matrices, mouseX, mouseY, delta); + returnSmoothAmount = false; + } + + /** + * awful code to only use smooth scroll state when rendering, + * not other code that needs target scroll amount + */ + @Override + public double getScrollAmount() { + if (returnSmoothAmount && doSmoothScrolling) + return smoothScrollAmount; + + return super.getScrollAmount(); + } + + protected void resetSmoothScrolling() { + this.smoothScrollAmount = getScrollAmount(); + } + + public void postRender(PoseStack matrices, int mouseX, int mouseY, float delta) { + for (E entry : children()) { + entry.postRender(matrices, mouseX, mouseY, delta); + } + } + + @Nullable + @Override + protected E getEntryAtPosition(double x, double y) { + y += getScrollAmount(); + + if (x < this.x0 || x > this.x1) + return null; + + int currentY = this.y0 - headerHeight + 4; + for (E entry : children()) { + if (y >= currentY && y <= currentY + entry.getItemHeight()) { + return entry; + } + + currentY += entry.getItemHeight(); + } + + return null; + } + + /* + below code is licensed from cloth-config under LGPL3 + modified to inherit vanilla's EntryListWidget and use yarn mappings + + code is responsible for having dynamic item heights + */ + + @Override + protected int getMaxPosition() { + return children().stream().map(E::getItemHeight).reduce(0, Integer::sum) + headerHeight; + } + + @Override + protected void centerScrollOn(E entry) { + double d = (this.height) / -2d; + for (int i = 0; i < this.children().indexOf(entry) && i < this.getItemCount(); i++) + d += children().get(i).getItemHeight(); + this.setScrollAmount(d); + } + + @Override + protected int getRowTop(int index) { + int integer = y0 + 4 - (int) this.getScrollAmount() + headerHeight; + for (int i = 0; i < children().size() && i < index; i++) + integer += children().get(i).getItemHeight(); + return integer; + } + + @Override + protected void renderList(PoseStack matrices, int mouseX, int mouseY, float delta) { + int left = this.getRowLeft(); + int right = this.getRowWidth(); + int count = this.getItemCount(); + + for(int i = 0; i < count; ++i) { + E entry = children().get(i); + int top = this.getRowTop(i); + int bottom = top + entry.getItemHeight(); + int entryHeight = entry.getItemHeight() - 4; + if (bottom >= this.y0 && top <= this.y1) { + this.renderItem(matrices, mouseX, mouseY, delta, i, left, top, right, entryHeight); + } + } + } + + /* END cloth config code */ + + public abstract static class Entry<E extends Entry<E>> extends ContainerObjectSelectionList.Entry<E> { + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + for (GuiEventListener child : this.children()) { + if (child.mouseClicked(mouseX, mouseY, button)) { + if (button == InputConstants.MOUSE_BUTTON_LEFT) + this.setDragging(true); + return true; + } + } + + return false; + } + + @Override + public boolean mouseDragged(double mouseX, double mouseY, int button, double deltaX, double deltaY) { + if (isDragging() && button == InputConstants.MOUSE_BUTTON_LEFT) { + for (GuiEventListener child : this.children()) { + if (child.mouseDragged(mouseX, mouseY, button, deltaX, deltaY)) + return true; + } + } + return false; + } + + public void postRender(PoseStack matrices, int mouseX, int mouseY, float delta) { + + } + + public int getItemHeight() { + return 22; + } + } +} diff --git a/common/src/main/java/dev/isxander/yacl/gui/LowProfileButtonWidget.java b/common/src/main/java/dev/isxander/yacl/gui/LowProfileButtonWidget.java new file mode 100644 index 0000000..e8bf59f --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl/gui/LowProfileButtonWidget.java @@ -0,0 +1,28 @@ +package dev.isxander.yacl.gui; + +import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.components.Tooltip; +import net.minecraft.network.chat.Component; + +public class LowProfileButtonWidget extends Button { + public LowProfileButtonWidget(int x, int y, int width, int height, Component message, OnPress onPress) { + super(x, y, width, height, message, onPress, DEFAULT_NARRATION); + } + + public LowProfileButtonWidget(int x, int y, int width, int height, Component message, OnPress onPress, Tooltip tooltip) { + this(x, y, width, height, message, onPress); + setTooltip(tooltip); + } + + @Override + public void renderWidget(PoseStack matrices, int mouseX, int mouseY, float deltaTicks) { + if (!isHoveredOrFocused() || !active) { + int j = this.active ? 0xFFFFFF : 0xA0A0A0; + this.renderString(matrices, Minecraft.getInstance().font, j); + } else { + super.renderWidget(matrices, mouseX, mouseY, deltaTicks); + } + } +} diff --git a/common/src/main/java/dev/isxander/yacl/gui/OptionListWidget.java b/common/src/main/java/dev/isxander/yacl/gui/OptionListWidget.java new file mode 100644 index 0000000..a73ce43 --- /dev/null +++ b/common/src/main/java/dev/isxander/yacl/gui/OptionListWidget.java @@ -0,0 +1,570 @@ +package dev.isxander.yacl.gui; + +import com.google.common.collect.ImmutableList; +import com.mojang.blaze3d.vertex.PoseStack; +import dev.isxander.yacl.api.*; +import dev.isxander.yacl.api.utils.Dimension; +import dev.isxander.yacl.impl.utils.YACLConstants; +import net.minecraft.ChatFormatting; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.components.MultiLineLabel; +import net.minecraft.client.gui.components.events.GuiEventListener; +import net.minecraft.client.gui.narration.NarratableEntry; +import net.minecraft.client.gui.narration.NarratedElementType; +import net.minecraft.client.gui.narration.NarrationElementOutput; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.Component; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +public class OptionListWidget extends ElementListWidgetExt<OptionListWidget.Entry> { + private final YACLScreen yaclScreen; + private boolean singleCategory = false; + + private ImmutableList<Entry> viewableChildren; + + public OptionListWidget(YACLScreen screen, Minecraft client, int width, int height) { + super(client, width / 3, 0, width / 3 * 2 + 1, height, true); + this.yaclScreen = screen; + + refreshOptions(); + + for (ConfigCategory category : screen.config.categories()) { + for (OptionGroup group : category.groups()) { + if (group instanceof ListOption<?> listOption) { + listOption.addRefreshListener(() -> refreshListEntries(listOption, category)); + } + } + } + } + + public void refreshOptions() { + clearEntries(); + + List<ConfigCategory> categories = new ArrayList<>(); + if (yaclScreen.getCurrentCategoryIdx() == -1) { + // -1 = no category, search in progress, so use all categories for search + categories.addAll(yaclScreen.config.categories()); + } else { + categories.add(yaclScreen.config.categories().get(yaclScreen.getCurrentCategoryIdx())); + } + singleCategory = categories.size() == 1; + + for (ConfigCategory category : categories) { + for (OptionGroup group : category.groups()) { + GroupSeparatorEntry groupSeparatorEntry; + if (!group.isRoot()) { + groupSeparatorEntry = group instanceof ListOption<?> listOption + ? new ListGroupSeparatorEntry(listOption, yaclScreen) + : new GroupSeparatorEntry(group, yaclScreen); + addEntry(groupSeparatorEntry); + } else { + groupSeparatorEntry = null; + } + + List<Entry> optionEntries = new ArrayList<>(); + + // add empty entry to make sure users know it's empty not just bugging out + if (groupSeparatorEntry instanceof ListGroupSeparatorEntry listGroupSeparatorEntry) { + if (listGroupSeparatorEntry.listOption.options().isEmpty()) { + EmptyListLabel emptyListLabel = new EmptyListLabel(listGroupSeparatorEntry, category); + addEntry(emptyListLabel); + optionEntries.add(emptyListLabel); + } + } + + for (Option<?> option : group.options()) { + OptionEntry entry = new OptionEntry(option, category, group, groupSeparatorEntry, option.controller().provideWidget(yaclScreen, getDefaultEntryDimension())); + addEntry(entry); + optionEntries.add(entry); + } + + if (groupSeparatorEntry != null) { + groupSeparatorEntry.setChildEntries(optionEntries); + } + } + } + + recacheViewableChildren(); + setScrollAmount(0); + resetSmoothScrolling(); + } + + private void refreshListEntries(ListOption<?> listOption, ConfigCategory category) { + // find group separator for group + ListGroupSeparatorEntry groupSeparator = super.children().stream().filter(e -> e instanceof ListGroupSeparatorEntry gs && gs.group == listOption).map(ListGroupSeparatorEntry.class::cast).findAny().orElse(null); + + if (groupSeparator == null) { + YACLConstants.LOGGER.warn("Can't find group seperator to refresh list option entries for list option " + listOption.name()); + return; + } + + for (Entry entry : groupSeparator.childEntries) + super.removeEntry(entry); + groupSeparator.childEntries.clear(); + + // if no entries, below loop won't run where addEntryBelow() recaches viewable children |
