From c865fb7ced2db5e27514be95371b9ea0ae4df3ea Mon Sep 17 00:00:00 2001 From: FoundationGames <43485105+FoundationGames@users.noreply.github.com> Date: Sat, 8 Jan 2022 00:34:40 -0800 Subject: [PATCH 1/5] Add utilities for GUI buttons, add setting file importing and exporting, change button textures --- src/main/java/net/coderbot/iris/Iris.java | 39 ++- .../net/coderbot/iris/gui/FileDialogUtil.java | 56 +++++ .../java/net/coderbot/iris/gui/GuiUtil.java | 14 +- .../iris/gui/element/IrisElementRow.java | 228 ++++++++++++++++++ .../gui/element/ShaderPackOptionList.java | 179 ++++++++++---- .../gui/element/ShaderPackSelectionList.java | 99 ++++---- .../iris/gui/screen/ShaderPackScreen.java | 89 +++++-- .../resources/assets/iris/lang/en_us.json | 6 + .../assets/iris/textures/gui/widgets.png | Bin 8932 -> 10367 bytes 9 files changed, 594 insertions(+), 116 deletions(-) create mode 100644 src/main/java/net/coderbot/iris/gui/FileDialogUtil.java create mode 100644 src/main/java/net/coderbot/iris/gui/element/IrisElementRow.java diff --git a/src/main/java/net/coderbot/iris/Iris.java b/src/main/java/net/coderbot/iris/Iris.java index 9ce8500a5f..34c28d6c54 100644 --- a/src/main/java/net/coderbot/iris/Iris.java +++ b/src/main/java/net/coderbot/iris/Iris.java @@ -1,6 +1,7 @@ package net.coderbot.iris; import java.io.IOException; +import java.io.OutputStream; import java.nio.file.FileSystem; import java.nio.file.FileSystemNotFoundException; import java.nio.file.FileSystems; @@ -21,10 +22,13 @@ import net.coderbot.iris.gui.screen.ShaderPackScreen; import net.coderbot.iris.pipeline.*; import net.coderbot.iris.shaderpack.DimensionId; +import net.coderbot.iris.shaderpack.OptionalBoolean; import net.coderbot.iris.shaderpack.ProgramSet; import net.coderbot.iris.shaderpack.ShaderPack; +import net.coderbot.iris.shaderpack.option.OptionSet; import net.coderbot.iris.shaderpack.option.Profile; import net.coderbot.iris.shaderpack.discovery.ShaderpackDirectoryManager; +import net.coderbot.iris.shaderpack.option.values.OptionValues; import net.fabricmc.loader.api.ModContainer; import net.irisshaders.iris.api.v0.IrisApi; import net.minecraft.ChatFormatting; @@ -69,6 +73,9 @@ public class Iris implements ClientModInitializer { private static KeyMapping shaderpackScreenKeybind; private static final Map shaderPackOptionQueue = new HashMap<>(); + // Flag variable used when reloading + // Used in favor of queueDefaultShaderPackOptionValues() for resetting as the + // behavior is more concrete and therefore is more likely to repair a user's issues private static boolean resetShaderPackOptions = false; private static String IRIS_VERSION; @@ -375,8 +382,8 @@ private static Optional loadConfigProperties(Path path) { } private static void saveConfigProperties(Path path, Properties properties) { - try { - properties.store(Files.newOutputStream(path), null); + try (OutputStream out = Files.newOutputStream(path)) { + properties.store(out, null); } catch (IOException e) { // TODO: Better error handling } @@ -426,6 +433,34 @@ public static void queueShaderPackOptionsFromProfile(Profile profile) { getShaderPackOptionQueue().putAll(profile.optionValues); } + public static void queueShaderPackOptionsFromProperties(Properties properties) { + queueDefaultShaderPackOptionValues(); + + properties.stringPropertyNames().forEach(key -> + getShaderPackOptionQueue().put(key, properties.getProperty(key))); + } + + // Used in favor of resetShaderPackOptions as the aforementioned requires the pack to be reloaded + public static void queueDefaultShaderPackOptionValues() { + clearShaderPackOptionQueue(); + + getCurrentPack().ifPresent(pack -> { + OptionSet options = pack.getShaderPackOptions().getOptionSet(); + OptionValues values = pack.getShaderPackOptions().getOptionValues(); + + options.getStringOptions().forEach((key, mOpt) -> { + if (values.getStringValue(key).isPresent()) { + getShaderPackOptionQueue().put(key, mOpt.getOption().getDefaultValue()); + } + }); + options.getBooleanOptions().forEach((key, mOpt) -> { + if (values.getBooleanValue(key) != OptionalBoolean.DEFAULT) { + getShaderPackOptionQueue().put(key, Boolean.toString(mOpt.getOption().getDefaultValue())); + } + }); + }); + } + public static void clearShaderPackOptionQueue() { getShaderPackOptionQueue().clear(); } diff --git a/src/main/java/net/coderbot/iris/gui/FileDialogUtil.java b/src/main/java/net/coderbot/iris/gui/FileDialogUtil.java new file mode 100644 index 0000000000..bc3f095a30 --- /dev/null +++ b/src/main/java/net/coderbot/iris/gui/FileDialogUtil.java @@ -0,0 +1,56 @@ +package net.coderbot.iris.gui; + +import org.jetbrains.annotations.Nullable; +import org.lwjgl.PointerBuffer; +import org.lwjgl.system.MemoryStack; +import org.lwjgl.util.tinyfd.TinyFileDialogs; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Optional; + +/** + * Class used to make interfacing with {@link TinyFileDialogs} easier. + */ +public final class FileDialogUtil { + private FileDialogUtil() {} + + /** + * Opens a file select dialog window. + * + *

Will stall the thread that the method is invoked within. + * + * @param dialog Whether to open a "save" dialog or an "open" dialog + * @param title The title of the dialog window + * @param origin The path that the window should start at + * @param filters The file extension filters used by the dialog, each formatted as {@code "*.extension"} + * @param filterDesc A message used to describe the file extensions used as filters + * @return a path to the file selected by the user, unless the dialog has been canceled. + */ + public static Optional fileSelectDialog(DialogType dialog, String title, @Nullable Path origin, String[] filters, @Nullable String filterDesc) { + String result = null; + + try (MemoryStack stack = MemoryStack.stackPush()) { + PointerBuffer filterBuffer = stack.mallocPointer(filters.length); + + for (String filter : filters) { + filterBuffer.put(stack.UTF8(filter)); + } + filterBuffer.flip(); + + String path = origin != null ? origin.toAbsolutePath().toString() : null; + + if (dialog == DialogType.SAVE) { + result = TinyFileDialogs.tinyfd_saveFileDialog(title, path, filterBuffer, filterDesc); + } else if (dialog == DialogType.OPEN) { + result = TinyFileDialogs.tinyfd_openFileDialog(title, path, filterBuffer, filterDesc, false); + } + } + + return Optional.ofNullable(result).map(Paths::get); + } + + public enum DialogType { + SAVE, OPEN; + } +} diff --git a/src/main/java/net/coderbot/iris/gui/GuiUtil.java b/src/main/java/net/coderbot/iris/gui/GuiUtil.java index 7f17a0efda..379b704df6 100644 --- a/src/main/java/net/coderbot/iris/gui/GuiUtil.java +++ b/src/main/java/net/coderbot/iris/gui/GuiUtil.java @@ -169,7 +169,11 @@ public static void playButtonClickSound() { public static class Icon { public static final Icon SEARCH = new Icon(0, 0, 7, 8); public static final Icon CLOSE = new Icon(7, 0, 5, 6); - public static final Icon REFRESH = new Icon(12, 0, 11, 11); + public static final Icon REFRESH = new Icon(12, 0, 10, 10); + public static final Icon EXPORT = new Icon(22, 0, 7, 8); + public static final Icon EXPORT_COLORED = new Icon(29, 0, 7, 8); + public static final Icon IMPORT = new Icon(22, 8, 7, 8); + public static final Icon IMPORT_COLORED = new Icon(29, 8, 7, 8); private final int u; private final int v; @@ -200,5 +204,13 @@ public void draw(PoseStack poseStack, int x, int y) { // Draw the texture to the screen GuiComponent.blit(poseStack, x, y, u, v, width, height, 256, 256); } + + public int getWidth() { + return width; + } + + public int getHeight() { + return height; + } } } diff --git a/src/main/java/net/coderbot/iris/gui/element/IrisElementRow.java b/src/main/java/net/coderbot/iris/gui/element/IrisElementRow.java new file mode 100644 index 0000000000..cc3a7ae73b --- /dev/null +++ b/src/main/java/net/coderbot/iris/gui/element/IrisElementRow.java @@ -0,0 +1,228 @@ +package net.coderbot.iris.gui.element; + +import com.mojang.blaze3d.vertex.PoseStack; +import net.coderbot.iris.gui.GuiUtil; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.network.chat.Component; +import org.lwjgl.glfw.GLFW; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; + +/** + * Intended to make very simple rows of buttons easier to make + */ +public class IrisElementRow { + private final Map elements = new HashMap<>(); + private final List orderedElements = new ArrayList<>(); + private final int spacing; + private int x; + private int y; + private int width; + private int height; + + public IrisElementRow(int spacing) { + this.spacing = spacing; + } + + public IrisElementRow() { + this(1); + } + + /** + * Adds an element to the right of this row. + * + * @param element The element to add + * @param width The width of the element in this row + * @return {@code this}, to be used for chaining statements + */ + public IrisElementRow add(Element element, int width) { + if (!this.orderedElements.contains(element)) { + this.orderedElements.add(element); + } + this.elements.put(element, width); + + this.width += width + this.spacing; + + return this; + } + + /** + * Modifies the width of an element. + * + * @param element The element whose width to modify + * @param width The width to be assigned to the specified element + */ + public void setWidth(Element element, int width) { + if (!this.elements.containsKey(element)) { + return; + } + + this.width -= this.elements.get(element) + 2; + + add(element, width); + } + + /** + * Renders the row, with the anchor point being the top left. + */ + public void render(PoseStack poseStack, int x, int y, int height, int mouseX, int mouseY, float tickDelta, boolean rowHovered) { + this.x = x; + this.y = y; + this.height = height; + + int currentX = x; + + for (Element element : this.orderedElements) { + int currentWidth = this.elements.get(element); + + element.render(poseStack, currentX, y, currentWidth, height, mouseX, mouseY, tickDelta, + rowHovered && sectionHovered(currentX, currentWidth, mouseX, mouseY)); + + currentX += currentWidth + this.spacing; + } + } + + /** + * Renders the row, with the anchor point being the top right. + */ + public void renderRightAligned(PoseStack poseStack, int x, int y, int height, int mouseX, int mouseY, float tickDelta, boolean hovered) { + render(poseStack, x - this.width, y, height, mouseX, mouseY, tickDelta, hovered); + } + + private boolean sectionHovered(int sectionX, int sectionWidth, double mx, double my) { + return mx > sectionX && mx < sectionX + sectionWidth && + my > this.y && my < this.y + this.height; + } + + private Optional getHovered(double mx, double my) { + int currentX = this.x; + + for (Element element : this.orderedElements) { + int currentWidth = this.elements.get(element); + + if (sectionHovered(currentX, currentWidth, mx, my)) { + return Optional.of(element); + } + + currentX += currentWidth + this.spacing; + } + + return Optional.empty(); + } + + public boolean mouseClicked(double mx, double my, int button) { + return getHovered(mx, my).map(element -> element.mouseClicked(mx, my, button)).orElse(false); + } + + public boolean mouseReleased(double mx, double my, int button) { + return getHovered(mx, my).map(element -> element.mouseReleased(mx, my, button)).orElse(false); + } + + public static abstract class Element { + public boolean disabled = false; + private boolean hovered = false; + + public void render(PoseStack poseStack, int x, int y, int width, int height, int mouseX, int mouseY, float tickDelta, boolean hovered) { + GuiUtil.bindIrisWidgetsTexture(); + GuiUtil.drawButton(poseStack, x, y, width, height, hovered, this.disabled); + + this.hovered = hovered; + this.renderLabel(poseStack, x, y, width, height, mouseX, mouseY, tickDelta, hovered); + } + + public abstract void renderLabel(PoseStack poseStack, int x, int y, int width, int height, int mouseX, int mouseY, float tickDelta, boolean hovered); + + public boolean mouseClicked(double mx, double my, int button) { + return false; + } + + public boolean mouseReleased(double mx, double my, int button) { + return false; + } + + public boolean isHovered() { + return hovered; + } + } + + public static abstract class ButtonElement> extends Element { + private final Function onClick; + + protected ButtonElement(Function onClick) { + this.onClick = onClick; + } + + @Override + public boolean mouseClicked(double mx, double my, int button) { + if (this.disabled) { + return false; + } + + if (button == GLFW.GLFW_MOUSE_BUTTON_1) { + return this.onClick.apply((T) this); + } + + return super.mouseClicked(mx, my, button); + } + } + + /** + * A clickable button element that uses a {@link net.coderbot.iris.gui.GuiUtil.Icon} as its label. + */ + public static class IconButtonElement extends ButtonElement { + public GuiUtil.Icon icon; + public GuiUtil.Icon hoveredIcon; + + public IconButtonElement(GuiUtil.Icon icon, GuiUtil.Icon hoveredIcon, Function onClick) { + super(onClick); + this.icon = icon; + this.hoveredIcon = hoveredIcon; + } + + public IconButtonElement(GuiUtil.Icon icon, Function onClick) { + this(icon, icon, onClick); + } + + @Override + public void renderLabel(PoseStack poseStack, int x, int y, int width, int height, int mouseX, int mouseY, float tickDelta, boolean hovered) { + int iconX = x + (int)((width - this.icon.getWidth()) * 0.5); + int iconY = y + (int)((height - this.icon.getHeight()) * 0.5); + + GuiUtil.bindIrisWidgetsTexture(); + if (!this.disabled && hovered) { + this.hoveredIcon.draw(poseStack, iconX, iconY); + } else { + this.icon.draw(poseStack, iconX, iconY); + } + } + } + + /** + * A clickable button element that uses a text component as its label. + */ + public static class TextButtonElement extends ButtonElement { + protected final Font font; + public Component text; + + public TextButtonElement(Component text, Function onClick) { + super(onClick); + + this.font = Minecraft.getInstance().font; + this.text = text; + } + + @Override + public void renderLabel(PoseStack poseStack, int x, int y, int width, int height, int mouseX, int mouseY, float tickDelta, boolean hovered) { + int textX = x + (int)((width - this.font.width(this.text)) * 0.5); + int textY = y + (int)((height - 8) * 0.5); + + this.font.drawShadow(poseStack, this.text, textX, textY, 0xFFFFFF); + } + } +} diff --git a/src/main/java/net/coderbot/iris/gui/element/ShaderPackOptionList.java b/src/main/java/net/coderbot/iris/gui/element/ShaderPackOptionList.java index 7fdf1be5a8..137b43d9e8 100644 --- a/src/main/java/net/coderbot/iris/gui/element/ShaderPackOptionList.java +++ b/src/main/java/net/coderbot/iris/gui/element/ShaderPackOptionList.java @@ -2,6 +2,7 @@ import com.mojang.blaze3d.vertex.PoseStack; import net.coderbot.iris.Iris; +import net.coderbot.iris.gui.FileDialogUtil; import net.coderbot.iris.gui.GuiUtil; import net.coderbot.iris.gui.NavigationController; import net.coderbot.iris.gui.element.widget.AbstractElementWidget; @@ -16,13 +17,28 @@ import net.minecraft.client.gui.screens.Screen; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.MutableComponent; +import net.minecraft.network.chat.TextColor; import net.minecraft.network.chat.TextComponent; import net.minecraft.network.chat.TranslatableComponent; import net.minecraft.util.Mth; +import org.jetbrains.annotations.Nullable; +import org.lwjgl.PointerBuffer; import org.lwjgl.glfw.GLFW; - +import org.lwjgl.system.MemoryStack; +import org.lwjgl.system.MemoryUtil; +import org.lwjgl.util.tinyfd.TinyFileDialogs; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; +import java.util.Optional; +import java.util.Properties; public class ShaderPackOptionList extends IrisObjectSelectionList { private final List> elementWidgets = new ArrayList<>(); @@ -102,32 +118,52 @@ public static class HeaderEntry extends BaseEntry { public static final MutableComponent RESET_HOLD_SHIFT_TOOLTIP = new TranslatableComponent("options.iris.reset.tooltip.holdShift").withStyle(ChatFormatting.GOLD); public static final MutableComponent RESET_TOOLTIP = new TranslatableComponent("options.iris.reset.tooltip").withStyle(ChatFormatting.RED); + public static final MutableComponent IMPORT_TOOLTIP = new TranslatableComponent("options.iris.importSettings.tooltip") + .withStyle(style -> style.withColor(TextColor.fromRgb(0x4da6ff))); + public static final MutableComponent EXPORT_TOOLTIP = new TranslatableComponent("options.iris.exportSettings.tooltip") + .withStyle(style -> style.withColor(TextColor.fromRgb(0xfc7d3d))); - private static final int SIDE_BUTTON_WIDTH = 42; - private static final int SIDE_BUTTON_HEIGHT = 16; + private static final int MIN_SIDE_BUTTON_WIDTH = 42; + private static final int BUTTON_HEIGHT = 16; private final ShaderPackScreen screen; - private final boolean hasBackButton; + private final @Nullable IrisElementRow backButton; + private final IrisElementRow utilityButtons = new IrisElementRow(); + private final IrisElementRow.TextButtonElement resetButton; + private final IrisElementRow.IconButtonElement importButton; + private final IrisElementRow.IconButtonElement exportButton; private final Component text; - private int cachedPosX; - private int cachedPosY; - private int cachedWidth; - public HeaderEntry(ShaderPackScreen screen, NavigationController navigation, Component text, boolean hasBackButton) { super(navigation); + if (hasBackButton) { + this.backButton = new IrisElementRow().add( + new IrisElementRow.TextButtonElement(BACK_BUTTON_TEXT, this::backButtonClicked), + Math.max(MIN_SIDE_BUTTON_WIDTH, Minecraft.getInstance().font.width(BACK_BUTTON_TEXT) + 8) + ); + } else { + this.backButton = null; + } + + this.resetButton = new IrisElementRow.TextButtonElement( + RESET_BUTTON_TEXT_INACTIVE, this::resetButtonClicked); + this.importButton = new IrisElementRow.IconButtonElement( + GuiUtil.Icon.IMPORT, GuiUtil.Icon.IMPORT_COLORED, this::importSettingsButtonClicked); + this.exportButton = new IrisElementRow.IconButtonElement( + GuiUtil.Icon.EXPORT, GuiUtil.Icon.EXPORT_COLORED, this::exportSettingsButtonClicked); + + this.utilityButtons + .add(this.importButton, 15) + .add(this.exportButton, 15) + .add(this.resetButton, Math.max(MIN_SIDE_BUTTON_WIDTH, Minecraft.getInstance().font.width(RESET_BUTTON_TEXT_INACTIVE) + 8)); + this.screen = screen; - this.hasBackButton = hasBackButton; this.text = text; } @Override public void render(PoseStack poseStack, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) { - this.cachedPosX = x; - this.cachedPosY = y; - this.cachedWidth = entryWidth; - // Draw dividing line fill(poseStack, x - 3, (y + entryHeight) - 2, x + entryWidth, (y + entryHeight) - 1, 0x66BEBEBE); @@ -139,66 +175,56 @@ public void render(PoseStack poseStack, int index, int y, int x, int entryWidth, GuiUtil.bindIrisWidgetsTexture(); // Draw back button if present - if (hasBackButton) { - GuiUtil.drawButton(poseStack, - x, y, - SIDE_BUTTON_WIDTH, SIDE_BUTTON_HEIGHT, - hovered && mouseX < x + SIDE_BUTTON_WIDTH && mouseY < y + SIDE_BUTTON_HEIGHT, - false); - - drawCenteredString(poseStack, font, BACK_BUTTON_TEXT, x + (int)(0.5 * SIDE_BUTTON_WIDTH), y + 4, 0xFFFFFF); + if (this.backButton != null) { + backButton.render(poseStack, x, y, BUTTON_HEIGHT, mouseX, mouseY, tickDelta, hovered); } boolean shiftDown = Screen.hasShiftDown(); - boolean resetButtonHovered = hovered && mouseX > (x + (entryWidth - 3)) - SIDE_BUTTON_WIDTH && mouseY < y + SIDE_BUTTON_HEIGHT; - GuiUtil.bindIrisWidgetsTexture(); + // Set the appearance of the reset button + this.resetButton.disabled = !shiftDown; + this.resetButton.text = shiftDown ? RESET_BUTTON_TEXT_ACTIVE : RESET_BUTTON_TEXT_INACTIVE; + + // Draw the utility buttons + this.utilityButtons.renderRightAligned(poseStack, (x + entryWidth) - 3, y, BUTTON_HEIGHT, mouseX, mouseY, tickDelta, hovered); - // Draw reset button - GuiUtil.drawButton(poseStack, - (x + (entryWidth - 3)) - SIDE_BUTTON_WIDTH, y, - SIDE_BUTTON_WIDTH, SIDE_BUTTON_HEIGHT, - resetButtonHovered, - !shiftDown); - - drawCenteredString(poseStack, - font, - shiftDown ? RESET_BUTTON_TEXT_ACTIVE : RESET_BUTTON_TEXT_INACTIVE, - (x + (entryWidth - 3)) - (int)(0.5 * SIDE_BUTTON_WIDTH), y + 4, - 0xFFFFFF); - - // Draw reset button tooltip - if (resetButtonHovered) { + // Draw the reset button's tooltip + if (this.resetButton.isHovered()) { Component tooltip = shiftDown ? RESET_TOOLTIP : RESET_HOLD_SHIFT_TOOLTIP; - ShaderPackScreen.TOP_LAYER_RENDER_QUEUE.add(() -> GuiUtil.drawTextPanel( - font, poseStack, tooltip, - mouseX - (font.width(tooltip) + 10), mouseY - 16 - )); + queueBottomRightAnchoredTooltip(poseStack, mouseX, mouseY, font, tooltip); + } + // Draw the import/export button tooltips + if (this.importButton.isHovered()) { + queueBottomRightAnchoredTooltip(poseStack, mouseX, mouseY, font, IMPORT_TOOLTIP); + } + if (this.exportButton.isHovered()) { + queueBottomRightAnchoredTooltip(poseStack, mouseX, mouseY, font, EXPORT_TOOLTIP); } } + private void queueBottomRightAnchoredTooltip(PoseStack poseStack, int x, int y, Font font, Component text) { + ShaderPackScreen.TOP_LAYER_RENDER_QUEUE.add(() -> GuiUtil.drawTextPanel( + font, poseStack, text, + x - (font.width(text) + 10), y - 16 + )); + } + @Override public boolean mouseClicked(double mouseX, double mouseY, int button) { - if (button == GLFW.GLFW_MOUSE_BUTTON_1) { - if (hasBackButton && mouseX < cachedPosX + SIDE_BUTTON_WIDTH && mouseY < cachedPosY + SIDE_BUTTON_HEIGHT) { - return backButtonClicked(mouseX, mouseY, button); - } - if (mouseX > (cachedPosX + (cachedWidth - 3)) - SIDE_BUTTON_WIDTH && mouseY < cachedPosY + SIDE_BUTTON_HEIGHT) { - return resetButtonClicked(mouseX, mouseY, button); - } - } + boolean backButtonResult = backButton != null && backButton.mouseClicked(mouseX, mouseY, button); + boolean utilButtonResult = utilityButtons.mouseClicked(mouseX, mouseY, button); - return super.mouseClicked(mouseX, mouseY, button); + return backButtonResult || utilButtonResult; } - private boolean backButtonClicked(double mouseX, double mouseY, int button) { + private boolean backButtonClicked(IrisElementRow.TextButtonElement button) { this.navigation.back(); GuiUtil.playButtonClickSound(); return true; } - private boolean resetButtonClicked(double mouseX, double mouseY, int button) { + private boolean resetButtonClicked(IrisElementRow.TextButtonElement button) { if (Screen.hasShiftDown()) { Iris.resetShaderPackOptionsOnNextReload(); this.screen.applyChanges(); @@ -209,6 +235,53 @@ private boolean resetButtonClicked(double mouseX, double mouseY, int button) { return false; } + + private boolean importSettingsButtonClicked(IrisElementRow.IconButtonElement button) { + GuiUtil.playButtonClickSound(); + + Optional path = FileDialogUtil.fileSelectDialog( + FileDialogUtil.DialogType.OPEN, "Import Shader Settings from File", + Iris.getShaderpacksDirectory(), new String[] {"*.txt"}, "Shader Pack Settings (.txt)"); + + path.ifPresent(this.screen::importPackOptions); + + return path.isPresent(); + } + + private boolean exportSettingsButtonClicked(IrisElementRow.IconButtonElement button) { + GuiUtil.playButtonClickSound(); + + // Invalid state to be in + if (!Iris.getCurrentPack().isPresent()) { + return false; + } + + Optional path = FileDialogUtil.fileSelectDialog( + FileDialogUtil.DialogType.SAVE, "Export Shader Settings to File", + Paths.get(Iris.getCurrentPackName() + ".txt"), new String[] {"*.txt"}, "Shader Pack Settings (.txt)"); + + path.ifPresent(p -> { + Properties toSave = new Properties(); + + // Dirty way of getting the currently applied settings as a Properties, directly + // opens and copies out of the saved settings file if it is present + Path sourceTxtPath = Iris.getShaderpacksDirectory().resolve(Iris.getCurrentPackName() + ".txt"); + if (Files.exists(sourceTxtPath)) { + try (InputStream in = Files.newInputStream(sourceTxtPath)) { + toSave.load(in); + } catch (IOException ignored) {} + } + + // Save properties to user determined file + try (OutputStream out = Files.newOutputStream(p)) { + toSave.store(out, null); + } catch (IOException e) { + Iris.logger.error("Error saving properties to \"" + p + "\"", e); + } + }); + + return path.isPresent(); + } } public static class ElementRowEntry extends BaseEntry { diff --git a/src/main/java/net/coderbot/iris/gui/element/ShaderPackSelectionList.java b/src/main/java/net/coderbot/iris/gui/element/ShaderPackSelectionList.java index bc243c04b7..99b0c2e031 100644 --- a/src/main/java/net/coderbot/iris/gui/element/ShaderPackSelectionList.java +++ b/src/main/java/net/coderbot/iris/gui/element/ShaderPackSelectionList.java @@ -15,11 +15,10 @@ import net.minecraft.network.chat.TranslatableComponent; import java.util.Collection; +import java.util.function.Function; public class ShaderPackSelectionList extends IrisObjectSelectionList { private static final Component PACK_LIST_LABEL = new TranslatableComponent("pack.iris.list.label").withStyle(ChatFormatting.ITALIC, ChatFormatting.GRAY); - private static final Component SHADERS_DISABLED_LABEL = new TranslatableComponent("options.iris.shaders.disabled"); - private static final Component SHADERS_ENABLED_LABEL = new TranslatableComponent("options.iris.shaders.enabled"); private final TopButtonRowEntry topButtonRow; private ShaderPackEntry applied = null; @@ -73,9 +72,9 @@ public void refresh() { this.addEntry(topButtonRow); - // Only show the enable / disable shaders button if the user has added a shader pack. Otherwise, the button - // doesn't really make sense. - topButtonRow.showEnableShadersButton = names.size() > 0; + // Only allow the enable/disable shaders button if the user has + // added a shader pack. Otherwise, the button will be disabled. + topButtonRow.allowEnableShadersButton = names.size() > 0; int index = 0; @@ -213,70 +212,84 @@ public void render(PoseStack poseStack, int index, int y, int x, int entryWidth, public static class TopButtonRowEntry extends BaseEntry { private static final Component REFRESH_SHADER_PACKS_LABEL = new TranslatableComponent("options.iris.refreshShaderPacks").withStyle(style -> style.withColor(TextColor.fromRgb(0x99ceff))); + private static final Component NONE_PRESENT_LABEL = new TranslatableComponent("options.iris.shaders.nonePresent").withStyle(ChatFormatting.GRAY); + private static final Component SHADERS_DISABLED_LABEL = new TranslatableComponent("options.iris.shaders.disabled"); + private static final Component SHADERS_ENABLED_LABEL = new TranslatableComponent("options.iris.shaders.enabled"); private static final int REFRESH_BUTTON_WIDTH = 18; private final ShaderPackSelectionList list; + private final IrisElementRow buttons = new IrisElementRow(); + private final EnableShadersButtonElement enableDisableButton; + private final IrisElementRow.Element refreshPacksButton; - public boolean showEnableShadersButton = true; + public boolean allowEnableShadersButton = true; public boolean shadersEnabled; - private int cachedButtonDivisionX; public TopButtonRowEntry(ShaderPackSelectionList list, boolean shadersEnabled) { this.list = list; this.shadersEnabled = shadersEnabled; + this.enableDisableButton = new EnableShadersButtonElement( + getEnableDisableLabel(), + button -> { + if (this.allowEnableShadersButton) { + this.shadersEnabled = !this.shadersEnabled; + button.text = getEnableDisableLabel(); + + GuiUtil.playButtonClickSound(); + return true; + } + + return false; + }); + this.refreshPacksButton = new IrisElementRow.IconButtonElement( + GuiUtil.Icon.REFRESH, + button -> { + this.list.refresh(); + + GuiUtil.playButtonClickSound(); + return true; + }); + this.buttons.add(this.enableDisableButton, 0).add(this.refreshPacksButton, REFRESH_BUTTON_WIDTH); } @Override public void render(PoseStack poseStack, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) { + this.buttons.setWidth(this.enableDisableButton, (entryWidth - 3) - REFRESH_BUTTON_WIDTH); + this.enableDisableButton.centerX = x + (int)(entryWidth * 0.5); - // Cache the x position dividing the enable/disable and refresh button - this.cachedButtonDivisionX = (x + entryWidth) - (REFRESH_BUTTON_WIDTH + 3); - - if (showEnableShadersButton) { - // Draw enable/disable button - GuiUtil.bindIrisWidgetsTexture(); - GuiUtil.drawButton(poseStack, x - 2, y - 3, (entryWidth - REFRESH_BUTTON_WIDTH) - 1, 18, hovered && mouseX < cachedButtonDivisionX, false); - - // Draw enabled/disabled text - Component label = this.shadersEnabled ? SHADERS_ENABLED_LABEL : SHADERS_DISABLED_LABEL; - drawCenteredString(poseStack, Minecraft.getInstance().font, label, (x + entryWidth / 2) - 2, y + (entryHeight - 11) / 2, 0xFFFFFF); - } - - boolean refreshButtonHovered = hovered && mouseX > cachedButtonDivisionX; - - // Draw refresh button - GuiUtil.bindIrisWidgetsTexture(); - GuiUtil.drawButton(poseStack, (x + entryWidth) - (REFRESH_BUTTON_WIDTH + 2), y - 3, REFRESH_BUTTON_WIDTH, 18, refreshButtonHovered, false); - GuiUtil.Icon.REFRESH.draw(poseStack, ((x + entryWidth) - REFRESH_BUTTON_WIDTH) + 2, y + 1); + this.buttons.render(poseStack, x - 2, y - 3, 18, mouseX, mouseY, tickDelta, hovered); - if (refreshButtonHovered) { + if (this.refreshPacksButton.isHovered()) { ShaderPackScreen.TOP_LAYER_RENDER_QUEUE.add(() -> GuiUtil.drawTextPanel(Minecraft.getInstance().font, poseStack, REFRESH_SHADER_PACKS_LABEL, (mouseX - 8) - Minecraft.getInstance().font.width(REFRESH_SHADER_PACKS_LABEL), mouseY - 16)); } } + private Component getEnableDisableLabel() { + return this.allowEnableShadersButton ? this.shadersEnabled ? SHADERS_ENABLED_LABEL : SHADERS_DISABLED_LABEL : NONE_PRESENT_LABEL; + } + @Override public boolean mouseClicked(double mouseX, double mouseY, int button) { - if (button == 0) { - if (mouseX < this.cachedButtonDivisionX) { - if (this.showEnableShadersButton) { - // Enable/Disable button pressed - this.shadersEnabled = !this.shadersEnabled; + return this.buttons.mouseClicked(mouseX, mouseY, button); + } - GuiUtil.playButtonClickSound(); - return true; - } - } else { - // Refresh button pressed - this.list.refresh(); - - GuiUtil.playButtonClickSound(); - return true; - } + // Renders the label at an offset as to not look misaligned with the rest of the menu + public static class EnableShadersButtonElement extends IrisElementRow.TextButtonElement { + private int centerX; + + public EnableShadersButtonElement(Component text, Function onClick) { + super(text, onClick); } - return super.mouseClicked(mouseX, mouseY, button); + @Override + public void renderLabel(PoseStack poseStack, int x, int y, int width, int height, int mouseX, int mouseY, float tickDelta, boolean hovered) { + int textX = this.centerX - (int)(this.font.width(this.text) * 0.5); + int textY = y + (int)((height - 8) * 0.5); + + this.font.drawShadow(poseStack, this.text, textX, textY, 0xFFFFFF); + } } } } diff --git a/src/main/java/net/coderbot/iris/gui/screen/ShaderPackScreen.java b/src/main/java/net/coderbot/iris/gui/screen/ShaderPackScreen.java index 5ce960c148..965ef4ff88 100644 --- a/src/main/java/net/coderbot/iris/gui/screen/ShaderPackScreen.java +++ b/src/main/java/net/coderbot/iris/gui/screen/ShaderPackScreen.java @@ -23,13 +23,16 @@ import org.jetbrains.annotations.Nullable; import java.io.IOException; +import java.io.InputStream; import java.nio.file.FileAlreadyExistsException; +import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Optional; +import java.util.Properties; import java.util.Set; import java.util.stream.Collectors; @@ -51,10 +54,10 @@ public class ShaderPackScreen extends Screen implements HudHideable { private @Nullable NavigationController navigation = null; private Button screenSwitchButton; - private Component addedPackDialog = null; - private int addedPackDialogTimer = 0; + private Component notificationDialog = null; + private int notificationDialogTimer = 0; - private @Nullable AbstractElementWidget hoveredElement = null; + private @Nullable AbstractElementWidget hoveredElement = null; private Optional hoveredElementCommentTitle = Optional.empty(); private List hoveredElementCommentBody = new ArrayList<>(); private int hoveredElementCommentTimer = 0; @@ -88,8 +91,8 @@ public void render(PoseStack poseStack, int mouseX, int mouseY, float delta) { drawCenteredString(poseStack, this.font, this.title, (int)(this.width * 0.5), 8, 0xFFFFFF); - if (addedPackDialog != null && addedPackDialogTimer > 0) { - drawCenteredString(poseStack, this.font, addedPackDialog, (int)(this.width * 0.5), 21, 0xFFFFFF); + if (notificationDialog != null && notificationDialogTimer > 0) { + drawCenteredString(poseStack, this.font, notificationDialog, (int)(this.width * 0.5), 21, 0xFFFFFF); } else { if (optionMenuOpen) { drawCenteredString(poseStack, this.font, CONFIGURE_TITLE, (int)(this.width * 0.5), 21, 0xFFFFFF); @@ -213,8 +216,8 @@ public void refreshScreenSwitchButton() { public void tick() { super.tick(); - if (this.addedPackDialogTimer > 0) { - this.addedPackDialogTimer--; + if (this.notificationDialogTimer > 0) { + this.notificationDialogTimer--; } if (this.hoveredElement != null) { @@ -226,6 +229,14 @@ public void tick() { @Override public void onFilesDrop(List paths) { + if (this.optionMenuOpen) { + onOptionMenuFilesDrop(paths); + } else { + onPackListFilesDrop(paths); + } + } + + public void onPackListFilesDrop(List paths) { List packs = paths.stream().filter(Iris::isValidShaderpack).collect(Collectors.toList()); for (Path pack : packs) { @@ -234,24 +245,24 @@ public void onFilesDrop(List paths) { try { Iris.getShaderpacksDirectoryManager().copyPackIntoDirectory(fileName, pack); } catch (FileAlreadyExistsException e) { - this.addedPackDialog = new TranslatableComponent( + this.notificationDialog = new TranslatableComponent( "options.iris.shaderPackSelection.copyErrorAlreadyExists", fileName ).withStyle(ChatFormatting.ITALIC, ChatFormatting.RED); - this.addedPackDialogTimer = 100; + this.notificationDialogTimer = 100; this.shaderPackList.refresh(); return; } catch (IOException e) { Iris.logger.warn("Error copying dragged shader pack", e); - this.addedPackDialog = new TranslatableComponent( + this.notificationDialog = new TranslatableComponent( "options.iris.shaderPackSelection.copyError", fileName ).withStyle(ChatFormatting.ITALIC, ChatFormatting.RED); - this.addedPackDialogTimer = 100; + this.notificationDialogTimer = 100; this.shaderPackList.refresh(); return; @@ -269,14 +280,14 @@ public void onFilesDrop(List paths) { // If a single pack could not be added, provide a message with that pack in the file name String fileName = paths.get(0).getFileName().toString(); - this.addedPackDialog = new TranslatableComponent( + this.notificationDialog = new TranslatableComponent( "options.iris.shaderPackSelection.failedAddSingle", fileName ).withStyle(ChatFormatting.ITALIC, ChatFormatting.RED); } else { // Otherwise, show a generic message. - this.addedPackDialog = new TranslatableComponent( + this.notificationDialog = new TranslatableComponent( "options.iris.shaderPackSelection.failedAdd" ).withStyle(ChatFormatting.ITALIC, ChatFormatting.RED); } @@ -285,7 +296,7 @@ public void onFilesDrop(List paths) { // In most cases, users will drag a single pack into the selection menu. So, let's special case it. String packName = packs.get(0).getFileName().toString(); - this.addedPackDialog = new TranslatableComponent( + this.notificationDialog = new TranslatableComponent( "options.iris.shaderPackSelection.addedPack", packName ).withStyle(ChatFormatting.ITALIC, ChatFormatting.YELLOW); @@ -296,14 +307,58 @@ public void onFilesDrop(List paths) { } else { // We also support multiple packs being dragged and dropped at a time. Just show a generic success message // in that case. - this.addedPackDialog = new TranslatableComponent( + this.notificationDialog = new TranslatableComponent( "options.iris.shaderPackSelection.addedPacks", packs.size() ).withStyle(ChatFormatting.ITALIC, ChatFormatting.YELLOW); } // Show the relevant message for 5 seconds (100 ticks) - this.addedPackDialogTimer = 100; + this.notificationDialogTimer = 100; + } + + public void onOptionMenuFilesDrop(List paths) { + // If more than one option file has been dragged, display an error + // as only one option file should be imported at a time + if (paths.size() > 1) { + this.notificationDialog = new TranslatableComponent( + "options.iris.shaderPackOptions.tooManyFiles" + ).withStyle(ChatFormatting.ITALIC, ChatFormatting.RED); + this.notificationDialogTimer = 100; // 5 seconds (100 ticks) + + return; + } + + this.importPackOptions(paths.get(0)); + } + + public void importPackOptions(Path settingFile) { + try (InputStream in = Files.newInputStream(settingFile)) { + Properties properties = new Properties(); + properties.load(in); + + Iris.queueShaderPackOptionsFromProperties(properties); + + this.notificationDialog = new TranslatableComponent( + "options.iris.shaderPackOptions.importedSettings", + settingFile.getFileName().toString() + ).withStyle(ChatFormatting.ITALIC, ChatFormatting.YELLOW); + this.notificationDialogTimer = 100; // 5 seconds (100 ticks) + + if (this.navigation != null) { + this.navigation.refresh(); + } + } catch (Exception e) { + // If the file could not be properly parsed or loaded, + // log the error and display a message to the user + Iris.logger.error("Error importing shader settings file \""+ settingFile.toString() +"\"", e); + + this.notificationDialog = new TranslatableComponent( + "options.iris.shaderPackOptions.failedImport", + settingFile.getFileName().toString() + ).withStyle(ChatFormatting.ITALIC, ChatFormatting.RED); + this.notificationDialogTimer = 100; // 5 seconds (100 ticks) + } } @Override @@ -358,7 +413,7 @@ private void openShaderPackFolder() { } // Let the screen know if an element is hovered or not, allowing for accurately updating which element is hovered - public void setElementHoveredStatus(AbstractElementWidget widget, boolean hovered) { + public void setElementHoveredStatus(AbstractElementWidget widget, boolean hovered) { if (hovered && widget != this.hoveredElement) { this.hoveredElement = widget; diff --git a/src/main/resources/assets/iris/lang/en_us.json b/src/main/resources/assets/iris/lang/en_us.json index 447d54b1d8..e37498b075 100644 --- a/src/main/resources/assets/iris/lang/en_us.json +++ b/src/main/resources/assets/iris/lang/en_us.json @@ -26,12 +26,18 @@ "options.iris.shaderPackSelection.failedAddSingle": "\"%s\" is not a valid Shader Pack", "options.iris.shaderPackSelection.copyError": "Could not add Shader Pack \"%s\"", "options.iris.shaderPackSelection.copyErrorAlreadyExists": "\"%s\" is already in your Shader Packs folder!", + "options.iris.shaderPackOptions.tooManyFiles": "Cannot import multiple Shader Settings files!", + "options.iris.shaderPackOptions.failedImport": "Failed to import Shader Settings file \"%s\"", + "options.iris.shaderPackOptions.importedSettings": "Imported Shader Settings from \"%s\"", "options.iris.shaders.disabled": "Shaders: Disabled", "options.iris.shaders.enabled": "Shaders: Enabled", + "options.iris.shaders.nonePresent": "Shaders: No Packs Present", "options.iris.back": "Back", "options.iris.reset": "Reset", "options.iris.reset.tooltip": "Reset ALL options and apply?", "options.iris.reset.tooltip.holdShift": "Hold SHIFT and click to reset", + "options.iris.importSettings.tooltip": "Import settings from file", + "options.iris.exportSettings.tooltip": "Export settings to file", "options.iris.setToDefault": "Set option to default value?", "options.iris.profile": "Profile", "options.iris.profile.custom": "Custom", diff --git a/src/main/resources/assets/iris/textures/gui/widgets.png b/src/main/resources/assets/iris/textures/gui/widgets.png index fb5cba7a695e88ef33eab6de7cc4df034dcc5c8c..d7fe6e520aeccbd2b682273850116884c49421ba 100644 GIT binary patch delta 5512 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 delta 4205 zcmaJ@cQl+$-@aFfE?N?zZK7q>6|54XL? z$;A%p9pGUHwexqf2LS({Tc)O%MQJ+XDr|*gD& zsblZbcXb}7Ac@6rWy4&c3@@_KADf@6o^0ch4ANxdCnK!^3ys@39R_w z*(6_ipU>K$j?EpP%yDw1dzEa5!4LZap6qH5OX~W@GitB!J{DDy$&y-Q93HakF6v0s ztn%Spbu`NSa9(82lG2p|7}24Ji+nDRTytub?I=S_Pc*P*(Gvq z)CD1!lo8-*s21?1=#4F)+%5Gpre-Cb>=kX>J5R|OpFCSj`64!+G=D}_zHm5Rnrx2P zb%@IaTV|GH@~2(NS}^wo#ypmS6Lo=z!-2#N_90vl~*(2wn4BLx^-GVx5aqRy~VukH|Q?o?Ozbs*VlwLel& z%KnDY9}MpCTRtI16`b?P>m`#q*!z+f+iA5Az3`(PR{|TJPsSQLf8{IshkV*0MOTeY zj)#ULq$r(#x638D_(@jC_i(|A>Wvq1sX`VZo#VcrWYK^4lc?!tZhi~t+F}|jGJeN^lBVBM<@ z+f>JBZkpVcdQv{E$FD^uHtdBL7LGFq8=j6OwK15y^dZ=2V+#!mg|}+VvdsM}EPV3Q z1ji~pSL)aq-haagn@9Qx-+lKM2Jlvp6)Z~#1Vd?Srj0bOt9JF1qWSb!o2XE(SnJJ9 zvzXEwi6_t)>xF9iueag~69Cb>e<@6v^((txI;VGEHE^{sX0#{|dh(|Ed5z~FSbock zRZ#K{TO{dZ!}8i%+xkD@sYO2;G90lp4z*&ebT)m~O}$-g)X8)uUXKkwSHLVCzmFLd za=_X&T!n7~Kkmj;<+chC0w)`HOa)Us) zk2EHB!3oW+9$`ZnNHBiUkZ~qLa_HW)c~q=$_)p(2o)=7HYM5xDc}7O%MbbB`(J zYiwFD=Z`~{x3Y_lL;GFZd`?jddBNhFA2>r+(1!5}Ps+{n8+rI=Nd=Qt%S$}vWrSpHnVnT)yHf=P$j-a4{Ls45`^ zQj%4)?`d)^ULZ+>JOQNg-|)28?F|!Ru~cNRVARz-!iFcbxqJ6oGZ3ySg*|jOdVl)L zOy(Jm$D5>kd1A#j(CWH32PM1fSHGtD^f$vCZHf#p-ozDc*y>5`8>|m65`JsG{d}j< zt0X*h*?`Y-qZ!7e;{)F{D2dj;{d=x7LaNIx6EmTEGAj{R+z%xG1fr z*2krGRo~pmc-AL>Q~sAQR0+z)UUX&Wt1K336%Im84-2SIJo-^$yCMd^gTh#?S6#I- z-{-{_%JKzHfSGp+IQ*;?c^<1NKb&rAY^mL%u4};>DJ;(P##lW@I2_?l@`Jaa^?kHO zL4(hOwj{@TCXuz4--)}XJmtpoRmIseuO83k0+nL+(Wy5JYeqHwQ8K@V5hp1B>u<@s z-CksAR3v2^HVKlpWgB#a_eL&hRq*3F&NXoFlv8ETL8JU&+fs3rkxeXBaj9j4fwa~^ z6Rsn2t`AFnPA~u_-028GJAikPA2|(4Z|`qIyE``pxHPdC<6TRH{v|d>Y!t z+vr>o-o@9ME)Xjl2D$T5hV7hhKZN3{ChF_Z7b=zJ?qNhzoT+)Id!)K-+DOKiQjQ9-*6c@%Nq4?H5tHJX+Pp{YKG zJn$z>o*>h|c;~)#X(uQ;5BI8>ZGZdb*mYtGs{JuZUs}-Xg?Tx?@vvEzPcTP)78kOS zFyAC_$&kGm`MV;Ys#mO0tel;gJ?;s9VU``+so#FOrng%3y45fOZoHCxJ7;XCDG26W zhJv9Is}&F@t+j8+ThfEwc@sY8-j;Ff*kfS6!}zPUSXRwVvZmIuT*~>?yjYO>n8}+| z(3qzja<5%%rK*S81>Lw1grOkfYsIm%;&y<(SQ=#Fx z!*Ww~rQ&Cnw&`qkG@VV<4OicN-EZpMNrlqAr08&i@y)8S{JiMQ(emZT;zu+5$)RC= zM|gq`m%r?YNVeIO!A)L$dOdz4XS zl0~@K8iL;-Mk(L-Ti=UcRSZ{#0ia zdcAszv2*%Cm59D)9kBci=)tvHtj#P)qXmRZRa2h7il1rHh|p)37fMfOM_5W^$T*Ey z{5!4Y-Od#X@~P&c67#IAUgr#e=oo!SKjrE=noC|;fSQ+W+B&eH#ihWKJ6|x#{t=CI zOw^o;%I(fl^d0%;>2Dc0&#&4_G$2IMikn{Gd};0E@*|bx5+z}8 zX`DNTVk=@o(G<)jYs@S(Yuv9+Q<-^#XWGtc7P*u_QX<3t^_xG|_$AKBAlBT0NWB-V zH90iN5E6Q@MuP5wgj*Vkd=5vzh2eH^DPf7{cF%=v#O#s6_mE=JG6*}k9a2gx*@z*D z7LJe`s5OVXQLC23`Q}+zT|k`LBPT>OQhDPC>b5xkVT=#8I7o_LTjiLV+yf3{GB5GoTBpI*UGB zJCB|_GKJX%$WCa&Wc+oKA$NJf+D8G0zb9Qg0k!+6mHzm|W1`PobC1HQVdv*;`Jlu2 zM54S==m5Nx6d(oY0Yw0S{8xa!rX!V}n56><0`v1!^Bs}Ytq5j9rCK5b{UlVg}0X8N<=_At}w>8UzKyLiu1 zVQps-|0(FGk|DC5462SmAVws#PKcozFxEu8Q-&XSlN^^Kp_Qy>$+Z;JV(IJWC!YSK zx~0WcBCi5cQYPSI;ULG!75KfkR|B&&g43*-;O&{6opp&Md=Vs@ze25OG2xWKXJ|OU zcd)Us@%xdvxw$%~#$jlX^K+6f*>0I!rA5#cAbP=&^PAe-L**v&xulZ`lfaD&YOL>wc9%pA~8NWZz5a4cf z=Gk)RQs122DSobZa8Mf|CFM{|JGx0S8W*M5M>4$St8e(hoia^iITefDoyu`v9ZU4x zZ3xa-8s+IJofBF)6TN}@)fF@5BM(xO4?n0$H`nO-Xp66|uEtbURFr@Cu$q|D9O~xj zDHazO_o4N#ef5_w*_Vu*KcS66dCC|VwL9xe6%}WvtEsQY@*MZ==tqA+E>88NzI!)0 zJ>BmiooOM>9k2J3HjRUgQc5C=))If625QxQkU7m#I z>dP^oEbZdt$VjpRN>_QZl1*7$%Cpwc5Z}6s;FJ(dR$$JyebUy} z7EN^LMJAJD=9&hp&JX`QR@yn*j*f2Z9#DOJ>}XlkoyVpE0;jUcy{xMe5D zSv*eQ*LXHl6O(VVv)Q^4_2juu8Q!v4`=>+$!?*9G+y~tV>0jsn zsW8918JF()Sgek}H-_N~r?v*&pd@BD&`p&!RaHqn&oM+INPLZ7{Jp(hoPuepL>+95 zUkW`0Hw1Bo6Pp8ncu37y(DCu{a^Sh#D9aPRXxMcNVvwoUd>4CC7Y^z<%Uq>EDC3O%P>5Z(hl>n*jdI zwSsFgAHXL5`DA__uhTcqPU#Eh8yc~Rh-;@+6b|8qPgPpuB8x1dp{L6X**$+Zq zT#7#YSIsEn!!aqUxx4%FA+k@Vze~ZAYj{p~+t^pGEDFz From e271d11d1148e34dea91e2079e0518c9c37a0db7 Mon Sep 17 00:00:00 2001 From: FoundationGames <43485105+FoundationGames@users.noreply.github.com> Date: Sat, 8 Jan 2022 18:51:22 -0800 Subject: [PATCH 2/5] Fix unintended behavior with sliders, allow the use of ESC to navigate backwards in the option menu, offthread file dialogs --- .../net/coderbot/iris/gui/FileDialogUtil.java | 53 ++++++++------ .../iris/gui/NavigationController.java | 4 + .../gui/element/ShaderPackOptionList.java | 73 +++++++++++++------ .../gui/element/ShaderPackSelectionList.java | 2 +- .../element/widget/SliderElementWidget.java | 2 +- .../iris/gui/screen/ShaderPackScreen.java | 21 +++++- .../resources/assets/iris/lang/en_us.json | 2 +- 7 files changed, 107 insertions(+), 50 deletions(-) diff --git a/src/main/java/net/coderbot/iris/gui/FileDialogUtil.java b/src/main/java/net/coderbot/iris/gui/FileDialogUtil.java index bc3f095a30..11020e5786 100644 --- a/src/main/java/net/coderbot/iris/gui/FileDialogUtil.java +++ b/src/main/java/net/coderbot/iris/gui/FileDialogUtil.java @@ -8,49 +8,58 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; /** - * Class used to make interfacing with {@link TinyFileDialogs} easier. + * Class used to make interfacing with {@link TinyFileDialogs} easier and asynchronous. */ public final class FileDialogUtil { + private static final ExecutorService FILE_DIALOG_EXECUTOR = Executors.newSingleThreadExecutor(); + private FileDialogUtil() {} /** - * Opens a file select dialog window. - * - *

Will stall the thread that the method is invoked within. + * Opens an asynchronous file select dialog window. * * @param dialog Whether to open a "save" dialog or an "open" dialog * @param title The title of the dialog window * @param origin The path that the window should start at + * @param filterLabel A label used to describe what file extensions are allowed and their purpose * @param filters The file extension filters used by the dialog, each formatted as {@code "*.extension"} - * @param filterDesc A message used to describe the file extensions used as filters - * @return a path to the file selected by the user, unless the dialog has been canceled. + * @return a {@link CompletableFuture} which is completed once a file is selected or the dialog is cancelled. */ - public static Optional fileSelectDialog(DialogType dialog, String title, @Nullable Path origin, String[] filters, @Nullable String filterDesc) { - String result = null; + public static CompletableFuture> fileSelectDialog(DialogType dialog, String title, @Nullable Path origin, @Nullable String filterLabel, String ... filters) { + CompletableFuture> future = new CompletableFuture<>(); - try (MemoryStack stack = MemoryStack.stackPush()) { - PointerBuffer filterBuffer = stack.mallocPointer(filters.length); + FILE_DIALOG_EXECUTOR.submit(() -> { + String result = null; - for (String filter : filters) { - filterBuffer.put(stack.UTF8(filter)); - } - filterBuffer.flip(); + try (MemoryStack stack = MemoryStack.stackPush()) { + PointerBuffer filterBuffer = stack.mallocPointer(filters.length); - String path = origin != null ? origin.toAbsolutePath().toString() : null; + for (String filter : filters) { + filterBuffer.put(stack.UTF8(filter)); + } + filterBuffer.flip(); - if (dialog == DialogType.SAVE) { - result = TinyFileDialogs.tinyfd_saveFileDialog(title, path, filterBuffer, filterDesc); - } else if (dialog == DialogType.OPEN) { - result = TinyFileDialogs.tinyfd_openFileDialog(title, path, filterBuffer, filterDesc, false); + String path = origin != null ? origin.toAbsolutePath().toString() : null; + + if (dialog == DialogType.SAVE) { + result = TinyFileDialogs.tinyfd_saveFileDialog(title, path, filterBuffer, filterLabel); + } else if (dialog == DialogType.OPEN) { + result = TinyFileDialogs.tinyfd_openFileDialog(title, path, filterBuffer, filterLabel, false); + } } - } - return Optional.ofNullable(result).map(Paths::get); + future.complete(Optional.ofNullable(result).map(Paths::get)); + }); + + return future; } public enum DialogType { - SAVE, OPEN; + SAVE, OPEN } } diff --git a/src/main/java/net/coderbot/iris/gui/NavigationController.java b/src/main/java/net/coderbot/iris/gui/NavigationController.java index 5113d9b310..0c7aa8481f 100644 --- a/src/main/java/net/coderbot/iris/gui/NavigationController.java +++ b/src/main/java/net/coderbot/iris/gui/NavigationController.java @@ -52,6 +52,10 @@ public void refresh() { } } + public boolean hasHistory() { + return this.history.size() > 0; + } + public void setActiveOptionList(ShaderPackOptionList optionList) { this.optionList = optionList; } diff --git a/src/main/java/net/coderbot/iris/gui/element/ShaderPackOptionList.java b/src/main/java/net/coderbot/iris/gui/element/ShaderPackOptionList.java index 137b43d9e8..485e4436dc 100644 --- a/src/main/java/net/coderbot/iris/gui/element/ShaderPackOptionList.java +++ b/src/main/java/net/coderbot/iris/gui/element/ShaderPackOptionList.java @@ -239,13 +239,30 @@ private boolean resetButtonClicked(IrisElementRow.TextButtonElement button) { private boolean importSettingsButtonClicked(IrisElementRow.IconButtonElement button) { GuiUtil.playButtonClickSound(); - Optional path = FileDialogUtil.fileSelectDialog( + // Invalid state to be in + if (!Iris.getCurrentPack().isPresent()) { + return false; + } + + final ShaderPackScreen originalScreen = this.screen; // Also used to prevent invalid state + + FileDialogUtil.fileSelectDialog( FileDialogUtil.DialogType.OPEN, "Import Shader Settings from File", - Iris.getShaderpacksDirectory(), new String[] {"*.txt"}, "Shader Pack Settings (.txt)"); + Iris.getShaderpacksDirectory().resolve(Iris.getCurrentPackName() + ".txt"), + "Shader Pack Settings (.txt)", "*.txt") + .whenComplete((path, err) -> { + if (err != null) { + Iris.logger.error("Error selecting shader settings from file", err); + + return; + } - path.ifPresent(this.screen::importPackOptions); + if (Minecraft.getInstance().screen == originalScreen) { + path.ifPresent(originalScreen::importPackOptions); + } + }); - return path.isPresent(); + return true; } private boolean exportSettingsButtonClicked(IrisElementRow.IconButtonElement button) { @@ -256,31 +273,39 @@ private boolean exportSettingsButtonClicked(IrisElementRow.IconButtonElement but return false; } - Optional path = FileDialogUtil.fileSelectDialog( + FileDialogUtil.fileSelectDialog( FileDialogUtil.DialogType.SAVE, "Export Shader Settings to File", - Paths.get(Iris.getCurrentPackName() + ".txt"), new String[] {"*.txt"}, "Shader Pack Settings (.txt)"); - - path.ifPresent(p -> { - Properties toSave = new Properties(); - - // Dirty way of getting the currently applied settings as a Properties, directly - // opens and copies out of the saved settings file if it is present - Path sourceTxtPath = Iris.getShaderpacksDirectory().resolve(Iris.getCurrentPackName() + ".txt"); - if (Files.exists(sourceTxtPath)) { - try (InputStream in = Files.newInputStream(sourceTxtPath)) { - toSave.load(in); - } catch (IOException ignored) {} - } + Iris.getShaderpacksDirectory().resolve(Iris.getCurrentPackName() + ".txt"), + "Shader Pack Settings (.txt)", "*.txt") + .whenComplete((path, err) -> { + if (err != null) { + Iris.logger.error("Error selecting file to export shader settings", err); - // Save properties to user determined file - try (OutputStream out = Files.newOutputStream(p)) { - toSave.store(out, null); - } catch (IOException e) { - Iris.logger.error("Error saving properties to \"" + p + "\"", e); + return; } + + path.ifPresent(p -> { + Properties toSave = new Properties(); + + // Dirty way of getting the currently applied settings as a Properties, directly + // opens and copies out of the saved settings file if it is present + Path sourceTxtPath = Iris.getShaderpacksDirectory().resolve(Iris.getCurrentPackName() + ".txt"); + if (Files.exists(sourceTxtPath)) { + try (InputStream in = Files.newInputStream(sourceTxtPath)) { + toSave.load(in); + } catch (IOException ignored) {} + } + + // Save properties to user determined file + try (OutputStream out = Files.newOutputStream(p)) { + toSave.store(out, null); + } catch (IOException e) { + Iris.logger.error("Error saving properties to \"" + p + "\"", e); + } + }); }); - return path.isPresent(); + return true; } } diff --git a/src/main/java/net/coderbot/iris/gui/element/ShaderPackSelectionList.java b/src/main/java/net/coderbot/iris/gui/element/ShaderPackSelectionList.java index 99b0c2e031..1fe2381759 100644 --- a/src/main/java/net/coderbot/iris/gui/element/ShaderPackSelectionList.java +++ b/src/main/java/net/coderbot/iris/gui/element/ShaderPackSelectionList.java @@ -254,7 +254,7 @@ public TopButtonRowEntry(ShaderPackSelectionList list, boolean shadersEnabled) { @Override public void render(PoseStack poseStack, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) { - this.buttons.setWidth(this.enableDisableButton, (entryWidth - 3) - REFRESH_BUTTON_WIDTH); + this.buttons.setWidth(this.enableDisableButton, (entryWidth - 1) - REFRESH_BUTTON_WIDTH); this.enableDisableButton.centerX = x + (int)(entryWidth * 0.5); this.buttons.render(poseStack, x - 2, y - 3, 18, mouseX, mouseY, tickDelta, hovered); diff --git a/src/main/java/net/coderbot/iris/gui/element/widget/SliderElementWidget.java b/src/main/java/net/coderbot/iris/gui/element/widget/SliderElementWidget.java index af7b4db2f4..4e7a3ede9c 100644 --- a/src/main/java/net/coderbot/iris/gui/element/widget/SliderElementWidget.java +++ b/src/main/java/net/coderbot/iris/gui/element/widget/SliderElementWidget.java @@ -103,7 +103,7 @@ public boolean mouseClicked(double mx, double my, int button) { return true; } - return super.mouseClicked(mx, my, button); + return false; } @Override diff --git a/src/main/java/net/coderbot/iris/gui/screen/ShaderPackScreen.java b/src/main/java/net/coderbot/iris/gui/screen/ShaderPackScreen.java index 965ef4ff88..88b054762c 100644 --- a/src/main/java/net/coderbot/iris/gui/screen/ShaderPackScreen.java +++ b/src/main/java/net/coderbot/iris/gui/screen/ShaderPackScreen.java @@ -21,6 +21,7 @@ import net.minecraft.network.chat.TranslatableComponent; import net.minecraft.util.FormattedCharSequence; import org.jetbrains.annotations.Nullable; +import org.lwjgl.glfw.GLFW; import java.io.IOException; import java.io.InputStream; @@ -227,6 +228,24 @@ public void tick() { } } + @Override + public boolean keyPressed(int key, int j, int k) { + if (key == GLFW.GLFW_KEY_ESCAPE) { + if (this.navigation != null && this.navigation.hasHistory()) { + this.navigation.back(); + + return true; + } else if (this.optionMenuOpen) { + this.optionMenuOpen = false; + this.init(); + + return true; + } + } + + return super.keyPressed(key, j, k); + } + @Override public void onFilesDrop(List paths) { if (this.optionMenuOpen) { @@ -320,7 +339,7 @@ public void onPackListFilesDrop(List paths) { public void onOptionMenuFilesDrop(List paths) { // If more than one option file has been dragged, display an error // as only one option file should be imported at a time - if (paths.size() > 1) { + if (paths.size() != 1) { this.notificationDialog = new TranslatableComponent( "options.iris.shaderPackOptions.tooManyFiles" ).withStyle(ChatFormatting.ITALIC, ChatFormatting.RED); diff --git a/src/main/resources/assets/iris/lang/en_us.json b/src/main/resources/assets/iris/lang/en_us.json index e37498b075..148aef3d52 100644 --- a/src/main/resources/assets/iris/lang/en_us.json +++ b/src/main/resources/assets/iris/lang/en_us.json @@ -37,7 +37,7 @@ "options.iris.reset.tooltip": "Reset ALL options and apply?", "options.iris.reset.tooltip.holdShift": "Hold SHIFT and click to reset", "options.iris.importSettings.tooltip": "Import settings from file", - "options.iris.exportSettings.tooltip": "Export settings to file", + "options.iris.exportSettings.tooltip": "Export applied settings to file", "options.iris.setToDefault": "Set option to default value?", "options.iris.profile": "Profile", "options.iris.profile.custom": "Custom", From c129f94ea544d5225140b1f598b7d0eff96bbbbe Mon Sep 17 00:00:00 2001 From: FoundationGames <43485105+FoundationGames@users.noreply.github.com> Date: Sat, 8 Jan 2022 19:41:55 -0800 Subject: [PATCH 3/5] Improve profile logging --- .../coderbot/iris/shaderpack/ShaderPack.java | 34 +++++++------------ 1 file changed, 12 insertions(+), 22 deletions(-) diff --git a/src/main/java/net/coderbot/iris/shaderpack/ShaderPack.java b/src/main/java/net/coderbot/iris/shaderpack/ShaderPack.java index 9da905f039..33393c32cc 100644 --- a/src/main/java/net/coderbot/iris/shaderpack/ShaderPack.java +++ b/src/main/java/net/coderbot/iris/shaderpack/ShaderPack.java @@ -18,6 +18,8 @@ import net.coderbot.iris.shaderpack.option.ProfileSet; import net.coderbot.iris.shaderpack.option.ShaderPackOptions; import net.coderbot.iris.shaderpack.option.menu.OptionMenuContainer; +import net.coderbot.iris.shaderpack.option.values.MutableOptionValues; +import net.coderbot.iris.shaderpack.option.values.OptionValues; import net.coderbot.iris.shaderpack.preprocessor.JcppProcessor; import net.coderbot.iris.shaderpack.texture.CustomTextureData; import net.coderbot.iris.shaderpack.texture.TextureFilteringData; @@ -36,6 +38,7 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -107,31 +110,22 @@ public ShaderPack(Path root, Map changedConfigs) throws IOExcept .orElseGet(ShaderProperties::empty); ProfileSet profiles = ProfileSet.fromTree(shaderProperties.getProfiles(), this.shaderPackOptions.getOptionSet()); + this.profile = profiles.scan(this.shaderPackOptions.getOptionSet(), this.shaderPackOptions.getOptionValues()); // Get programs that should be disabled from the detected profile List disabledPrograms = new ArrayList<>(); - profiles.scan(this.shaderPackOptions.getOptionSet(), this.shaderPackOptions.getOptionValues()).current - .ifPresent(profile -> disabledPrograms.addAll(profile.disabledPrograms)); + this.profile.current.ifPresent(profile -> disabledPrograms.addAll(profile.disabledPrograms)); this.menuContainer = new OptionMenuContainer(shaderProperties, this.shaderPackOptions, profiles); - this.profile = profiles.scan(this.shaderPackOptions.getOptionSet(), this.shaderPackOptions.getOptionValues()); - { - // Note: We always use English for this, because this will only show up in logs & on the debug screen - // so trying to detect / handle the current MC language would just be a little too complex; - Map translations = getLanguageMap().getTranslations("en_us"); - String profileName; - String internalProfileName = getCurrentProfileName(); - - if (translations != null) { - profileName = translations.getOrDefault("profile." + internalProfileName, internalProfileName); - } else { - profileName = internalProfileName; - } + String profileName = getCurrentProfileName(); + OptionValues profileOptions = new MutableOptionValues( + this.shaderPackOptions.getOptionSet(), this.profile.current.map(p -> p.optionValues).orElse(new HashMap<>())); - // TODO: show the options changed in relation to the current profile, and not just total. - this.profileInfo = "Profile: " + profileName + " (" + getShaderPackOptions().getOptionValues().getOptionsChanged() + " options changed)"; + int userOptionsChanged = this.shaderPackOptions.getOptionValues().getOptionsChanged() - profileOptions.getOptionsChanged(); + + this.profileInfo = "Profile: " + profileName + " (+" + userOptionsChanged + " option" + (userOptionsChanged == 1 ? "" : "s") + " changed by user)"; } Iris.logger.info(this.profileInfo); @@ -214,11 +208,7 @@ public ShaderPack(Path root, Map changedConfigs) throws IOExcept } private String getCurrentProfileName() { - if (profile.current.isPresent()) { - return profile.current.get().name; - } else { - return "Custom"; - } + return profile.current.map(p -> p.name).orElse("Custom"); } public String getProfileInfo() { From 9747638ac154c1bcc15ae9edfa87281c65bcf638 Mon Sep 17 00:00:00 2001 From: FoundationGames <43485105+FoundationGames@users.noreply.github.com> Date: Mon, 17 Jan 2022 12:59:39 -0800 Subject: [PATCH 4/5] Fix slider reset tooltip priority, fix setting boolean options to default, only create text file when options are changed and exclude default options from text file --- src/main/java/net/coderbot/iris/Iris.java | 51 +++++++++++++------ .../element/widget/BooleanElementWidget.java | 2 +- .../element/widget/SliderElementWidget.java | 6 +-- .../option/values/MutableOptionValues.java | 13 +++-- 4 files changed, 49 insertions(+), 23 deletions(-) diff --git a/src/main/java/net/coderbot/iris/Iris.java b/src/main/java/net/coderbot/iris/Iris.java index 34c28d6c54..96e52ef9a3 100644 --- a/src/main/java/net/coderbot/iris/Iris.java +++ b/src/main/java/net/coderbot/iris/Iris.java @@ -28,6 +28,7 @@ import net.coderbot.iris.shaderpack.option.OptionSet; import net.coderbot.iris.shaderpack.option.Profile; import net.coderbot.iris.shaderpack.discovery.ShaderpackDirectoryManager; +import net.coderbot.iris.shaderpack.option.values.MutableOptionValues; import net.coderbot.iris.shaderpack.option.values.OptionValues; import net.fabricmc.loader.api.ModContainer; import net.irisshaders.iris.api.v0.IrisApi; @@ -284,7 +285,7 @@ private static boolean loadExternalShaderpack(String name) { return false; } - Map changedConfigs = loadConfigProperties(shaderPackConfigTxt) + Map changedConfigs = tryReadConfigProperties(shaderPackConfigTxt) .map(properties -> (Map) (Map) properties) .orElse(new HashMap<>()); @@ -296,12 +297,17 @@ private static boolean loadExternalShaderpack(String name) { } resetShaderPackOptions = false; - Properties configsToSave = new Properties(); - configsToSave.putAll(changedConfigs); - saveConfigProperties(shaderPackConfigTxt, configsToSave); - try { currentPack = new ShaderPack(shaderPackPath, changedConfigs); + + MutableOptionValues changedConfigsValues = currentPack.getShaderPackOptions().getOptionValues().mutableCopy(); + + // Store changed values from those currently in use by the shader pack + Properties configsToSave = new Properties(); + changedConfigsValues.getBooleanValues().forEach((k, v) -> configsToSave.setProperty(k, Boolean.toString(v))); + changedConfigsValues.getStringValues().forEach(configsToSave::setProperty); + + tryUpdateConfigPropertiesFile(shaderPackConfigTxt, configsToSave); } catch (Exception e) { logger.error("Failed to load the shaderpack \"{}\"!", name); logger.catching(e); @@ -366,24 +372,37 @@ private static void setShadersDisabled() { logger.info("Shaders are disabled"); } - private static Optional loadConfigProperties(Path path) { + private static Optional tryReadConfigProperties(Path path) { Properties properties = new Properties(); - try { - // NB: config properties are specified to be encoded with ISO-8859-1 by OptiFine, - // so we don't need to do the UTF-8 workaround here. - properties.load(Files.newInputStream(path)); - } catch (IOException e) { - // TODO: Better error handling - return Optional.empty(); + if (Files.exists(path)) { + try { + // NB: config properties are specified to be encoded with ISO-8859-1 by OptiFine, + // so we don't need to do the UTF-8 workaround here. + properties.load(Files.newInputStream(path)); + } catch (IOException e) { + // TODO: Better error handling + return Optional.empty(); + } } return Optional.of(properties); } - private static void saveConfigProperties(Path path, Properties properties) { - try (OutputStream out = Files.newOutputStream(path)) { - properties.store(out, null); + private static void tryUpdateConfigPropertiesFile(Path path, Properties properties) { + try { + if (properties.isEmpty()) { + // Delete the file or don't create it if there are no changed configs + if (Files.exists(path)) { + Files.delete(path); + } + + return; + } + + try (OutputStream out = Files.newOutputStream(path)) { + properties.store(out, null); + } } catch (IOException e) { // TODO: Better error handling } diff --git a/src/main/java/net/coderbot/iris/gui/element/widget/BooleanElementWidget.java b/src/main/java/net/coderbot/iris/gui/element/widget/BooleanElementWidget.java index 8afe8f3f9e..b57b704a57 100644 --- a/src/main/java/net/coderbot/iris/gui/element/widget/BooleanElementWidget.java +++ b/src/main/java/net/coderbot/iris/gui/element/widget/BooleanElementWidget.java @@ -83,7 +83,7 @@ public boolean applyPreviousValue() { @Override public boolean applyOriginalValue() { - this.value = this.appliedValue; + this.value = this.option.getDefaultValue(); this.queue(); return true; diff --git a/src/main/java/net/coderbot/iris/gui/element/widget/SliderElementWidget.java b/src/main/java/net/coderbot/iris/gui/element/widget/SliderElementWidget.java index 4e7a3ede9c..f26def58de 100644 --- a/src/main/java/net/coderbot/iris/gui/element/widget/SliderElementWidget.java +++ b/src/main/java/net/coderbot/iris/gui/element/widget/SliderElementWidget.java @@ -29,10 +29,10 @@ public void render(PoseStack poseStack, int x, int y, int width, int height, int this.renderSlider(poseStack, x, y, width, height, mouseX, mouseY, tickDelta); } - if (!this.screen.isDisplayingComment()) { - renderTooltip(poseStack, this.unmodifiedLabel, mouseX, mouseY, hovered); - } else if (Screen.hasShiftDown()) { + if (Screen.hasShiftDown()) { renderTooltip(poseStack, SET_TO_DEFAULT, mouseX, mouseY, hovered); + } else if (!this.screen.isDisplayingComment()) { + renderTooltip(poseStack, this.unmodifiedLabel, mouseX, mouseY, hovered); } if (this.mouseDown) { diff --git a/src/main/java/net/coderbot/iris/shaderpack/option/values/MutableOptionValues.java b/src/main/java/net/coderbot/iris/shaderpack/option/values/MutableOptionValues.java index 4900edaa4a..ca1a446ac0 100644 --- a/src/main/java/net/coderbot/iris/shaderpack/option/values/MutableOptionValues.java +++ b/src/main/java/net/coderbot/iris/shaderpack/option/values/MutableOptionValues.java @@ -13,10 +13,17 @@ public class MutableOptionValues implements OptionValues { private final Map booleanValues; private final Map stringValues; - MutableOptionValues(OptionSet options, Map flippedBooleanValues, Map stringValues) { + MutableOptionValues(OptionSet options, Map booleanValues, Map stringValues) { + Map values = new HashMap<>(); + + booleanValues.forEach((k, v) -> values.put(k, Boolean.toString(v))); + values.putAll(stringValues); + this.options = options; - this.booleanValues = flippedBooleanValues; - this.stringValues = stringValues; + this.booleanValues = new HashMap<>(); + this.stringValues = new HashMap<>(); + + this.addAll(values); } public MutableOptionValues(OptionSet options, Map values) { From 290eba97ee51612053a621db464839c98fd44ab7 Mon Sep 17 00:00:00 2001 From: FoundationGames <43485105+FoundationGames@users.noreply.github.com> Date: Mon, 17 Jan 2022 13:12:43 -0800 Subject: [PATCH 5/5] Add comment explaining the cancellation of parent widget click behavior in sliders --- .../coderbot/iris/gui/element/widget/SliderElementWidget.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/net/coderbot/iris/gui/element/widget/SliderElementWidget.java b/src/main/java/net/coderbot/iris/gui/element/widget/SliderElementWidget.java index f26def58de..0e2e659e54 100644 --- a/src/main/java/net/coderbot/iris/gui/element/widget/SliderElementWidget.java +++ b/src/main/java/net/coderbot/iris/gui/element/widget/SliderElementWidget.java @@ -103,6 +103,8 @@ public boolean mouseClicked(double mx, double my, int button) { return true; } + + // Do not use base widget's button click behavior return false; }