diff --git a/common/src/main/java/com/wynntils/core/persisted/config/Category.java b/common/src/main/java/com/wynntils/core/persisted/config/Category.java index 439cb3c17e..acbaaa6c69 100644 --- a/common/src/main/java/com/wynntils/core/persisted/config/Category.java +++ b/common/src/main/java/com/wynntils/core/persisted/config/Category.java @@ -1,32 +1,40 @@ /* - * Copyright © Wynntils 2022-2023. + * Copyright © Wynntils 2022-2024. * This file is released under LGPLv3. See LICENSE for full license details. */ package com.wynntils.core.persisted.config; +import com.wynntils.utils.render.Texture; import java.util.Locale; import net.minecraft.client.resources.language.I18n; public enum Category { - UNCATEGORIZED, - CHAT, - COMBAT, - COMMANDS, - DEBUG, - EMBELLISHMENTS, - INVENTORY, - MAP, - OVERLAYS, - PLAYERS, - REDIRECTS, - TOOLTIPS, - TRADEMARKET, - UI, - UTILITIES, - WYNNTILS; + UNCATEGORIZED(Texture.UNCATEGORIZED_CONFIG_ICON), + CHAT(Texture.CHAT_CONFIG_ICON), + COMBAT(Texture.COMBAT_CONFIG_ICON), + COMMANDS(Texture.COMMANDS_CONFIG_ICON), + DEBUG(Texture.DEBUG_CONFIG_ICON), + EMBELLISHMENTS(Texture.EMBELLISHMENTS_CONFIG_ICON), + INVENTORY(Texture.INVENTORY_CONFIG_ICON), + MAP(Texture.MAP_CONFIG_ICON), + OVERLAYS(Texture.OVERLAYS_CONFIG_ICON), + PLAYERS(Texture.PLAYERS_CONFIG_ICON), + REDIRECTS(Texture.REDIRECTS_CONFIG_ICON), + TOOLTIPS(Texture.TOOLTIPS_CONFIG_ICON), + TRADEMARKET(Texture.TRADE_MARKET_CONFIG_ICON), + UI(Texture.UI_CONFIG_ICON), + UTILITIES(Texture.UTILITIES_CONFIG_ICON), + WYNNTILS(Texture.WYNNTILS_CONFIG_ICON); - Category() { + private final Texture categoryIcon; + + Category(Texture categoryIcon) { assert !toString().startsWith("core.wynntils"); + this.categoryIcon = categoryIcon; + } + + public Texture getCategoryIcon() { + return categoryIcon; } @Override diff --git a/common/src/main/java/com/wynntils/core/persisted/config/ConfigManager.java b/common/src/main/java/com/wynntils/core/persisted/config/ConfigManager.java index 60dc22b4ad..0132382e33 100644 --- a/common/src/main/java/com/wynntils/core/persisted/config/ConfigManager.java +++ b/common/src/main/java/com/wynntils/core/persisted/config/ConfigManager.java @@ -4,9 +4,11 @@ */ package com.wynntils.core.persisted.config; +import com.google.common.reflect.TypeToken; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import com.google.gson.JsonSyntaxException; import com.mojang.util.UndashedUuid; import com.wynntils.core.WynntilsMod; import com.wynntils.core.components.Manager; @@ -24,8 +26,10 @@ import java.io.File; import java.lang.reflect.Field; import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.TreeSet; import java.util.stream.Stream; @@ -267,4 +271,66 @@ public Stream> getConfigsForOwner(PersistedOwner owner) { return getConfigs() .filter(config -> Managers.Persisted.getMetadata(config).owner() == owner); } + + public boolean importConfig(String jsonInput, List configsToImport) { + try { + Map configData = + Managers.Json.GSON.fromJson(jsonInput, new TypeToken>() {}.getType()); + + if (configData == null) { + WynntilsMod.warn("Unable to import config due to invalid input"); + return false; + } + + // Loop through all features chosen to import to + for (Configurable feature : configsToImport) { + // Loop through the visible configs only as they are the only configs to be imported + for (Config configOption : feature.getVisibleConfigOptions()) { + String configOptionName = configOption.getJsonName(); + + // If the config data contains this config option, then it can be imported + if (configData.containsKey(configOptionName)) { + Object configOptionValue = configData.get(configOptionName); + setConfigValue(configOption, configOptionValue); + } + } + } + + return true; + } catch (JsonSyntaxException ex) { + WynntilsMod.warn("Failed to import config ", ex); + return false; + } + } + + public String exportConfig(List featuresToExport) { + Map configData = new HashMap<>(); + + // Loop through all features to be exported + for (Configurable feature : featuresToExport) { + List> visibleConfigOptions = feature.getVisibleConfigOptions(); + + // Loop through visible config options, as we don't want this to export + // hidden configs since they should be exportable in their + // own features, like favorites and waypoints + for (Config configOption : visibleConfigOptions) { + String configOptionName = configOption.getJsonName(); + Object configOptionValue = configOption.get(); + + // Save the config option to the map + configData.put(configOptionName, configOptionValue); + } + } + + // Return the json string of the exported settings + return Managers.Json.GSON.toJson(configData); + } + + private void setConfigValue(Config config, Object value) { + T typedValue = config.tryParseStringValue(value.toString()); + + if (typedValue != null) { + config.setValue(typedValue); + } + } } diff --git a/common/src/main/java/com/wynntils/screens/chattabs/ChatTabEditingScreen.java b/common/src/main/java/com/wynntils/screens/chattabs/ChatTabEditingScreen.java index a14f107216..ec33f5a989 100644 --- a/common/src/main/java/com/wynntils/screens/chattabs/ChatTabEditingScreen.java +++ b/common/src/main/java/com/wynntils/screens/chattabs/ChatTabEditingScreen.java @@ -1,5 +1,5 @@ /* - * Copyright © Wynntils 2022-2023. + * Copyright © Wynntils 2022-2024. * This file is released under LGPLv3. See LICENSE for full license details. */ package com.wynntils.screens.chattabs; diff --git a/common/src/main/java/com/wynntils/screens/overlays/selection/OverlaySelectionScreen.java b/common/src/main/java/com/wynntils/screens/overlays/selection/OverlaySelectionScreen.java index cbdb3de61b..b0c7b16063 100644 --- a/common/src/main/java/com/wynntils/screens/overlays/selection/OverlaySelectionScreen.java +++ b/common/src/main/java/com/wynntils/screens/overlays/selection/OverlaySelectionScreen.java @@ -488,6 +488,8 @@ public void populateOverlays() { setSelectedOverlay(newSelected); } + + scrollOverlays(overlayScrollOffset); } public void setSelectedOverlay(Overlay selectedOverlay) { @@ -604,6 +606,8 @@ private void populateConfigs() { renderY += 43; } + + scrollConfigs(configScrollOffset); } private void togglePreview(boolean enabled) { diff --git a/common/src/main/java/com/wynntils/screens/settings/WynntilsBookSettingsScreen.java b/common/src/main/java/com/wynntils/screens/settings/WynntilsBookSettingsScreen.java index d749a893a1..ad39858820 100644 --- a/common/src/main/java/com/wynntils/screens/settings/WynntilsBookSettingsScreen.java +++ b/common/src/main/java/com/wynntils/screens/settings/WynntilsBookSettingsScreen.java @@ -4,7 +4,6 @@ */ package com.wynntils.screens.settings; -import com.mojang.blaze3d.systems.RenderSystem; import com.mojang.blaze3d.vertex.PoseStack; import com.wynntils.core.components.Managers; import com.wynntils.core.consumers.features.Configurable; @@ -16,17 +15,22 @@ import com.wynntils.core.persisted.config.Category; import com.wynntils.core.persisted.config.Config; import com.wynntils.core.text.StyledText; +import com.wynntils.screens.base.TooltipProvider; import com.wynntils.screens.base.widgets.SearchWidget; import com.wynntils.screens.base.widgets.TextInputBoxWidget; import com.wynntils.screens.base.widgets.WynntilsButton; -import com.wynntils.screens.settings.widgets.ApplyButton; import com.wynntils.screens.settings.widgets.CategoryButton; -import com.wynntils.screens.settings.widgets.CloseButton; import com.wynntils.screens.settings.widgets.ConfigTile; import com.wynntils.screens.settings.widgets.ConfigurableButton; +import com.wynntils.screens.settings.widgets.SettingsCategoryTabButton; +import com.wynntils.screens.settings.widgets.SettingsPageTabButton; +import com.wynntils.screens.settings.widgets.SettingsSearchWidget; +import com.wynntils.screens.settings.widgets.SettingsSideTabButton; import com.wynntils.utils.MathUtils; import com.wynntils.utils.StringUtils; import com.wynntils.utils.colors.CommonColors; +import com.wynntils.utils.mc.ComponentUtils; +import com.wynntils.utils.mc.McUtils; import com.wynntils.utils.render.FontRenderer; import com.wynntils.utils.render.RenderUtils; import com.wynntils.utils.render.Texture; @@ -34,6 +38,7 @@ import com.wynntils.utils.render.type.TextShadow; import com.wynntils.utils.render.type.VerticalAlignment; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Comparator; import java.util.List; @@ -42,44 +47,75 @@ import java.util.stream.Stream; import net.minecraft.ChatFormatting; import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.AbstractWidget; import net.minecraft.client.gui.components.Renderable; import net.minecraft.client.gui.components.events.GuiEventListener; import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.resources.language.I18n; import net.minecraft.network.chat.Component; import org.lwjgl.glfw.GLFW; public final class WynntilsBookSettingsScreen extends WynntilsScreen { + // Constants private static final float SCROLL_FACTOR = 10f; - private static final int CONFIG_MASK_TOP_Y = 21; + private static final int MASK_TOP_Y = 21; private static final int CONFIG_MASK_BOTTOM_Y = 205; - private static final int CONFIGURABLES_PER_PAGE = 13; + private static final int CONFIGURABLE_MASK_BOTTOM_Y = 211; + private static final int CONFIGURABLES_PER_PAGE = 16; private static final int CONFIGS_PER_PAGE = 4; - private static final int CONFIGURABLE_SCROLL_X = 23; + private static final int CONFIGURABLE_SCROLL_X = (int) (Texture.CONFIG_BOOK_BACKGROUND.width() / 2f - 12); private static final int CONFIG_SCROLL_X = Texture.CONFIG_BOOK_BACKGROUND.width() - 23; - private static final int SCROLL_START_Y = 17; + private static final int MAX_DISPLAYED_CATEGORIES = 10; + private static final int SCROLL_AREA_HEIGHT = 186; + private static final int SCROLL_START_Y = 21; + // Collections + private final List sortedCategories; private final List configurables = new ArrayList<>(); private final List configs = new ArrayList<>(); private List configurableList; + private List categoryButtons = new ArrayList<>(); - private TextInputBoxWidget focusedTextInput; + // Renderables private final SearchWidget searchWidget; + private SettingsCategoryTabButton allCategoriesButton; + private SettingsCategoryTabButton selectedCategoryButton; + private TextInputBoxWidget focusedTextInput; - private Configurable selected = null; - + // UI size, postions, etc private boolean draggingConfigurableScroll = false; private boolean draggingConfigScroll = false; - private int configurableScrollOffset = 0; + private int categoriesScrollOffset = 0; + private int configurablesScrollOffset = 0; private int configScrollOffset = 0; private float configurableScrollRenderY; private float configScrollRenderY; + private float translationX; + private float translationY; + + // Settings display + private Category selectedCategory; + private Configurable selectedConfigurable = null; private WynntilsBookSettingsScreen() { super(Component.translatable("screens.wynntils.settingsScreen.name")); - searchWidget = new SearchWidget( - 95, Texture.CONFIG_BOOK_BACKGROUND.height() - 32, 100, 20, s -> reloadConfigurableButtons(), this); + searchWidget = new SettingsSearchWidget( + 55, + Texture.CONFIG_BOOK_BACKGROUND.height() + 6, + 120, + 20, + (s) -> { + configurablesScrollOffset = 0; + getFilteredConfigurables(); + populateConfigurables(); + }, + this); setFocusedTextInput(searchWidget); + + // Get all categories, sort a-z + sortedCategories = Arrays.asList(Category.values()); + sortedCategories.sort(Comparator.comparing(Enum::name)); } public static Screen create() { @@ -88,39 +124,228 @@ public static Screen create() { @Override protected void doInit() { - reloadConfigurableButtons(); + populateCategories(); + getFilteredConfigurables(); + populateConfigurables(); + // Render position for the book background + translationX = (this.width - Texture.CONFIG_BOOK_BACKGROUND.width()) / 2f; + translationY = (this.height - Texture.CONFIG_BOOK_BACKGROUND.height()) / 2f; + + int yPos = Texture.TAG_BLUE.height() / 2; + + // region Side tags + this.addRenderableWidget(new SettingsSideTabButton( + (int) -(Texture.TAG_BLUE.width() * 0.75f), + yPos, + Texture.TAG_BLUE.width(), + Texture.TAG_BLUE.height(), + this::importSettings, + ComponentUtils.wrapTooltips( + List.of( + Component.translatable("screens.wynntils.settingsScreen.import") + .withStyle(ChatFormatting.YELLOW), + Component.translatable("screens.wynntils.settingsScreen.import.all") + .withStyle(ChatFormatting.GRAY), + Component.translatable("screens.wynntils.settingsScreen.import.selected") + .withStyle(ChatFormatting.GRAY)), + 150), + Texture.TAG_BLUE, + Texture.IMPORT_SETTINGS_ICON)); + + yPos += 15 + Texture.TAG_BLUE.height() / 2; + + this.addRenderableWidget(new SettingsSideTabButton( + (int) -(Texture.TAG_BLUE.width() * 0.75f), + yPos, + Texture.TAG_BLUE.width(), + Texture.TAG_BLUE.height(), + this::exportSettings, + ComponentUtils.wrapTooltips( + List.of( + Component.translatable("screens.wynntils.settingsScreen.export") + .withStyle(ChatFormatting.BLUE), + Component.translatable("screens.wynntils.settingsScreen.export.all") + .withStyle(ChatFormatting.GRAY), + Component.translatable("screens.wynntils.settingsScreen.export.selected") + .withStyle(ChatFormatting.GRAY)), + 150), + Texture.TAG_BLUE, + Texture.EXPORT_SETTINGS_ICON)); + + yPos += 15 + Texture.TAG_BLUE.height() / 2; + + this.addRenderableWidget(new SettingsSideTabButton( + (int) -(Texture.TAG_BLUE.width() * 0.75f), + yPos, + Texture.TAG_BLUE.width(), + Texture.TAG_BLUE.height(), + (b) -> { + Managers.Config.saveConfig(); + onClose(); + }, + ComponentUtils.wrapTooltips( + List.of( + Component.translatable("screens.wynntils.settingsScreen.apply") + .withStyle(ChatFormatting.GREEN), + Component.translatable("screens.wynntils.settingsScreen.apply.description") + .withStyle(ChatFormatting.GRAY)), + 150), + Texture.TAG_BLUE, + Texture.APPLY_SETTINGS_ICON)); + + yPos += 15 + Texture.TAG_BLUE.height() / 2; + + this.addRenderableWidget(new SettingsSideTabButton( + (int) -(Texture.TAG_BLUE.width() * 0.75f), + yPos, + Texture.TAG_BLUE.width(), + Texture.TAG_BLUE.height(), + (b) -> onClose(), + ComponentUtils.wrapTooltips( + List.of( + Component.translatable("screens.wynntils.settingsScreen.close") + .withStyle(ChatFormatting.RED), + Component.translatable("screens.wynntils.settingsScreen.close.description") + .withStyle(ChatFormatting.GRAY)), + 150), + Texture.TAG_BLUE, + Texture.DISCARD_SETTINGS_ICON)); + // endregion + + // region Category tags + int xPos = (int) (Texture.TAG_RED.width() * 0.85); + + allCategoriesButton = this.addRenderableWidget(new SettingsCategoryTabButton( + xPos, + (int) -(Texture.TAG_RED.height() * 0.75f), + Texture.TAG_RED.width(), + Texture.TAG_RED.height(), + (b) -> changeCategory(null), + List.of(Component.literal("All")), + selectedCategory == null)); + + if (selectedCategory == null) { + selectedCategoryButton = allCategoriesButton; + } - this.addRenderableWidget(searchWidget); + xPos += Texture.TAG_RED.width() * 2 + 1; + + this.addRenderableWidget(new SettingsPageTabButton( + xPos, + (int) -(Texture.TAG_RED.height() * 0.75f), + Texture.TAG_RED.width(), + Texture.TAG_RED.height(), + (b) -> scrollCategorories(-1), + List.of(Component.translatable("screens.wynntils.settingsScreen.previous")), + false)); + + xPos += (Texture.TAG_RED.width() * 1.25) * (MAX_DISPLAYED_CATEGORIES + 1) - Texture.TAG_RED.width() * 0.25; - this.addRenderableWidget(new ApplyButton(this)); + this.addRenderableWidget(new SettingsPageTabButton( + xPos, + (int) -(Texture.TAG_RED.height() * 0.75f), + Texture.TAG_RED.width(), + Texture.TAG_RED.height(), + (b) -> scrollCategorories(1), + List.of(Component.translatable("screens.wynntils.settingsScreen.next")), + true)); - this.addRenderableWidget(new CloseButton(this)); + this.addRenderableWidget(searchWidget); } @Override public void doRender(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) { - float backgroundRenderX = getTranslationX(); - float backgroundRenderY = getTranslationY(); - PoseStack poseStack = guiGraphics.pose(); poseStack.pushPose(); - poseStack.translate(backgroundRenderX, backgroundRenderY, 0); + poseStack.translate(translationX, translationY, 0); + + int adjustedMouseX = mouseX - (int) translationX; + int adjustedMouseY = mouseY - (int) translationY; + + renderTags(guiGraphics, adjustedMouseX, adjustedMouseY, partialTick); renderBg(poseStack); - renderScrollArea(poseStack); + String categoryName = selectedCategory == null + ? I18n.get("screens.wynntils.settingsScreen.all") + : I18n.get(selectedCategory.toString()); - renderConfigurableScroll(poseStack); + FontRenderer.getInstance() + .renderText( + poseStack, + StyledText.fromString(categoryName), + Texture.CONFIG_BOOK_BACKGROUND.width() * 0.25f, + McUtils.mc().font.lineHeight + 5, + CommonColors.LIGHT_GRAY, + HorizontalAlignment.CENTER, + VerticalAlignment.MIDDLE, + TextShadow.NORMAL); + + RenderUtils.drawLine( + poseStack, CommonColors.GRAY, 11, 19, Texture.CONFIG_BOOK_BACKGROUND.width() / 2f - 6, 19, 0, 1); + + if (selectedConfigurable != null) { + String textToRender = selectedConfigurable.getTranslatedName(); + + // Show the custom name for info boxes/custom bars if given + if (selectedConfigurable instanceof CustomNameProperty customNameProperty) { + if (!customNameProperty.getCustomName().get().isEmpty()) { + textToRender = customNameProperty.getCustomName().get(); + } + } - renderWidgets(guiGraphics, mouseX, mouseY, partialTick); + FontRenderer.getInstance() + .renderText( + poseStack, + StyledText.fromString(textToRender), + Texture.CONFIG_BOOK_BACKGROUND.width() * 0.75f, + McUtils.mc().font.lineHeight + 5, + CommonColors.LIGHT_GRAY, + HorizontalAlignment.CENTER, + VerticalAlignment.MIDDLE, + TextShadow.NORMAL); + + RenderUtils.drawLine( + poseStack, + CommonColors.GRAY, + Texture.CONFIG_BOOK_BACKGROUND.width() / 2f + 6, + 19, + Texture.CONFIG_BOOK_BACKGROUND.width() - 11, + 19, + 0, + 1); + } else { + FontRenderer.getInstance() + .renderAlignedTextInBox( + poseStack, + StyledText.fromComponent( + Component.translatable("screens.wynntils.settingsScreen.unselectedConfig")), + Texture.CONFIG_BOOK_BACKGROUND.width() / 2f, + Texture.CONFIG_BOOK_BACKGROUND.width(), + Texture.CONFIG_BOOK_BACKGROUND.height() * 0.25f, + Texture.CONFIG_BOOK_BACKGROUND.height() * 0.75f, + Texture.CONFIG_BOOK_BACKGROUND.width() / 3f, + CommonColors.WHITE, + HorizontalAlignment.CENTER, + VerticalAlignment.TOP, + TextShadow.NORMAL); + } - if (selected != null) { - renderConfigTitle(poseStack); + if (configurables.size() > CONFIGURABLES_PER_PAGE) { + renderConfigurableScroll(poseStack); + } + if (configs.size() > CONFIGS_PER_PAGE) { renderConfigScroll(poseStack); } + renderConfigs(guiGraphics, adjustedMouseX, adjustedMouseY, partialTick); + + renderConfigurables(guiGraphics, adjustedMouseX, adjustedMouseY, partialTick); + + renderTooltips(guiGraphics, adjustedMouseX, adjustedMouseY); + poseStack.popPose(); } @@ -138,8 +363,8 @@ public void onClose() { @Override public boolean doMouseClicked(double mouseX, double mouseY, int button) { - double adjustedMouseX = mouseX - getTranslationX(); - double adjustedMouseY = mouseY - getTranslationY(); + double adjustedMouseX = mouseX - translationX; + double adjustedMouseY = mouseY - translationY; for (GuiEventListener listener : getWidgetsForIteration().toList()) { if (listener.isMouseOver(adjustedMouseX, adjustedMouseY)) { @@ -177,18 +402,16 @@ public boolean doMouseClicked(double mouseX, double mouseY, int button) { @Override public boolean mouseDragged(double mouseX, double mouseY, int button, double dragX, double dragY) { - double adjustedMouseX = mouseX - getTranslationX(); - double adjustedMouseY = mouseY - getTranslationY(); + double adjustedMouseX = mouseX - translationX; + double adjustedMouseY = mouseY - translationY; if (draggingConfigurableScroll) { int scrollAreaStartY = SCROLL_START_Y + 7; - int scrollAreaHeight = - (int) (CONFIGURABLES_PER_PAGE * 12 - Texture.CONFIG_BOOK_SCROLL_BUTTON.height() / 2f); int newOffset = Math.round(MathUtils.map( (float) adjustedMouseY, scrollAreaStartY, - scrollAreaStartY + scrollAreaHeight, + scrollAreaStartY + SCROLL_AREA_HEIGHT - Texture.CONFIG_BOOK_SCROLL_BUTTON.height() / 2f, 0, getMaxConfigurableScrollOffset())); @@ -201,12 +424,11 @@ public boolean mouseDragged(double mouseX, double mouseY, int button, double dra if (draggingConfigScroll) { int scrollAreaStartY = SCROLL_START_Y + 7; - int scrollAreaHeight = (int) (CONFIGS_PER_PAGE * 46 - Texture.CONFIG_BOOK_SCROLL_BUTTON.height() / 2f); int newOffset = Math.round(MathUtils.map( (float) adjustedMouseY, scrollAreaStartY, - scrollAreaStartY + scrollAreaHeight, + scrollAreaStartY + SCROLL_AREA_HEIGHT - Texture.CONFIG_BOOK_SCROLL_BUTTON.height() / 2f, 0, getMaxConfigScrollOffset())); @@ -228,8 +450,8 @@ public boolean mouseDragged(double mouseX, double mouseY, int button, double dra @Override public boolean mouseReleased(double mouseX, double mouseY, int button) { - double adjustedMouseX = mouseX - getTranslationX(); - double adjustedMouseY = mouseY - getTranslationY(); + double adjustedMouseX = mouseX - translationX; + double adjustedMouseY = mouseY - translationY; for (GuiEventListener listener : getWidgetsForIteration().toList()) { listener.mouseReleased(adjustedMouseX, adjustedMouseY, button); @@ -243,12 +465,17 @@ public boolean mouseReleased(double mouseX, double mouseY, int button) { @Override public boolean mouseScrolled(double mouseX, double mouseY, double deltaX, double deltaY) { - double adjustedMouseX = mouseX - getTranslationX(); + double adjustedMouseX = mouseX - translationX; int scrollAmount = (int) (-deltaY * SCROLL_FACTOR); - if (adjustedMouseX <= Texture.CONFIG_BOOK_BACKGROUND.width() / 2f) { + // When mouse above the book, scroll the categories. + // When below top of book and left side scroll configurables + // Otherwise scroll configs + if (mouseY <= translationY) { + scrollCategorories((int) -Math.signum(deltaY)); + } else if (adjustedMouseX <= Texture.CONFIG_BOOK_BACKGROUND.width() / 2f) { int newOffset = - Math.max(0, Math.min(configurableScrollOffset + scrollAmount, getMaxConfigurableScrollOffset())); + Math.max(0, Math.min(configurablesScrollOffset + scrollAmount, getMaxConfigurableScrollOffset())); scrollConfigurables(newOffset); } else if (configs.size() > CONFIGS_PER_PAGE) { int newOffset = Math.max(0, Math.min(configScrollOffset + scrollAmount, getMaxConfigScrollOffset())); @@ -283,155 +510,258 @@ public void setFocusedTextInput(TextInputBoxWidget focusedTextInput) { this.focusedTextInput = focusedTextInput; } - public boolean configOptionContains(Config config) { - return !searchWidget.getTextBoxInput().isEmpty() - && StringUtils.containsIgnoreCase(config.getDisplayName(), searchWidget.getTextBoxInput()); - } + public void populateConfigurables() { + configurables.clear(); - public Configurable getSelected() { - return selected; - } + Category oldCategory = selectedCategory; - public void setSelected(Configurable selected) { - this.selected = selected; - reloadConfigButtons(); - } + int renderY = 21; - public float getTranslationY() { - return (this.height - Texture.CONFIG_BOOK_BACKGROUND.height()) / 2f; - } + for (Configurable configurable : configurableList) { + Category category; - public float getTranslationX() { - return (this.width - Texture.CONFIG_BOOK_BACKGROUND.width()) / 2f; - } + if (configurable instanceof Feature feature) { + category = feature.getCategory(); + } else if (configurable instanceof Overlay overlay) { + category = Managers.Overlay.getOverlayParent(overlay).getCategory(); + } else { + throw new IllegalStateException("Unknown configurable type: " + configurable.getClass()); + } - public int getConfigMaskTopY() { - return CONFIG_MASK_TOP_Y; - } + if (category != oldCategory) { + CategoryButton categoryButton = new CategoryButton(12, renderY, 170, 10, category); + categoryButton.visible = renderY >= (21 - 12) && renderY <= (21 + (CONFIGURABLES_PER_PAGE + 1) * 11); + configurables.add(categoryButton); + oldCategory = category; + renderY += 12; + } - public int getConfigMaskBottomY() { - return CONFIG_MASK_BOTTOM_Y; - } + int matchingConfigs = 0; + + for (Config config : configurable.getVisibleConfigOptions()) { + if (configOptionContains(config)) { + matchingConfigs++; + } + } - private void renderConfigTitle(PoseStack poseStack) { - String name = ""; - boolean enabled = false; - if (selected instanceof Overlay selectedOverlay) { - enabled = Managers.Overlay.isEnabled(selectedOverlay); - name = selectedOverlay.getTranslatedName(); + ConfigurableButton configurableButton = + new ConfigurableButton(12, renderY, 170, 10, configurable, this, matchingConfigs); + configurableButton.visible = renderY >= (21 - 12) && renderY <= (21 + (CONFIGURABLES_PER_PAGE + 1) * 11); + configurables.add(configurableButton); - if (selected instanceof CustomNameProperty customNameProperty) { - if (!customNameProperty.getCustomName().get().isEmpty()) { - name = customNameProperty.getCustomName().get(); + renderY += 12; + + if (configurable instanceof Feature feature) { + for (Overlay overlay : Managers.Overlay.getFeatureOverlays(feature).stream() + .sorted() + .toList()) { + matchingConfigs = 0; + + for (Config config : overlay.getVisibleConfigOptions()) { + if (configOptionContains(config)) { + matchingConfigs++; + } + } + + ConfigurableButton overlayButton = + new ConfigurableButton(12, renderY, 170, 10, overlay, this, matchingConfigs); + overlayButton.visible = renderY >= (21 - 12) && renderY <= (21 + (CONFIGURABLES_PER_PAGE + 1) * 11); + configurables.add(overlayButton); + renderY += 12; } } - } else if (selected instanceof Feature selectedFeature) { - enabled = selectedFeature.isEnabled(); - name = selectedFeature.getTranslatedName(); } - FontRenderer.getInstance() - .renderScrollingText( - poseStack, - StyledText.fromString(name + ": " - + (enabled - ? ChatFormatting.DARK_GREEN + "Enabled" - : ChatFormatting.DARK_RED + "Disabled")), - Texture.CONFIG_BOOK_BACKGROUND.width() / 2f + 10, - 10, - Texture.CONFIG_BOOK_BACKGROUND.width() / 2f - 20, - getTranslationX(), - getTranslationY(), - CommonColors.BLACK, - HorizontalAlignment.LEFT, - VerticalAlignment.TOP, - TextShadow.NONE, - 0.8f); - } - - private void renderWidgets(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) { - int adjustedMouseX = mouseX - (int) getTranslationX(); - int adjustedMouseY = mouseY - (int) getTranslationY(); + if (selectedConfigurable != null) { + Stream configurablesList = Stream.concat( + Managers.Feature.getFeatures().stream(), + Managers.Feature.getFeatures().stream() + .map(Managers.Overlay::getFeatureOverlays) + .flatMap(Collection::stream) + .map(overlay -> (Configurable) overlay)); - for (Renderable renderable : renderables) { - renderable.render(guiGraphics, adjustedMouseX, adjustedMouseY, partialTick); + Configurable newSelected = configurablesList + .filter(configurable -> configurable.getJsonName().equals(selectedConfigurable.getJsonName())) + .findFirst() + .orElse(null); + + setSelectedConfigurable(newSelected); } - RenderUtils.createRectMask(guiGraphics.pose(), 37, 16, 140, 163); + scrollConfigurables(configurablesScrollOffset); + } - for (WynntilsButton configurable : configurables) { - configurable.render(guiGraphics, adjustedMouseX, adjustedMouseY, partialTick); + public void populateConfigs() { + configs.clear(); + + if (selectedConfigurable == null) return; + + List> configsOptions = selectedConfigurable.getVisibleConfigOptions().stream() + .sorted(Comparator.comparing(config -> !Objects.equals(config.getFieldName(), "userEnabled"))) + .toList(); + + int renderY = 21; + + for (Config config : configsOptions) { + ConfigTile configTile = + new ConfigTile(Texture.CONFIG_BOOK_BACKGROUND.width() / 2 + 10, renderY, 160, 45, this, config); + configTile.visible = renderY >= (21 - 46) && renderY <= (21 + CONFIGS_PER_PAGE * 45); + + configs.add(configTile); + + renderY += 46; } - RenderUtils.clearMask(); + scrollConfigs(configScrollOffset); + } - RenderUtils.createRectMask( - guiGraphics.pose(), Texture.CONFIG_BOOK_BACKGROUND.width() / 2f + 10, 21, 160, CONFIGS_PER_PAGE * 46); + public boolean configOptionContains(Config config) { + return !searchWidget.getTextBoxInput().isEmpty() + && StringUtils.containsIgnoreCase(config.getDisplayName(), searchWidget.getTextBoxInput()); + } - for (WynntilsButton config : configs) { - config.render(guiGraphics, adjustedMouseX, adjustedMouseY, partialTick); + public void setSelectedConfigurable(Configurable selectedConfigurable) { + boolean skipScroll = true; + + // Only reset offset when a new configurable is selected + if (this.selectedConfigurable != selectedConfigurable) { + configScrollOffset = 0; + skipScroll = false; } - RenderUtils.clearMask(); + this.selectedConfigurable = selectedConfigurable; + populateConfigs(); + + // If we are already on this configurable, then we don't want to scroll to any matching configs + if (skipScroll) return; + + // If there is a search query, scroll the configs list so that the matching config + // is found unless the configurable itself matches the search query + if (!searchWidget.getTextBoxInput().isEmpty()) { + if (!searchMatches(selectedConfigurable)) { + scrollToMatchingConfig(); + } + } } - private static void renderScrollArea(PoseStack poseStack) { - RenderSystem.enableBlend(); - RenderUtils.drawTexturedRect( - poseStack, - Texture.CONFIG_BOOK_SCROLL_AREA, - (Texture.CONFIG_BOOK_BACKGROUND.width() / 2f - Texture.CONFIG_BOOK_SCROLL_AREA.width()) / 2f, - 10); - RenderSystem.disableBlend(); + public Configurable getSelectedConfigurable() { + return selectedConfigurable; } - private void renderConfigurableScroll(PoseStack poseStack) { - configurableScrollRenderY = SCROLL_START_Y - + MathUtils.map( - configurableScrollOffset, - 0, - getMaxConfigurableScrollOffset(), - 0, - 161 - Texture.CONFIG_BOOK_SCROLL_BUTTON.height() / 2f); + public float getTranslationX() { + return translationX; + } - RenderUtils.drawHoverableTexturedRect( - poseStack, - Texture.CONFIG_BOOK_SCROLL_BUTTON, - CONFIGURABLE_SCROLL_X, - configurableScrollRenderY, - draggingConfigurableScroll); + public float getTranslationY() { + return translationY; } - private void renderConfigScroll(PoseStack poseStack) { - if (configs.size() <= CONFIGS_PER_PAGE) return; + public int getMaskTopY() { + return MASK_TOP_Y; + } - RenderUtils.drawRect( - poseStack, - CommonColors.GRAY, - CONFIG_SCROLL_X, - SCROLL_START_Y, - 0, - Texture.CONFIG_BOOK_SCROLL_BUTTON.width(), - CONFIGS_PER_PAGE * 46); + public int getConfigMaskBottomY() { + return CONFIG_MASK_BOTTOM_Y; + } - configScrollRenderY = SCROLL_START_Y - + MathUtils.map( - configScrollOffset, - 0, - getMaxConfigScrollOffset(), - 0, - CONFIGS_PER_PAGE * 46 - Texture.CONFIG_BOOK_SCROLL_BUTTON.height() / 2f); + public int getConfigurableMaskBottomY() { + return CONFIGURABLE_MASK_BOTTOM_Y; + } - RenderUtils.drawHoverableTexturedRect( - poseStack, - Texture.CONFIG_BOOK_SCROLL_BUTTON, - CONFIG_SCROLL_X, - configScrollRenderY, - draggingConfigScroll); + private void populateCategories() { + for (AbstractWidget widget : categoryButtons) { + this.removeWidget(widget); + } + + int xPos = (int) (Texture.TAG_RED.width() * 2.85 + 1); + + categoryButtons = new ArrayList<>(); + + for (int i = 0; i < MAX_DISPLAYED_CATEGORIES; i++) { + xPos += Texture.TAG_RED.width() + Texture.TAG_RED.width() * 0.25; + + int categoryIndex; + + if (i + categoriesScrollOffset < 0) { + categoryIndex = (i + categoriesScrollOffset) + sortedCategories.size(); + } else if (i + categoriesScrollOffset > sortedCategories.size() - 1) { + categoryIndex = (i + categoriesScrollOffset) - sortedCategories.size(); + } else { + categoryIndex = (i + categoriesScrollOffset); + } + + Category category = sortedCategories.get(categoryIndex); + + categoryButtons.add(this.addRenderableWidget(new SettingsCategoryTabButton( + xPos, + (int) -(Texture.TAG_RED.height() * 0.75f), + Texture.TAG_RED.width(), + Texture.TAG_RED.height(), + (b) -> changeCategory(category), + List.of(Component.literal(I18n.get(category.toString()))), + category, + selectedCategory == category))); + } } - private static void renderBg(PoseStack poseStack) { - RenderUtils.drawTexturedRect(poseStack, Texture.CONFIG_BOOK_BACKGROUND, 0, 0); + private void getFilteredConfigurables() { + configurableList = new ArrayList<>(); + + List filteredConfigurables; + + // Add all configurables for selected category + if (selectedCategory != null) { + filteredConfigurables = Managers.Feature.getFeatures().stream() + .filter(feature -> searchMatches(feature) + || feature.getVisibleConfigOptions().stream().anyMatch(this::configOptionContains)) + .map(feature -> (Configurable) feature) + .sorted() + .filter(configurable -> isCategoryMatching(configurable, selectedCategory)) + .collect(Collectors.toList()); + + filteredConfigurables.addAll(Managers.Overlay.getOverlays().stream() + .filter(overlay -> !filteredConfigurables.contains(Managers.Overlay.getOverlayParent(overlay))) + .filter(overlay -> searchMatches(overlay) + || overlay.getVisibleConfigOptions().stream().anyMatch(this::configOptionContains)) + .sorted() + .filter(configurable -> isCategoryMatching(configurable, selectedCategory)) + .toList()); + + // If there is a search query, add all matches from every other category + if (!searchWidget.getTextBoxInput().isEmpty()) { + filteredConfigurables.addAll(Managers.Feature.getFeatures().stream() + .filter(feature -> searchMatches(feature) + || feature.getVisibleConfigOptions().stream().anyMatch(this::configOptionContains)) + .map(feature -> (Configurable) feature) + .sorted() + .filter(configurable -> !isCategoryMatching(configurable, selectedCategory)) + .toList()); + + filteredConfigurables.addAll(Managers.Overlay.getOverlays().stream() + .filter(overlay -> !filteredConfigurables.contains(Managers.Overlay.getOverlayParent(overlay))) + .filter(overlay -> searchMatches(overlay) + || overlay.getVisibleConfigOptions().stream().anyMatch(this::configOptionContains)) + .sorted() + .filter(configurable -> !isCategoryMatching(configurable, selectedCategory)) + .toList()); + } + } else { // All tab, add all configurables + filteredConfigurables = Managers.Feature.getFeatures().stream() + .filter(feature -> searchMatches(feature) + || feature.getVisibleConfigOptions().stream().anyMatch(this::configOptionContains)) + .map(feature -> (Configurable) feature) + .sorted() + .collect(Collectors.toList()); + + filteredConfigurables.addAll(Managers.Overlay.getOverlays().stream() + .filter(overlay -> !filteredConfigurables.contains(Managers.Overlay.getOverlayParent(overlay))) + .filter(overlay -> searchMatches(overlay) + || overlay.getVisibleConfigOptions().stream().anyMatch(this::configOptionContains)) + .sorted() + .toList()); + } + + configurableList.addAll(filteredConfigurables); } private Stream getWidgetsForIteration() { @@ -439,10 +769,10 @@ private Stream getWidgetsForIteration() { } private void scrollConfigurables(int newOffset) { - configurableScrollOffset = newOffset; + configurablesScrollOffset = newOffset; for (WynntilsButton configurable : configurables) { - int newY = 21 + (configurables.indexOf(configurable) * 12) - configurableScrollOffset; + int newY = 21 + (configurables.indexOf(configurable) * 12) - configurablesScrollOffset; configurable.setY(newY); configurable.visible = newY >= (21 - 12) && newY <= (21 + (CONFIGURABLES_PER_PAGE + 1) * 11); @@ -468,81 +798,77 @@ private int getMaxConfigScrollOffset() { return (configs.size() - CONFIGS_PER_PAGE) * 46; } - private void reloadConfigurableButtons() { - configurables.clear(); - configurableScrollOffset = 0; - - Category oldCategory = null; - - configurableList = Managers.Feature.getFeatures().stream() - .filter(feature -> searchMatches(feature) - || feature.getVisibleConfigOptions().stream().anyMatch(this::configOptionContains)) - .map(feature -> (Configurable) feature) - .sorted() - .collect(Collectors.toList()); - - configurableList.addAll(Managers.Overlay.getOverlays().stream() - .filter(overlay -> !configurableList.contains(Managers.Overlay.getOverlayParent(overlay))) - .filter(overlay -> searchMatches(overlay) - || overlay.getVisibleConfigOptions().stream().anyMatch(this::configOptionContains)) - .sorted() - .toList()); + private void scrollCategorories(int direction) { + if (Math.abs(categoriesScrollOffset + direction) == sortedCategories.size()) { + categoriesScrollOffset = 0; + } else { + categoriesScrollOffset = MathUtils.clamp( + categoriesScrollOffset + direction, -(sortedCategories.size() - 1), (sortedCategories.size() - 1)); + } - int renderY = 21; + populateCategories(); + } - for (int i = 0; i < configurableList.size(); i++) { - Configurable configurable = configurableList.get(i); + private boolean isCategoryMatching(Configurable configurable, Category selectedCategory) { + return getCategory(configurable) == selectedCategory; + } - Category category; + private void scrollToMatchingConfig() { + List> configsOptions = selectedConfigurable.getVisibleConfigOptions().stream() + .sorted(Comparator.comparing(config -> !Objects.equals(config.getFieldName(), "userEnabled"))) + .toList(); - if (configurable instanceof Feature feature) { - category = feature.getCategory(); - } else if (configurable instanceof Overlay overlay) { - category = Managers.Overlay.getOverlayParent(overlay).getCategory(); - } else { - throw new IllegalStateException("Unknown configurable type: " + configurable.getClass()); + // Find a config that matches current search query and get scroll offset to make that config visible + for (Config config : configsOptions) { + if (StringUtils.containsIgnoreCase(config.getDisplayName(), searchWidget.getTextBoxInput())) { + int newOffset = Math.max( + 0, + Math.min( + (configsOptions.indexOf(config) - (CONFIGS_PER_PAGE - 1)) * 46, + getMaxConfigScrollOffset())); + scrollConfigs(newOffset); + return; } + } + } - if (category != oldCategory) { - CategoryButton categoryButton = new CategoryButton(37, renderY, 140, 10, category); - categoryButton.visible = renderY >= (21 - 12) && renderY <= (21 + (CONFIGURABLES_PER_PAGE + 1) * 11); - configurables.add(categoryButton); - oldCategory = category; - renderY += 12; - } + private void changeCategory(Category category) { + configurablesScrollOffset = 0; - ConfigurableButton configurableButton = new ConfigurableButton(37, renderY, 140, 10, configurable, this); - configurableButton.visible = renderY >= (21 - 12) && renderY <= (21 + (CONFIGURABLES_PER_PAGE + 1) * 11); - configurables.add(configurableButton); - - renderY += 12; + // Deselect old category, reset texture to default + if (selectedCategoryButton != null) { + selectedCategoryButton.setSelectedCategory(false); + } - if (configurable instanceof Feature feature) { - for (Overlay overlay : Managers.Overlay.getFeatureOverlays(feature).stream() - .sorted() - .toList()) { - ConfigurableButton overlayButton = new ConfigurableButton(37, renderY, 140, 10, overlay, this); - overlayButton.visible = renderY >= (21 - 12) && renderY <= (21 + (CONFIGURABLES_PER_PAGE + 1) * 11); - configurables.add(overlayButton); - renderY += 12; + selectedCategory = category; + + // If null, the all categories button was selected. + // Otherwise find which was selected and update that + if (category == null) { + selectedCategoryButton = allCategoriesButton; + allCategoriesButton.setSelectedCategory(true); + } else { + for (SettingsCategoryTabButton settingsTabButton : categoryButtons) { + if (settingsTabButton.getCategory() == selectedCategory) { + selectedCategoryButton = settingsTabButton; + settingsTabButton.setSelectedCategory(true); + break; } } } - if (selected != null) { - Stream configurablesList = Stream.concat( - Managers.Feature.getFeatures().stream(), - Managers.Feature.getFeatures().stream() - .map(Managers.Overlay::getFeatureOverlays) - .flatMap(Collection::stream) - .map(overlay -> (Configurable) overlay)); - - Configurable newSelected = configurablesList - .filter(configurable -> configurable.getJsonName().equals(selected.getJsonName())) - .findFirst() - .orElse(null); + getFilteredConfigurables(); + populateConfigurables(); + populateCategories(); + } - setSelected(newSelected); + private Category getCategory(Configurable configurable) { + if (configurable instanceof Feature feature) { + return feature.getCategory(); + } else if (configurable instanceof Overlay overlay) { + return Managers.Overlay.getOverlayParent(overlay).getCategory(); + } else { + throw new IllegalStateException("Unknown configurable type: " + configurable.getClass()); } } @@ -558,28 +884,180 @@ private boolean searchMatches(Translatable translatable) { return StringUtils.partialMatch(translatable.getTranslatedName(), searchWidget.getTextBoxInput()); } - private void reloadConfigButtons() { - configs.clear(); - configScrollOffset = 0; + private void renderBg(PoseStack poseStack) { + RenderUtils.drawTexturedRect(poseStack, Texture.CONFIG_BOOK_BACKGROUND, 0, 0); + } - if (selected == null) return; + private void renderTags(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) { + for (Renderable renderable : renderables) { + renderable.render(guiGraphics, mouseX, mouseY, partialTick); + } + } - List> configsOptions = selected.getVisibleConfigOptions().stream() - .sorted(Comparator.comparing(config -> !Objects.equals(config.getFieldName(), "userEnabled"))) - .toList(); + private void renderConfigurables(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) { + RenderUtils.createRectMask(guiGraphics.pose(), 12, 21, 170, CONFIGURABLES_PER_PAGE * 12 - 3); - int renderY = 21; + for (WynntilsButton configurable : configurables) { + configurable.render(guiGraphics, mouseX, mouseY, partialTick); + } - for (int i = 0; i < configsOptions.size(); i++) { - Config config = configsOptions.get(i); + RenderUtils.clearMask(); + } - ConfigTile configTile = - new ConfigTile(Texture.CONFIG_BOOK_BACKGROUND.width() / 2 + 10, renderY, 160, 45, this, config); - configTile.visible = renderY >= (21 - 46) && renderY <= (21 + CONFIGS_PER_PAGE * 45); + private void renderConfigurableScroll(PoseStack poseStack) { + RenderUtils.drawRect( + poseStack, + CommonColors.GRAY, + CONFIGURABLE_SCROLL_X, + 21, + 0, + Texture.CONFIG_BOOK_SCROLL_BUTTON.width(), + SCROLL_AREA_HEIGHT); - configs.add(configTile); + configurableScrollRenderY = SCROLL_START_Y + + MathUtils.map( + configurablesScrollOffset, + 0, + getMaxConfigurableScrollOffset(), + 0, + SCROLL_AREA_HEIGHT - Texture.CONFIG_BOOK_SCROLL_BUTTON.height() / 2f); - renderY += 46; + RenderUtils.drawHoverableTexturedRect( + poseStack, + Texture.CONFIG_BOOK_SCROLL_BUTTON, + CONFIGURABLE_SCROLL_X, + configurableScrollRenderY, + draggingConfigurableScroll); + } + + private void renderConfigs(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) { + RenderUtils.createRectMask( + guiGraphics.pose(), Texture.CONFIG_BOOK_BACKGROUND.width() / 2f + 10, 21, 160, CONFIGS_PER_PAGE * 46); + + for (WynntilsButton config : configs) { + config.render(guiGraphics, mouseX, mouseY, partialTick); + } + + RenderUtils.clearMask(); + } + + private void renderConfigScroll(PoseStack poseStack) { + if (configs.size() <= CONFIGS_PER_PAGE) return; + + RenderUtils.drawRect( + poseStack, + CommonColors.GRAY, + CONFIG_SCROLL_X, + SCROLL_START_Y, + 0, + Texture.CONFIG_BOOK_SCROLL_BUTTON.width(), + SCROLL_AREA_HEIGHT); + + configScrollRenderY = SCROLL_START_Y + + MathUtils.map( + configScrollOffset, + 0, + getMaxConfigScrollOffset(), + 0, + SCROLL_AREA_HEIGHT - Texture.CONFIG_BOOK_SCROLL_BUTTON.height() / 2f); + + RenderUtils.drawHoverableTexturedRect( + poseStack, + Texture.CONFIG_BOOK_SCROLL_BUTTON, + CONFIG_SCROLL_X, + configScrollRenderY, + draggingConfigScroll); + } + + private void renderTooltips(GuiGraphics guiGraphics, int mouseX, int mouseY) { + // The tags have a slight bit rendered underneath the book, we don't want to render the tooltip + // when hovering that bit. + if (mouseX >= 0 && mouseY >= 0) return; + + for (GuiEventListener child : children()) { + if (child instanceof TooltipProvider tooltipProvider && child.isMouseOver(mouseX, mouseY)) { + guiGraphics.renderComponentTooltip( + FontRenderer.getInstance().getFont(), tooltipProvider.getTooltipLines(), mouseX, mouseY); + break; + } + } + } + + private void importSettings(int clicked) { + String clipboard = McUtils.mc().keyboardHandler.getClipboard(); + + if (clicked == GLFW.GLFW_MOUSE_BUTTON_LEFT) { + List configsToImport = Managers.Feature.getFeatures().stream() + .map(feature -> (Configurable) feature) + .collect(Collectors.toList()); + + configsToImport.addAll(Managers.Overlay.getOverlays().stream() + .filter(overlay -> !configsToImport.contains(Managers.Overlay.getOverlayParent(overlay))) + .toList()); + + boolean imported = Managers.Config.importConfig(clipboard, configsToImport); + + if (imported) { + McUtils.sendMessageToClient(Component.translatable("screens.wynntils.settingsScreen.importedAll") + .withStyle(ChatFormatting.GREEN)); + } else { + McUtils.sendMessageToClient(Component.translatable("screens.wynntils.settingsScreen.import.failed") + .withStyle(ChatFormatting.RED)); + } + } else if (clicked == GLFW.GLFW_MOUSE_BUTTON_RIGHT) { + if (selectedConfigurable != null) { + boolean imported = Managers.Config.importConfig(clipboard, List.of(selectedConfigurable)); + + if (imported) { + McUtils.sendMessageToClient(Component.translatable( + "screens.wynntils.settingsScreen.imported", + selectedConfigurable.getTranslatedName()) + .withStyle(ChatFormatting.GREEN)); + } else { + McUtils.sendMessageToClient(Component.translatable("screens.wynntils.settingsScreen.import.failed") + .withStyle(ChatFormatting.RED)); + } + } else { + McUtils.sendMessageToClient(Component.translatable("screens.wynntils.settingsScreen.needToSelect") + .withStyle(ChatFormatting.RED)); + } + } + + // Repopulate the configurables after importing + populateConfigurables(); + } + + private void exportSettings(int clicked) { + String exportedSettings = ""; + + if (clicked == GLFW.GLFW_MOUSE_BUTTON_LEFT) { + // Get all features and overlays into a list + List featuresToExport = Managers.Feature.getFeatures().stream() + .map(feature -> (Configurable) feature) + .collect(Collectors.toList()); + + featuresToExport.addAll(Managers.Overlay.getOverlays().stream() + .filter(overlay -> !featuresToExport.contains(Managers.Overlay.getOverlayParent(overlay))) + .toList()); + + exportedSettings = Managers.Config.exportConfig(featuresToExport); + + McUtils.sendMessageToClient(Component.translatable("screens.wynntils.settingsScreen.exportedAll") + .withStyle(ChatFormatting.GREEN)); + } else if (clicked == GLFW.GLFW_MOUSE_BUTTON_RIGHT) { + if (selectedConfigurable != null) { + exportedSettings = Managers.Config.exportConfig(List.of(selectedConfigurable)); + + McUtils.sendMessageToClient(Component.translatable( + "screens.wynntils.settingsScreen.exported", selectedConfigurable.getTranslatedName()) + .withStyle(ChatFormatting.GREEN)); + } else { + McUtils.sendMessageToClient(Component.translatable("screens.wynntils.settingsScreen.needToSelect") + .withStyle(ChatFormatting.RED)); + } } + + // Save to clipboard + McUtils.mc().keyboardHandler.setClipboard(exportedSettings); } } diff --git a/common/src/main/java/com/wynntils/screens/settings/widgets/ApplyButton.java b/common/src/main/java/com/wynntils/screens/settings/widgets/ApplyButton.java deleted file mode 100644 index 2d049d5b53..0000000000 --- a/common/src/main/java/com/wynntils/screens/settings/widgets/ApplyButton.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright © Wynntils 2023-2024. - * This file is released under LGPLv3. See LICENSE for full license details. - */ -package com.wynntils.screens.settings.widgets; - -import com.wynntils.core.components.Managers; -import com.wynntils.screens.settings.WynntilsBookSettingsScreen; -import com.wynntils.utils.mc.McUtils; -import com.wynntils.utils.render.Texture; -import java.util.List; -import net.minecraft.ChatFormatting; -import net.minecraft.network.chat.Component; - -public class ApplyButton extends GeneralSettingsButton { - private final WynntilsBookSettingsScreen screen; - - public ApplyButton(WynntilsBookSettingsScreen screen) { - super( - 55, - Texture.CONFIG_BOOK_BACKGROUND.height() - 30, - 35, - 14, - Component.translatable("screens.wynntils.settingsScreen.apply"), - List.of(Component.translatable("screens.wynntils.settingsScreen.apply.description") - .withStyle(ChatFormatting.GREEN)), - 0, - McUtils.mc().screen.height); - this.screen = screen; - } - - @Override - public void onPress() { - Managers.Config.saveConfig(); - screen.onClose(); - } -} diff --git a/common/src/main/java/com/wynntils/screens/settings/widgets/BooleanSettingsButton.java b/common/src/main/java/com/wynntils/screens/settings/widgets/BooleanSettingsButton.java index 6d75c21630..de67e0a103 100644 --- a/common/src/main/java/com/wynntils/screens/settings/widgets/BooleanSettingsButton.java +++ b/common/src/main/java/com/wynntils/screens/settings/widgets/BooleanSettingsButton.java @@ -5,10 +5,11 @@ package com.wynntils.screens.settings.widgets; import com.wynntils.core.persisted.config.Config; +import com.wynntils.screens.settings.WynntilsBookSettingsScreen; import com.wynntils.utils.colors.CommonColors; import com.wynntils.utils.colors.CustomColor; import com.wynntils.utils.mc.ComponentUtils; -import com.wynntils.utils.render.FontRenderer; +import com.wynntils.utils.mc.McUtils; import java.util.List; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.MutableComponent; @@ -16,16 +17,25 @@ public class BooleanSettingsButton extends GeneralSettingsButton { private final Config config; - public BooleanSettingsButton(int x, int y, Config config, int maskTopY, int maskBottomY) { + public BooleanSettingsButton( + int x, + int y, + Config config, + int maskTopY, + int maskBottomY, + float translationX, + float translationY) { super( x, y, - 50, - FontRenderer.getInstance().getFont().lineHeight + 8, + 90, + 20, getTitle(config), ComponentUtils.wrapTooltips(List.of(Component.literal(config.getDescription())), 150), maskTopY, - maskBottomY); + maskBottomY, + translationX, + translationY); this.config = config; } @@ -33,6 +43,12 @@ public BooleanSettingsButton(int x, int y, Config config, int maskTopY, public void onPress() { config.setValue(!isEnabled(config)); setMessage(getTitle(config)); + + // Reload the configurables in case the enabled button was toggled so the checkboxes + // can change state + if (McUtils.mc().screen instanceof WynntilsBookSettingsScreen bookSettingsScreen) { + bookSettingsScreen.populateConfigurables(); + } } private static MutableComponent getTitle(Config config) { diff --git a/common/src/main/java/com/wynntils/screens/settings/widgets/CategoryButton.java b/common/src/main/java/com/wynntils/screens/settings/widgets/CategoryButton.java index 047f6b0fd1..d39a33d81d 100644 --- a/common/src/main/java/com/wynntils/screens/settings/widgets/CategoryButton.java +++ b/common/src/main/java/com/wynntils/screens/settings/widgets/CategoryButton.java @@ -1,5 +1,5 @@ /* - * Copyright © Wynntils 2022-2023. + * Copyright © Wynntils 2022-2024. * This file is released under LGPLv3. See LICENSE for full license details. */ package com.wynntils.screens.settings.widgets; @@ -36,7 +36,7 @@ public void renderWidget(GuiGraphics guiGraphics, int mouseX, int mouseY, float this.getY(), 0, CommonColors.CYAN, - HorizontalAlignment.CENTER, + HorizontalAlignment.LEFT, TextShadow.NORMAL); } diff --git a/common/src/main/java/com/wynntils/screens/settings/widgets/CloseButton.java b/common/src/main/java/com/wynntils/screens/settings/widgets/CloseButton.java deleted file mode 100644 index 3fa01f4135..0000000000 --- a/common/src/main/java/com/wynntils/screens/settings/widgets/CloseButton.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright © Wynntils 2023-2024. - * This file is released under LGPLv3. See LICENSE for full license details. - */ -package com.wynntils.screens.settings.widgets; - -import com.wynntils.screens.settings.WynntilsBookSettingsScreen; -import com.wynntils.utils.mc.McUtils; -import com.wynntils.utils.render.Texture; -import java.util.List; -import net.minecraft.ChatFormatting; -import net.minecraft.network.chat.Component; - -public class CloseButton extends GeneralSettingsButton { - private final WynntilsBookSettingsScreen screen; - - public CloseButton(WynntilsBookSettingsScreen screen) { - super( - 15, - Texture.CONFIG_BOOK_BACKGROUND.height() - 30, - 35, - 14, - Component.translatable("screens.wynntils.settingsScreen.close"), - List.of(Component.translatable("screens.wynntils.settingsScreen.close.description") - .withStyle(ChatFormatting.DARK_RED)), - 0, - McUtils.mc().screen.height); - this.screen = screen; - } - - @Override - public void onPress() { - screen.onClose(); - } -} diff --git a/common/src/main/java/com/wynntils/screens/settings/widgets/ConfigTile.java b/common/src/main/java/com/wynntils/screens/settings/widgets/ConfigTile.java index c212011df4..3b2979dd10 100644 --- a/common/src/main/java/com/wynntils/screens/settings/widgets/ConfigTile.java +++ b/common/src/main/java/com/wynntils/screens/settings/widgets/ConfigTile.java @@ -26,7 +26,6 @@ public class ConfigTile extends WynntilsButton { private final TextboxScreen screen; - private final Config config; private final int maskTopY; private final int maskBottomY; private final float translationX; @@ -40,7 +39,7 @@ public ConfigTile(int x, int y, int width, int height, TextboxScreen screen, Con this.screen = screen; if (screen instanceof WynntilsBookSettingsScreen settingsScreen) { - maskTopY = settingsScreen.getConfigMaskTopY(); + maskTopY = settingsScreen.getMaskTopY(); maskBottomY = settingsScreen.getConfigMaskBottomY(); displayName = settingsScreen.configOptionContains(config) ? StyledText.fromString(ChatFormatting.UNDERLINE + config.getDisplayName()) @@ -63,7 +62,6 @@ public ConfigTile(int x, int y, int width, int height, TextboxScreen screen, Con translationY = 0; } - this.config = config; this.configOptionElement = getWidgetFromConfig(config); this.resetButton = new ResetButton( config, @@ -160,10 +158,22 @@ private int getRenderX() { private > AbstractWidget getWidgetFromConfig(Config configOption) { if (configOption.getType().equals(Boolean.class)) { return new BooleanSettingsButton( - getRenderX(), getRenderY(), (Config) configOption, maskTopY, maskBottomY); + getRenderX(), + getRenderY(), + (Config) configOption, + maskTopY, + maskBottomY, + translationX, + translationY); } else if (configOption.isEnum()) { return new EnumSettingsButton<>( - getRenderX(), getRenderY(), (Config) configOption, maskTopY, maskBottomY); + getRenderX(), + getRenderY(), + (Config) configOption, + maskTopY, + maskBottomY, + translationX, + translationY); } else if (configOption.getType().equals(CustomColor.class)) { return new CustomColorSettingsButton( getRenderX(), getRenderY(), (Config) configOption, screen, maskTopY, maskBottomY); diff --git a/common/src/main/java/com/wynntils/screens/settings/widgets/ConfigurableButton.java b/common/src/main/java/com/wynntils/screens/settings/widgets/ConfigurableButton.java index 94626dd632..d0806c2bda 100644 --- a/common/src/main/java/com/wynntils/screens/settings/widgets/ConfigurableButton.java +++ b/common/src/main/java/com/wynntils/screens/settings/widgets/ConfigurableButton.java @@ -6,12 +6,15 @@ import com.google.common.collect.Lists; import com.mojang.blaze3d.vertex.PoseStack; +import com.wynntils.core.components.Managers; import com.wynntils.core.consumers.features.Configurable; import com.wynntils.core.consumers.features.Feature; import com.wynntils.core.consumers.overlays.CustomNameProperty; import com.wynntils.core.consumers.overlays.Overlay; +import com.wynntils.core.persisted.config.Config; import com.wynntils.core.text.StyledText; import com.wynntils.screens.base.widgets.WynntilsButton; +import com.wynntils.screens.base.widgets.WynntilsCheckbox; import com.wynntils.screens.settings.WynntilsBookSettingsScreen; import com.wynntils.utils.colors.CommonColors; import com.wynntils.utils.colors.CustomColor; @@ -22,17 +25,28 @@ import com.wynntils.utils.render.type.TextShadow; import com.wynntils.utils.render.type.VerticalAlignment; import java.util.List; +import java.util.Optional; +import net.minecraft.ChatFormatting; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.network.chat.Component; public class ConfigurableButton extends WynntilsButton { private final Configurable configurable; - private final WynntilsBookSettingsScreen settingsScreen; - + private final WynntilsCheckbox enabledCheckbox; + private final int maskTopY; + private final int maskBottomY; + private final int matchingConfigs; private final List descriptionTooltip; + private final WynntilsBookSettingsScreen settingsScreen; public ConfigurableButton( - int x, int y, int width, int height, Configurable configurable, WynntilsBookSettingsScreen screen) { + int x, + int y, + int width, + int height, + Configurable configurable, + WynntilsBookSettingsScreen screen, + int matchingConfigs) { super(x, y, width, height, Component.literal(configurable.getTranslatedName())); this.configurable = configurable; this.settingsScreen = screen; @@ -43,16 +57,34 @@ public ConfigurableButton( } else { descriptionTooltip = List.of(); } + + boolean enabled = false; + if (configurable instanceof Overlay selectedOverlay) { + enabled = Managers.Overlay.isEnabled(selectedOverlay); + } else if (configurable instanceof Feature selectedFeature) { + enabled = selectedFeature.isEnabled(); + } + + this.enabledCheckbox = new WynntilsCheckbox(x + width - 10, y, 10, 10, Component.literal(""), enabled, 0); + + this.maskTopY = settingsScreen.getMaskTopY(); + this.maskBottomY = settingsScreen.getConfigurableMaskBottomY(); + this.matchingConfigs = matchingConfigs; } @Override public void renderWidget(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) { PoseStack poseStack = guiGraphics.pose(); + // Don't want to display tooltip when the tile is outside the mask from the screen + if (isHovered && (mouseY <= maskTopY || mouseY >= maskBottomY)) { + isHovered = false; + } + CustomColor color = isHovered ? CommonColors.YELLOW : CommonColors.WHITE; if (McUtils.mc().screen instanceof WynntilsBookSettingsScreen bookSettingsScreen) { - if (bookSettingsScreen.getSelected() == configurable) { + if (bookSettingsScreen.getSelectedConfigurable() == configurable) { color = CommonColors.GRAY; } } @@ -67,13 +99,17 @@ public void renderWidget(GuiGraphics guiGraphics, int mouseX, int mouseY, float } } + if (matchingConfigs > 0) { + textToRender += ChatFormatting.GRAY + " [" + matchingConfigs + "]"; + } + FontRenderer.getInstance() .renderScrollingText( poseStack, StyledText.fromString(textToRender), (isOverlay ? this.getX() + 12 : this.getX()), this.getY(), - (isOverlay ? this.width - 12 : this.width), + (isOverlay ? this.width - 12 : this.width) - 11, settingsScreen.getTranslationX(), settingsScreen.getTranslationY(), color, @@ -82,6 +118,8 @@ public void renderWidget(GuiGraphics guiGraphics, int mouseX, int mouseY, float TextShadow.NORMAL, 1f); + enabledCheckbox.render(guiGraphics, mouseX, mouseY, partialTick); + if (isHovered && configurable instanceof Feature) { McUtils.mc() .screen @@ -89,10 +127,48 @@ public void renderWidget(GuiGraphics guiGraphics, int mouseX, int mouseY, float } } + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + // Prevent interaction when the tile is outside of the mask from the screen + if ((mouseY <= maskTopY || mouseY >= maskBottomY)) return false; + + // Toggle the enabled state of the configurable when toggling the checkbox + if (enabledCheckbox.isMouseOver(mouseX, mouseY)) { + if (configurable instanceof Feature feature) { + feature.setUserEnabled(!feature.isEnabled()); + } else if (configurable instanceof Overlay) { + Optional> configOpt = configurable.getConfigOptionFromString("userEnabled"); + + if (configOpt.isPresent()) { + Config config = (Config) configOpt.get(); + config.setValue(!config.get()); + } else { + return false; + } + } + + // Repopulate screen to update new enabled/disabled states + if (McUtils.mc().screen instanceof WynntilsBookSettingsScreen bookSettingsScreen) { + bookSettingsScreen.populateConfigurables(); + } + + return enabledCheckbox.mouseClicked(mouseX, mouseY, button); + } + + return super.mouseClicked(mouseX, mouseY, button); + } + @Override public void onPress() { if (McUtils.mc().screen instanceof WynntilsBookSettingsScreen bookSettingsScreen) { - bookSettingsScreen.setSelected(configurable); + bookSettingsScreen.setSelectedConfigurable(configurable); } } + + @Override + public void setY(int y) { + super.setY(y); + + enabledCheckbox.setY(y); + } } diff --git a/common/src/main/java/com/wynntils/screens/settings/widgets/CustomColorSettingsButton.java b/common/src/main/java/com/wynntils/screens/settings/widgets/CustomColorSettingsButton.java index 38b780aebf..7e9ff98cfa 100644 --- a/common/src/main/java/com/wynntils/screens/settings/widgets/CustomColorSettingsButton.java +++ b/common/src/main/java/com/wynntils/screens/settings/widgets/CustomColorSettingsButton.java @@ -14,7 +14,7 @@ public class CustomColorSettingsButton extends TextInputBoxSettingsWidget { public CustomColorSettingsButton( int x, int y, Config config, TextboxScreen textboxScreen, int maskTopY, int maskBottomY) { - super(x, y, config, textboxScreen, 80, maskTopY, maskBottomY); + super(x, y, config, textboxScreen, maskTopY, maskBottomY); } @Override @@ -24,6 +24,6 @@ public void renderWidget(GuiGraphics guiGraphics, int mouseX, int mouseY, float PoseStack poseStack = guiGraphics.pose(); CustomColor value = config.get(); - RenderUtils.drawRect(poseStack, value, width + 5, 6, 0, height, height); + RenderUtils.drawRect(poseStack, value, getX() + getWidth() + 4, getY(), 0, height, height); } } diff --git a/common/src/main/java/com/wynntils/screens/settings/widgets/EnumSettingsButton.java b/common/src/main/java/com/wynntils/screens/settings/widgets/EnumSettingsButton.java index 39b3a5f19e..ad8fbec5c9 100644 --- a/common/src/main/java/com/wynntils/screens/settings/widgets/EnumSettingsButton.java +++ b/common/src/main/java/com/wynntils/screens/settings/widgets/EnumSettingsButton.java @@ -5,14 +5,10 @@ package com.wynntils.screens.settings.widgets; import com.wynntils.core.persisted.config.Config; -import com.wynntils.utils.EnumUtils; import com.wynntils.utils.mc.ComponentUtils; import com.wynntils.utils.mc.McUtils; -import com.wynntils.utils.render.FontRenderer; -import java.lang.reflect.Type; import java.util.EnumSet; import java.util.List; -import net.minecraft.client.gui.Font; import net.minecraft.network.chat.Component; import org.lwjgl.glfw.GLFW; @@ -20,16 +16,19 @@ public class EnumSettingsButton> extends GeneralSettingsButton private final Config config; private final List enumConstants; - public EnumSettingsButton(int x, int y, Config config, int maskTopY, int maskBottomY) { + public EnumSettingsButton( + int x, int y, Config config, int maskTopY, int maskBottomY, float translationX, float translationY) { super( x, y, - getWidth(config.getType()), - FontRenderer.getInstance().getFont().lineHeight + 8, + 90, + 20, Component.literal(config.getValueString()), ComponentUtils.wrapTooltips(List.of(Component.literal(config.getDescription())), 150), maskTopY, - maskBottomY); + maskBottomY, + translationX, + translationY); this.config = config; enumConstants = EnumSet.allOf((Class) config.getType()).stream().toList(); } @@ -63,13 +62,4 @@ public boolean mouseClicked(double mouseX, double mouseY, int button) { public void onPress() { // We use instead AbstractWidget#mouseClicked, because we also want to have an action on the right mouse button } - - private static > int getWidth(Type type) { - Font font = FontRenderer.getInstance().getFont(); - int maxWidth = EnumSet.allOf((Class) type).stream() - .mapToInt(enumValue -> font.width(EnumUtils.toNiceString(enumValue))) - .max() - .orElse(0); - return maxWidth + 8; - } } diff --git a/common/src/main/java/com/wynntils/screens/settings/widgets/GeneralSettingsButton.java b/common/src/main/java/com/wynntils/screens/settings/widgets/GeneralSettingsButton.java index 17df60fef6..9c761f89ca 100644 --- a/common/src/main/java/com/wynntils/screens/settings/widgets/GeneralSettingsButton.java +++ b/common/src/main/java/com/wynntils/screens/settings/widgets/GeneralSettingsButton.java @@ -25,6 +25,8 @@ public abstract class GeneralSettingsButton extends WynntilsButton { public static final CustomColor HOVER_BACKGROUND_COLOR = new CustomColor(158, 52, 16); private final int maskTopY; private final int maskBottomY; + private final float translationX; + private final float translationY; private final List tooltip; protected GeneralSettingsButton( @@ -35,11 +37,15 @@ protected GeneralSettingsButton( Component title, List tooltip, int maskTopY, - int maskBottomY) { + int maskBottomY, + float translationX, + float translationY) { super(x, y, width, height, title); this.tooltip = tooltip; this.maskTopY = maskTopY; this.maskBottomY = maskBottomY; + this.translationX = translationX; + this.translationY = translationY; } @Override @@ -59,14 +65,16 @@ public void renderWidget(GuiGraphics guiGraphics, int mouseX, int mouseY, float 3); FontRenderer.getInstance() - .renderAlignedTextInBox( + .renderScrollingAlignedTextInBox( poseStack, StyledText.fromComponent(getMessage()), this.getX(), this.getX() + this.width, this.getY(), this.getY() + this.height, - 0, + this.width - 2, + translationX, + translationY, getTextColor(), HorizontalAlignment.CENTER, VerticalAlignment.MIDDLE, diff --git a/common/src/main/java/com/wynntils/screens/settings/widgets/GeneralSettingsTabButton.java b/common/src/main/java/com/wynntils/screens/settings/widgets/GeneralSettingsTabButton.java new file mode 100644 index 0000000000..6ff36a27ae --- /dev/null +++ b/common/src/main/java/com/wynntils/screens/settings/widgets/GeneralSettingsTabButton.java @@ -0,0 +1,105 @@ +/* + * Copyright © Wynntils 2023-2024. + * This file is released under LGPLv3. See LICENSE for full license details. + */ +package com.wynntils.screens.settings.widgets; + +import com.mojang.blaze3d.vertex.PoseStack; +import com.wynntils.screens.base.widgets.BasicTexturedButton; +import com.wynntils.utils.render.RenderUtils; +import com.wynntils.utils.render.Texture; +import java.util.List; +import java.util.function.Consumer; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.network.chat.Component; + +public abstract class GeneralSettingsTabButton extends BasicTexturedButton { + private static final int MAX_OFFSET = -8; + private static final int MIN_OFFSET = 0; + + protected boolean selectedTab = false; + protected Texture tagTexture; + + private final OffsetDirection offsetDirection; + private final Texture iconTexture; + + private int hoverOffset = 0; + + GeneralSettingsTabButton( + int x, + int y, + int width, + int height, + Consumer onClick, + List tooltip, + Texture tagTexture, + Texture iconTexture, + OffsetDirection offsetDirection) { + super(x, y, width, height, tagTexture, onClick, tooltip); + this.tagTexture = tagTexture; + this.iconTexture = iconTexture; + this.offsetDirection = offsetDirection; + } + + @Override + public void renderWidget(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) { + PoseStack poseStack = guiGraphics.pose(); + + // Don't count as hovered if mouse is hovering the book as the tags render + // slightly underneath the book + if (isHovered + && ((offsetDirection == OffsetDirection.UP && mouseY >= 0) + || (offsetDirection == OffsetDirection.RIGHT + && mouseX <= Texture.CONFIG_BOOK_BACKGROUND.width()) + || (offsetDirection == OffsetDirection.DOWN + && mouseY <= Texture.CONFIG_BOOK_BACKGROUND.height()) + || (offsetDirection == OffsetDirection.LEFT && mouseX >= 0))) { + isHovered = false; + } + + // Determine the offset of the tag. When selected the max offset should be used, otherwise when hovered + // increase until limit reached. + if (selectedTab) { + this.hoverOffset = (offsetDirection == OffsetDirection.UP || offsetDirection == OffsetDirection.LEFT) + ? MAX_OFFSET + : -MAX_OFFSET; + } else if (this.isHovered) { + hoverOffset += (offsetDirection == OffsetDirection.UP || offsetDirection == OffsetDirection.LEFT) ? -1 : 1; + hoverOffset = (offsetDirection == OffsetDirection.UP || offsetDirection == OffsetDirection.LEFT) + ? Math.max(hoverOffset, MAX_OFFSET) + : Math.min(hoverOffset, -MAX_OFFSET); + } else { + hoverOffset += (offsetDirection == OffsetDirection.UP || offsetDirection == OffsetDirection.LEFT) ? 1 : -1; + hoverOffset = (offsetDirection == OffsetDirection.UP || offsetDirection == OffsetDirection.LEFT) + ? Math.min(hoverOffset, MIN_OFFSET) + : Math.max(hoverOffset, MIN_OFFSET); + } + + // Move the tag render position + poseStack.pushPose(); + poseStack.translate( + (offsetDirection == OffsetDirection.RIGHT || offsetDirection == OffsetDirection.LEFT) ? hoverOffset : 0, + (offsetDirection == OffsetDirection.UP || offsetDirection == OffsetDirection.DOWN) ? hoverOffset : 0, + 0); + + RenderUtils.drawTexturedRect(poseStack, tagTexture, this.getX(), this.getY()); + + // Render icon on tag + if (offsetDirection == OffsetDirection.UP || offsetDirection == OffsetDirection.DOWN) { + RenderUtils.drawTexturedRect( + poseStack, iconTexture, getX() + (getWidth() - iconTexture.width()) / 2f, getY() + 14); + } else { + RenderUtils.drawTexturedRect( + poseStack, iconTexture, getX() + 14, getY() + (getHeight() - iconTexture.height()) / 2f); + } + + poseStack.popPose(); + } + + protected enum OffsetDirection { + UP, + RIGHT, + DOWN, + LEFT + } +} diff --git a/common/src/main/java/com/wynntils/screens/settings/widgets/ResetButton.java b/common/src/main/java/com/wynntils/screens/settings/widgets/ResetButton.java index 87931bc2e0..229b552ecc 100644 --- a/common/src/main/java/com/wynntils/screens/settings/widgets/ResetButton.java +++ b/common/src/main/java/com/wynntils/screens/settings/widgets/ResetButton.java @@ -5,8 +5,10 @@ package com.wynntils.screens.settings.widgets; import com.wynntils.core.persisted.config.Config; +import com.wynntils.screens.settings.WynntilsBookSettingsScreen; import com.wynntils.utils.colors.CommonColors; import com.wynntils.utils.colors.CustomColor; +import com.wynntils.utils.mc.McUtils; import com.wynntils.utils.render.FontRenderer; import java.util.List; import net.minecraft.client.sounds.SoundManager; @@ -25,7 +27,9 @@ public class ResetButton extends GeneralSettingsButton { Component.translatable("screens.wynntils.settingsScreen.reset.name"), List.of(Component.translatable("screens.wynntils.settingsScreen.reset.description")), maskTopY, - maskBottomY); + maskBottomY, + 0, + 0); this.config = config; this.onClick = onClick; } @@ -51,5 +55,10 @@ public void onPress() { if (!config.valueChanged()) return; config.reset(); onClick.run(); + + // Reload configurables to update checkbox + if (McUtils.mc().screen instanceof WynntilsBookSettingsScreen bookSettingsScreen) { + bookSettingsScreen.populateConfigurables(); + } } } diff --git a/common/src/main/java/com/wynntils/screens/settings/widgets/SettingsCategoryTabButton.java b/common/src/main/java/com/wynntils/screens/settings/widgets/SettingsCategoryTabButton.java new file mode 100644 index 0000000000..b61c5b950c --- /dev/null +++ b/common/src/main/java/com/wynntils/screens/settings/widgets/SettingsCategoryTabButton.java @@ -0,0 +1,61 @@ +/* + * Copyright © Wynntils 2023-2024. + * This file is released under LGPLv3. See LICENSE for full license details. + */ +package com.wynntils.screens.settings.widgets; + +import com.wynntils.core.persisted.config.Category; +import com.wynntils.utils.render.Texture; +import java.util.List; +import java.util.function.Consumer; +import net.minecraft.network.chat.Component; + +public class SettingsCategoryTabButton extends GeneralSettingsTabButton { + private Category category; + + public SettingsCategoryTabButton( + int x, + int y, + int width, + int height, + Consumer onClick, + List tooltip, + Category category, + boolean selectedCategory) { + super(x, y, width, height, onClick, tooltip, Texture.TAG_RED, category.getCategoryIcon(), OffsetDirection.UP); + this.category = category; + this.selectedTab = selectedCategory; + + setSelectedCategory(selectedCategory); + } + + public SettingsCategoryTabButton( + int x, + int y, + int width, + int height, + Consumer onClick, + List tooltip, + boolean selectedCategory) { + super(x, y, width, height, onClick, tooltip, Texture.TAG_RED, Texture.ALL_CONFIG_ICON, OffsetDirection.UP); + this.category = null; + this.selectedTab = selectedCategory; + + setSelectedCategory(selectedCategory); + } + + public void setSelectedCategory(boolean selectedCategory) { + this.selectedTab = selectedCategory; + + // When selected use a different texture to show the category has been selected + if (this.selectedTab) { + tagTexture = Texture.TAG_RED_SELECTED; + } else { + tagTexture = Texture.TAG_RED; + } + } + + public Category getCategory() { + return category; + } +} diff --git a/common/src/main/java/com/wynntils/screens/settings/widgets/SettingsPageTabButton.java b/common/src/main/java/com/wynntils/screens/settings/widgets/SettingsPageTabButton.java new file mode 100644 index 0000000000..a89962c7e9 --- /dev/null +++ b/common/src/main/java/com/wynntils/screens/settings/widgets/SettingsPageTabButton.java @@ -0,0 +1,26 @@ +/* + * Copyright © Wynntils 2023-2024. + * This file is released under LGPLv3. See LICENSE for full license details. + */ +package com.wynntils.screens.settings.widgets; + +import com.wynntils.utils.render.Texture; +import java.util.List; +import java.util.function.Consumer; +import net.minecraft.network.chat.Component; + +public class SettingsPageTabButton extends GeneralSettingsTabButton { + public SettingsPageTabButton( + int x, int y, int width, int height, Consumer onClick, List tooltip, boolean nextPage) { + super( + x, + y, + width, + height, + onClick, + tooltip, + Texture.TAG_RED, + nextPage ? Texture.NEXT : Texture.PREVIOUS, + OffsetDirection.UP); + } +} diff --git a/common/src/main/java/com/wynntils/screens/settings/widgets/SettingsSearchWidget.java b/common/src/main/java/com/wynntils/screens/settings/widgets/SettingsSearchWidget.java new file mode 100644 index 0000000000..99dcce2343 --- /dev/null +++ b/common/src/main/java/com/wynntils/screens/settings/widgets/SettingsSearchWidget.java @@ -0,0 +1,24 @@ +/* + * Copyright © Wynntils 2024. + * This file is released under LGPLv3. See LICENSE for full license details. + */ +package com.wynntils.screens.settings.widgets; + +import com.mojang.blaze3d.vertex.PoseStack; +import com.wynntils.screens.base.TextboxScreen; +import com.wynntils.screens.base.widgets.SearchWidget; +import com.wynntils.utils.render.RenderUtils; +import com.wynntils.utils.render.Texture; +import java.util.function.Consumer; + +public class SettingsSearchWidget extends SearchWidget { + public SettingsSearchWidget( + int x, int y, int width, int height, Consumer onUpdateConsumer, TextboxScreen textboxScreen) { + super(x, y, width, height, onUpdateConsumer, textboxScreen); + } + + @Override + protected void renderBackground(PoseStack poseStack) { + RenderUtils.drawTexturedRect(poseStack, Texture.TAG_SEARCH, 30, Texture.CONFIG_BOOK_BACKGROUND.height() - 3); + } +} diff --git a/common/src/main/java/com/wynntils/screens/settings/widgets/SettingsSideTabButton.java b/common/src/main/java/com/wynntils/screens/settings/widgets/SettingsSideTabButton.java new file mode 100644 index 0000000000..1339421329 --- /dev/null +++ b/common/src/main/java/com/wynntils/screens/settings/widgets/SettingsSideTabButton.java @@ -0,0 +1,24 @@ +/* + * Copyright © Wynntils 2023-2024. + * This file is released under LGPLv3. See LICENSE for full license details. + */ +package com.wynntils.screens.settings.widgets; + +import com.wynntils.utils.render.Texture; +import java.util.List; +import java.util.function.Consumer; +import net.minecraft.network.chat.Component; + +public class SettingsSideTabButton extends GeneralSettingsTabButton { + public SettingsSideTabButton( + int x, + int y, + int width, + int height, + Consumer onClick, + List tooltip, + Texture tagTexture, + Texture iconTexture) { + super(x, y, width, height, onClick, tooltip, tagTexture, iconTexture, OffsetDirection.LEFT); + } +} diff --git a/common/src/main/java/com/wynntils/screens/settings/widgets/TextInputBoxSettingsWidget.java b/common/src/main/java/com/wynntils/screens/settings/widgets/TextInputBoxSettingsWidget.java index d30a69d288..618322fbf2 100644 --- a/common/src/main/java/com/wynntils/screens/settings/widgets/TextInputBoxSettingsWidget.java +++ b/common/src/main/java/com/wynntils/screens/settings/widgets/TextInputBoxSettingsWidget.java @@ -9,7 +9,6 @@ import com.wynntils.screens.base.widgets.TextInputBoxWidget; import com.wynntils.utils.colors.CommonColors; import com.wynntils.utils.mc.ComponentUtils; -import com.wynntils.utils.render.FontRenderer; import java.util.List; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.network.chat.Component; @@ -20,8 +19,8 @@ public class TextInputBoxSettingsWidget extends TextInputBoxWidget { private final int maskBottomY; protected TextInputBoxSettingsWidget( - int x, int y, Config config, TextboxScreen textboxScreen, int width, int maskTopY, int maskBottomY) { - super(x, y, width, FontRenderer.getInstance().getFont().lineHeight + 8, null, textboxScreen); + int x, int y, Config config, TextboxScreen textboxScreen, int maskTopY, int maskBottomY) { + super(x, y, 90, 20, null, textboxScreen); this.config = config; this.maskTopY = maskTopY; this.maskBottomY = maskBottomY; @@ -29,11 +28,6 @@ protected TextInputBoxSettingsWidget( tooltip = ComponentUtils.wrapTooltips(List.of(Component.literal(config.getDescription())), 150); } - public TextInputBoxSettingsWidget( - int x, int y, Config config, TextboxScreen textboxScreen, int maskTopY, int maskBottomY) { - this(x, y, config, textboxScreen, 100, maskTopY, maskBottomY); - } - @Override public void renderWidget(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) { // Don't want to display tooltip when the tile is outside the mask from the screen diff --git a/common/src/main/java/com/wynntils/utils/render/Texture.java b/common/src/main/java/com/wynntils/utils/render/Texture.java index 928162ea17..1f644e555d 100644 --- a/common/src/main/java/com/wynntils/utils/render/Texture.java +++ b/common/src/main/java/com/wynntils/utils/render/Texture.java @@ -26,6 +26,10 @@ public enum Texture { CONTENT_BOOK_BACKGROUND("content_book/content_book.png", 339, 220), CONTENT_BOOK_SEARCH("content_book/content_book_search.png", 133, 23), CONTENT_BOOK_TITLE("content_book/content_book_title.png", 168, 33), + TAG_BLUE("content_book/tag_blue.png", 44, 22), + TAG_RED("content_book/tag_red.png", 22, 44), + TAG_RED_SELECTED("content_book/tag_red_selected.png", 22, 44), + TAG_SEARCH("content_book/tag_search.png", 140, 30), // endregion // region Icons @@ -38,6 +42,29 @@ public enum Texture { ACTIVITY_STARTED("icons/activities/activity_started_icon.png", 7, 7), CAVE_AVALIABLE_ICON("icons/activities/cave_avaliable_icon.png", 7, 7), + // Config Categories + ALL_CONFIG_ICON("icons/config_categories/all_config_icon.png", 16, 16), + CHAT_CONFIG_ICON("icons/config_categories/chat_config_icon.png", 16, 16), + COMBAT_CONFIG_ICON("icons/config_categories/combat_config_icon.png", 16, 16), + COMMANDS_CONFIG_ICON("icons/config_categories/commands_config_icon.png", 16, 16), + DEBUG_CONFIG_ICON("icons/config_categories/debug_config_icon.png", 16, 16), + EMBELLISHMENTS_CONFIG_ICON("icons/config_categories/embellishments_config_icon.png", 16, 16), + INVENTORY_CONFIG_ICON("icons/config_categories/inventory_config_icon.png", 16, 16), + MAP_CONFIG_ICON("icons/config_categories/map_config_icon.png", 16, 16), + OVERLAYS_CONFIG_ICON("icons/config_categories/overlays_config_icon.png", 16, 16), + PLAYERS_CONFIG_ICON("icons/config_categories/players_config_icon.png", 16, 16), + REDIRECTS_CONFIG_ICON("icons/config_categories/redirects_config_icon.png", 16, 16), + TOOLTIPS_CONFIG_ICON("icons/config_categories/tooltips_config_icon.png", 16, 16), + TRADE_MARKET_CONFIG_ICON("icons/config_categories/trade_market_config_icon.png", 16, 16), + UI_CONFIG_ICON("icons/config_categories/ui_config_icon.png", 16, 16), + UNCATEGORIZED_CONFIG_ICON("icons/config_categories/uncategorized_config_icon.png", 16, 16), + UTILITIES_CONFIG_ICON("icons/config_categories/utilities_config_icon.png", 16, 16), + WYNNTILS_CONFIG_ICON("icons/config_categories/wynntils_config_icon.png", 16, 16), + APPLY_SETTINGS_ICON("icons/config_categories/apply_settings_icon.png", 16, 16), + DISCARD_SETTINGS_ICON("icons/config_categories/discard_settings_icon.png", 16, 16), + EXPORT_SETTINGS_ICON("icons/config_categories/export_settings_icon.png", 16, 16), + IMPORT_SETTINGS_ICON("icons/config_categories/import_settings_icon.png", 16, 16), + // Content Book DIALOGUE_ICON("icons/content_book/dialogue_icon.png", 14, 11), DISCOVERIES_ICON("icons/content_book/discoveries_icon.png", 16, 32), @@ -76,15 +103,19 @@ public enum Texture { CHECKMARK_GRAY("icons/generic/check_gray.png", 16, 16), CHECKMARK_GREEN("icons/generic/check_green.png", 16, 16), CHECKMARK_YELLOW("icons/generic/check_yellow.png", 16, 16), + CLOSE("icons/generic/close.png", 16, 16), DEFENSE_FILTER_ICON("icons/generic/defense_filter_icon.png", 16, 16), EDIT_ICON("icons/generic/edit_icon.png", 6, 16), FAVORITE_ICON("icons/generic/favorite_icon.png", 18, 18), HELP_ICON("icons/generic/help_icon.png", 10, 16), INFO("icons/generic/info.png", 25, 25), ITEM_LOCK("icons/generic/item_lock_icon.png", 16, 16), + NEXT("icons/generic/next.png", 16, 16), OVERLAY_EXTRA_ICON("icons/generic/overlay_extra_icon.png", 16, 16), + PREVIOUS("icons/generic/previous.png", 16, 16), QUESTION_MARK("icons/generic/question_mark.png", 4, 7), QUESTS_SCROLL_ICON("icons/generic/quests_scroll_icon.png", 16, 16), + SAVE("icons/generic/save.png", 16, 16), SHARE_ICON("icons/generic/share_icon.png", 16, 14), SIGN_ICON("icons/generic/sign_icon.png", 17, 18), SMALL_ADD_ICON("icons/generic/small_add_icon.png", 16, 16), diff --git a/common/src/main/resources/assets/wynntils/lang/en_us.json b/common/src/main/resources/assets/wynntils/lang/en_us.json index 2189d17bfa..2ecab95734 100644 --- a/common/src/main/resources/assets/wynntils/lang/en_us.json +++ b/common/src/main/resources/assets/wynntils/lang/en_us.json @@ -2334,15 +2334,31 @@ "screens.wynntils.savedItems.name": "Item Record", "screens.wynntils.savedItems.unableToShare": "Unable to share item", "screens.wynntils.searchWidget.defaultSearchText": "Search...", + "screens.wynntils.settingsScreen.all": "All", "screens.wynntils.settingsScreen.apply": "Apply", "screens.wynntils.settingsScreen.apply.description": "Save changes", "screens.wynntils.settingsScreen.booleanConfig.disabled": "Disabled", "screens.wynntils.settingsScreen.booleanConfig.enabled": "Enabled", - "screens.wynntils.settingsScreen.close": "Close", + "screens.wynntils.settingsScreen.close": "Discard", "screens.wynntils.settingsScreen.close.description": "Close without saving changes", + "screens.wynntils.settingsScreen.export": "Export Settings", + "screens.wynntils.settingsScreen.export.all": "- Left click to export settings from your clipboard for all settings.", + "screens.wynntils.settingsScreen.export.selected": "- Right click to export settings from your clipboard for the currently selected setting.", + "screens.wynntils.settingsScreen.exported": "Sucessfully exported settings for %s", + "screens.wynntils.settingsScreen.exportedAll": "Sucessfully exported all settings", + "screens.wynntils.settingsScreen.import": "Import Settings", + "screens.wynntils.settingsScreen.import.all": "- Left click to import settings from your clipboard for all settings.", + "screens.wynntils.settingsScreen.import.failed": "Failed to import settings", + "screens.wynntils.settingsScreen.import.selected": "- Right click to import settings from your clipboard for the currently selected setting.", + "screens.wynntils.settingsScreen.imported": "Sucessfully imported settings for %s", + "screens.wynntils.settingsScreen.importedAll": "Sucessfully imported all settings", "screens.wynntils.settingsScreen.name": "Wynntils Settings", + "screens.wynntils.settingsScreen.needToSelect": "You need to select a setting to perform this action!", + "screens.wynntils.settingsScreen.next": "Next", + "screens.wynntils.settingsScreen.previous": "Previous", "screens.wynntils.settingsScreen.reset.description": "Reset config to default value.", "screens.wynntils.settingsScreen.reset.name": "Reset", + "screens.wynntils.settingsScreen.unselectedConfig": "Here you can edit all of the various features available in Wynntils. Click any of the features in the left page of the book and the options available for that feature will appear here!", "screens.wynntils.skillPointLoadouts.assigned": "Assigned (%d)", "screens.wynntils.skillPointLoadouts.confirm": "Confirm", "screens.wynntils.skillPointLoadouts.convert": "Convert Types", @@ -2448,7 +2464,7 @@ "screens.wynntils.wynntilsGuides.screenDescription": "Here you can see all the guides Wynntils has. These guides usually show all the items of a specific category.", "screens.wynntils.wynntilsGuides.tomeGuide.name": "Tome Guide", "screens.wynntils.wynntilsMenu.configs.description": "Change the settings the way you want.", - "screens.wynntils.wynntilsMenu.configs.name": "Configuration", + "screens.wynntils.wynntilsMenu.configs.name": "Settings", "screens.wynntils.wynntilsMenu.crowdSourcing.description": "Help us collect data about Wynncraft.", "screens.wynntils.wynntilsMenu.crowdSourcing.name": "Crowd Sourcing", "screens.wynntils.wynntilsMenu.leftClickToSelect": "Left click to select", diff --git a/common/src/main/resources/assets/wynntils/textures/content_book/config_book_scroll_area.png b/common/src/main/resources/assets/wynntils/textures/content_book/config_book_scroll_area.png deleted file mode 100644 index e196c35f56..0000000000 Binary files a/common/src/main/resources/assets/wynntils/textures/content_book/config_book_scroll_area.png and /dev/null differ diff --git a/common/src/main/resources/assets/wynntils/textures/content_book/tag_blue.png b/common/src/main/resources/assets/wynntils/textures/content_book/tag_blue.png new file mode 100644 index 0000000000..540d923afa Binary files /dev/null and b/common/src/main/resources/assets/wynntils/textures/content_book/tag_blue.png differ diff --git a/common/src/main/resources/assets/wynntils/textures/content_book/tag_red.png b/common/src/main/resources/assets/wynntils/textures/content_book/tag_red.png new file mode 100644 index 0000000000..5ab1939710 Binary files /dev/null and b/common/src/main/resources/assets/wynntils/textures/content_book/tag_red.png differ diff --git a/common/src/main/resources/assets/wynntils/textures/content_book/tag_red_selected.png b/common/src/main/resources/assets/wynntils/textures/content_book/tag_red_selected.png new file mode 100644 index 0000000000..6b994758cd Binary files /dev/null and b/common/src/main/resources/assets/wynntils/textures/content_book/tag_red_selected.png differ diff --git a/common/src/main/resources/assets/wynntils/textures/content_book/tag_search.png b/common/src/main/resources/assets/wynntils/textures/content_book/tag_search.png new file mode 100644 index 0000000000..6a310c045e Binary files /dev/null and b/common/src/main/resources/assets/wynntils/textures/content_book/tag_search.png differ diff --git a/common/src/main/resources/assets/wynntils/textures/icons/config_categories/all_config_icon.png b/common/src/main/resources/assets/wynntils/textures/icons/config_categories/all_config_icon.png new file mode 100644 index 0000000000..c57cca3f48 Binary files /dev/null and b/common/src/main/resources/assets/wynntils/textures/icons/config_categories/all_config_icon.png differ diff --git a/common/src/main/resources/assets/wynntils/textures/icons/config_categories/apply_settings_icon.png b/common/src/main/resources/assets/wynntils/textures/icons/config_categories/apply_settings_icon.png new file mode 100644 index 0000000000..7211d6cc7f Binary files /dev/null and b/common/src/main/resources/assets/wynntils/textures/icons/config_categories/apply_settings_icon.png differ diff --git a/common/src/main/resources/assets/wynntils/textures/icons/config_categories/chat_config_icon.png b/common/src/main/resources/assets/wynntils/textures/icons/config_categories/chat_config_icon.png new file mode 100644 index 0000000000..0ac6b31c96 Binary files /dev/null and b/common/src/main/resources/assets/wynntils/textures/icons/config_categories/chat_config_icon.png differ diff --git a/common/src/main/resources/assets/wynntils/textures/icons/config_categories/combat_config_icon.png b/common/src/main/resources/assets/wynntils/textures/icons/config_categories/combat_config_icon.png new file mode 100644 index 0000000000..56f8f5b1b8 Binary files /dev/null and b/common/src/main/resources/assets/wynntils/textures/icons/config_categories/combat_config_icon.png differ diff --git a/common/src/main/resources/assets/wynntils/textures/icons/config_categories/commands_config_icon.png b/common/src/main/resources/assets/wynntils/textures/icons/config_categories/commands_config_icon.png new file mode 100644 index 0000000000..e5907a8846 Binary files /dev/null and b/common/src/main/resources/assets/wynntils/textures/icons/config_categories/commands_config_icon.png differ diff --git a/common/src/main/resources/assets/wynntils/textures/icons/config_categories/debug_config_icon.png b/common/src/main/resources/assets/wynntils/textures/icons/config_categories/debug_config_icon.png new file mode 100644 index 0000000000..1c6e39a009 Binary files /dev/null and b/common/src/main/resources/assets/wynntils/textures/icons/config_categories/debug_config_icon.png differ diff --git a/common/src/main/resources/assets/wynntils/textures/icons/config_categories/discard_settings_icon.png b/common/src/main/resources/assets/wynntils/textures/icons/config_categories/discard_settings_icon.png new file mode 100644 index 0000000000..e100ae87a2 Binary files /dev/null and b/common/src/main/resources/assets/wynntils/textures/icons/config_categories/discard_settings_icon.png differ diff --git a/common/src/main/resources/assets/wynntils/textures/icons/config_categories/embellishments_config_icon.png b/common/src/main/resources/assets/wynntils/textures/icons/config_categories/embellishments_config_icon.png new file mode 100644 index 0000000000..e2e73ed1af Binary files /dev/null and b/common/src/main/resources/assets/wynntils/textures/icons/config_categories/embellishments_config_icon.png differ diff --git a/common/src/main/resources/assets/wynntils/textures/icons/config_categories/export_settings_icon.png b/common/src/main/resources/assets/wynntils/textures/icons/config_categories/export_settings_icon.png new file mode 100644 index 0000000000..d216004f5b Binary files /dev/null and b/common/src/main/resources/assets/wynntils/textures/icons/config_categories/export_settings_icon.png differ diff --git a/common/src/main/resources/assets/wynntils/textures/icons/config_categories/import_settings_icon.png b/common/src/main/resources/assets/wynntils/textures/icons/config_categories/import_settings_icon.png new file mode 100644 index 0000000000..ba1ce77455 Binary files /dev/null and b/common/src/main/resources/assets/wynntils/textures/icons/config_categories/import_settings_icon.png differ diff --git a/common/src/main/resources/assets/wynntils/textures/icons/config_categories/inventory_config_icon.png b/common/src/main/resources/assets/wynntils/textures/icons/config_categories/inventory_config_icon.png new file mode 100644 index 0000000000..1f985faa79 Binary files /dev/null and b/common/src/main/resources/assets/wynntils/textures/icons/config_categories/inventory_config_icon.png differ diff --git a/common/src/main/resources/assets/wynntils/textures/icons/config_categories/map_config_icon.png b/common/src/main/resources/assets/wynntils/textures/icons/config_categories/map_config_icon.png new file mode 100644 index 0000000000..0395262ca1 Binary files /dev/null and b/common/src/main/resources/assets/wynntils/textures/icons/config_categories/map_config_icon.png differ diff --git a/common/src/main/resources/assets/wynntils/textures/icons/config_categories/overlays_config_icon.png b/common/src/main/resources/assets/wynntils/textures/icons/config_categories/overlays_config_icon.png new file mode 100644 index 0000000000..9c10b004dd Binary files /dev/null and b/common/src/main/resources/assets/wynntils/textures/icons/config_categories/overlays_config_icon.png differ diff --git a/common/src/main/resources/assets/wynntils/textures/icons/config_categories/players_config_icon.png b/common/src/main/resources/assets/wynntils/textures/icons/config_categories/players_config_icon.png new file mode 100644 index 0000000000..65f0aa8a10 Binary files /dev/null and b/common/src/main/resources/assets/wynntils/textures/icons/config_categories/players_config_icon.png differ diff --git a/common/src/main/resources/assets/wynntils/textures/icons/config_categories/redirects_config_icon.png b/common/src/main/resources/assets/wynntils/textures/icons/config_categories/redirects_config_icon.png new file mode 100644 index 0000000000..34b57345b0 Binary files /dev/null and b/common/src/main/resources/assets/wynntils/textures/icons/config_categories/redirects_config_icon.png differ diff --git a/common/src/main/resources/assets/wynntils/textures/icons/config_categories/tooltips_config_icon.png b/common/src/main/resources/assets/wynntils/textures/icons/config_categories/tooltips_config_icon.png new file mode 100644 index 0000000000..615dbee813 Binary files /dev/null and b/common/src/main/resources/assets/wynntils/textures/icons/config_categories/tooltips_config_icon.png differ diff --git a/common/src/main/resources/assets/wynntils/textures/icons/config_categories/trade_market_config_icon.png b/common/src/main/resources/assets/wynntils/textures/icons/config_categories/trade_market_config_icon.png new file mode 100644 index 0000000000..feff43089a Binary files /dev/null and b/common/src/main/resources/assets/wynntils/textures/icons/config_categories/trade_market_config_icon.png differ diff --git a/common/src/main/resources/assets/wynntils/textures/icons/config_categories/ui_config_icon.png b/common/src/main/resources/assets/wynntils/textures/icons/config_categories/ui_config_icon.png new file mode 100644 index 0000000000..34baae644e Binary files /dev/null and b/common/src/main/resources/assets/wynntils/textures/icons/config_categories/ui_config_icon.png differ diff --git a/common/src/main/resources/assets/wynntils/textures/icons/config_categories/uncategorized_config_icon.png b/common/src/main/resources/assets/wynntils/textures/icons/config_categories/uncategorized_config_icon.png new file mode 100644 index 0000000000..4853f150fc Binary files /dev/null and b/common/src/main/resources/assets/wynntils/textures/icons/config_categories/uncategorized_config_icon.png differ diff --git a/common/src/main/resources/assets/wynntils/textures/icons/config_categories/utilities_config_icon.png b/common/src/main/resources/assets/wynntils/textures/icons/config_categories/utilities_config_icon.png new file mode 100644 index 0000000000..6604af89e7 Binary files /dev/null and b/common/src/main/resources/assets/wynntils/textures/icons/config_categories/utilities_config_icon.png differ diff --git a/common/src/main/resources/assets/wynntils/textures/icons/config_categories/wynntils_config_icon.png b/common/src/main/resources/assets/wynntils/textures/icons/config_categories/wynntils_config_icon.png new file mode 100644 index 0000000000..adbaf1cf8c Binary files /dev/null and b/common/src/main/resources/assets/wynntils/textures/icons/config_categories/wynntils_config_icon.png differ diff --git a/common/src/main/resources/assets/wynntils/textures/icons/generic/next.png b/common/src/main/resources/assets/wynntils/textures/icons/generic/next.png new file mode 100644 index 0000000000..e790f02d97 Binary files /dev/null and b/common/src/main/resources/assets/wynntils/textures/icons/generic/next.png differ diff --git a/common/src/main/resources/assets/wynntils/textures/icons/generic/previous.png b/common/src/main/resources/assets/wynntils/textures/icons/generic/previous.png new file mode 100644 index 0000000000..505ea87459 Binary files /dev/null and b/common/src/main/resources/assets/wynntils/textures/icons/generic/previous.png differ