aboutsummaryrefslogtreecommitdiff
path: root/common/src/main/java/dev/isxander/yacl3/gui
diff options
context:
space:
mode:
Diffstat (limited to 'common/src/main/java/dev/isxander/yacl3/gui')
-rw-r--r--common/src/main/java/dev/isxander/yacl3/gui/AbstractWidget.java94
-rw-r--r--common/src/main/java/dev/isxander/yacl3/gui/DescriptionWithName.java11
-rw-r--r--common/src/main/java/dev/isxander/yacl3/gui/ElementListWidgetExt.java222
-rw-r--r--common/src/main/java/dev/isxander/yacl3/gui/ImageRenderer.java386
-rw-r--r--common/src/main/java/dev/isxander/yacl3/gui/LowProfileButtonWidget.java28
-rw-r--r--common/src/main/java/dev/isxander/yacl3/gui/OptionDescriptionWidget.java215
-rw-r--r--common/src/main/java/dev/isxander/yacl3/gui/OptionListWidget.java572
-rw-r--r--common/src/main/java/dev/isxander/yacl3/gui/RequireRestartScreen.java21
-rw-r--r--common/src/main/java/dev/isxander/yacl3/gui/SearchFieldWidget.java61
-rw-r--r--common/src/main/java/dev/isxander/yacl3/gui/TextScaledButtonWidget.java34
-rw-r--r--common/src/main/java/dev/isxander/yacl3/gui/TooltipButtonWidget.java25
-rw-r--r--common/src/main/java/dev/isxander/yacl3/gui/YACLScreen.java358
-rw-r--r--common/src/main/java/dev/isxander/yacl3/gui/YACLTooltipPositioner.java48
-rw-r--r--common/src/main/java/dev/isxander/yacl3/gui/controllers/ActionController.java120
-rw-r--r--common/src/main/java/dev/isxander/yacl3/gui/controllers/BooleanController.java157
-rw-r--r--common/src/main/java/dev/isxander/yacl3/gui/controllers/ColorController.java220
-rw-r--r--common/src/main/java/dev/isxander/yacl3/gui/controllers/ControllerWidget.java157
-rw-r--r--common/src/main/java/dev/isxander/yacl3/gui/controllers/LabelController.java193
-rw-r--r--common/src/main/java/dev/isxander/yacl3/gui/controllers/ListEntryWidget.java128
-rw-r--r--common/src/main/java/dev/isxander/yacl3/gui/controllers/TickBoxController.java119
-rw-r--r--common/src/main/java/dev/isxander/yacl3/gui/controllers/cycling/CyclingControllerElement.java60
-rw-r--r--common/src/main/java/dev/isxander/yacl3/gui/controllers/cycling/CyclingListController.java79
-rw-r--r--common/src/main/java/dev/isxander/yacl3/gui/controllers/cycling/EnumController.java43
-rw-r--r--common/src/main/java/dev/isxander/yacl3/gui/controllers/cycling/ICyclingController.java38
-rw-r--r--common/src/main/java/dev/isxander/yacl3/gui/controllers/package-info.java12
-rw-r--r--common/src/main/java/dev/isxander/yacl3/gui/controllers/slider/DoubleSliderController.java114
-rw-r--r--common/src/main/java/dev/isxander/yacl3/gui/controllers/slider/FloatSliderController.java114
-rw-r--r--common/src/main/java/dev/isxander/yacl3/gui/controllers/slider/ISliderController.java54
-rw-r--r--common/src/main/java/dev/isxander/yacl3/gui/controllers/slider/IntegerSliderController.java111
-rw-r--r--common/src/main/java/dev/isxander/yacl3/gui/controllers/slider/LongSliderController.java111
-rw-r--r--common/src/main/java/dev/isxander/yacl3/gui/controllers/slider/SliderControllerElement.java157
-rw-r--r--common/src/main/java/dev/isxander/yacl3/gui/controllers/slider/package-info.java10
-rw-r--r--common/src/main/java/dev/isxander/yacl3/gui/controllers/string/IStringController.java44
-rw-r--r--common/src/main/java/dev/isxander/yacl3/gui/controllers/string/StringController.java37
-rw-r--r--common/src/main/java/dev/isxander/yacl3/gui/controllers/string/StringControllerElement.java403
-rw-r--r--common/src/main/java/dev/isxander/yacl3/gui/controllers/string/number/DoubleFieldController.java104
-rw-r--r--common/src/main/java/dev/isxander/yacl3/gui/controllers/string/number/FloatFieldController.java104
-rw-r--r--common/src/main/java/dev/isxander/yacl3/gui/controllers/string/number/IntegerFieldController.java109
-rw-r--r--common/src/main/java/dev/isxander/yacl3/gui/controllers/string/number/LongFieldController.java109
-rw-r--r--common/src/main/java/dev/isxander/yacl3/gui/controllers/string/number/NumberFieldController.java69
-rw-r--r--common/src/main/java/dev/isxander/yacl3/gui/controllers/string/number/package-info.java10
-rw-r--r--common/src/main/java/dev/isxander/yacl3/gui/tab/ListHolderWidget.java116
-rw-r--r--common/src/main/java/dev/isxander/yacl3/gui/tab/ScrollableNavigationBar.java110
-rw-r--r--common/src/main/java/dev/isxander/yacl3/gui/tab/TabExt.java9
-rw-r--r--common/src/main/java/dev/isxander/yacl3/gui/utils/GuiUtils.java32
45 files changed, 5328 insertions, 0 deletions
diff --git a/common/src/main/java/dev/isxander/yacl3/gui/AbstractWidget.java b/common/src/main/java/dev/isxander/yacl3/gui/AbstractWidget.java
new file mode 100644
index 0000000..8b9779c
--- /dev/null
+++ b/common/src/main/java/dev/isxander/yacl3/gui/AbstractWidget.java
@@ -0,0 +1,94 @@
+package dev.isxander.yacl3.gui;
+
+import dev.isxander.yacl3.api.utils.Dimension;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.Font;
+import net.minecraft.client.gui.GuiGraphics;
+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.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 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(GuiGraphics graphics, 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;
+
+ int i = !enabled ? 0 : hovered ? 2 : 1;
+ graphics.blit(net.minecraft.client.gui.components.AbstractWidget.WIDGETS_LOCATION, x1, y1, 0, 0, 46 + i * 20, width / 2, height, 256, 256);
+ graphics.blit(net.minecraft.client.gui.components.AbstractWidget.WIDGETS_LOCATION, 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/yacl3/gui/DescriptionWithName.java b/common/src/main/java/dev/isxander/yacl3/gui/DescriptionWithName.java
new file mode 100644
index 0000000..6ad72e8
--- /dev/null
+++ b/common/src/main/java/dev/isxander/yacl3/gui/DescriptionWithName.java
@@ -0,0 +1,11 @@
+package dev.isxander.yacl3.gui;
+
+import dev.isxander.yacl3.api.OptionDescription;
+import net.minecraft.ChatFormatting;
+import net.minecraft.network.chat.Component;
+
+public record DescriptionWithName(Component name, OptionDescription description) {
+ public static DescriptionWithName of(Component name, OptionDescription description) {
+ return new DescriptionWithName(name.copy().withStyle(ChatFormatting.BOLD), description);
+ }
+}
diff --git a/common/src/main/java/dev/isxander/yacl3/gui/ElementListWidgetExt.java b/common/src/main/java/dev/isxander/yacl3/gui/ElementListWidgetExt.java
new file mode 100644
index 0000000..e3944ee
--- /dev/null
+++ b/common/src/main/java/dev/isxander/yacl3/gui/ElementListWidgetExt.java
@@ -0,0 +1,222 @@
+package dev.isxander.yacl3.gui;
+
+import com.mojang.blaze3d.platform.InputConstants;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.GuiGraphics;
+import net.minecraft.client.gui.components.AbstractWidget;
+import net.minecraft.client.gui.components.ContainerObjectSelectionList;
+import net.minecraft.client.gui.components.events.GuiEventListener;
+import net.minecraft.client.gui.layouts.LayoutElement;
+import net.minecraft.client.gui.navigation.ScreenRectangle;
+import net.minecraft.util.Mth;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.function.Consumer;
+
+public class ElementListWidgetExt<E extends ElementListWidgetExt.Entry<E>> extends ContainerObjectSelectionList<E> implements LayoutElement {
+ protected 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(GuiGraphics graphics) {
+ // render transparent background if in-game.
+ setRenderBackground(true);
+ setRenderTopAndBottom(false);
+ }
+
+ @Override
+ protected int getScrollbarPosition() {
+ // default implementation does not respect left/right
+ return this.x1 - 2;
+ }
+
+ @Override
+ public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) {
+ smoothScrollAmount = Mth.lerp(Minecraft.getInstance().getDeltaFrameTime() * 0.5, smoothScrollAmount, getScrollAmount());
+ returnSmoothAmount = true;
+
+ graphics.enableScissor(x0, y0, x1, y1);
+
+ super.render(graphics, mouseX, mouseY, delta);
+
+ graphics.disableScissor();
+
+ returnSmoothAmount = false;
+ }
+
+ public void updateDimensions(ScreenRectangle rectangle) {
+ this.x0 = rectangle.left();
+ this.y0 = rectangle.top();
+ this.x1 = rectangle.right();
+ this.y1 = rectangle.bottom();
+ this.width = rectangle.width();
+ this.height = rectangle.height();
+ }
+
+ /**
+ * 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();
+ }
+
+ @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(GuiGraphics graphics, 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(graphics, mouseX, mouseY, delta, i, left, top, right, entryHeight);
+ }
+ }
+ }
+
+ /* END cloth config code */
+
+ @Override
+ public void setX(int i) {
+ this.x = x0 = i;
+ this.x1 = x0 + width;
+ }
+
+ @Override
+ public void setY(int i) {
+ this.y = y0 = i;
+ this.y1 = y0 + height;
+ }
+
+ @Override
+ public int getX() {
+ return x;
+ }
+
+ @Override
+ public int getY() {
+ return y;
+ }
+
+ @Override
+ public int getWidth() {
+ return width;
+ }
+
+ @Override
+ public int getHeight() {
+ return height;
+ }
+
+ @Override
+ public void visitWidgets(Consumer<AbstractWidget> consumer) {
+ }
+
+ 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 int getItemHeight() {
+ return 22;
+ }
+ }
+}
diff --git a/common/src/main/java/dev/isxander/yacl3/gui/ImageRenderer.java b/common/src/main/java/dev/isxander/yacl3/gui/ImageRenderer.java
new file mode 100644
index 0000000..5b5da97
--- /dev/null
+++ b/common/src/main/java/dev/isxander/yacl3/gui/ImageRenderer.java
@@ -0,0 +1,386 @@
+package dev.isxander.yacl3.gui;
+
+import com.mojang.blaze3d.Blaze3D;
+import com.mojang.blaze3d.platform.NativeImage;
+import com.twelvemonkeys.imageio.plugins.webp.WebPImageReaderSpi;
+import dev.isxander.yacl3.impl.utils.YACLConstants;
+import net.minecraft.CrashReport;
+import net.minecraft.CrashReportCategory;
+import net.minecraft.ReportedException;
+import net.minecraft.client.Minecraft;
+import net.minecraft.client.gui.GuiGraphics;
+import net.minecraft.client.renderer.texture.DynamicTexture;
+import net.minecraft.client.renderer.texture.TextureManager;
+import net.minecraft.resources.ResourceLocation;
+import net.minecraft.server.packs.resources.Resource;
+import net.minecraft.server.packs.resources.ResourceManager;
+import net.minecraft.util.FastColor;
+
+import javax.imageio.ImageIO;
+import javax.imageio.ImageReader;
+import javax.imageio.metadata.IIOMetadata;
+import javax.imageio.metadata.IIOMetadataNode;
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Field;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Supplier;
+import java.util.stream.IntStream;
+
+public interface ImageRenderer {
+ int render(GuiGraphics graphics, int x, int y, int renderWidth);
+
+ void close();
+
+ Map<ResourceLocation, CompletableFuture<Optional<ImageRenderer>>> CACHE = new ConcurrentHashMap<>();
+
+ static CompletableFuture<Optional<ImageRenderer>> getOrMakeAsync(ResourceLocation id, Supplier<Optional<ImageRenderer>> factory) {
+ return CACHE.computeIfAbsent(id, key -> CompletableFuture.supplyAsync(factory, YACLConstants.SINGLE_THREAD_EXECUTOR));
+ }
+
+ static CompletableFuture<Optional<ImageRenderer>> getOrMakeSync(ResourceLocation id, Supplier<Optional<ImageRenderer>> factory) {
+ return CACHE.computeIfAbsent(id, key -> CompletableFuture.completedFuture(factory.get()));
+ }
+
+ static void closeAll() {
+ CACHE.values().forEach(future -> future.thenAccept(opt -> opt.ifPresent(ImageRenderer::close)));
+ CACHE.clear();
+ }
+
+ class TextureBacked implements ImageRenderer {
+ private final ResourceLocation location;
+ private final int width, height;
+ private final int textureWidth, textureHeight;
+ private final float u, v;
+
+ public TextureBacked(ResourceLocation location, float u, float v, int width, int height, int textureWidth, int textureHeight) {
+ this.location = location;
+ this.width = width;
+ this.height = height;
+ this.textureWidth = textureWidth;
+ this.textureHeight = textureHeight;
+ this.u = u;
+ this.v = v;
+ }
+
+ @Override
+ public int render(GuiGraphics graphics, int x, int y, int renderWidth) {
+ float ratio = renderWidth / (float)this.width;
+ int targetHeight = (int) (this.height * ratio);
+
+ graphics.pose().pushPose();
+ graphics.pose().translate(x, y, 0);
+ graphics.pose().scale(ratio, ratio, 1);
+ graphics.blit(location, 0, 0, this.u, this.v, this.width, this.height, this.textureWidth, this.textureHeight);
+ graphics.pose().popPose();
+
+ return targetHeight;
+ }
+
+ @Override
+ public void close() {
+
+ }
+ }
+
+ class NativeImageBacked implements ImageRenderer {
+ protected static final TextureManager textureManager = Minecraft.getInstance().getTextureManager();
+
+ protected NativeImage image;
+ protected DynamicTexture texture;
+ protected final ResourceLocation uniqueLocation;
+ protected final int width, height;
+
+ public NativeImageBacked(NativeImage image, ResourceLocation uniqueLocation) {
+ this.image = image;
+ this.texture = new DynamicTexture(image);
+ this.uniqueLocation = uniqueLocation;
+ textureManager.register(this.uniqueLocation, this.texture);
+ this.width = image.getWidth();
+ this.height = image.getHeight();
+ }
+
+ private NativeImageBacked(Path imagePath, ResourceLocation uniqueLocation) throws IOException {
+ this.uniqueLocation = uniqueLocation;
+ this.image = NativeImage.read(new FileInputStream(imagePath.toFile()));
+ this.width = image.getWidth();
+ this.height = image.getHeight();
+ this.texture = new DynamicTexture(image);
+ textureManager.register(this.uniqueLocation, this.texture);
+ }
+
+ public static Optional<ImageRenderer> createFromPath(Path path, ResourceLocation uniqueLocation) {
+ try {
+ return Optional.of(new NativeImageBacked(path, uniqueLocation));
+ } catch (IOException e) {
+ e.printStackTrace();
+ return Optional.empty();
+ }
+ }
+
+ @Override
+ public int render(GuiGraphics graphics, int x, int y, int renderWidth) {
+ if (image == null) return 0;
+
+ float ratio = renderWidth / (float)this.width;
+ int targetHeight = (int) (this.height * ratio);
+
+ graphics.pose().pushPose();
+ graphics.pose().translate(x, y, 0);
+ graphics.pose().scale(ratio, ratio, 1);
+ graphics.blit(uniqueLocation, 0, 0, 0, 0, this.width, this.height, this.width, this.height);
+ graphics.pose().popPose();
+
+ return targetHeight;
+ }
+
+ @Override
+ public void close() {
+ image.close();
+ image = null;
+ texture = null;
+ textureManager.release(uniqueLocation);
+ }
+ }
+
+ class AnimatedNativeImageBacked extends NativeImageBacked {
+ private int currentFrame;
+ private double lastFrameTime;
+
+ private final double[] frameDelays;
+ private final int frameCount;
+
+ private final int packCols, packRows;
+ private final int frameWidth, frameHeight;
+
+ public AnimatedNativeImageBacked(NativeImage image, int frameWidth, int frameHeight, int frameCount, double[] frameDelayMS, int packCols, int packRows, ResourceLocation uniqueLocation) {
+ super(image, uniqueLocation);
+ this.frameWidth = frameWidth;
+ this.frameHeight = frameHeight;
+ this.frameCount = frameCount;
+ this.frameDelays = frameDelayMS;
+ this.packCols = packCols;
+ this.packRows = packRows;
+ }
+
+ public static AnimatedNativeImageBacked createGIFFromTexture(ResourceLocation textureLocation) throws IOException {
+ ResourceManager resourceManager = Minecraft.getInstance().getResourceManager();
+ Resource resource = resourceManager.getResource(textureLocation).orElseThrow();
+
+ return createGIF(resource.open(), textureLocation);
+ }
+
+ public static AnimatedNativeImageBacked createWEBPFromTexture(ResourceLocation textureLocation) throws IOException {
+ ResourceManager resourceManager = Minecraft.getInstance().getResourceManager();
+ Resource resource = resourceManager.getResource(textureLocation).orElseThrow();
+
+ return createWEBP(resource.open(), textureLocation);
+ }
+
+ public static AnimatedNativeImageBacked createGIF(InputStream is, ResourceLocation uniqueLocation) {
+ try (is) {
+ ImageReader reader = ImageIO.getImageReadersBySuffix("gif").next();
+ reader.setInput(ImageIO.createImageInputStream(is));
+
+
+
+ AnimFrameProvider animFrameFunction = i -> {
+ IIOMetadata metadata = reader.getImageMetadata(i);
+ String metaFormatName = metadata.getNativeMetadataFormatName();
+ IIOMetadataNode root = (IIOMetadataNode) metadata.getAsTree(metaFormatName);
+ IIOMetadataNode graphicsControlExtensionNode = (IIOMetadataNode) root.getElementsByTagName("GraphicControlExtension").item(0);
+ int delay = Integer.parseInt(graphicsControlExtensionNode.getAttribute("delayTime")) * 10;
+
+ return new AnimFrame(delay, 0, 0);
+ };
+
+ return createFromImageReader(reader, animFrameFunction, uniqueLocation);
+ } catch (Exception e) {
+ CrashReport crashReport = CrashReport.forThrowable(e, "Failed to load GIF image");
+ CrashReportCategory category = crashReport.addCategory("YACL Gui");
+ category.setDetail("Image identifier", uniqueLocation.toString());
+ throw new ReportedException(crashReport);
+ }
+ }
+
+ public static AnimatedNativeImageBacked createWEBP(InputStream is, ResourceLocation uniqueLocation) {
+ try (is) {
+ ImageReader reader = new WebPImageReaderSpi().createReaderInstance();
+ reader.setInput(ImageIO.createImageInputStream(is));
+
+ int numImages = reader.getNumImages(true); // Force reading of all frames
+ AnimFrameProvider animFrameFunction = i -> null;
+ if (numImages > 1) {
+ // WebP reader does not expose frame delay, prepare for reflection hell
+ Class<?> webpReaderClass = Class.forName("com.twelvemonkeys.imageio.plugins.webp.WebPImageReader");
+ Field framesField = webpReaderClass.getDeclaredField("frames");
+ framesField.setAccessible(true);
+ List<?> frames = (List<?>) framesField.get(reader);
+
+ Class<?> animationFrameClass = Class.forName("com.twelvemonkeys.imageio.plugins.webp.AnimationFrame");
+ Field durationField = animationFrameClass.getDeclaredField("duration");
+