diff options
Diffstat (limited to 'src/client/java/dev/isxander/yacl/gui')
38 files changed, 3969 insertions, 0 deletions
diff --git a/src/client/java/dev/isxander/yacl/gui/AbstractWidget.java b/src/client/java/dev/isxander/yacl/gui/AbstractWidget.java new file mode 100644 index 0000000..529748d --- /dev/null +++ b/src/client/java/dev/isxander/yacl/gui/AbstractWidget.java @@ -0,0 +1,108 @@ +package dev.isxander.yacl.gui; + +import com.mojang.blaze3d.systems.RenderSystem; +import dev.isxander.yacl.api.utils.Dimension; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.font.TextRenderer; +import net.minecraft.client.gui.Drawable; +import net.minecraft.client.gui.DrawableHelper; +import net.minecraft.client.gui.Element; +import net.minecraft.client.gui.Selectable; +import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder; +import net.minecraft.client.gui.widget.ClickableWidget; +import net.minecraft.client.render.GameRenderer; +import net.minecraft.client.sound.PositionedSoundInstance; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.sound.SoundEvents; + +import java.awt.Color; + +public abstract class AbstractWidget implements Element, Drawable, Selectable { + protected final MinecraftClient client = MinecraftClient.getInstance(); + protected final TextRenderer textRenderer = client.textRenderer; + protected final int inactiveColor = 0xFFA0A0A0; + + private Dimension<Integer> dim; + + public AbstractWidget(Dimension<Integer> dim) { + this.dim = dim; + } + + public void postRender(MatrixStack 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 SelectionType getType() { + return SelectionType.NONE; + } + + public void unfocus() { + + } + + public boolean matchesSearch(String query) { + return true; + } + + @Override + public void appendNarrations(NarrationMessageBuilder builder) { + + } + + protected void drawButtonRect(MatrixStack 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::getPositionTexProgram); + RenderSystem.setShaderTexture(0, ClickableWidget.WIDGETS_TEXTURE); + RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); + int i = !enabled ? 0 : hovered ? 2 : 1; + RenderSystem.enableBlend(); + RenderSystem.defaultBlendFunc(); + RenderSystem.enableDepthTest(); + DrawableHelper.drawTexture(matrices, x1, y1, 0, 0, 46 + i * 20, width / 2, height, 256, 256); + DrawableHelper.drawTexture(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() { + MinecraftClient.getInstance().getSoundManager().play(PositionedSoundInstance.master(SoundEvents.UI_BUTTON_CLICK, 1.0F)); + } +} diff --git a/src/client/java/dev/isxander/yacl/gui/CategoryListWidget.java b/src/client/java/dev/isxander/yacl/gui/CategoryListWidget.java new file mode 100644 index 0000000..4dbcb11 --- /dev/null +++ b/src/client/java/dev/isxander/yacl/gui/CategoryListWidget.java @@ -0,0 +1,106 @@ +package dev.isxander.yacl.gui; + +import com.google.common.collect.ImmutableList; +import com.mojang.blaze3d.systems.RenderSystem; +import dev.isxander.yacl.api.ConfigCategory; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.Element; +import net.minecraft.client.gui.Selectable; +import net.minecraft.client.gui.widget.ElementListWidget; +import net.minecraft.client.util.math.MatrixStack; + +import java.util.List; + +public class CategoryListWidget extends ElementListWidgetExt<CategoryListWidget.CategoryEntry> { + private final YACLScreen yaclScreen; + + public CategoryListWidget(MinecraftClient client, YACLScreen yaclScreen, int screenWidth, int screenHeight) { + super(client, 0, 0, screenWidth / 3, yaclScreen.searchFieldWidget.getY() - 5, true); + this.yaclScreen = yaclScreen; + setRenderBackground(false); + setRenderHorizontalShadows(false); + + for (ConfigCategory category : yaclScreen.config.categories()) { + addEntry(new CategoryEntry(category)); + } + } + + @Override + public void render(MatrixStack matrices, int mouseX, int mouseY, float delta) { + double d = this.client.getWindow().getScaleFactor(); + RenderSystem.enableScissor(0, (int)((yaclScreen.height - bottom) * d), (int)(width * d), (int)(height * d)); + super.render(matrices, mouseX, mouseY, delta); + RenderSystem.disableScissor(); + } + + public void postRender(MatrixStack matrices, int mouseX, int mouseY, float delta) { + for (CategoryEntry entry : children()) { + entry.postRender(matrices, mouseX, mouseY, delta); + } + } + + @Override + public int getRowWidth() { + return Math.min(width - width / 10, 396); + } + + @Override + public int getRowLeft() { + return super.getRowLeft() - 2; + } + + @Override + protected int getScrollbarPositionX() { + return width - 2; + } + + @Override + protected void renderBackground(MatrixStack 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(MatrixStack matrices, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) { + if (mouseY > bottom) { + mouseY = -20; + } + + categoryButton.setY(y); + categoryButton.render(matrices, mouseX, mouseY, tickDelta); + } + + public void postRender(MatrixStack matrices, int mouseX, int mouseY, float tickDelta) { + categoryButton.renderHoveredTooltip(matrices); + } + + @Override + public int getItemHeight() { + return 21; + } + + @Override + public List<? extends Element> children() { + return ImmutableList.of(categoryButton); + } + + @Override + public List<? extends Selectable> selectableChildren() { + return ImmutableList.of(categoryButton); + } + } +} diff --git a/src/client/java/dev/isxander/yacl/gui/CategoryWidget.java b/src/client/java/dev/isxander/yacl/gui/CategoryWidget.java new file mode 100644 index 0000000..3c5d8d2 --- /dev/null +++ b/src/client/java/dev/isxander/yacl/gui/CategoryWidget.java @@ -0,0 +1,31 @@ +package dev.isxander.yacl.gui; + +import dev.isxander.yacl.api.ConfigCategory; +import net.minecraft.client.sound.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.setText(""); + screen.changeCategory(categoryIndex); + }); + this.categoryIndex = categoryIndex; + } + + private boolean isCurrentCategory() { + return ((YACLScreen) screen).getCurrentCategoryIdx() == categoryIndex; + } + + @Override + protected int getYImage(boolean hovered) { + return super.getYImage(hovered || isCurrentCategory()); + } + + @Override + public void playDownSound(SoundManager soundManager) { + if (!isCurrentCategory()) + super.playDownSound(soundManager); + } +} diff --git a/src/client/java/dev/isxander/yacl/gui/ElementListWidgetExt.java b/src/client/java/dev/isxander/yacl/gui/ElementListWidgetExt.java new file mode 100644 index 0000000..f2a19f2 --- /dev/null +++ b/src/client/java/dev/isxander/yacl/gui/ElementListWidgetExt.java @@ -0,0 +1,152 @@ +package dev.isxander.yacl.gui; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.widget.ElementListWidget; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.util.math.MathHelper; +import org.jetbrains.annotations.Nullable; + +public class ElementListWidgetExt<E extends ElementListWidgetExt.Entry<E>> extends ElementListWidget<E> { + protected final int x, y; + + private double smoothScrollAmount = getScrollAmount(); + private boolean returnSmoothAmount = false; + private final boolean doSmoothScrolling; + + public ElementListWidgetExt(MinecraftClient client, int x, int y, int width, int height, boolean smoothScrolling) { + super(client, width, height, y, y + height, 22); + this.x = x; + this.y = y; + this.left = x; + this.right = this.left + 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(MatrixStack matrices) { + // render transparent background if in-game. + setRenderBackground(client.world == null); + if (client.world != null) + fill(matrices, left, top, right, bottom, 0x6B000000); + } + + @Override + protected int getScrollbarPositionX() { + // default implementation does not respect left/right + return this.right - 2; + } + + @Override + public void render(MatrixStack matrices, int mouseX, int mouseY, float delta) { + smoothScrollAmount = MathHelper.lerp(MinecraftClient.getInstance().getLastFrameDuration() * 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(MatrixStack matrices, int mouseX, int mouseY, float delta) { + for (E entry : children()) { + entry.postRender(matrices, mouseX, mouseY, delta); + } + } + + /* + 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 + */ + + @Nullable + @Override + protected E getEntryAtPosition(double x, double y) { + int listMiddleX = this.left + this.width / 2; + int minX = listMiddleX - this.getRowWidth() / 2; + int maxX = listMiddleX + this.getRowWidth() / 2; + int currentY = MathHelper.floor(y - (double) this.top) - this.headerHeight + (int) this.getScrollAmount() - 4; + int itemY = 0; + int itemIndex = -1; + for (int i = 0; i < children().size(); i++) { + E item = children().get(i); + itemY += item.getItemHeight(); + if (itemY > currentY) { + itemIndex = i; + break; + } + } + return x < (double) this.getScrollbarPositionX() && x >= minX && y <= maxX && itemIndex >= 0 && currentY >= 0 && itemIndex < this.getEntryCount() ? this.children().get(itemIndex) : null; + } + + @Override + protected int getMaxPosition() { + return children().stream().map(E::getItemHeight).reduce(0, Integer::sum) + headerHeight; + } + + @Override + protected void centerScrollOn(E entry) { + double d = (this.bottom - this.top) / -2d; + for (int i = 0; i < this.children().indexOf(entry) && i < this.getEntryCount(); i++) + d += children().get(i).getItemHeight(); + this.setScrollAmount(d); + } + + @Override + protected int getRowTop(int index) { + int integer = top + 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(MatrixStack matrices, int mouseX, int mouseY, float delta) { + int left = this.getRowLeft(); + int right = this.getRowWidth(); + int count = this.getEntryCount(); + + 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.top && top <= this.bottom) { + this.renderEntry(matrices, mouseX, mouseY, delta, i, left, top, right, entryHeight); + } + } + } + + /* END cloth config code */ + + public abstract static class Entry<E extends ElementListWidgetExt.Entry<E>> extends ElementListWidget.Entry<E> { + public void postRender(MatrixStack matrices, int mouseX, int mouseY, float delta) { + + } + + public int getItemHeight() { + return 22; + } + } +} diff --git a/src/client/java/dev/isxander/yacl/gui/LowProfileButtonWidget.java b/src/client/java/dev/isxander/yacl/gui/LowProfileButtonWidget.java new file mode 100644 index 0000000..9fa01a7 --- /dev/null +++ b/src/client/java/dev/isxander/yacl/gui/LowProfileButtonWidget.java @@ -0,0 +1,29 @@ +package dev.isxander.yacl.gui; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.tooltip.Tooltip; +import net.minecraft.client.gui.widget.ButtonWidget; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.text.Text; +import net.minecraft.util.math.MathHelper; + +public class LowProfileButtonWidget extends ButtonWidget { + public LowProfileButtonWidget(int x, int y, int width, int height, Text message, PressAction onPress) { + super(x, y, width, height, message, onPress, DEFAULT_NARRATION_SUPPLIER); + } + + public LowProfileButtonWidget(int x, int y, int width, int height, Text message, PressAction onPress, Tooltip tooltip) { + this(x, y, width, height, message, onPress); + setTooltip(tooltip); + } + + @Override + public void renderButton(MatrixStack matrices, int mouseX, int mouseY, float delta) { + if (!isHovered()) { + int j = this.active ? 0xFFFFFF : 0xA0A0A0; + drawCenteredText(matrices, MinecraftClient.getInstance().textRenderer, this.getMessage(), this.getX() + this.width / 2, this.getY() + (this.height - 8) / 2, j | MathHelper.ceil(this.alpha * 255.0F) << 24); + } else { + super.renderButton(matrices, mouseX, mouseY, delta); + } + } +} diff --git a/src/client/java/dev/isxander/yacl/gui/OptionListWidget.java b/src/client/java/dev/isxander/yacl/gui/OptionListWidget.java new file mode 100644 index 0000000..7ea275b --- /dev/null +++ b/src/client/java/dev/isxander/yacl/gui/OptionListWidget.java @@ -0,0 +1,361 @@ +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 net.minecraft.client.MinecraftClient; +import net.minecraft.client.font.MultilineText; +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.Text; +import net.minecraft.util.math.MathHelper; +import org.jetbrains.annotations.Nullable; + +import java.util.*; +import java.util.function.Supplier; + +public class OptionListWidget extends ElementListWidgetExt<OptionListWidget.Entry> { + private final YACLScreen yaclScreen; + private boolean singleCategory = false; + + private ImmutableList<Entry> viewableChildren; + + public OptionListWidget(YACLScreen screen, MinecraftClient client, int width, int height) { + super(client, width / 3, 0, width / 3 * 2, height, true); + this.yaclScreen = screen; + + refreshOptions(); + } + + public void refreshOptions() { + clearEntries(); + + List<ConfigCategory> categories = new ArrayList<>(); + if (yaclScreen.getCurrentCategoryIdx() == -1) { + 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()) { + Supplier<Boolean> viewableSupplier; + GroupSeparatorEntry groupSeparatorEntry = null; + if (!group.isRoot()) { + groupSeparatorEntry = new GroupSeparatorEntry(group, yaclScreen); + viewableSupplier = groupSeparatorEntry::isExpanded; + addEntry(groupSeparatorEntry); + } else { + viewableSupplier = () -> true; + } + + List<OptionEntry> optionEntries = new ArrayList<>(); + for (Option<?> option : group.options()) { + OptionEntry entry = new OptionEntry(option, category, group, option.controller().provideWidget(yaclScreen, Dimension.ofInt(getRowLeft(), 0, getRowWidth(), 20)), viewableSupplier); + addEntry(entry); + optionEntries.add(entry); + } + + if (groupSeparatorEntry != null) { + groupSeparatorEntry.setOptionEntries(optionEntries); + } + } + } + + recacheViewableChildren(); + setScrollAmount(0); + resetSmoothScrolling(); + } + + public void expandAllGroups() { + for (Entry entry : super.children()) { + if (entry instanceof GroupSeparatorEntry groupSeparatorEntry) { + groupSeparatorEntry.setExpanded(true); + } + } + } + + @Override + public int getRowWidth() { + return Math.min(396, (int)(width / 1.3f)); + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + for (Entry child : children()) { + if (child != getEntryAtPosition(mouseX, mouseY) && child instanceof OptionEntry optionEntry) + optionEntry.widget.unfocus(); + } + + return super.mouseClicked(mouseX, mouseY, button); + } + + @Override + public boolean mouseScrolled(double mouseX, double mouseY, double amount) { + super.mouseScrolled(mouseX, mouseY, amount); + + for (Entry child : children()) { + if (child.mouseScrolled(mouseX, mouseY, amount)) + break; + } + + return true; + } + + @Override + public boolean keyPressed(int keyCode, int scanCode, int modifiers) { + for (Entry child : children()) { + if (child.keyPressed(keyCode, scanCode, modifiers)) + return true; + } + + return super.keyPressed(keyCode, scanCode, modifiers); + } + + @Override + public boolean charTyped(char chr, int modifiers) { + for (Entry child : children()) { + if (child.charTyped(chr, modifiers)) + return true; + } + + return super.charTyped(chr, modifiers); + } + + @Override + protected int getScrollbarPositionX() { + return right - (int)(width * 0.05f); + } + + public void recacheViewableChildren() { + this.viewableChildren = ImmutableList.copyOf(super.children().stream().filter(Entry::isViewable).toList()); + + // update y positions before they need to be rendered are rendered + int i = 0; + for (Entry entry : viewableChildren) { + if (entry instanceof OptionEntry optionEntry) + optionEntry.widget.setDimension(optionEntry.widget.getDimension().withY(getRowTop(i))); + i++; + } + } + + @Override + public List<Entry> children() { + return viewableChildren; + } + + public abstract class Entry extends ElementListWidgetExt.Entry<Entry> { + public boolean isViewable() { + return true; + } + + protected boolean isHovered() { + return Objects.equals(getHoveredEntry(), this); + } + } + + public class OptionEntry extends Entry { + public final Option<?> option; + public final ConfigCategory category; + public final OptionGroup group; + + public final AbstractWidget widget; + private final Supplier<Boolean> viewableSupplier; + + private final TextScaledButtonWidget resetButton; + + private final String categoryName; + private final String groupName; + + private OptionEntry(Option<?> option, ConfigCategory category, OptionGroup group, AbstractWidget widget, Supplier<Boolean> viewableSupplier) { + this.option = option; + this.category = category; + this.group = group; + this.widget = widget; + this.viewableSupplier = viewableSupplier; + this.categoryName = category.name().getString().toLowerCase(); + this.groupName = group.name().getString().toLowerCase(); + if (this.widget.canReset()) { + this.widget.setDimension(this.widget.getDimension().expanded(-21, 0)); + this.resetButton = new TextScaledButtonWidget(widget.getDimension().xLimit() + 1, -50, 20, 20, 2f, Text.of("\u21BB"), button -> { + option.requestSetDefault(); + }); + option.addListener((opt, val) -> this.resetButton.active = !opt.isPendingValueDefault() && opt.available()); + this.resetButton.active = !option.isPendingValueDefault() && option.available(); + } else { + this.resetButton = null; + } + } + + @Override + public void render(Matrix |
