Skip to content
This repository was archived by the owner on Nov 28, 2025. It is now read-only.

Commit 3c518f4

Browse files
committed
add buttons to switch skin variants
1 parent 6288e5d commit 3c518f4

File tree

21 files changed

+586
-257
lines changed

21 files changed

+586
-257
lines changed

1.16_combat-6/src/main/java/io/github/axolotlclient/modules/auth/skin/SkinManagementScreen.java

Lines changed: 69 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,9 @@
4141
import io.github.axolotlclient.modules.hud.util.DrawUtil;
4242
import io.github.axolotlclient.util.ButtonWidgetTextures;
4343
import io.github.axolotlclient.util.ClientColors;
44+
import io.github.axolotlclient.util.ThreadExecuter;
4445
import io.github.axolotlclient.util.Watcher;
46+
import io.github.axolotlclient.util.notifications.Notifications;
4547
import net.fabricmc.loader.api.FabricLoader;
4648
import net.minecraft.client.MinecraftClient;
4749
import net.minecraft.client.font.TextRenderer;
@@ -56,6 +58,7 @@
5658
import net.minecraft.client.gui.widget.ElementListWidget;
5759
import net.minecraft.client.render.Tessellator;
5860
import net.minecraft.client.render.VertexFormats;
61+
import net.minecraft.client.resource.language.I18n;
5962
import net.minecraft.client.util.math.MatrixStack;
6063
import net.minecraft.text.LiteralText;
6164
import net.minecraft.text.MutableText;
@@ -87,7 +90,10 @@ public SkinManagementScreen(Screen parent, Account account) {
8790
super(new TranslatableText("skins.manage"));
8891
this.parent = parent;
8992
this.account = account;
90-
skinDirWatcher = Watcher.createSelfTicking(SKINS_DIR, this::loadSkinsList);
93+
skinDirWatcher = Watcher.createSelfTicking(SKINS_DIR, () -> {
94+
AxolotlClientCommon.getInstance().getLogger().info("Reloading screen as local files changed!");
95+
loadSkinsList();
96+
});
9197
if (account.needsRefresh()) {
9298
refreshFuture = account.refresh(Auth.getInstance().getMsApi());
9399
} else {
@@ -367,14 +373,33 @@ private void populateSkinList(List<? extends Skin> skins, int columns) {
367373
@Override
368374
public void filesDragged(List<Path> packs) {
369375
if (packs.isEmpty()) return;
370-
packs.forEach(p -> {
371-
try {
372-
Files.copy(p, SKINS_DIR.resolve(p.getFileName()));
373-
} catch (IOException e) {
374-
AxolotlClientCommon.getInstance().getLogger().warn("Failed to copy skin file: ", e);
375-
}
376-
});
377-
loadSkinsList();
376+
377+
CompletableFuture<?>[] futs = new CompletableFuture[packs.size()];
378+
for (int i = 0; i < packs.size(); i++) {
379+
Path p = packs.get(i);
380+
futs[i] = CompletableFuture.runAsync(() -> {
381+
try {
382+
var target = SKINS_DIR.resolve(p.getFileName());
383+
if (Files.exists(target)) {
384+
int counter = 0;
385+
do {
386+
counter++;
387+
target = target.resolveSibling(target.getFileName().toString()+"_"+counter);
388+
} while (Files.exists(target));
389+
}
390+
var skin = Auth.getInstance().getSkinManager().read(p, false);
391+
if (skin != null) {
392+
Files.write(target, skin.image().join());
393+
} else {
394+
AxolotlClientCommon.getInstance().getLogger().info("Skipping dragged file {} because it does not seem to be a valid skin!", p);
395+
Notifications.getInstance().addStatus("skins.notification.title", "skins.notification.not_copied", p.getFileName());
396+
}
397+
} catch (IOException e) {
398+
AxolotlClientCommon.getInstance().getLogger().warn("Failed to copy skin file: ", e);
399+
}
400+
}, ThreadExecuter.service());
401+
}
402+
CompletableFuture.allOf(futs).thenRun(this::loadSkinsList);
378403
}
379404

380405
private @NotNull Entry createEntryForSkin(Skin skin, int entryHeight) {
@@ -538,9 +563,40 @@ public Entry(int height, SkinWidget widget, @Nullable Text label) {
538563
super(0, 0, widget.getWidth(), height, LiteralText.EMPTY);
539564
widget.setWidth(getWidth() - 4);
540565
var asset = widget.getFocusedAsset();
566+
class SpriteButton extends ButtonWidget {
567+
private Identifier sprite;
568+
569+
public SpriteButton(Text message, PressAction onPress, Identifier sprite) {
570+
super(0, 0, 11, 11, message, onPress);
571+
this.sprite = sprite;
572+
}
573+
574+
@Override
575+
public void renderButton(MatrixStack graphics, int mouseX, int mouseY, float delta) {
576+
Identifier tex = ButtonWidgetTextures.get(getYImage(hovered));
577+
DrawUtil.blitSprite(tex, x, y, width, height, new DrawUtil.NineSlice(200, 20, 3));
578+
client.getTextureManager().bindTexture(sprite);
579+
drawTexture(graphics, x + 2, y + 2, 0, 0, 7, 7, 7, 7);
580+
if (this.isHovered()) {
581+
tooltip = getMessage();
582+
}
583+
}
584+
}
585+
if (asset instanceof Skin skin) {
586+
var wideSprite = new Identifier("axolotlclient", "textures/gui/sprites/wide.png");
587+
var slimSprite = new Identifier("axolotlclient", "textures/gui/sprites/slim.png");
588+
var slimText = new TranslatableText("skins.manage.variant.classic");
589+
var wideText = new TranslatableText("skins.manage.variant.slim");
590+
actionButtons.add(new SpriteButton(skin.classicVariant() ? wideText : slimText, btn -> {
591+
var self = (SpriteButton) btn;
592+
skin.classicVariant(!skin.classicVariant());
593+
self.sprite = skin.classicVariant() ? slimSprite : wideSprite;
594+
self.setMessage(skin.classicVariant() ? wideText : slimText);
595+
}, skin.classicVariant() ? slimSprite : wideSprite));
596+
}
541597
if (asset != null) {
542598
if (asset.isLocal()) {
543-
var delete = new ButtonWidget(0, 0, 11, 11, new TranslatableText("skins.manage.delete"), btn -> {
599+
this.actionButtons.add(new SpriteButton(new TranslatableText("skins.manage.delete"), btn -> {
544600
btn.active = false;
545601
client.openScreen(new ConfirmScreen(confirmed -> {
546602
client.openScreen(SkinManagementScreen.this);
@@ -557,25 +613,10 @@ public Entry(int height, SkinWidget widget, @Nullable Text label) {
557613
new TranslatableText("skins.manage.delete.confirm.desc_active") :
558614
new TranslatableText("skins.manage.delete.confirm.desc")
559615
).br$color(Colors.RED.toInt())));
560-
}) {
561-
562-
private final Identifier sprite = new Identifier("axolotlclient", "textures/gui/sprites/delete.png");
563-
564-
@Override
565-
public void renderButton(MatrixStack graphics, int mouseX, int mouseY, float delta) {
566-
Identifier tex = ButtonWidgetTextures.get(getYImage(hovered));
567-
DrawUtil.blitSprite(tex, x, y, width, height, new DrawUtil.NineSlice(200, 20, 3));
568-
client.getTextureManager().bindTexture(sprite);
569-
drawTexture(graphics, x + 2, y + 2, 0, 0, 7, 7, 7, 7);
570-
if (this.isHovered()) {
571-
tooltip = getMessage();
572-
}
573-
}
574-
};
575-
this.actionButtons.add(delete);
616+
}, new Identifier("axolotlclient", "textures/gui/sprites/delete.png")));
576617
}
577618
if (asset.supportsDownload() && !asset.isLocal()) {
578-
var download = new ButtonWidget(0, 0, 11, 11, new TranslatableText("skins.manage.download"), btn -> {
619+
this.actionButtons.add(new SpriteButton(new TranslatableText("skins.manage.download"), btn -> {
579620
btn.active = false;
580621
asset.image().thenAcceptAsync(b -> {
581622
try {
@@ -588,21 +629,7 @@ public void renderButton(MatrixStack graphics, int mouseX, int mouseY, float del
588629
refreshCurrentList();
589630
btn.active = true;
590631
});
591-
}) {
592-
private final Identifier sprite = new Identifier("axolotlclient", "textures/gui/sprites/download.png");
593-
594-
@Override
595-
public void renderButton(MatrixStack graphics, int mouseX, int mouseY, float delta) {
596-
Identifier tex = ButtonWidgetTextures.get(getYImage(hovered));
597-
DrawUtil.blitSprite(tex, x, y, width, height, new DrawUtil.NineSlice(200, 20, 3));
598-
client.getTextureManager().bindTexture(sprite);
599-
drawTexture(graphics, x + 2, y + 2, 0, 0, 7, 7, 7, 7);
600-
if (this.isHovered()) {
601-
tooltip = getMessage();
602-
}
603-
}
604-
};
605-
this.actionButtons.add(download);
632+
}, new Identifier("axolotlclient", "textures/gui/sprites/download.png")));
606633
}
607634
}
608635
if (label != null) {

1.16_combat-6/src/main/java/io/github/axolotlclient/modules/auth/skin/SkinManager.java

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,12 @@ public class SkinManager {
5151

5252
private final Set<AxoIdentifier> loadedTextures = new ConcurrentSkipListSet<>(Comparator.comparing(Object::toString));
5353

54-
@SuppressWarnings("UnstableApiUsage")
5554
public Skin read(Path p) {
55+
return read(p, true);
56+
}
57+
58+
@SuppressWarnings("UnstableApiUsage")
59+
public Skin read(Path p, boolean fix) {
5660
boolean slim;
5761
String sha256;
5862
try {
@@ -67,13 +71,16 @@ public Skin read(Path p) {
6771
int height = img.getHeight();
6872
if (width != 64) return null;
6973
if (height == 32) {
70-
var img2 = PlayerSkinTextureAccessor.invokeRemapTexture(img);
71-
img2.writeFile(p);
72-
slim = ClientColors.ARGB.alpha(img2.getPixelColor(47, 63)) == 0;
74+
if (fix) {
75+
try (var img2 = PlayerSkinTextureAccessor.invokeRemapTexture(img)) {
76+
img2.writeFile(p);
77+
}
78+
}
79+
slim = false;
7380
} else if (height != 64) {
7481
return null;
7582
} else {
76-
slim = ClientColors.ARGB.alpha(img.getPixelColor(47, 63)) == 0;
83+
slim = ClientColors.ARGB.alpha(img.getPixelColor(63, 63)) == 0;
7784
}
7885
}
7986
}

1.16_combat-6/src/main/java/io/github/axolotlclient/modules/auth/skin/SkinWidget.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ public void renderButton(MatrixStack guiGraphics, int mouseX, int mouseY, float
9090
CompletableFuture<AxoIdentifier> loader = skin == null ? null : skinManager.loadSkin(skin);
9191
if (loader != null && loader.isDone()) {
9292
skinRl = loader.join();
93-
classic = skin.isClassicVariant();
93+
classic = skin.classicVariant();
9494
} else {
9595
var uuid = UUIDHelper.fromUndashed(owner.getUuid());
9696
classic = DefaultSkinHelper.getModel(uuid).equals("default");

1.20/src/main/java/io/github/axolotlclient/modules/auth/skin/SkinManagementScreen.java

Lines changed: 75 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@
3939
import io.github.axolotlclient.modules.auth.Auth;
4040
import io.github.axolotlclient.modules.auth.MSApi;
4141
import io.github.axolotlclient.util.ClientColors;
42+
import io.github.axolotlclient.util.ThreadExecuter;
4243
import io.github.axolotlclient.util.Watcher;
44+
import io.github.axolotlclient.util.notifications.Notifications;
4345
import net.fabricmc.loader.api.FabricLoader;
4446
import net.minecraft.client.MinecraftClient;
4547
import net.minecraft.client.font.TextRenderer;
@@ -78,7 +80,10 @@ public SkinManagementScreen(Screen parent, Account account) {
7880
super(Text.translatable("skins.manage"));
7981
this.parent = parent;
8082
this.account = account;
81-
skinDirWatcher = Watcher.createSelfTicking(SKINS_DIR, this::loadSkinsList);
83+
skinDirWatcher = Watcher.createSelfTicking(SKINS_DIR, () -> {
84+
AxolotlClientCommon.getInstance().getLogger().info("Reloading screen as local files changed!");
85+
loadSkinsList();
86+
});
8287
if (account.needsRefresh()) {
8388
refreshFuture = account.refresh(Auth.getInstance().getMsApi());
8489
} else {
@@ -333,14 +338,33 @@ private void populateSkinList(List<? extends Skin> skins, int columns) {
333338
@Override
334339
public void filesDragged(List<Path> packs) {
335340
if (packs.isEmpty()) return;
336-
packs.forEach(p -> {
337-
try {
338-
Files.copy(p, SKINS_DIR.resolve(p.getFileName()));
339-
} catch (IOException e) {
340-
AxolotlClientCommon.getInstance().getLogger().warn("Failed to copy skin file: ", e);
341-
}
342-
});
343-
loadSkinsList();
341+
342+
CompletableFuture<?>[] futs = new CompletableFuture[packs.size()];
343+
for (int i = 0; i < packs.size(); i++) {
344+
Path p = packs.get(i);
345+
futs[i] = CompletableFuture.runAsync(() -> {
346+
try {
347+
var target = SKINS_DIR.resolve(p.getFileName());
348+
if (Files.exists(target)) {
349+
int counter = 0;
350+
do {
351+
counter++;
352+
target = target.resolveSibling(target.getFileName().toString()+"_"+counter);
353+
} while (Files.exists(target));
354+
}
355+
var skin = Auth.getInstance().getSkinManager().read(p, false);
356+
if (skin != null) {
357+
Files.write(target, skin.image().join());
358+
} else {
359+
AxolotlClientCommon.getInstance().getLogger().info("Skipping dragged file {} because it does not seem to be a valid skin!", p);
360+
Notifications.getInstance().addStatus("skins.notification.title", "skins.notification.not_copied", p.getFileName());
361+
}
362+
} catch (IOException e) {
363+
AxolotlClientCommon.getInstance().getLogger().warn("Failed to copy skin file: ", e);
364+
}
365+
}, ThreadExecuter.service());
366+
}
367+
CompletableFuture.allOf(futs).thenRun(this::loadSkinsList);
344368
}
345369

346370
private @NotNull Entry createEntryForSkin(Skin skin, int entryHeight) {
@@ -511,8 +535,46 @@ public Entry(int height, SkinWidget widget, @Nullable Text label) {
511535
widget.setWidth(getWidth() - 4);
512536
var asset = widget.getFocusedAsset();
513537
if (asset != null) {
538+
class SpriteButton extends ButtonWidget {
539+
private Identifier sprite;
540+
541+
public SpriteButton(Text message, PressAction onPress, Identifier sprite) {
542+
super(0, 0, 11, 11, message, onPress, DEFAULT_NARRATION);
543+
this.sprite = sprite;
544+
setTooltip(Tooltip.create(message, Text.empty()));
545+
}
546+
547+
@Override
548+
public void setMessage(Text message) {
549+
super.setMessage(message);
550+
setTooltip(Tooltip.create(message, Text.empty()));
551+
}
552+
553+
@Override
554+
protected void drawWidget(GuiGraphics graphics, int mouseX, int mouseY, float delta) {
555+
super.drawWidget(graphics, mouseX, mouseY, delta);
556+
graphics.drawTexture(sprite, getX() + 2, getY() + 2, 0, 0, 7, 7, 7, 7);
557+
}
558+
559+
@Override
560+
public void drawScrollableText(GuiGraphics graphics, TextRenderer renderer, int color) {
561+
562+
}
563+
}
564+
if (asset instanceof Skin skin) {
565+
var wideSprite = new Identifier("axolotlclient", "textures/gui/sprites/wide.png");
566+
var slimSprite = new Identifier("axolotlclient", "textures/gui/sprites/slim.png");
567+
var slimText = Text.translatable("skins.manage.variant.classic");
568+
var wideText = Text.translatable("skins.manage.variant.slim");
569+
actionButtons.add(new SpriteButton(skin.classicVariant() ? wideText : slimText, btn -> {
570+
var self = (SpriteButton) btn;
571+
skin.classicVariant(!skin.classicVariant());
572+
self.sprite = skin.classicVariant() ? slimSprite : wideSprite;
573+
self.setMessage(skin.classicVariant() ? wideText : slimText);
574+
}, skin.classicVariant() ? slimSprite : wideSprite));
575+
}
514576
if (asset.isLocal()) {
515-
var delete = new ButtonWidget(0, 0, 11, 11, Text.translatable("skins.manage.delete"), btn -> {
577+
this.actionButtons.add(new SpriteButton(Text.translatable("skins.manage.delete"), btn -> {
516578
btn.active = false;
517579
client.setScreen(new ConfirmScreen(confirmed -> {
518580
client.setScreen(SkinManagementScreen.this);
@@ -529,25 +591,10 @@ public Entry(int height, SkinWidget widget, @Nullable Text label) {
529591
Text.translatable("skins.manage.delete.confirm.desc_active") :
530592
Text.translatable("skins.manage.delete.confirm.desc")
531593
).br$color(Colors.RED.toInt())));
532-
}, Supplier::get) {
533-
private final Identifier SPRITE = new Identifier("axolotlclient", "textures/gui/sprites/delete.png");
534-
535-
@Override
536-
protected void drawWidget(GuiGraphics graphics, int mouseX, int mouseY, float delta) {
537-
super.drawWidget(graphics, mouseX, mouseY, delta);
538-
graphics.drawTexture(SPRITE, getX() + 2, getY() + 2, 0, 0, 7, 7, 7, 7);
539-
}
540-
541-
@Override
542-
public void drawScrollableText(GuiGraphics graphics, TextRenderer renderer, int color) {
543-
544-
}
545-
};
546-
delete.setTooltip(Tooltip.create(delete.getMessage()));
547-
this.actionButtons.add(delete);
594+
}, new Identifier("axolotlclient", "textures/gui/sprites/delete.png")));
548595
}
549596
if (asset.supportsDownload() && !asset.isLocal()) {
550-
var download = new ButtonWidget(0, 0, 11, 11, Text.translatable("skins.manage.download"), btn -> {
597+
this.actionButtons.add(new SpriteButton(Text.translatable("skins.manage.download"), btn -> {
551598
btn.active = false;
552599
asset.image().thenAcceptAsync(b -> {
553600
try {
@@ -560,22 +607,7 @@ public void drawScrollableText(GuiGraphics graphics, TextRenderer renderer, int
560607
refreshCurrentList();
561608
btn.active = true;
562609
});
563-
}, Supplier::get) {
564-
private final Identifier SPRITE = new Identifier("axolotlclient", "textures/gui/sprites/download.png");
565-
566-
@Override
567-
protected void drawWidget(GuiGraphics graphics, int mouseX, int mouseY, float delta) {
568-
super.drawWidget(graphics, mouseX, mouseY, delta);
569-
graphics.drawTexture(SPRITE, getX() + 2, getY() + 2, 0, 0, 7, 7, 7, 7);
570-
}
571-
572-
@Override
573-
public void drawScrollableText(GuiGraphics graphics, TextRenderer renderer, int color) {
574-
575-
}
576-
};
577-
download.setTooltip(Tooltip.create(download.getMessage()));
578-
this.actionButtons.add(download);
610+
}, new Identifier("axolotlclient", "textures/gui/sprites/download.png")));
579611
}
580612
}
581613
if (label != null) {

0 commit comments

Comments
 (0)