diff --git a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiChainedAbilityConfig.java b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiChainedAbilityConfig.java index 1fc123d37..4e271893c 100644 --- a/src/main/java/noppes/npcs/client/gui/advanced/SubGuiChainedAbilityConfig.java +++ b/src/main/java/noppes/npcs/client/gui/advanced/SubGuiChainedAbilityConfig.java @@ -175,7 +175,7 @@ public class SubGuiChainedAbilityConfig extends SubGuiInterface implements IText java.util.function.BooleanSupplier layerVisible = () -> chainIcon.getLayerCount() > idx; String sectionLabel = net.minecraft.util.StatCollector.translateToLocal("ability.icon.layer") + " " + (idx + 1); fieldDefs.add(FieldDef.section(sectionLabel).tab("Icon").visibleWhen(layerVisible)); - fieldDefs.add(FieldDef.stringField("gui.texture", + fieldDefs.add(FieldDef.textureSubGui("gui.texture", () -> chainIcon.getLayer(idx).texture, t -> chainIcon.setLayerTexture(idx, t)) .tab("Icon").visibleWhen(layerVisible)); diff --git a/src/main/java/noppes/npcs/client/gui/builder/FieldDef.java b/src/main/java/noppes/npcs/client/gui/builder/FieldDef.java index 4d03cb2dc..df63b1d4c 100644 --- a/src/main/java/noppes/npcs/client/gui/builder/FieldDef.java +++ b/src/main/java/noppes/npcs/client/gui/builder/FieldDef.java @@ -5,6 +5,7 @@ import cpw.mods.fml.relauncher.SideOnly; import noppes.npcs.client.gui.SubGuiColorSelector; import noppes.npcs.client.gui.select.GuiAnimationSelection; import noppes.npcs.client.gui.select.GuiSoundSelection; +import noppes.npcs.client.gui.select.GuiTexturePathSelection; import noppes.npcs.client.gui.util.SubGuiInterface; import noppes.npcs.controllers.AnimationController; import noppes.npcs.controllers.data.Animation; @@ -175,6 +176,20 @@ public class FieldDef { .clearable(() -> setter.accept("")); } + public static FieldDef textureSubGui(String label, Supplier getter, Consumer setter) { + return subGuiField(label, + () -> new GuiTexturePathSelection(getter.get()), + gui -> { + GuiTexturePathSelection t = (GuiTexturePathSelection) gui; + if (t.selectedResource != null) setter.accept(t.selectedResource.toString()); + }) + .buttonLabel(() -> { + String s = getter.get(); + return s == null || s.isEmpty() ? "gui.none" : s; + }) + .clearable(() -> setter.accept("")); + } + public static FieldDef animSubGui(String label, Supplier idGetter, Consumer idSetter, Supplier nameGetter, Consumer nameSetter) { diff --git a/src/main/java/noppes/npcs/client/gui/select/GuiTexturePathSelection.java b/src/main/java/noppes/npcs/client/gui/select/GuiTexturePathSelection.java new file mode 100644 index 000000000..7e67a32d1 --- /dev/null +++ b/src/main/java/noppes/npcs/client/gui/select/GuiTexturePathSelection.java @@ -0,0 +1,319 @@ +package noppes.npcs.client.gui.select; + +import com.google.common.collect.Lists; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiButton; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.StatCollector; +import noppes.npcs.client.ClientCacheHandler; +import noppes.npcs.client.gui.util.GuiCustomScroll; +import noppes.npcs.client.gui.util.GuiNpcButton; +import noppes.npcs.client.gui.util.GuiNpcTextField; +import noppes.npcs.client.gui.util.ICustomScrollListener; +import noppes.npcs.client.gui.util.SubGuiInterface; +import noppes.npcs.client.renderer.ImageData; +import org.lwjgl.opengl.GL11; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +/** + * Texture path selector SubGui with a texture preview panel instead of NPC rendering. + * Reuses the cached texture data from {@link GuiTextureSelection}. + */ +public class GuiTexturePathSelection extends SubGuiInterface implements ICustomScrollListener { + + private final String up = "..<" + StatCollector.translateToLocal("gui.up") + ">.."; + private GuiCustomScroll scrollCategories; + private GuiCustomScroll scrollTextures; + + private String location = ""; + private String selectedDomain; + public ResourceLocation selectedResource; + + private final HashMap> domains = new HashMap<>(); + private final HashMap textures = new HashMap<>(); + + private String catSearch = ""; + private String texSearch = ""; + + // Preview + private static final int PREVIEW_SIZE = 90; + + public GuiTexturePathSelection(String texture) { + drawDefaultBackground = false; + title = ""; + setBackground("menubg.png"); + xSize = 366; + ySize = 226; + + // Populate from GuiTextureSelection's cache (trigger a scan if needed) + long now = System.currentTimeMillis(); + if (GuiTextureSelection.cachedTextures != null && !GuiTextureSelection.cachedTextures.isEmpty() + && now - GuiTextureSelection.lastCacheTime() < GuiTextureSelection.CACHE_DURATION_MS) { + domains.putAll(GuiTextureSelection.cachedDomains()); + } else { + // Force a scan by creating a temporary GuiTextureSelection + GuiTextureSelection temp = new GuiTextureSelection(null, null); + domains.putAll(GuiTextureSelection.cachedDomains()); + } + + if (texture != null && !texture.isEmpty()) { + selectedResource = new ResourceLocation(texture); + selectedDomain = selectedResource.getResourceDomain(); + if (!domains.containsKey(selectedDomain)) { + selectedDomain = null; + } + int i = selectedResource.getResourcePath().lastIndexOf('/'); + location = selectedResource.getResourcePath().substring(0, i + 1); + } + } + + @Override + public void initGui() { + super.initGui(); + + if (selectedDomain != null) { + title = selectedDomain + ":" + location; + } else { + title = ""; + } + + // Buttons + this.addButton(new GuiNpcButton(2, guiLeft + xSize - 46, guiTop + ySize - 28, 42, 20, "gui.done")); + this.addButton(new GuiNpcButton(1, guiLeft + xSize - 92, guiTop + ySize - 28, 42, 20, "gui.cancel")); + + // Category scroll + if (scrollCategories == null) { + scrollCategories = new GuiCustomScroll(this, 0); + scrollCategories.setSize(90, 163); + } + if (selectedDomain == null) { + scrollCategories.setList(getCatSearch()); + } else { + List list = new ArrayList<>(); + list.add(up); + List data = domains.get(selectedDomain); + if (data != null) { + for (GuiTextureSelection.TextureData td : data) { + if (location.isEmpty() || td.path.startsWith(location) && !td.path.equals(location)) { + String path = td.path.substring(location.length()); + int i = path.indexOf('/'); + if (i < 0) continue; + path = path.substring(0, i); + if (!path.isEmpty() && !list.contains(path)) { + list.add(path); + } + } + } + } + scrollCategories.setList(list); + } + scrollCategories.guiLeft = guiLeft + 4; + scrollCategories.guiTop = guiTop + 14; + this.addScroll(scrollCategories); + addTextField(new GuiNpcTextField(55, this, fontRendererObj, guiLeft + 4, guiTop + 179, 90, 20, catSearch)); + + // Texture scroll + if (scrollTextures == null) { + scrollTextures = new GuiCustomScroll(this, 1); + scrollTextures.setSize(160, 163); + } + if (selectedDomain != null) { + textures.clear(); + List data = domains.get(selectedDomain); + List allList = new ArrayList<>(); + String loc = location; + if (scrollCategories.hasSelected() && !scrollCategories.getSelected().equals(up)) { + loc += scrollCategories.getSelected() + '/'; + } + if (data != null) { + for (GuiTextureSelection.TextureData td : data) { + if (td.path.equals(loc) && !allList.contains(td.name)) { + allList.add(td.name); + textures.put(td.name, td); + } + } + } + scrollTextures.setList(filterTexSearch(allList)); + } + if (selectedResource != null) { + scrollTextures.setSelected(selectedResource.getResourcePath()); + } + scrollTextures.guiLeft = guiLeft + 95; + scrollTextures.guiTop = guiTop + 14; + this.addScroll(scrollTextures); + addTextField(new GuiNpcTextField(66, this, fontRendererObj, guiLeft + 95, guiTop + 179, 160, 20, texSearch)); + } + + @Override + public void drawScreen(int mouseX, int mouseY, float partialTicks) { + super.drawScreen(mouseX, mouseY, partialTicks); + drawTexturePreview(); + } + + private void drawTexturePreview() { + int previewX = guiLeft + 260; + int previewY = guiTop + 14; + + // Draw border/background for preview area + drawRect(previewX - 1, previewY - 1, previewX + PREVIEW_SIZE + 1, previewY + PREVIEW_SIZE + 1, 0xFFA0A0A0); + drawRect(previewX, previewY, previewX + PREVIEW_SIZE, previewY + PREVIEW_SIZE, 0xFF000000); + + if (selectedResource == null) { + String none = StatCollector.translateToLocal("gui.none"); + int w = fontRendererObj.getStringWidth(none); + fontRendererObj.drawString(none, previewX + (PREVIEW_SIZE - w) / 2, previewY + PREVIEW_SIZE / 2 - 4, 0xFF808080); + return; + } + + ImageData imageData = ClientCacheHandler.getImageData(selectedResource.toString()); + if (imageData == null || !imageData.imageLoaded()) { + String loading = "..."; + int w = fontRendererObj.getStringWidth(loading); + fontRendererObj.drawString(loading, previewX + (PREVIEW_SIZE - w) / 2, previewY + PREVIEW_SIZE / 2 - 4, 0xFF808080); + return; + } + + int texW = imageData.getTotalWidth(); + int texH = imageData.getTotalHeight(); + if (texW <= 0 || texH <= 0) return; + + // Scale to fit within PREVIEW_SIZE while maintaining aspect ratio + float scale = Math.min((float) PREVIEW_SIZE / texW, (float) PREVIEW_SIZE / texH); + int drawW = (int) (texW * scale); + int drawH = (int) (texH * scale); + int drawX = previewX + (PREVIEW_SIZE - drawW) / 2; + int drawY = previewY + (PREVIEW_SIZE - drawH) / 2; + + GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F); + imageData.bindTexture(); + drawScaledTexture(drawX, drawY, drawW, drawH); + + // Draw dimensions label below preview + String dims = texW + " x " + texH; + int dw = fontRendererObj.getStringWidth(dims); + fontRendererObj.drawString(dims, previewX + (PREVIEW_SIZE - dw) / 2, previewY + PREVIEW_SIZE + 4, 0xFFCCCCCC); + + // Draw resource path below dimensions + String path = selectedResource.toString(); + if (fontRendererObj.getStringWidth(path) > PREVIEW_SIZE + 4) { + // Truncate from the left + while (fontRendererObj.getStringWidth("..." + path) > PREVIEW_SIZE + 4 && path.length() > 1) { + path = path.substring(1); + } + path = "..." + path; + } + int pw = fontRendererObj.getStringWidth(path); + fontRendererObj.drawString(path, previewX + (PREVIEW_SIZE - pw) / 2, previewY + PREVIEW_SIZE + 15, 0xFF999999); + } + + private void drawScaledTexture(int x, int y, int width, int height) { + Tessellator tess = Tessellator.instance; + tess.startDrawingQuads(); + tess.addVertexWithUV(x, y + height, zLevel, 0.0, 1.0); + tess.addVertexWithUV(x + width, y + height, zLevel, 1.0, 1.0); + tess.addVertexWithUV(x + width, y, zLevel, 1.0, 0.0); + tess.addVertexWithUV(x, y, zLevel, 0.0, 0.0); + tess.draw(); + } + + @Override + protected void actionPerformed(GuiButton guibutton) { + super.actionPerformed(guibutton); + if (guibutton.id == 1) { + // Cancel - clear selection + selectedResource = null; + } + close(); + } + + @Override + public void customScrollClicked(int i, int j, int k, GuiCustomScroll scroll) { + if (scroll == scrollTextures) { + if (scroll.id == 1) { + GuiTextureSelection.TextureData data = textures.get(scroll.getSelected()); + if (data != null) { + selectedResource = new ResourceLocation(selectedDomain, data.absoluteName); + } + } + } else { + initGui(); + scrollTextures.resetScroll(); + } + } + + @Override + public void customScrollDoubleClicked(String selection, GuiCustomScroll scroll) { + if (scroll == scrollCategories) { + if (selectedDomain == null) { + selectedDomain = selection; + } else if (selection.equals(up)) { + int i = location.lastIndexOf('/', location.length() - 2); + if (i < 0) { + if (location.isEmpty()) { + selectedDomain = null; + } + location = ""; + } else { + location = location.substring(0, i + 1); + } + } else { + location = location + selection + '/'; + } + scrollCategories.selected = -1; + scrollTextures.selected = -1; + initGui(); + } else { + // Double-click on texture = confirm + close(); + } + } + + @Override + public void keyTyped(char c, int i) { + super.keyTyped(c, i); + if (getTextField(55) != null && getTextField(55).isFocused()) { + if (!catSearch.equals(getTextField(55).getText())) { + catSearch = getTextField(55).getText().toLowerCase(); + scrollCategories.resetScroll(); + if (selectedDomain == null) { + scrollCategories.setList(getCatSearch()); + } + } + } + if (getTextField(66) != null && getTextField(66).isFocused()) { + if (!texSearch.equals(getTextField(66).getText())) { + texSearch = getTextField(66).getText().toLowerCase(); + scrollTextures.resetScroll(); + initGui(); + } + } + } + + private List getCatSearch() { + if (catSearch.isEmpty()) { + return new ArrayList<>(domains.keySet()); + } + List list = new ArrayList<>(); + for (String name : domains.keySet()) { + if (name.toLowerCase().contains(catSearch)) + list.add(name); + } + return list; + } + + private List filterTexSearch(List source) { + if (texSearch.isEmpty()) { + return source; + } + List list = new ArrayList<>(); + for (String name : source) { + if (name.toLowerCase().contains(texSearch)) + list.add(name); + } + return list; + } +} diff --git a/src/main/java/noppes/npcs/client/gui/select/GuiTextureSelection.java b/src/main/java/noppes/npcs/client/gui/select/GuiTextureSelection.java index 3b8fda84d..b1cab23f4 100644 --- a/src/main/java/noppes/npcs/client/gui/select/GuiTextureSelection.java +++ b/src/main/java/noppes/npcs/client/gui/select/GuiTextureSelection.java @@ -54,11 +54,21 @@ public class GuiTextureSelection extends SubGuiInterface implements ICustomScrol private final HashMap textures = new HashMap<>(); // --- Cache static fields --- - private static final long CACHE_DURATION = 180000L; // 3 minutes in milliseconds + static final long CACHE_DURATION = 180000L; // 3 minutes in milliseconds private static long lastCacheTime = 0; private static HashMap> cachedDomains = new HashMap<>(); public static HashMap cachedTextures = new HashMap<>(); + public static final long CACHE_DURATION_MS = CACHE_DURATION; + + public static long lastCacheTime() { + return lastCacheTime; + } + + public static HashMap> cachedDomains() { + return cachedDomains; + } + public GuiTextureSelection(EntityNPCInterface npc, String texture) { this.npc = npc; drawDefaultBackground = false;