From a18fa3962c145646e6d3a499954a98df563e300c Mon Sep 17 00:00:00 2001 From: CallMeEchoCodes Date: Sun, 15 Mar 2026 15:44:10 +1100 Subject: [PATCH 1/5] feat: port to 1.21.11 --- build.gradle.kts | 2 +- gradle/libs.versions.toml | 12 +++---- .../dev/spiritstudios/snapper/Snapper.java | 6 ++-- .../spiritstudios/snapper/SnapperConfig.java | 2 +- .../gui/overlay/ExternalDialogOverlay.java | 6 ++-- .../snapper/gui/screen/ConfigScreen.java | 12 +++---- .../gui/screen/PanoramaViewerScreen.java | 6 ++-- .../gui/screen/PrivacyNoticeScreen.java | 17 +++++++--- .../gui/screen/ScreenshotListScreen.java | 18 +++++------ .../gui/screen/ScreenshotViewerScreen.java | 10 +++--- .../snapper/gui/toast/SnapperToast.java | 14 ++++----- .../gui/widget/FolderSelectWidget.java | 6 ++-- .../gui/widget/ScreenshotGridWidget.java | 4 +-- .../gui/widget/ScreenshotListWidget.java | 18 +++++------ .../snapper/gui/widget/ScreenshotsWidget.java | 10 +++--- .../snapper/mixin/CameraMixin.java | 8 ++--- .../snapper/mixin/MinecraftMixin.java | 2 +- .../snapper/mixin/PauseScreenMixin.java | 4 +-- .../snapper/mixin/TitleScreenMixin.java | 4 +-- .../snapper/util/DynamicCubemapTexture.java | 12 ++++--- .../snapper/util/PlatformHelper.java | 2 +- .../snapper/util/ScreenshotTexture.java | 31 +++++++------------ .../snapper/util/SnapperUtil.java | 2 +- .../util/uploading/AxolotlClientApi.java | 2 +- src/main/resources/fabric.mod.json | 2 +- 25 files changed, 103 insertions(+), 109 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index c7a740b..f61e119 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -126,7 +126,7 @@ publishMods { modrinth { accessToken = providers.gradleProperty("secrets.modrinth_token") - projectId = modrinthProject + projectId = "MZQyESDC" minecraftVersions.add(libs.versions.minecraft.get()) projectDescription = providers.fileContents(layout.projectDirectory.file("README.md")).asText diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 769d1de..0e34c81 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,14 +2,14 @@ fabric_loom = "1.15-SNAPSHOT" modpublish = "1.1.0" -minecraft = "1.21.10" -parchment = "2025.10.12" +minecraft = "1.21.11" +parchment = "2025.12.20" fabric_loader = "0.18.1" -fabric_api = "0.138.3+1.21.10" +fabric_api = "0.141.3+1.21.11" -greenhouse_config = "3.0.0-beta.4+1.21.10" -modmenu = "16.0.0" +greenhouse_config = "3.0.0-beta.4+1.21.11" +modmenu = "17.0.0-beta.2" objc_bridge = "1.0.0" @@ -19,7 +19,7 @@ modpublish = { id = "me.modmuss50.mod-publish-plugin", version.ref = "modpublish [libraries] minecraft = { group = "mojang", name = "minecraft", version.ref = "minecraft" } -parchment = { group = "org.parchmentmc.data", name = "parchment-1.21.10", version.ref = "parchment" } +parchment = { group = "org.parchmentmc.data", name = "parchment-1.21.11", version.ref = "parchment" } fabric_loader = { group = "net.fabricmc", name = "fabric-loader", version.ref = "fabric_loader" } fabric_api = { group = "net.fabricmc.fabric-api", name = "fabric-api", version.ref = "fabric_api" } diff --git a/src/client/java/dev/spiritstudios/snapper/Snapper.java b/src/client/java/dev/spiritstudios/snapper/Snapper.java index 978dd6d..0ed1289 100644 --- a/src/client/java/dev/spiritstudios/snapper/Snapper.java +++ b/src/client/java/dev/spiritstudios/snapper/Snapper.java @@ -3,7 +3,7 @@ import dev.spiritstudios.snapper.util.uploading.ScreenshotUploading; import net.fabricmc.api.ClientModInitializer; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -18,7 +18,7 @@ public void onInitializeClient() { ClientLifecycleEvents.CLIENT_STOPPING.register(client -> ScreenshotUploading.close()); } - public static ResourceLocation id(String path) { - return ResourceLocation.fromNamespaceAndPath(MOD_ID, path); + public static Identifier id(String path) { + return Identifier.fromNamespaceAndPath(MOD_ID, path); } } \ No newline at end of file diff --git a/src/client/java/dev/spiritstudios/snapper/SnapperConfig.java b/src/client/java/dev/spiritstudios/snapper/SnapperConfig.java index 946d47e..742abe7 100644 --- a/src/client/java/dev/spiritstudios/snapper/SnapperConfig.java +++ b/src/client/java/dev/spiritstudios/snapper/SnapperConfig.java @@ -12,7 +12,7 @@ import lgbt.greenhouse.config.api.v3.dfu.fix.GreenhouseConfigRelocateFieldsFix; import lgbt.greenhouse.config.api.v3.lang.GreenhouseConfigJsonCLang; import lgbt.greenhouse.config.api.v3.lang.GreenhouseConfigJsonLang; -import net.minecraft.Util; +import net.minecraft.util.Util; import java.nio.file.Path; import java.util.concurrent.CompletableFuture; diff --git a/src/client/java/dev/spiritstudios/snapper/gui/overlay/ExternalDialogOverlay.java b/src/client/java/dev/spiritstudios/snapper/gui/overlay/ExternalDialogOverlay.java index 91c0901..beca903 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/overlay/ExternalDialogOverlay.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/overlay/ExternalDialogOverlay.java @@ -6,14 +6,14 @@ import net.minecraft.client.gui.screens.Overlay; import net.minecraft.client.renderer.RenderPipelines; import net.minecraft.network.chat.Component; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import net.minecraft.util.CommonColors; public class ExternalDialogOverlay extends Overlay { private final Minecraft client = Minecraft.getInstance(); - public static final ResourceLocation MENU_BACKGROUND_TEXTURE = ResourceLocation.withDefaultNamespace("textures/gui/menu_background.png"); - private static final ResourceLocation INWORLD_MENU_BACKGROUND_TEXTURE = ResourceLocation.withDefaultNamespace("textures/gui/inworld_menu_background.png"); + public static final Identifier MENU_BACKGROUND_TEXTURE = Identifier.withDefaultNamespace("textures/gui/menu_background.png"); + private static final Identifier INWORLD_MENU_BACKGROUND_TEXTURE = Identifier.withDefaultNamespace("textures/gui/inworld_menu_background.png"); @Override public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTick) { diff --git a/src/client/java/dev/spiritstudios/snapper/gui/screen/ConfigScreen.java b/src/client/java/dev/spiritstudios/snapper/gui/screen/ConfigScreen.java index 08d8c1e..887a02d 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/screen/ConfigScreen.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/screen/ConfigScreen.java @@ -137,14 +137,14 @@ protected void repositionElements() { private AbstractWidget booleanButton(String name, Consumer setter, boolean currentValue) { Tooltip tooltip = getTooltip(name); - return CycleButton.builder( + return CycleButton.builder( boolean_ -> boolean_ ? CommonComponents.OPTION_ON - : CommonComponents.OPTION_OFF + : CommonComponents.OPTION_OFF, + currentValue ) .withValues(List.of(Boolean.TRUE, Boolean.FALSE)) .withTooltip(b -> tooltip) - .withInitialValue(currentValue) .create( 0, 0, 150, 20, @@ -163,12 +163,12 @@ private > AbstractWidget enumButton( ) { Tooltip tooltip = getTooltip(name); - return CycleButton.builder( - t -> Component.translatable("config.snapper." + name + "." + t.toString().toLowerCase()) + return CycleButton.builder( + t -> Component.translatable("config.snapper." + name + "." + t.toString().toLowerCase()), + currentValue ) .withValues(Arrays.asList(clazz.getEnumConstants())) .withTooltip(b -> tooltip) - .withInitialValue(currentValue) .create( 0, 0, 150, 20, diff --git a/src/client/java/dev/spiritstudios/snapper/gui/screen/PanoramaViewerScreen.java b/src/client/java/dev/spiritstudios/snapper/gui/screen/PanoramaViewerScreen.java index 4b0fd60..9f0e2ac 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/screen/PanoramaViewerScreen.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/screen/PanoramaViewerScreen.java @@ -4,7 +4,7 @@ import dev.spiritstudios.snapper.util.DynamicCubemapTexture; import dev.spiritstudios.snapper.util.SafeFiles; import dev.spiritstudios.snapper.util.SnapperUtil; -import net.minecraft.Util; +import net.minecraft.util.Util; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.components.Button; @@ -13,7 +13,7 @@ import net.minecraft.client.renderer.PanoramaRenderer; import net.minecraft.network.chat.CommonComponents; import net.minecraft.network.chat.Component; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import net.minecraft.util.CommonColors; import org.jetbrains.annotations.Nullable; @@ -23,7 +23,7 @@ import java.util.stream.Stream; public class PanoramaViewerScreen extends Screen { - protected static final ResourceLocation ID = Snapper.id("screenshots/panorama"); + protected static final Identifier ID = Snapper.id("screenshots/panorama"); protected static final CubeMap PANORAMA_RENDERER = new CubeMap(ID); private final PanoramaRenderer rotatingPanoramaRenderer = new PanoramaRenderer(PANORAMA_RENDERER); diff --git a/src/client/java/dev/spiritstudios/snapper/gui/screen/PrivacyNoticeScreen.java b/src/client/java/dev/spiritstudios/snapper/gui/screen/PrivacyNoticeScreen.java index c7d94d9..c0e027c 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/screen/PrivacyNoticeScreen.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/screen/PrivacyNoticeScreen.java @@ -30,7 +30,8 @@ import dev.spiritstudios.snapper.SnapperConfig; import dev.spiritstudios.snapper.util.uploading.AxolotlClientApi; -import net.minecraft.Util; +import net.minecraft.client.gui.TextAlignment; +import net.minecraft.util.Util; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.components.Button; @@ -59,10 +60,16 @@ public PrivacyNoticeScreen(Screen parent, Consumer accepted) { } @Override - public void render(GuiGraphics context, int mouseX, int mouseY, float delta) { - super.render(context, mouseX, mouseY, delta); - context.drawCenteredString(this.font, this.title, this.width / 2, getTitleY(), CommonColors.WHITE); - message.render(context, MultiLineLabel.Align.CENTER, width / 2, getMessageY(), 10, true, CommonColors.WHITE); + public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { + super.render(graphics, mouseX, mouseY, delta); + graphics.drawCenteredString(this.font, this.title, this.width / 2, getTitleY(), CommonColors.WHITE); + + message.visitLines( + TextAlignment.CENTER, + width / 2, getMessageY(), + 10, + graphics.textRenderer() + ); } @Override diff --git a/src/client/java/dev/spiritstudios/snapper/gui/screen/ScreenshotListScreen.java b/src/client/java/dev/spiritstudios/snapper/gui/screen/ScreenshotListScreen.java index 02b6280..62621dc 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/screen/ScreenshotListScreen.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/screen/ScreenshotListScreen.java @@ -11,7 +11,6 @@ import dev.spiritstudios.snapper.util.ScreenshotActions; import dev.spiritstudios.snapper.util.SnapperUtil; import dev.spiritstudios.snapper.util.uploading.ScreenshotUploading; -import net.minecraft.Util; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.components.Button; import net.minecraft.client.gui.components.SpriteIconButton; @@ -23,23 +22,24 @@ import net.minecraft.client.input.KeyEvent; import net.minecraft.network.chat.CommonComponents; import net.minecraft.network.chat.Component; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import net.minecraft.util.CommonColors; import net.minecraft.util.StringRepresentable; +import net.minecraft.util.Util; import org.jetbrains.annotations.Nullable; import org.jspecify.annotations.NonNull; import java.nio.file.Path; public class ScreenshotListScreen extends Screen { - private static final ResourceLocation PANORAMA_BUTTON_ICON = Snapper.id("screenshots/panorama"); - private static final ResourceLocation PANORAMA_BUTTON_DISABLED_ICON = Snapper.id("screenshots/panorama_disabled"); + private static final Identifier PANORAMA_BUTTON_ICON = Snapper.id("screenshots/panorama"); + private static final Identifier PANORAMA_BUTTON_DISABLED_ICON = Snapper.id("screenshots/panorama_disabled"); - private static final ResourceLocation SETTINGS_ICON = Snapper.id("screenshots/settings"); + private static final Identifier SETTINGS_ICON = Snapper.id("screenshots/settings"); - private static final ResourceLocation VIEW_MODE_ICON_LIST = Snapper.id("screenshots/show_list"); + private static final Identifier VIEW_MODE_ICON_LIST = Snapper.id("screenshots/show_list"); - private static final ResourceLocation VIEW_MODE_ICON_GRID = Snapper.id("screenshots/show_grid"); + private static final Identifier VIEW_MODE_ICON_GRID = Snapper.id("screenshots/show_grid"); private final Screen parent; private final boolean isOffline; @@ -96,8 +96,6 @@ private void recreateViewModeButton() { @Override protected void init() { - assert minecraft != null; - recreateList(); int secondRowButtonWidth = 100; @@ -254,8 +252,6 @@ public void toggleGrid() { @Override public boolean keyPressed(KeyEvent input) { - assert minecraft != null; - if (super.keyPressed(input)) { return true; } diff --git a/src/client/java/dev/spiritstudios/snapper/gui/screen/ScreenshotViewerScreen.java b/src/client/java/dev/spiritstudios/snapper/gui/screen/ScreenshotViewerScreen.java index 2ec0567..77eeadc 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/screen/ScreenshotViewerScreen.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/screen/ScreenshotViewerScreen.java @@ -7,7 +7,7 @@ import dev.spiritstudios.snapper.util.SnapperUtil; import dev.spiritstudios.snapper.util.uploading.ScreenshotUploading; import net.fabricmc.loader.api.FabricLoader; -import net.minecraft.Util; +import net.minecraft.util.Util; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.components.Button; @@ -20,7 +20,7 @@ import net.minecraft.client.renderer.RenderPipelines; import net.minecraft.network.chat.CommonComponents; import net.minecraft.network.chat.Component; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import net.minecraft.util.CommonColors; import org.jetbrains.annotations.Nullable; @@ -34,8 +34,8 @@ import java.util.List; public class ScreenshotViewerScreen extends Screen { - private static final ResourceLocation MENU_DECOR_BACKGROUND_TEXTURE = ResourceLocation.withDefaultNamespace("textures/gui/menu_list_background.png"); - private static final ResourceLocation INWORLD_MENU_DECOR_BACKGROUND_TEXTURE = ResourceLocation.withDefaultNamespace("textures/gui/inworld_menu_list_background.png"); + private static final Identifier MENU_DECOR_BACKGROUND_TEXTURE = Identifier.withDefaultNamespace("textures/gui/menu_list_background.png"); + private static final Identifier INWORLD_MENU_DECOR_BACKGROUND_TEXTURE = Identifier.withDefaultNamespace("textures/gui/inworld_menu_list_background.png"); private final Minecraft client = Minecraft.getInstance(); private final ScreenshotTexture image; @@ -196,7 +196,7 @@ public void render(GuiGraphics context, int mouseX, int mouseY, float delta) { context.blit( RenderPipelines.GUI_TEXTURED, - this.image.getTextureId(), + this.image.textureLocation(), (this.width / 2) - (finalWidth / 2), this.height - 70 - finalHeight, 0, 0, finalWidth, finalHeight, diff --git a/src/client/java/dev/spiritstudios/snapper/gui/toast/SnapperToast.java b/src/client/java/dev/spiritstudios/snapper/gui/toast/SnapperToast.java index 219819d..331f472 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/toast/SnapperToast.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/toast/SnapperToast.java @@ -8,7 +8,7 @@ import net.minecraft.client.gui.components.toasts.ToastManager; import net.minecraft.client.renderer.RenderPipelines; import net.minecraft.network.chat.Component; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import net.minecraft.util.CommonColors; import net.minecraft.util.FormattedCharSequence; import org.jspecify.annotations.NonNull; @@ -16,11 +16,11 @@ import java.util.List; public class SnapperToast implements Toast { - private static final ResourceLocation TEXTURE = Snapper.id("toast/snapper"); - private static final ResourceLocation SCREENSHOT_ICON = Snapper.id("icon/image"); - private static final ResourceLocation PANORAMA_ICON = Snapper.id("icon/panorama"); - private static final ResourceLocation UPLOAD_ICON = Snapper.id("icon/upload"); - private static final ResourceLocation DENY_ICON = Snapper.id("icon/nuh_uh"); + private static final Identifier TEXTURE = Snapper.id("toast/snapper"); + private static final Identifier SCREENSHOT_ICON = Snapper.id("icon/image"); + private static final Identifier PANORAMA_ICON = Snapper.id("icon/panorama"); + private static final Identifier UPLOAD_ICON = Snapper.id("icon/upload"); + private static final Identifier DENY_ICON = Snapper.id("icon/nuh_uh"); private static final int VISIBILITY_DURATION = 5000; private static final int WIDTH = 256; private static final int LINE_HEIGHT = 12; @@ -85,7 +85,7 @@ public int height() { return PADDING * 2 + Math.max(this.lines.size(), 1) * LINE_HEIGHT; } - private ResourceLocation getCurrentComponenture() { + private Identifier getCurrentComponenture() { return switch (type) { case UPLOAD -> UPLOAD_ICON; case PANORAMA -> PANORAMA_ICON; diff --git a/src/client/java/dev/spiritstudios/snapper/gui/widget/FolderSelectWidget.java b/src/client/java/dev/spiritstudios/snapper/gui/widget/FolderSelectWidget.java index 624c0b7..51b20bb 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/widget/FolderSelectWidget.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/widget/FolderSelectWidget.java @@ -12,7 +12,7 @@ import net.minecraft.client.input.MouseButtonEvent; import net.minecraft.network.chat.CommonComponents; import net.minecraft.network.chat.Component; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import net.minecraft.util.CommonColors; import org.jetbrains.annotations.Nullable; import org.jspecify.annotations.NonNull; @@ -24,8 +24,8 @@ import java.util.function.Consumer; public class FolderSelectWidget extends AbstractContainerWidget implements ContainerEventHandler { - private static final ResourceLocation FOLDER_ICON = Snapper.id("screenshots/folder"); - private static final ResourceLocation RESET_ICON = Snapper.id("screenshots/reset"); + private static final Identifier FOLDER_ICON = Snapper.id("screenshots/folder"); + private static final Identifier RESET_ICON = Snapper.id("screenshots/reset"); private static final int BUTTON_WIDTH = 25; // Includes padding diff --git a/src/client/java/dev/spiritstudios/snapper/gui/widget/ScreenshotGridWidget.java b/src/client/java/dev/spiritstudios/snapper/gui/widget/ScreenshotGridWidget.java index 24b5909..0a68fe9 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/widget/ScreenshotGridWidget.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/widget/ScreenshotGridWidget.java @@ -2,7 +2,7 @@ import dev.spiritstudios.snapper.util.ScreenshotTexture; import dev.spiritstudios.snapper.util.SnapperUtil; -import net.minecraft.Util; +import net.minecraft.util.Util; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.navigation.ScreenDirection; @@ -141,7 +141,7 @@ public void renderContent(GuiGraphics graphics, int mouseX, int mouseY, boolean if (this.icon.loaded()) { graphics.blit( RenderPipelines.GUI_TEXTURED, - this.icon.getTextureId(), + this.icon.textureLocation(), getContentX(), getContentY(), 0, 0, getContentWidth(), getContentHeight(), diff --git a/src/client/java/dev/spiritstudios/snapper/gui/widget/ScreenshotListWidget.java b/src/client/java/dev/spiritstudios/snapper/gui/widget/ScreenshotListWidget.java index 62e241c..690c23a 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/widget/ScreenshotListWidget.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/widget/ScreenshotListWidget.java @@ -2,22 +2,17 @@ import dev.spiritstudios.snapper.util.ScreenshotTexture; import dev.spiritstudios.snapper.util.SnapperUtil; -import net.minecraft.Util; +import net.minecraft.network.chat.Component; +import net.minecraft.util.FormattedCharSequence; +import net.minecraft.util.Util; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.screens.Screen; import net.minecraft.client.input.MouseButtonEvent; import net.minecraft.client.renderer.RenderPipelines; -import net.minecraft.network.chat.Component; import net.minecraft.util.CommonColors; -import net.minecraft.util.StringUtil; import org.jetbrains.annotations.Nullable; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.attribute.BasicFileAttributes; -import java.time.Instant; - public class ScreenshotListWidget extends ScreenshotsWidget { public ScreenshotListWidget( Minecraft client, @@ -64,7 +59,10 @@ public void renderContent(GuiGraphics graphics, int mouseX, int mouseY, boolean graphics.drawString( minecraft.font, - creation, + FormattedCharSequence.composite( + Component.translatable("text.snapper.created").getVisualOrderText(), + creation.getVisualOrderText() + ), getContentX() + 35, getContentY() + 12, CommonColors.GRAY, false @@ -73,7 +71,7 @@ public void renderContent(GuiGraphics graphics, int mouseX, int mouseY, boolean if (icon.loaded()) { graphics.blit( RenderPipelines.GUI_TEXTURED, - this.icon.getTextureId(), + this.icon.textureLocation(), getContentX(), getContentY(), (icon.getHeight()) / 3.0f + 32, 0, getContentHeight(), getContentHeight(), diff --git a/src/client/java/dev/spiritstudios/snapper/gui/widget/ScreenshotsWidget.java b/src/client/java/dev/spiritstudios/snapper/gui/widget/ScreenshotsWidget.java index 5f74db1..47bd5c6 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/widget/ScreenshotsWidget.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/widget/ScreenshotsWidget.java @@ -8,7 +8,7 @@ import dev.spiritstudios.snapper.util.SafeFiles; import dev.spiritstudios.snapper.util.ScreenshotActions; import dev.spiritstudios.snapper.util.ScreenshotTexture; -import net.minecraft.Util; +import net.minecraft.util.Util; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.components.ObjectSelectionList; @@ -16,7 +16,7 @@ import net.minecraft.client.gui.screens.Screen; import net.minecraft.client.input.KeyEvent; import net.minecraft.network.chat.Component; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import net.minecraft.util.CommonColors; import net.minecraft.util.StringUtil; import org.jetbrains.annotations.Nullable; @@ -37,9 +37,9 @@ import java.util.concurrent.CompletableFuture; public abstract class ScreenshotsWidget extends ObjectSelectionList { - protected static final ResourceLocation VIEW_SPRITE = Snapper.id("screenshots/view"); - protected static final ResourceLocation VIEW_HIGHLIGHTED_SPRITE = Snapper.id("screenshots/view_highlighted"); - protected static final ResourceLocation GRID_SELECTION_BACKGROUND_TEXTURE = Snapper.id("textures/gui/grid_selection_background.png"); + protected static final Identifier VIEW_SPRITE = Snapper.id("screenshots/view"); + protected static final Identifier VIEW_HIGHLIGHTED_SPRITE = Snapper.id("screenshots/view_highlighted"); + protected static final Identifier GRID_SELECTION_BACKGROUND_TEXTURE = Snapper.id("textures/gui/grid_selection_background.png"); protected final Screen parent; public final CompletableFuture> loadFuture; diff --git a/src/client/java/dev/spiritstudios/snapper/mixin/CameraMixin.java b/src/client/java/dev/spiritstudios/snapper/mixin/CameraMixin.java index 348e132..e012cfc 100644 --- a/src/client/java/dev/spiritstudios/snapper/mixin/CameraMixin.java +++ b/src/client/java/dev/spiritstudios/snapper/mixin/CameraMixin.java @@ -4,6 +4,7 @@ import net.minecraft.client.Minecraft; import net.minecraft.world.entity.Entity; import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.Level; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; @@ -13,12 +14,7 @@ public abstract class CameraMixin { @Inject(method = "setup", at = @At("HEAD"), cancellable = true) private void blockUpdateDuringPanoramaRender( - BlockGetter level, - Entity entity, - boolean detached, - boolean thirdPersonReverse, - float partialTick, - CallbackInfo ci + Level level, Entity entity, boolean detached, boolean mirror, float partialTickTime, CallbackInfo ci ) { if (Minecraft.getInstance().gameRenderer.isPanoramicMode() && detached) ci.cancel(); } diff --git a/src/client/java/dev/spiritstudios/snapper/mixin/MinecraftMixin.java b/src/client/java/dev/spiritstudios/snapper/mixin/MinecraftMixin.java index 464e383..7765b12 100644 --- a/src/client/java/dev/spiritstudios/snapper/mixin/MinecraftMixin.java +++ b/src/client/java/dev/spiritstudios/snapper/mixin/MinecraftMixin.java @@ -7,7 +7,7 @@ import com.mojang.blaze3d.pipeline.RenderTarget; import dev.spiritstudios.snapper.SnapperConfig; import dev.spiritstudios.snapper.mixin.accessor.CameraAccessor; -import net.minecraft.Util; +import net.minecraft.util.Util; import net.minecraft.client.Minecraft; import net.minecraft.client.Options; import net.minecraft.client.main.GameConfig; diff --git a/src/client/java/dev/spiritstudios/snapper/mixin/PauseScreenMixin.java b/src/client/java/dev/spiritstudios/snapper/mixin/PauseScreenMixin.java index 6aabaa8..d93496d 100644 --- a/src/client/java/dev/spiritstudios/snapper/mixin/PauseScreenMixin.java +++ b/src/client/java/dev/spiritstudios/snapper/mixin/PauseScreenMixin.java @@ -8,7 +8,7 @@ import net.minecraft.client.gui.screens.PauseScreen; import net.minecraft.client.gui.screens.Screen; import net.minecraft.network.chat.Component; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; @@ -22,7 +22,7 @@ protected PauseScreenMixin(Component title) { } @Unique - private static final ResourceLocation SNAPPER_BUTTON_ICON = Snapper.id("screenshots/screenshot"); + private static final Identifier SNAPPER_BUTTON_ICON = Snapper.id("screenshots/screenshot"); @Inject( method = "init", diff --git a/src/client/java/dev/spiritstudios/snapper/mixin/TitleScreenMixin.java b/src/client/java/dev/spiritstudios/snapper/mixin/TitleScreenMixin.java index 110c8eb..aa86054 100644 --- a/src/client/java/dev/spiritstudios/snapper/mixin/TitleScreenMixin.java +++ b/src/client/java/dev/spiritstudios/snapper/mixin/TitleScreenMixin.java @@ -7,7 +7,7 @@ import net.minecraft.client.gui.screens.Screen; import net.minecraft.client.gui.screens.TitleScreen; import net.minecraft.network.chat.Component; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; @@ -19,7 +19,7 @@ @Mixin(TitleScreen.class) public abstract class TitleScreenMixin extends Screen { @Unique - private static final ResourceLocation SNAPPER_BUTTON_ICON = Snapper.id("screenshots/screenshot"); + private static final Identifier SNAPPER_BUTTON_ICON = Snapper.id("screenshots/screenshot"); protected TitleScreenMixin(Component title) { super(title); diff --git a/src/client/java/dev/spiritstudios/snapper/util/DynamicCubemapTexture.java b/src/client/java/dev/spiritstudios/snapper/util/DynamicCubemapTexture.java index 0020af5..7ba31e7 100644 --- a/src/client/java/dev/spiritstudios/snapper/util/DynamicCubemapTexture.java +++ b/src/client/java/dev/spiritstudios/snapper/util/DynamicCubemapTexture.java @@ -3,9 +3,10 @@ import com.mojang.blaze3d.platform.NativeImage; import dev.spiritstudios.snapper.Snapper; import net.minecraft.client.renderer.texture.CubeMapTexture; +import net.minecraft.client.renderer.texture.MipmapStrategy; import net.minecraft.client.renderer.texture.TextureContents; import net.minecraft.client.resources.metadata.texture.TextureMetadataSection; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import net.minecraft.server.packs.resources.ResourceManager; import java.io.IOException; @@ -18,7 +19,7 @@ public class DynamicCubemapTexture extends CubeMapTexture { private static final String[] TEXTURE_SUFFIXES = new String[]{"_1.png", "_3.png", "_5.png", "_4.png", "_0.png", "_2.png"}; private final Path path; - public DynamicCubemapTexture(ResourceLocation id, Path path) { + public DynamicCubemapTexture(Identifier id, Path path) { super(id); this.path = path; } @@ -47,12 +48,15 @@ public TextureContents loadContents(ResourceManager resourceManager) throws IOEx } baseImage.close(); - contents = new TextureContents(image, new TextureMetadataSection(true, false)); + contents = new TextureContents( + image, + new TextureMetadataSection(true, false, MipmapStrategy.MEAN, 0.0F) + ); } return contents; } - public static Optional createPanorama(ResourceLocation id, Path path) { + public static Optional createPanorama(Identifier id, Path path) { return Optional.of(new DynamicCubemapTexture( id, path diff --git a/src/client/java/dev/spiritstudios/snapper/util/PlatformHelper.java b/src/client/java/dev/spiritstudios/snapper/util/PlatformHelper.java index 0597a91..c85e349 100644 --- a/src/client/java/dev/spiritstudios/snapper/util/PlatformHelper.java +++ b/src/client/java/dev/spiritstudios/snapper/util/PlatformHelper.java @@ -2,7 +2,7 @@ import dev.spiritstudios.snapper.util.actions.GeneralPlatformActions; import dev.spiritstudios.snapper.util.actions.MacPlatformActions; -import net.minecraft.Util; +import net.minecraft.util.Util; import java.nio.file.Path; diff --git a/src/client/java/dev/spiritstudios/snapper/util/ScreenshotTexture.java b/src/client/java/dev/spiritstudios/snapper/util/ScreenshotTexture.java index 3070105..0831486 100644 --- a/src/client/java/dev/spiritstudios/snapper/util/ScreenshotTexture.java +++ b/src/client/java/dev/spiritstudios/snapper/util/ScreenshotTexture.java @@ -2,11 +2,11 @@ import com.mojang.blaze3d.platform.NativeImage; import dev.spiritstudios.snapper.Snapper; -import net.minecraft.Util; +import net.minecraft.util.Util; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.texture.DynamicTexture; import net.minecraft.client.renderer.texture.TextureManager; -import net.minecraft.resources.ResourceLocation; +import net.minecraft.resources.Identifier; import java.io.IOException; import java.io.InputStream; @@ -16,18 +16,18 @@ import java.util.concurrent.CompletableFuture; public class ScreenshotTexture implements AutoCloseable { - private static final ResourceLocation UNKNOWN_SERVER = ResourceLocation.withDefaultNamespace("textures/misc/unknown_server.png"); + private static final Identifier UNKNOWN_SERVER = Identifier.withDefaultNamespace("textures/misc/unknown_server.png"); private final TextureManager textureManager; - private final ResourceLocation id; + private final Identifier textureLocation; private final Path path; private final NativeImage image; private DynamicTexture texture; - private ScreenshotTexture(TextureManager textureManager, ResourceLocation id, Path path) throws IOException { + private ScreenshotTexture(TextureManager textureManager, Identifier textureLocation, Path path) throws IOException { this.textureManager = textureManager; - this.id = id; + this.textureLocation = textureLocation; this.path = path; @@ -38,8 +38,8 @@ private ScreenshotTexture(TextureManager textureManager, ResourceLocation id, Pa public CompletableFuture load() { return Minecraft.getInstance().submit(() -> { - this.texture = new DynamicTexture(this.id::toString, this.image); - this.textureManager.register(this.id, this.texture); + this.texture = new DynamicTexture(this.textureLocation::toString, this.image); + this.textureManager.register(this.textureLocation, this.texture); }); } @@ -48,7 +48,7 @@ public static Optional createScreenshot(TextureManager textur return Optional.of(new ScreenshotTexture( textureManager, Snapper.id( - "screenshots/" + Util.sanitizeName(path.getFileName().toString(), ResourceLocation::validPathChar) + "/icon" + "screenshots/" + Util.sanitizeName(path.getFileName().toString(), Identifier::validPathChar) + "/icon" ), path )); @@ -57,15 +57,8 @@ public static Optional createScreenshot(TextureManager textur } } - /* - * Must be called on render thread - */ - public void enableFiltering() { - this.texture.setFilter(true, true); - } - public void destroy() { - this.textureManager.release(this.id); + this.textureManager.release(this.textureLocation); this.texture.close(); } @@ -77,8 +70,8 @@ public int getHeight() { return this.texture != null ? this.texture.getTexture().getHeight(0) : 64; } - public ResourceLocation getTextureId() { - return this.texture != null ? this.id : UNKNOWN_SERVER; + public Identifier textureLocation() { + return this.texture != null ? this.textureLocation : UNKNOWN_SERVER; } public boolean loaded() { diff --git a/src/client/java/dev/spiritstudios/snapper/util/SnapperUtil.java b/src/client/java/dev/spiritstudios/snapper/util/SnapperUtil.java index 7e392f7..eaf877f 100644 --- a/src/client/java/dev/spiritstudios/snapper/util/SnapperUtil.java +++ b/src/client/java/dev/spiritstudios/snapper/util/SnapperUtil.java @@ -4,7 +4,6 @@ import com.mojang.serialization.DataResult; import dev.spiritstudios.snapper.Snapper; import dev.spiritstudios.snapper.SnapperConfig; -import net.minecraft.Util; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.Font; import net.minecraft.locale.Language; @@ -12,6 +11,7 @@ import net.minecraft.network.chat.Component; import net.minecraft.network.chat.FormattedText; import net.minecraft.util.FormattedCharSequence; +import net.minecraft.util.Util; import org.apache.commons.lang3.SystemProperties; import java.nio.file.Files; diff --git a/src/client/java/dev/spiritstudios/snapper/util/uploading/AxolotlClientApi.java b/src/client/java/dev/spiritstudios/snapper/util/uploading/AxolotlClientApi.java index 56307ca..af9bdaa 100644 --- a/src/client/java/dev/spiritstudios/snapper/util/uploading/AxolotlClientApi.java +++ b/src/client/java/dev/spiritstudios/snapper/util/uploading/AxolotlClientApi.java @@ -8,7 +8,7 @@ import dev.spiritstudios.snapper.Snapper; import dev.spiritstudios.snapper.SnapperConfig; import dev.spiritstudios.snapper.gui.toast.SnapperToast; -import net.minecraft.Util; +import net.minecraft.util.Util; import net.minecraft.client.Minecraft; import net.minecraft.client.User; import net.minecraft.network.chat.Component; diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 8998d6a..c36e497 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -39,7 +39,7 @@ ], "depends": { "fabricloader": ">=${loader_version}", - "minecraft": "1.21.10", + "minecraft": "1.21.11", "fabric-api": "*", "java": ">=21", "greenhouseconfig": ">=3.0.0-" From 8129c4d7a1bffb9f90ac2f39a6ca4aa53c53157c Mon Sep 17 00:00:00 2001 From: CallMeEchoCodes Date: Mon, 16 Mar 2026 14:43:55 +1100 Subject: [PATCH 2/5] feat(ui): on-demand texture loading and improved layouting --- .../snapper/SnapperKeybindings.java | 2 - .../snapper/gui/screen/ConfigScreen.java | 9 +- .../gui/screen/ScreenshotListScreen.java | 212 ++++++-------- .../gui/screen/ScreenshotViewerScreen.java | 259 +++++++----------- .../snapper/gui/widget/DeadSpaceElement.java | 54 ++++ .../gui/widget/ScreenshotGridWidget.java | 22 +- .../gui/widget/ScreenshotListWidget.java | 35 +-- .../snapper/gui/widget/ScreenshotsWidget.java | 105 +++---- .../snapper/gui/widget/ViewModeButton.java | 41 +++ .../snapper/util/ScreenshotTexture.java | 104 ++++--- 10 files changed, 431 insertions(+), 412 deletions(-) create mode 100644 src/client/java/dev/spiritstudios/snapper/gui/widget/DeadSpaceElement.java create mode 100644 src/client/java/dev/spiritstudios/snapper/gui/widget/ViewModeButton.java diff --git a/src/client/java/dev/spiritstudios/snapper/SnapperKeybindings.java b/src/client/java/dev/spiritstudios/snapper/SnapperKeybindings.java index b89a644..02b38b5 100644 --- a/src/client/java/dev/spiritstudios/snapper/SnapperKeybindings.java +++ b/src/client/java/dev/spiritstudios/snapper/SnapperKeybindings.java @@ -79,10 +79,8 @@ private static void openRecentScreenshot(Minecraft client) { image -> { client.setScreen(new ScreenshotViewerScreen( image, - latestPath, client.screen )); - image.load(); }, () -> SnapperToast.push( SnapperToast.Type.DENY, diff --git a/src/client/java/dev/spiritstudios/snapper/gui/screen/ConfigScreen.java b/src/client/java/dev/spiritstudios/snapper/gui/screen/ConfigScreen.java index 887a02d..d9441ea 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/screen/ConfigScreen.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/screen/ConfigScreen.java @@ -249,13 +249,14 @@ public void reset() { @Override public void onClose() { - assert minecraft != null; + var viewMode = SnapperConfig.HOLDER.get().viewMode(); - minecraft.setScreen(lastScreen); config.saveAsync().thenRun(() -> { - if (lastScreen instanceof ScreenshotListScreen screenshotsScreen) { - screenshotsScreen.refresh(); + if (lastScreen instanceof ScreenshotListScreen screenshotsScreen && config.viewMode != viewMode) { + screenshotsScreen.recreateList(); } }); + + minecraft.setScreen(lastScreen); } } diff --git a/src/client/java/dev/spiritstudios/snapper/gui/screen/ScreenshotListScreen.java b/src/client/java/dev/spiritstudios/snapper/gui/screen/ScreenshotListScreen.java index 62621dc..2d41780 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/screen/ScreenshotListScreen.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/screen/ScreenshotListScreen.java @@ -5,25 +5,24 @@ import dev.spiritstudios.snapper.Snapper; import dev.spiritstudios.snapper.SnapperConfig; import dev.spiritstudios.snapper.gui.toast.SnapperToast; +import dev.spiritstudios.snapper.gui.widget.DeadSpaceElement; import dev.spiritstudios.snapper.gui.widget.ScreenshotListWidget; import dev.spiritstudios.snapper.gui.widget.ScreenshotsWidget; +import dev.spiritstudios.snapper.gui.widget.ViewModeButton; import dev.spiritstudios.snapper.util.PlatformHelper; import dev.spiritstudios.snapper.util.ScreenshotActions; import dev.spiritstudios.snapper.util.SnapperUtil; import dev.spiritstudios.snapper.util.uploading.ScreenshotUploading; -import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.components.Button; import net.minecraft.client.gui.components.SpriteIconButton; import net.minecraft.client.gui.components.Tooltip; -import net.minecraft.client.gui.layouts.EqualSpacingLayout; -import net.minecraft.client.gui.layouts.FrameLayout; +import net.minecraft.client.gui.layouts.HeaderAndFooterLayout; import net.minecraft.client.gui.layouts.LinearLayout; import net.minecraft.client.gui.screens.Screen; import net.minecraft.client.input.KeyEvent; import net.minecraft.network.chat.CommonComponents; import net.minecraft.network.chat.Component; import net.minecraft.resources.Identifier; -import net.minecraft.util.CommonColors; import net.minecraft.util.StringRepresentable; import net.minecraft.util.Util; import org.jetbrains.annotations.Nullable; @@ -37,10 +36,6 @@ public class ScreenshotListScreen extends Screen { private static final Identifier SETTINGS_ICON = Snapper.id("screenshots/settings"); - private static final Identifier VIEW_MODE_ICON_LIST = Snapper.id("screenshots/show_list"); - - private static final Identifier VIEW_MODE_ICON_GRID = Snapper.id("screenshots/show_grid"); - private final Screen parent; private final boolean isOffline; @@ -52,21 +47,19 @@ public class ScreenshotListScreen extends Screen { private Button copyButton; private Button openButton; private Button uploadButton; - private SpriteIconButton viewModeButton; + private @Nullable ScreenshotListWidget.ScreenshotEntry selectedScreenshot = null; + private final HeaderAndFooterLayout layout = new HeaderAndFooterLayout(this, 33, 60); + public ScreenshotListScreen(Screen parent) { super(Component.translatable("menu.snapper.screenshot_menu")); this.parent = parent; this.isOffline = SnapperUtil.isOfflineAccount(); + this.recreateList(); } - public synchronized void refresh() { - recreateList(); - recreateViewModeButton(); - } - - private void recreateList() { + public synchronized void recreateList() { if (screenshots != null) { this.removeWidget(screenshots); } @@ -79,158 +72,132 @@ private void recreateList() { screenshots, this )); - } - private void recreateViewModeButton() { - if (viewModeButton != null) { - removeWidget(this.viewModeButton); - } + repositionElements(); + } - this.viewModeButton = addRenderableWidget(SpriteIconButton.builder( - Component.translatable("config.snapper.viewMode"), - button -> this.toggleGrid(), - true - ).width(20).sprite(SnapperConfig.HOLDER.get().viewMode() == ViewMode.LIST ? VIEW_MODE_ICON_LIST : VIEW_MODE_ICON_GRID, 15, 15).build()); - viewModeButton.setPosition(width / 2 - 178, height - 56); + @Override + protected void repositionElements() { + this.layout.arrangeElements(); + screenshots.updateSize( + width, + layout + ); } @Override protected void init() { - recreateList(); + this.layout.addTitleHeader(this.title, this.font); + + final int hSpacing = 4; + + final int buttonWidth = 74; + final int bottomButtonWidth = 100; + + LinearLayout vertical = this.layout.addToFooter(LinearLayout.vertical().spacing(4)); + vertical.defaultCellSetting().alignHorizontallyCenter(); - int secondRowButtonWidth = 100; + LinearLayout topRow = vertical.addChild(LinearLayout.horizontal().spacing(hSpacing)); + LinearLayout bottomRow = vertical.addChild(LinearLayout.horizontal().spacing(hSpacing)); - Button folderButton = addRenderableWidget(Button.builder( + bottomRow.addChild(SpriteIconButton.builder( + Component.translatable("config.snapper.title"), + button -> this.minecraft.setScreen( + new ConfigScreen(this)), + true + ).width(20).sprite(SETTINGS_ICON, 15, 15).build()); + + bottomRow.addChild(Button.builder( Component.translatable("button.snapper.folder"), button -> Util.getPlatform().openPath(SnapperUtil.getConfiguredScreenshotDirectory()) - ).width(secondRowButtonWidth).build()); + ).width(bottomButtonWidth).build()); - - this.openButton = addRenderableWidget(Button.builder( + this.openButton = bottomRow.addChild(Button.builder( Component.translatable("button.snapper.open"), button -> { if (selectedScreenshot != null) { - Util.getPlatform().openPath(selectedScreenshot.icon.getPath()); + Util.getPlatform().openPath(selectedScreenshot.texture.path); } } - ).width(secondRowButtonWidth).build()); + ).width(bottomButtonWidth).build()); - Button doneButton = addRenderableWidget(Button.builder( + bottomRow.addChild(Button.builder( CommonComponents.GUI_DONE, button -> this.onClose() - ).width(secondRowButtonWidth).build()); + ).width(bottomButtonWidth).build()); + Path panoramaDir = SnapperUtil.getConfiguredScreenshotDirectory().resolve("panorama"); + boolean hasPanorama = SnapperUtil.panoramaPresent(panoramaDir); + + SpriteIconButton panoramaButton = bottomRow.addChild( + SpriteIconButton.builder( + Component.translatable("button.snapper.screenshots"), + button -> this.minecraft.setScreen(new PanoramaViewerScreen(Component.translatable("menu.snapper.panorama").getString(), this)), + true + ).width(20).sprite(hasPanorama ? PANORAMA_BUTTON_ICON : PANORAMA_BUTTON_DISABLED_ICON, 15, 15).build()); - int firstRowButtonWidth = 58; + panoramaButton.active = hasPanorama; + + panoramaButton.setTooltip(Tooltip.create(Component.translatable(hasPanorama ? + "button.snapper.panorama.tooltip" : + "text.snapper.panorama_encourage"))); - this.deleteButton = addRenderableWidget(Button.builder( + topRow.addChild(new ViewModeButton( + button -> this.toggleGrid(), + null + )); + + this.deleteButton = topRow.addChild(Button.builder( Component.translatable("button.snapper.delete"), button -> { if (selectedScreenshot != null) { - ScreenshotActions.deleteScreenshot(selectedScreenshot.icon.getPath(), this); + ScreenshotActions.deleteScreenshot(selectedScreenshot.texture.path, this); } } - ).width(firstRowButtonWidth).build()); + ).width(buttonWidth).build()); - this.renameButton = addRenderableWidget(Button.builder( + this.renameButton = topRow.addChild(Button.builder( Component.translatable("button.snapper.rename"), button -> { if (this.selectedScreenshot != null) { - minecraft.setScreen(new ScreenshotRenameScreen(this.selectedScreenshot.icon.getPath(), this)); + minecraft.setScreen(new ScreenshotRenameScreen(this.selectedScreenshot.texture.path, this)); } } - ).width(firstRowButtonWidth).build()); + ).width(buttonWidth).build()); - this.copyButton = addRenderableWidget(Button.builder( + this.copyButton = topRow.addChild(Button.builder( Component.translatable("button.snapper.copy"), button -> { if (selectedScreenshot != null) { - PlatformHelper.INSTANCE.copyScreenshot(selectedScreenshot.icon.getPath()); - } - } - ).width(firstRowButtonWidth).build()); - - this.viewButton = addRenderableWidget(Button.builder( - Component.translatable("button.snapper.view"), - button -> { - if (selectedScreenshot != null) { - this.minecraft.setScreen(new ScreenshotViewerScreen( - selectedScreenshot.icon, - selectedScreenshot.icon.getPath(), - selectedScreenshot.screenParent - )); + PlatformHelper.INSTANCE.copyScreenshot(selectedScreenshot.texture.path); } } - ).width(firstRowButtonWidth).build()); + ).width(buttonWidth).build()); - this.uploadButton = addRenderableWidget(Button.builder(Component.translatable("button.snapper.upload"), button -> { + this.uploadButton = topRow.addChild(Button.builder(Component.translatable("button.snapper.upload"), button -> { if (selectedScreenshot == null) return; button.active = false; - ScreenshotUploading.upload(selectedScreenshot.icon.getPath()) + ScreenshotUploading.upload(selectedScreenshot.texture.path) .thenRun(() -> button.active = true); - }).width(firstRowButtonWidth).build()); + }).width(buttonWidth).build()); if (isOffline) { this.uploadButton.setTooltip(Tooltip.create(Component.translatable("button.snapper.upload.tooltip"))); } - LinearLayout verticalButtonLayout = LinearLayout.vertical() - .spacing(4); - - EqualSpacingLayout firstRowWidget = verticalButtonLayout.addChild(new EqualSpacingLayout( - 308, - 20, - EqualSpacingLayout.Orientation.HORIZONTAL - )); - - firstRowWidget.addChild(this.deleteButton); - firstRowWidget.addChild(this.renameButton); - firstRowWidget.addChild(this.copyButton); - firstRowWidget.addChild(this.viewButton); - firstRowWidget.addChild(this.uploadButton); - - EqualSpacingLayout secondRowWidget = verticalButtonLayout.addChild(new EqualSpacingLayout( - 308, - 20, - EqualSpacingLayout.Orientation.HORIZONTAL - )); - - secondRowWidget.addChild(folderButton); - secondRowWidget.addChild(openButton); - secondRowWidget.addChild(doneButton); - - verticalButtonLayout.arrangeElements(); - FrameLayout.centerInRectangle(verticalButtonLayout, 0, this.height - 66, this.width, 64); - - SpriteIconButton settingsButton = addRenderableWidget(SpriteIconButton.builder( - Component.translatable("config.snapper.title"), - button -> this.minecraft.setScreen( - new ConfigScreen(new ScreenshotListScreen(this.parent))), - true - ).width(20).sprite(SETTINGS_ICON, 15, 15).build()); - - settingsButton.setPosition(width / 2 - 178, height - 32); - - recreateViewModeButton(); + topRow.addChild(new DeadSpaceElement(20, 20)); - Path panoramaDir = SnapperUtil.getConfiguredScreenshotDirectory().resolve("panorama"); - boolean hasPanorama = SnapperUtil.panoramaPresent(panoramaDir); - - SpriteIconButton panoramaButton = addRenderableWidget(SpriteIconButton.builder( - Component.translatable("button.snapper.screenshots"), - button -> this.minecraft.setScreen(new PanoramaViewerScreen(Component.translatable("menu.snapper.panorama").getString(), this)), - true - ).width(20).sprite(hasPanorama ? PANORAMA_BUTTON_ICON : PANORAMA_BUTTON_DISABLED_ICON, 15, 15).build()); - - panoramaButton.active = hasPanorama; - panoramaButton.setPosition(width / 2 + 158, height - 32); + this.imageSelected(selectedScreenshot); - panoramaButton.setTooltip(Tooltip.create(Component.translatable(hasPanorama ? - "button.snapper.panorama.tooltip" : - "text.snapper.panorama_encourage"))); + this.layout.visitWidgets(this::addRenderableWidget); + this.repositionElements(); + } - this.imageSelected(selectedScreenshot); + @Override + public void onClose() { + super.onClose(); + screenshots.clearEntries(); } public void imageSelected(@Nullable ScreenshotListWidget.ScreenshotEntry screenshot) { @@ -239,7 +206,7 @@ public void imageSelected(@Nullable ScreenshotListWidget.ScreenshotEntry screens this.deleteButton.active = hasScreenshot; this.openButton.active = hasScreenshot; this.renameButton.active = hasScreenshot; - this.viewButton.active = hasScreenshot; +// this.viewButton.active = hasScreenshot; this.selectedScreenshot = screenshot; this.uploadButton.active = !isOffline && hasScreenshot; } @@ -247,7 +214,7 @@ public void imageSelected(@Nullable ScreenshotListWidget.ScreenshotEntry screens public void toggleGrid() { SnapperConfig.edit(m -> m.viewMode = SnapperConfig.HOLDER.get().viewMode() == ViewMode.GRID ? ViewMode.LIST : ViewMode.GRID); - refresh(); + recreateList(); } @Override @@ -264,25 +231,19 @@ public boolean keyPressed(KeyEvent input) { if (selectedScreenshot == null) return false; if ((input.modifiers() & InputConstants.MOD_CONTROL) != 0 && input.key() == InputConstants.KEY_C) { - PlatformHelper.INSTANCE.copyScreenshot(selectedScreenshot.icon.getPath()); + PlatformHelper.INSTANCE.copyScreenshot(selectedScreenshot.texture.path); SnapperToast.push(SnapperToast.Type.SCREENSHOT, Component.translatable("toast.snapper.screenshot.copy"), null); return true; } if (input.key() == InputConstants.KEY_RETURN) { - minecraft.setScreen(new ScreenshotViewerScreen(selectedScreenshot.icon, selectedScreenshot.icon.getPath(), this)); + minecraft.setScreen(new ScreenshotViewerScreen(selectedScreenshot.texture, this)); return true; } return false; } - @Override - public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { - super.render(graphics, mouseX, mouseY, delta); - graphics.drawCenteredString(this.font, this.title, this.width / 2, 20, CommonColors.WHITE); - } - public enum ViewMode implements StringRepresentable { LIST("list"), GRID("grid"); @@ -300,4 +261,5 @@ public enum ViewMode implements StringRepresentable { return name; } } + } diff --git a/src/client/java/dev/spiritstudios/snapper/gui/screen/ScreenshotViewerScreen.java b/src/client/java/dev/spiritstudios/snapper/gui/screen/ScreenshotViewerScreen.java index 77eeadc..4ee0c92 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/screen/ScreenshotViewerScreen.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/screen/ScreenshotViewerScreen.java @@ -1,19 +1,15 @@ package dev.spiritstudios.snapper.gui.screen; -import dev.spiritstudios.snapper.Snapper; import dev.spiritstudios.snapper.util.PlatformHelper; import dev.spiritstudios.snapper.util.ScreenshotActions; import dev.spiritstudios.snapper.util.ScreenshotTexture; import dev.spiritstudios.snapper.util.SnapperUtil; import dev.spiritstudios.snapper.util.uploading.ScreenshotUploading; import net.fabricmc.loader.api.FabricLoader; -import net.minecraft.util.Util; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.components.Button; import net.minecraft.client.gui.components.Tooltip; -import net.minecraft.client.gui.layouts.EqualSpacingLayout; -import net.minecraft.client.gui.layouts.FrameLayout; import net.minecraft.client.gui.layouts.HeaderAndFooterLayout; import net.minecraft.client.gui.layouts.LinearLayout; import net.minecraft.client.gui.screens.Screen; @@ -22,189 +18,147 @@ import net.minecraft.network.chat.Component; import net.minecraft.resources.Identifier; import net.minecraft.util.CommonColors; +import net.minecraft.util.Util; import org.jetbrains.annotations.Nullable; -import javax.imageio.ImageIO; -import java.awt.image.BufferedImage; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Files; import java.nio.file.Path; import java.util.List; public class ScreenshotViewerScreen extends Screen { - private static final Identifier MENU_DECOR_BACKGROUND_TEXTURE = Identifier.withDefaultNamespace("textures/gui/menu_list_background.png"); - private static final Identifier INWORLD_MENU_DECOR_BACKGROUND_TEXTURE = Identifier.withDefaultNamespace("textures/gui/inworld_menu_list_background.png"); + private static final Identifier MENU_LIST_BACKGROUND = Identifier.withDefaultNamespace("textures/gui/menu_list_background.png"); + private static final Identifier INWORLD_MENU_LIST_BACKGROUND = Identifier.withDefaultNamespace("textures/gui/inworld_menu_list_background.png"); private final Minecraft client = Minecraft.getInstance(); - private final ScreenshotTexture image; - private final String title; - private final int imageWidth; - private final int imageHeight; + private final ScreenshotTexture texture; + private final Screen parent; - private final Path screenshot; private final @Nullable List screenshots; private final int screenshotIndex; - private final Path iconPath; - private final HeaderAndFooterLayout layout = new HeaderAndFooterLayout(this); + private final HeaderAndFooterLayout layout = new HeaderAndFooterLayout(this, 33, 60); - public ScreenshotViewerScreen(ScreenshotTexture icon, Path screenshot, Screen parent) { - this(icon, screenshot, parent, null); + public ScreenshotViewerScreen(ScreenshotTexture texture, Screen parent) { + this(texture, parent, null); } - public ScreenshotViewerScreen(ScreenshotTexture icon, Path iconPath, Screen parent, @Nullable List screenshots) { - super(Component.translatable("menu.snapper.viewer_menu")); + public ScreenshotViewerScreen(ScreenshotTexture texture, Screen parent, @Nullable List screenshots) { + super(Component.literal(texture.path.getFileName().toString())); this.parent = parent; - this.iconPath = iconPath; - - BufferedImage image = null; - - try (InputStream stream = Files.newInputStream(iconPath)) { - image = ImageIO.read(stream); - } catch (IOException e) { - Snapper.LOGGER.error("Failed to read image.", e); - this.client.setScreen(parent); - } - this.image = icon; - this.title = iconPath.getFileName().toString(); + this.texture = texture; + texture.startLoading(Minecraft.getInstance()); - this.imageWidth = image != null ? image.getWidth() : 0; - this.imageHeight = image != null ? image.getHeight() : 0; - - this.screenshot = iconPath; this.screenshots = screenshots; - this.screenshotIndex = this.screenshots != null ? this.screenshots.indexOf(this.screenshot) : -1; + this.screenshotIndex = this.screenshots != null ? this.screenshots.indexOf(this.texture.path) : -1; } @Override public void onClose() { + if (!(parent instanceof ScreenshotListScreen)) { + this.texture.close(); + } + this.client.setScreen(this.parent); } + @Override + protected void repositionElements() { + this.layout.arrangeElements(); + } + @Override protected void init() { + // TODO: Dedupe code from here and ScreenshotListScreen + this.layout.addTitleHeader(this.title, this.font); - int firstRowButtonWidth = 74; + final int hSpacing = 4; - // OPEN FOLDER + final int buttonWidth = 74; + final int bottomButtonWidth = 100; - Button folderButton = addRenderableWidget(Button.builder( - Component.translatable("button.snapper.folder"), - button -> Util.getPlatform().openFile(new File(client.gameDirectory, "screenshots")) - ) - .width(100) - .build() - ); + LinearLayout vertical = this.layout.addToFooter(LinearLayout.vertical().spacing(4)); + vertical.defaultCellSetting().alignHorizontallyCenter(); - // OPEN IMAGE EXTERNALLY + LinearLayout topRow = vertical.addChild(LinearLayout.horizontal().spacing(hSpacing)); + LinearLayout bottomRow = vertical.addChild(LinearLayout.horizontal().spacing(hSpacing)); - Button openButton = addRenderableWidget(Button.builder( - Component.translatable("button.snapper.open"), - button -> Util.getPlatform().openPath(this.iconPath) - ).width(100).build()); + bottomRow.addChild(Button.builder( + Component.translatable("button.snapper.folder"), + button -> Util.getPlatform().openPath(SnapperUtil.getConfiguredScreenshotDirectory()) + ).width(bottomButtonWidth).build()); - // EXIT PAGE + bottomRow.addChild(Button.builder( + Component.translatable("button.snapper.open"), + button -> { + Util.getPlatform().openPath(this.texture.path); + } + ).width(bottomButtonWidth).build()); - Button doneButton = addRenderableWidget(Button.builder( + bottomRow.addChild(Button.builder( CommonComponents.GUI_DONE, button -> this.onClose() - ).width(100).build()); - - // DELETE SCREENSHOT + ).width(bottomButtonWidth).build()); - Button deleteButton = addRenderableWidget(Button.builder( + topRow.addChild(Button.builder( Component.translatable("button.snapper.delete"), - button -> ScreenshotActions.deleteScreenshot(this.screenshot, this.parent) - ).width(firstRowButtonWidth).build()); - - // RENAME SCREENSHOT + button -> { + ScreenshotActions.deleteScreenshot(this.texture.path, parent); + } + ).width(buttonWidth).build()); - Button renameButton = addRenderableWidget(Button.builder( + topRow.addChild(Button.builder( Component.translatable("button.snapper.rename"), button -> { - if (this.screenshot != null) - client.setScreen(new ScreenshotRenameScreen(this.screenshot, this.parent)); + minecraft.setScreen(new ScreenshotRenameScreen(this.texture.path, this)); } - ).width(firstRowButtonWidth).build()); + ).width(buttonWidth).build()); - // COPY SCREENSHOT - - Button copyButton = addRenderableWidget(Button.builder( + topRow.addChild(Button.builder( Component.translatable("button.snapper.copy"), - button -> PlatformHelper.INSTANCE.copyScreenshot(this.screenshot) - ).width(firstRowButtonWidth).build()); - - // UPLOAD SCREENSHOT - - Button uploadButton = addRenderableWidget(Button.builder( - Component.translatable("button.snapper.upload"), button -> { - button.active = false; - ScreenshotUploading.upload(iconPath).thenRun(() -> button.active = true); + PlatformHelper.INSTANCE.copyScreenshot(this.texture.path); } - ).width(firstRowButtonWidth).build()); + ).width(buttonWidth).build()); + + var uploadButton = topRow.addChild(Button.builder(Component.translatable("button.snapper.upload"), button -> { + button.active = false; + ScreenshotUploading.upload(this.texture.path) + .thenRun(() -> button.active = true); + }).width(buttonWidth).build()); if (SnapperUtil.isOfflineAccount()) { uploadButton.active = false; uploadButton.setTooltip(Tooltip.create(Component.translatable("button.snapper.upload.tooltip"))); } - LinearLayout verticalButtonLayout = LinearLayout.vertical().spacing(4); - - EqualSpacingLayout firstRowWidget = verticalButtonLayout.addChild(new EqualSpacingLayout( - 308, - 20, - EqualSpacingLayout.Orientation.HORIZONTAL) - ); - - firstRowWidget.addChild(deleteButton); - firstRowWidget.addChild(renameButton); - firstRowWidget.addChild(copyButton); - firstRowWidget.addChild(uploadButton); - - EqualSpacingLayout secondRowWidget = verticalButtonLayout.addChild(new EqualSpacingLayout( - 308, - 20, - EqualSpacingLayout.Orientation.HORIZONTAL) - ); - - secondRowWidget.addChild(folderButton); - secondRowWidget.addChild(openButton); - secondRowWidget.addChild(doneButton); - - verticalButtonLayout.arrangeElements(); - FrameLayout.centerInRectangle(verticalButtonLayout, 0, this.height - 66, this.width, 64); - - layout.setHeaderHeight(46); - layout.setFooterHeight(height - 68); + this.layout.visitWidgets(this::addRenderableWidget); + this.repositionElements(); } @Override - public void render(GuiGraphics context, int mouseX, int mouseY, float delta) { - super.render(context, mouseX, mouseY, delta); - - this.drawMenuBackground(context); - this.drawHeaderAndFooterSeparators(context); - context.drawCenteredString(this.font, this.title, this.width / 2, 20, CommonColors.WHITE); - - int finalHeight = this.height - 50 - 68; - float scaleFactor = (float) finalHeight / imageHeight; - int finalWidth = (int) (imageWidth * scaleFactor); - - context.blit( - RenderPipelines.GUI_TEXTURED, - this.image.textureLocation(), - (this.width / 2) - (finalWidth / 2), this.height - 70 - finalHeight, - 0, 0, - finalWidth, finalHeight, - finalWidth, finalHeight - ); + public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { + super.render(graphics, mouseX, mouseY, delta); + + this.drawMenuBackground(graphics); + this.drawHeaderAndFooterSeparators(graphics); + + int finalHeight = layout.getContentHeight(); + float scaleFactor = (float) finalHeight / texture.getHeight(); + int finalWidth = (int) (texture.getWidth() * scaleFactor); + + if (texture.isLoaded()) { + graphics.blit( + RenderPipelines.GUI_TEXTURED, + this.texture.textureLocation(), + (this.width / 2) - (finalWidth / 2), layout.getHeaderHeight(), + 0, 0, + finalWidth, finalHeight, + finalWidth, finalHeight + ); + } if (screenshotIndex != -1 && screenshots != null) { - context.drawCenteredString( + graphics.drawCenteredString( this.font, "Screenshot %d/%d".formatted(screenshotIndex + 1, screenshots.size()), this.width / 2, @@ -215,15 +169,15 @@ public void render(GuiGraphics context, int mouseX, int mouseY, float delta) { // TODO: Maybe add an option to the debug menu to turn this off if (FabricLoader.getInstance().isDevelopmentEnvironment()) { - context.drawCenteredString( + graphics.drawCenteredString( this.font, - Component.translatable("text.snapper.image_size", imageWidth, imageHeight), + Component.translatable("text.snapper.image_size", texture.getWidth(), texture.getHeight()), this.width / 2, 40, CommonColors.WHITE ); - context.drawCenteredString( + graphics.drawCenteredString( this.font, Component.translatable("text.snapper.screen_size", this.width, this.height), this.width / 2, @@ -231,14 +185,14 @@ public void render(GuiGraphics context, int mouseX, int mouseY, float delta) { CommonColors.WHITE ); - context.drawCenteredString(this.font, + graphics.drawCenteredString(this.font, Component.translatable("text.snapper.scale_factor", scaleFactor), this.width / 2, 60, CommonColors.WHITE ); - context.drawCenteredString( + graphics.drawCenteredString( this.font, Component.translatable("text.snapper.scale_size", finalWidth, finalHeight), this.width / 2, @@ -248,42 +202,35 @@ public void render(GuiGraphics context, int mouseX, int mouseY, float delta) { } } - private void drawMenuBackground(GuiGraphics context) { - context.blit( + private void drawMenuBackground(GuiGraphics graphics) { + graphics.blit( RenderPipelines.GUI_TEXTURED, - this.client.level == null ? - MENU_DECOR_BACKGROUND_TEXTURE : - INWORLD_MENU_DECOR_BACKGROUND_TEXTURE, - 0, - 48, - 0, + this.minecraft.level == null ? MENU_LIST_BACKGROUND : INWORLD_MENU_LIST_BACKGROUND, 0, + layout.getHeaderHeight(), + 0, 0, width, - height - 68 - 48, + layout.getContentHeight(), 32, 32 ); } - private void drawHeaderAndFooterSeparators(GuiGraphics context) { - context.blit( + private void drawHeaderAndFooterSeparators(GuiGraphics graphics) { + graphics.blit( RenderPipelines.GUI_TEXTURED, - this.client.level == null ? - Screen.HEADER_SEPARATOR : - Screen.INWORLD_HEADER_SEPARATOR, - 0, layout.getHeaderHeight(), - 0, 0, + this.minecraft.level == null ? Screen.HEADER_SEPARATOR : Screen.INWORLD_HEADER_SEPARATOR, + 0, layout.getHeaderHeight() - 2, + 0.0F, 0.0F, width, 2, 32, 2 ); - context.blit( + graphics.blit( RenderPipelines.GUI_TEXTURED, - this.client.level == null ? - Screen.FOOTER_SEPARATOR : - Screen.INWORLD_FOOTER_SEPARATOR, - 0, this.layout.getFooterHeight() - 2, - 0, 0, + this.minecraft.level == null ? Screen.FOOTER_SEPARATOR : Screen.INWORLD_FOOTER_SEPARATOR, + 0, layout.getHeaderHeight() + layout.getContentHeight(), + 0.0F, 0.0F, width, 2, 32, 2 ); diff --git a/src/client/java/dev/spiritstudios/snapper/gui/widget/DeadSpaceElement.java b/src/client/java/dev/spiritstudios/snapper/gui/widget/DeadSpaceElement.java new file mode 100644 index 0000000..e618ef2 --- /dev/null +++ b/src/client/java/dev/spiritstudios/snapper/gui/widget/DeadSpaceElement.java @@ -0,0 +1,54 @@ +package dev.spiritstudios.snapper.gui.widget; + +import net.minecraft.client.gui.components.AbstractWidget; +import net.minecraft.client.gui.layouts.LayoutElement; + +import java.util.function.Consumer; + +public class DeadSpaceElement implements LayoutElement { + private final int width; + private final int height; + + private int x = 0; + private int y = 0; + + public DeadSpaceElement(int width, int height) { + this.width = width; + this.height = height; + } + + @Override + public void setX(int x) { + this.x = x; + } + + @Override + public void setY(int y) { + this.y = y; + } + + @Override + public int getX() { + return x; + } + + @Override + public int getY() { + return y; + } + + @Override + public int getWidth() { + return width; + } + + @Override + public int getHeight() { + return height; + } + + @Override + public void visitWidgets(Consumer consumer) { + + } +} diff --git a/src/client/java/dev/spiritstudios/snapper/gui/widget/ScreenshotGridWidget.java b/src/client/java/dev/spiritstudios/snapper/gui/widget/ScreenshotGridWidget.java index 0a68fe9..ceb27e9 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/widget/ScreenshotGridWidget.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/widget/ScreenshotGridWidget.java @@ -2,7 +2,6 @@ import dev.spiritstudios.snapper.util.ScreenshotTexture; import dev.spiritstudios.snapper.util.SnapperUtil; -import net.minecraft.util.Util; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.navigation.ScreenDirection; @@ -11,6 +10,7 @@ import net.minecraft.client.renderer.RenderPipelines; import net.minecraft.network.chat.Component; import net.minecraft.util.CommonColors; +import net.minecraft.util.Util; import org.jetbrains.annotations.Nullable; import java.util.function.Predicate; @@ -117,13 +117,13 @@ public void repositionEntries() { } @Override - protected ScreenshotEntry createEntry(ScreenshotTexture icon) { - return new GridScreenshotEntry(icon); + protected ScreenshotEntry createEntry(ScreenshotTexture texture) { + return new GridScreenshotEntry(texture); } private class GridScreenshotEntry extends ScreenshotEntry { - public GridScreenshotEntry(ScreenshotTexture icon) { - super(icon); + public GridScreenshotEntry(ScreenshotTexture texture) { + super(texture); } private boolean safeIsSelected(Entry entry) { @@ -133,20 +133,22 @@ private boolean safeIsSelected(Entry entry) { @Override public void renderContent(GuiGraphics graphics, int mouseX, int mouseY, boolean isHovering, float partialTick) { + texture.startLoading(minecraft); + int centreX = getContentX() + getContentWidth() / 2; int centreY = getContentY() + getContentHeight() / 2; clickThroughHovered = SnapperUtil.inBoundingBox(centreX - 16, centreY - 16, 32, 32, mouseX, mouseY); - if (this.icon.loaded()) { + if (this.texture.isLoaded()) { graphics.blit( RenderPipelines.GUI_TEXTURED, - this.icon.textureLocation(), + this.texture.textureLocation(), getContentX(), getContentY(), 0, 0, getContentWidth(), getContentHeight(), - icon.getWidth(), icon.getHeight(), - icon.getWidth(), icon.getHeight() + texture.getWidth(), texture.getHeight(), + texture.getWidth(), texture.getHeight() ); } @@ -163,7 +165,7 @@ public void renderContent(GuiGraphics graphics, int mouseX, int mouseY, boolean graphics.blitSprite( RenderPipelines.GUI_TEXTURED, - clickThroughHovered && icon.loaded() ? + clickThroughHovered && texture.isLoaded() ? ScreenshotsWidget.VIEW_HIGHLIGHTED_SPRITE : ScreenshotsWidget.VIEW_SPRITE, centreX - 16, centreY - 16, diff --git a/src/client/java/dev/spiritstudios/snapper/gui/widget/ScreenshotListWidget.java b/src/client/java/dev/spiritstudios/snapper/gui/widget/ScreenshotListWidget.java index 690c23a..e5aa501 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/widget/ScreenshotListWidget.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/widget/ScreenshotListWidget.java @@ -2,15 +2,15 @@ import dev.spiritstudios.snapper.util.ScreenshotTexture; import dev.spiritstudios.snapper.util.SnapperUtil; -import net.minecraft.network.chat.Component; -import net.minecraft.util.FormattedCharSequence; -import net.minecraft.util.Util; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.screens.Screen; import net.minecraft.client.input.MouseButtonEvent; import net.minecraft.client.renderer.RenderPipelines; +import net.minecraft.network.chat.CommonComponents; +import net.minecraft.network.chat.Component; import net.minecraft.util.CommonColors; +import net.minecraft.util.Util; import org.jetbrains.annotations.Nullable; public class ScreenshotListWidget extends ScreenshotsWidget { @@ -38,17 +38,19 @@ protected void renderSelection(GuiGraphics context, Entry entry, int color) { } @Override - protected ScreenshotEntry createEntry(ScreenshotTexture icon) { - return new ListScreenshotEntry(icon); + protected ScreenshotEntry createEntry(ScreenshotTexture texture) { + return new ListScreenshotEntry(texture); } private class ListScreenshotEntry extends ScreenshotEntry { - public ListScreenshotEntry(ScreenshotTexture icon) { - super(icon); + public ListScreenshotEntry(ScreenshotTexture texture) { + super(texture); } @Override public void renderContent(GuiGraphics graphics, int mouseX, int mouseY, boolean isHovering, float partialTick) { + texture.startLoading(minecraft); + graphics.drawString( minecraft.font, SnapperUtil.clipText(minecraft.font, fileName, getContentWidth() - 32 - 6), @@ -59,24 +61,23 @@ public void renderContent(GuiGraphics graphics, int mouseX, int mouseY, boolean graphics.drawString( minecraft.font, - FormattedCharSequence.composite( - Component.translatable("text.snapper.created").getVisualOrderText(), - creation.getVisualOrderText() - ), + Component.translatable("text.snapper.created") + .append(CommonComponents.space()) + .append(creation), getContentX() + 35, getContentY() + 12, CommonColors.GRAY, false ); - if (icon.loaded()) { + if (texture.isLoaded()) { graphics.blit( RenderPipelines.GUI_TEXTURED, - this.icon.textureLocation(), + this.texture.textureLocation(), getContentX(), getContentY(), - (icon.getHeight()) / 3.0f + 32, 0, + (texture.getHeight()) / 3.0f + 32, 0, getContentHeight(), getContentHeight(), - icon.getHeight(), icon.getHeight(), - icon.getWidth(), icon.getHeight() + texture.getHeight(), texture.getHeight(), + texture.getWidth(), texture.getHeight() ); } @@ -84,7 +85,7 @@ public void renderContent(GuiGraphics graphics, int mouseX, int mouseY, boolean graphics.fill(getContentX(), getContentY(), getContentX() + 32, getContentY() + 32, 0xA0909090); graphics.blitSprite( RenderPipelines.GUI_TEXTURED, - mouseX - getContentX() < 32 && this.icon.loaded() ? + mouseX - getContentX() < 32 && this.texture.isLoaded() ? ScreenshotsWidget.VIEW_HIGHLIGHTED_SPRITE : ScreenshotsWidget.VIEW_SPRITE, getContentX(), getContentY(), diff --git a/src/client/java/dev/spiritstudios/snapper/gui/widget/ScreenshotsWidget.java b/src/client/java/dev/spiritstudios/snapper/gui/widget/ScreenshotsWidget.java index 47bd5c6..17860c2 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/widget/ScreenshotsWidget.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/widget/ScreenshotsWidget.java @@ -8,7 +8,6 @@ import dev.spiritstudios.snapper.util.SafeFiles; import dev.spiritstudios.snapper.util.ScreenshotActions; import dev.spiritstudios.snapper.util.ScreenshotTexture; -import net.minecraft.util.Util; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.components.ObjectSelectionList; @@ -19,6 +18,7 @@ import net.minecraft.resources.Identifier; import net.minecraft.util.CommonColors; import net.minecraft.util.StringUtil; +import net.minecraft.util.Util; import org.jetbrains.annotations.Nullable; import org.jspecify.annotations.NonNull; @@ -31,10 +31,8 @@ import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.time.format.FormatStyle; -import java.util.Collections; -import java.util.Comparator; +import java.util.ArrayList; import java.util.List; -import java.util.concurrent.CompletableFuture; public abstract class ScreenshotsWidget extends ObjectSelectionList { protected static final Identifier VIEW_SPRITE = Snapper.id("screenshots/view"); @@ -42,7 +40,7 @@ public abstract class ScreenshotsWidget extends ObjectSelectionList> loadFuture; + protected final List textures; public static ScreenshotsWidget create( Minecraft client, @@ -58,48 +56,48 @@ public static ScreenshotsWidget create( } public ScreenshotsWidget( - Minecraft client, + Minecraft minecraft, int width, int height, int y, int itemHeight, @Nullable ScreenshotsWidget previous, Screen parent ) { - super(client, width, height, y, itemHeight); + super(minecraft, width, height, y, itemHeight); this.parent = parent; - this.addEntry(new ScreenshotsWidget.LoadingEntry(client)); + this.addEntry(new ScreenshotsWidget.LoadingEntry(minecraft)); - this.loadFuture = previous != null ? previous.loadFuture : load(client); + if (previous == null) { + this.textures = new ArrayList<>(); - this.loadFuture.thenAccept(textures -> { - this.clearEntries(); + List screenshots = ScreenshotActions.getScreenshots(); - if (textures.isEmpty()) { - this.addEntry(new ScreenshotsWidget.EmptyEntry(client)); - } else { - for (ScreenshotTexture texture : textures) { - addEntry(createEntry(texture)); - } + for (Path screenshot : screenshots) { + textures.add(ScreenshotTexture.createScreenshot( + this.minecraft.getTextureManager(), + screenshot + ).orElseThrow()); } + } else { + this.textures = previous.textures; - repositionEntries(); - }).exceptionally(ex -> { - clearEntries(); - // TODO: Error Entry - this.addEntry(new ScreenshotsWidget.EmptyEntry(client)); - - repositionEntries(); + } - Snapper.LOGGER.error("Failed to load textures", ex); + clearEntries(); - return null; - }); + if (textures.isEmpty()) { + addEntry(new EmptyEntry(minecraft)); + } else { + for (ScreenshotTexture texture : textures) { + addEntry(createEntry(texture)); + } + } repositionEntries(); } @Override - protected void clearEntries() { + public void clearEntries() { this.children().forEach(Entry::close); super.clearEntries(); } @@ -121,29 +119,7 @@ public boolean keyPressed(KeyEvent input) { return super.keyPressed(input); } - - protected abstract ScreenshotEntry createEntry(ScreenshotTexture icon); - - public CompletableFuture> load(Minecraft client) { - return CompletableFuture.supplyAsync(() -> { - List screenshots = ScreenshotActions.getScreenshots(); - - return screenshots.parallelStream() - .flatMap(path -> ScreenshotTexture.createScreenshot(client.getTextureManager(), path).stream()) - .peek(screenshotImage -> screenshotImage.load() - .exceptionally(throwable -> { - Snapper.LOGGER.error("An error occurred while loading the screenshot list", throwable); - return null; - })) - .sorted(Comparator.comparing(texture -> - SafeFiles.getLastModifiedTime(texture.getPath()).orElse(FileTime.fromMillis(0L))).reversed()) - .toList(); - }) - .exceptionally(throwable -> { - Snapper.LOGGER.error("An error occurred while loading the screenshot list", throwable); - return Collections.emptyList(); - }); - } + protected abstract ScreenshotEntry createEntry(ScreenshotTexture texture); public abstract static class Entry extends ObjectSelectionList.Entry implements AutoCloseable { public void close() { @@ -232,9 +208,8 @@ public abstract class ScreenshotEntry extends Entry implements AutoCloseable { .ofLocalizedDateTime(FormatStyle.SHORT) .withZone(ZoneId.systemDefault()); + public final ScreenshotTexture texture; public final FileTime lastModified; - public final ScreenshotTexture icon; - public final Screen screenParent; protected final Component fileName; protected final Component creation; @@ -243,13 +218,13 @@ public abstract class ScreenshotEntry extends Entry implements AutoCloseable { protected boolean clickThroughHovered = false; protected final int index; - public ScreenshotEntry(ScreenshotTexture icon) { - this.screenParent = parent; - this.icon = icon; + public ScreenshotEntry(ScreenshotTexture texture) { + this.texture = texture; + this.index = children().indexOf(this); - this.lastModified = SafeFiles.getLastModifiedTime(icon.getPath()).orElse(FileTime.fromMillis(0L)); + this.lastModified = SafeFiles.getLastModifiedTime(texture.path).orElse(FileTime.fromMillis(0L)); - String fileName = icon.getPath().getFileName().toString(); + String fileName = texture.path.getFileName().toString(); this.fileName = StringUtil.isNullOrEmpty(fileName) ? Component.translatable("text.snapper.generic", this.index + 1) : @@ -259,11 +234,12 @@ public ScreenshotEntry(ScreenshotTexture icon) { long creationTime = 0; try { - creationTime = Files.readAttributes(icon.getPath(), BasicFileAttributes.class).creationTime().toMillis(); + creationTime = Files.readAttributes(texture.path, BasicFileAttributes.class).creationTime().toMillis(); } catch (IOException ignored) { } - if (creationTime != -1L) creation = Component.literal(DATE_FORMAT.format(Instant.ofEpochMilli(creationTime))); + if (creationTime != -1L) + creation = Component.literal(DATE_FORMAT.format(Instant.ofEpochMilli(creationTime))); this.creation = creation; } @@ -282,19 +258,14 @@ public void setFocused(boolean focused) { } public boolean click() { - if (this.icon == null) return false; playButtonClickSound(minecraft.getSoundManager()); - minecraft.setScreen(new ScreenshotViewerScreen(this.icon, icon.getPath(), this.screenParent, null)); + minecraft.setScreen(new ScreenshotViewerScreen(texture, parent, null)); return true; } @Override public void close() { - this.icon.close(); - } - - public long lastModified() { - return lastModified.toMillis(); + this.texture.close(); } } } diff --git a/src/client/java/dev/spiritstudios/snapper/gui/widget/ViewModeButton.java b/src/client/java/dev/spiritstudios/snapper/gui/widget/ViewModeButton.java new file mode 100644 index 0000000..224650a --- /dev/null +++ b/src/client/java/dev/spiritstudios/snapper/gui/widget/ViewModeButton.java @@ -0,0 +1,41 @@ +package dev.spiritstudios.snapper.gui.widget; + +import dev.spiritstudios.snapper.Snapper; +import dev.spiritstudios.snapper.SnapperConfig; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.components.WidgetSprites; +import net.minecraft.client.renderer.RenderPipelines; +import net.minecraft.network.chat.Component; +import org.jspecify.annotations.Nullable; + +public class ViewModeButton extends Button { + private static final int SPRITE_SIZE = 15; + + private static final WidgetSprites GRID_SPRITES = new WidgetSprites(Snapper.id("screenshots/show_grid")); + private static final WidgetSprites LIST_SPRITES = new WidgetSprites(Snapper.id("screenshots/show_list")); + + public ViewModeButton(OnPress onPress, @Nullable CreateNarration createNarration) { + super(0, 0, 20, 20, Component.translatable("config.snapper.viewMode"), onPress, createNarration == null ? DEFAULT_NARRATION : createNarration); + } + + @Override + protected void renderContents(GuiGraphics graphics, int mouseX, int mouseY, float partialTick) { + this.renderDefaultSprite(graphics); + int x = this.getX() + this.getWidth() / 2 - SPRITE_SIZE / 2; + int y = this.getY() + this.getHeight() / 2 - SPRITE_SIZE / 2; + + WidgetSprites sprite = switch (SnapperConfig.HOLDER.get().viewMode()) { + case LIST -> LIST_SPRITES; + case GRID -> GRID_SPRITES; + }; + + graphics.blitSprite( + RenderPipelines.GUI_TEXTURED, + sprite.get(this.isActive(), this.isHoveredOrFocused()), + x, y, + SPRITE_SIZE, SPRITE_SIZE, + this.alpha + ); + } +} diff --git a/src/client/java/dev/spiritstudios/snapper/util/ScreenshotTexture.java b/src/client/java/dev/spiritstudios/snapper/util/ScreenshotTexture.java index 0831486..d153ba6 100644 --- a/src/client/java/dev/spiritstudios/snapper/util/ScreenshotTexture.java +++ b/src/client/java/dev/spiritstudios/snapper/util/ScreenshotTexture.java @@ -1,5 +1,6 @@ package dev.spiritstudios.snapper.util; +import com.google.common.hash.Hashing; import com.mojang.blaze3d.platform.NativeImage; import dev.spiritstudios.snapper.Snapper; import net.minecraft.util.Util; @@ -20,36 +21,29 @@ public class ScreenshotTexture implements AutoCloseable { private final TextureManager textureManager; private final Identifier textureLocation; - private final Path path; + public final Path path; - private final NativeImage image; + private boolean loaded; private DynamicTexture texture; + private boolean closed; private ScreenshotTexture(TextureManager textureManager, Identifier textureLocation, Path path) throws IOException { this.textureManager = textureManager; this.textureLocation = textureLocation; this.path = path; - - try (InputStream stream = Files.newInputStream(path)) { - this.image = NativeImage.read(stream); - } - } - - public CompletableFuture load() { - return Minecraft.getInstance().submit(() -> { - this.texture = new DynamicTexture(this.textureLocation::toString, this.image); - this.textureManager.register(this.textureLocation, this.texture); - }); } + @SuppressWarnings("deprecation") // Vanilla uses sha1 for this, copying it public static Optional createScreenshot(TextureManager textureManager, Path path) { + String name = path.getFileName().toString(); + try { return Optional.of(new ScreenshotTexture( textureManager, - Snapper.id( - "screenshots/" + Util.sanitizeName(path.getFileName().toString(), Identifier::validPathChar) + "/icon" - ), + Snapper.id( + "screenshots/" + Util.sanitizeName(name, Identifier::validPathChar) + "/" + Hashing.sha1().hashUnencodedChars(name) + ), path )); } catch (IOException e) { @@ -57,32 +51,80 @@ public static Optional createScreenshot(TextureManager textur } } - public void destroy() { - this.textureManager.release(this.textureLocation); - this.texture.close(); + public NativeImage load() { + try (InputStream stream = Files.newInputStream(path)) { + return NativeImage.read(stream); + } catch (IOException e) { + throw new RuntimeException(e); + } } - public int getWidth() { - return this.texture != null ? this.texture.getTexture().getWidth(0) : 64; + public void startLoading(Minecraft minecraft) { + if (!isLoaded()) { + CompletableFuture + .supplyAsync(this::load, Util.nonCriticalIoPool()) + .thenAcceptAsync(this::upload, minecraft); + } } - public int getHeight() { - return this.texture != null ? this.texture.getTexture().getHeight(0) : 64; + public void upload(NativeImage image) { + try { + this.checkOpen(); + if (this.texture == null) { + this.texture = new DynamicTexture(() -> "Screenshot " + this.textureLocation, image); + } else { + this.texture.setPixels(image); + this.texture.upload(); + } + + this.textureManager.register(this.textureLocation, this.texture); + loaded = true; + } catch (Throwable throwable) { + image.close(); + this.clear(); + throw throwable; + } } - public Identifier textureLocation() { - return this.texture != null ? this.textureLocation : UNKNOWN_SERVER; + + public void clear() { + this.checkOpen(); + if (this.texture != null) { + this.textureManager.release(this.textureLocation); + this.texture.close(); + this.texture = null; + this.loaded = false; + } } - public boolean loaded() { - return texture != null; + public void close() { + this.clear(); + this.closed = true; } - public Path getPath() { - return path; + public boolean isLoaded() { + return this.loaded; } - public void close() { - this.destroy(); + public boolean isClosed() { + return this.closed; + } + + private void checkOpen() { + if (this.closed) { + throw new IllegalStateException("ScreenshotTexture already closed"); + } + } + + public int getWidth() { + return this.texture != null ? this.texture.getTexture().getWidth(0) : 64; + } + + public int getHeight() { + return this.texture != null ? this.texture.getTexture().getHeight(0) : 64; + } + + public Identifier textureLocation() { + return this.texture != null ? this.textureLocation : UNKNOWN_SERVER; } } From cf068124d2502fb4b56638a00de354ef187062d2 Mon Sep 17 00:00:00 2001 From: CallMeEchoCodes Date: Fri, 20 Mar 2026 22:02:06 +1100 Subject: [PATCH 3/5] fix: several bugfixes with reloading --- .../snapper/gui/screen/ConfigScreen.java | 4 ++ .../snapper/gui/screen/ReloadableScreen.java | 5 ++ .../gui/screen/ScreenshotListScreen.java | 19 +++++++- .../gui/screen/ScreenshotRenameScreen.java | 5 ++ .../gui/screen/ScreenshotViewerScreen.java | 13 ++++- .../gui/widget/ScreenshotListWidget.java | 5 -- .../snapper/gui/widget/ScreenshotsWidget.java | 48 +++++++++++++------ .../snapper/util/ScreenshotActions.java | 6 +++ .../resources/assets/snapper/lang/en_us.json | 3 +- 9 files changed, 84 insertions(+), 24 deletions(-) create mode 100644 src/client/java/dev/spiritstudios/snapper/gui/screen/ReloadableScreen.java diff --git a/src/client/java/dev/spiritstudios/snapper/gui/screen/ConfigScreen.java b/src/client/java/dev/spiritstudios/snapper/gui/screen/ConfigScreen.java index d9441ea..3f0b45e 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/screen/ConfigScreen.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/screen/ConfigScreen.java @@ -252,6 +252,10 @@ public void onClose() { var viewMode = SnapperConfig.HOLDER.get().viewMode(); config.saveAsync().thenRun(() -> { + if (lastScreen instanceof ReloadableScreen reloadableScreen) { + reloadableScreen.reload(); + } + if (lastScreen instanceof ScreenshotListScreen screenshotsScreen && config.viewMode != viewMode) { screenshotsScreen.recreateList(); } diff --git a/src/client/java/dev/spiritstudios/snapper/gui/screen/ReloadableScreen.java b/src/client/java/dev/spiritstudios/snapper/gui/screen/ReloadableScreen.java new file mode 100644 index 0000000..ec2f543 --- /dev/null +++ b/src/client/java/dev/spiritstudios/snapper/gui/screen/ReloadableScreen.java @@ -0,0 +1,5 @@ +package dev.spiritstudios.snapper.gui.screen; + +public interface ReloadableScreen { + void reload(); +} diff --git a/src/client/java/dev/spiritstudios/snapper/gui/screen/ScreenshotListScreen.java b/src/client/java/dev/spiritstudios/snapper/gui/screen/ScreenshotListScreen.java index 2d41780..4058a91 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/screen/ScreenshotListScreen.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/screen/ScreenshotListScreen.java @@ -30,11 +30,12 @@ import java.nio.file.Path; -public class ScreenshotListScreen extends Screen { +public class ScreenshotListScreen extends Screen implements ReloadableScreen { private static final Identifier PANORAMA_BUTTON_ICON = Snapper.id("screenshots/panorama"); private static final Identifier PANORAMA_BUTTON_DISABLED_ICON = Snapper.id("screenshots/panorama_disabled"); private static final Identifier SETTINGS_ICON = Snapper.id("screenshots/settings"); + private static final Identifier RELOAD_ICON = Snapper.id("screenshots/reset"); private final Screen parent; private final boolean isOffline; @@ -185,7 +186,12 @@ protected void init() { this.uploadButton.setTooltip(Tooltip.create(Component.translatable("button.snapper.upload.tooltip"))); } - topRow.addChild(new DeadSpaceElement(20, 20)); + topRow.addChild( + SpriteIconButton.builder( + Component.translatable("button.snapper.reload"), + button -> this.reload(), + true + ).width(20).sprite(RELOAD_ICON, 15, 15).build()); this.imageSelected(selectedScreenshot); @@ -194,6 +200,10 @@ protected void init() { this.repositionElements(); } + public ScreenshotsWidget getScreenshots() { + return screenshots; + } + @Override public void onClose() { super.onClose(); @@ -244,6 +254,11 @@ public boolean keyPressed(KeyEvent input) { return false; } + @Override + public void reload() { + screenshots.reload(); + } + public enum ViewMode implements StringRepresentable { LIST("list"), GRID("grid"); diff --git a/src/client/java/dev/spiritstudios/snapper/gui/screen/ScreenshotRenameScreen.java b/src/client/java/dev/spiritstudios/snapper/gui/screen/ScreenshotRenameScreen.java index 4997397..6e96b2b 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/screen/ScreenshotRenameScreen.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/screen/ScreenshotRenameScreen.java @@ -51,6 +51,11 @@ private void renameScreenshot(String newName) { if (newName == null || !newName.endsWith(".png")) return; ScreenshotActions.renameScreenshot(screenshot, newName); + + if (this.parent instanceof ReloadableScreen reloadableScreen) { + reloadableScreen.reload(); + } + client.setScreen(this.parent); } diff --git a/src/client/java/dev/spiritstudios/snapper/gui/screen/ScreenshotViewerScreen.java b/src/client/java/dev/spiritstudios/snapper/gui/screen/ScreenshotViewerScreen.java index 4ee0c92..015beb1 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/screen/ScreenshotViewerScreen.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/screen/ScreenshotViewerScreen.java @@ -24,7 +24,7 @@ import java.nio.file.Path; import java.util.List; -public class ScreenshotViewerScreen extends Screen { +public class ScreenshotViewerScreen extends Screen implements ReloadableScreen { private static final Identifier MENU_LIST_BACKGROUND = Identifier.withDefaultNamespace("textures/gui/menu_list_background.png"); private static final Identifier INWORLD_MENU_LIST_BACKGROUND = Identifier.withDefaultNamespace("textures/gui/inworld_menu_list_background.png"); @@ -36,6 +36,8 @@ public class ScreenshotViewerScreen extends Screen { private final int screenshotIndex; private final HeaderAndFooterLayout layout = new HeaderAndFooterLayout(this, 33, 60); + public boolean shouldReloadParent = false; + public ScreenshotViewerScreen(ScreenshotTexture texture, Screen parent) { this(texture, parent, null); } @@ -54,8 +56,10 @@ public ScreenshotViewerScreen(ScreenshotTexture texture, Screen parent, @Nullabl @Override public void onClose() { - if (!(parent instanceof ScreenshotListScreen)) { + if (!(parent instanceof ScreenshotListScreen listScreen)) { this.texture.close(); + } else if (shouldReloadParent) { + listScreen.getScreenshots().reload(); } this.client.setScreen(this.parent); @@ -235,4 +239,9 @@ private void drawHeaderAndFooterSeparators(GuiGraphics graphics) { 32, 2 ); } + + @Override + public void reload() { + this.shouldReloadParent = true; + } } diff --git a/src/client/java/dev/spiritstudios/snapper/gui/widget/ScreenshotListWidget.java b/src/client/java/dev/spiritstudios/snapper/gui/widget/ScreenshotListWidget.java index e5aa501..c54b1fc 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/widget/ScreenshotListWidget.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/widget/ScreenshotListWidget.java @@ -32,11 +32,6 @@ public void repositionEntries() { } } - @Override - protected void renderSelection(GuiGraphics context, Entry entry, int color) { - // let elements handle it - } - @Override protected ScreenshotEntry createEntry(ScreenshotTexture texture) { return new ListScreenshotEntry(texture); diff --git a/src/client/java/dev/spiritstudios/snapper/gui/widget/ScreenshotsWidget.java b/src/client/java/dev/spiritstudios/snapper/gui/widget/ScreenshotsWidget.java index 17860c2..306d4ff 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/widget/ScreenshotsWidget.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/widget/ScreenshotsWidget.java @@ -40,7 +40,12 @@ public abstract class ScreenshotsWidget extends ObjectSelectionList textures; + protected List textures; + + @Override + public void removeEntry(Entry entry) { + super.removeEntry(entry); + } public static ScreenshotsWidget create( Minecraft client, @@ -70,9 +75,7 @@ public ScreenshotsWidget( if (previous == null) { this.textures = new ArrayList<>(); - List screenshots = ScreenshotActions.getScreenshots(); - - for (Path screenshot : screenshots) { + for (Path screenshot : ScreenshotActions.getScreenshots()) { textures.add(ScreenshotTexture.createScreenshot( this.minecraft.getTextureManager(), screenshot @@ -80,11 +83,26 @@ public ScreenshotsWidget( } } else { this.textures = previous.textures; + } + super.clearEntries(); + addEntries(); + } + + @Override + public void clearEntries() { + for (ScreenshotTexture texture : this.textures) { + if (!texture.isClosed()) { + texture.close(); + } } - clearEntries(); + textures.clear(); + + super.clearEntries(); + } + protected void addEntries() { if (textures.isEmpty()) { addEntry(new EmptyEntry(minecraft)); } else { @@ -96,10 +114,17 @@ public ScreenshotsWidget( repositionEntries(); } - @Override - public void clearEntries() { - this.children().forEach(Entry::close); - super.clearEntries(); + public void reload() { + clearEntries(); + + for (Path screenshot : ScreenshotActions.getScreenshots()) { + textures.add(ScreenshotTexture.createScreenshot( + this.minecraft.getTextureManager(), + screenshot + ).orElseThrow()); + } + + addEntries(); } protected void setEntrySelected(@Nullable ScreenshotEntry entry) { @@ -262,10 +287,5 @@ public boolean click() { minecraft.setScreen(new ScreenshotViewerScreen(texture, parent, null)); return true; } - - @Override - public void close() { - this.texture.close(); - } } } diff --git a/src/client/java/dev/spiritstudios/snapper/util/ScreenshotActions.java b/src/client/java/dev/spiritstudios/snapper/util/ScreenshotActions.java index f1712bc..fa49e7a 100644 --- a/src/client/java/dev/spiritstudios/snapper/util/ScreenshotActions.java +++ b/src/client/java/dev/spiritstudios/snapper/util/ScreenshotActions.java @@ -1,6 +1,7 @@ package dev.spiritstudios.snapper.util; import dev.spiritstudios.snapper.Snapper; +import dev.spiritstudios.snapper.gui.screen.ScreenshotListScreen; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.screens.ConfirmScreen; import net.minecraft.client.gui.screens.ProgressScreen; @@ -31,7 +32,12 @@ public static void deleteScreenshot(Path path, Screen screen) { } catch (IOException e) { Snapper.LOGGER.error("Failed to delete file", e); } + + if (screen instanceof ScreenshotListScreen listScreen) { + listScreen.getScreenshots().reload(); + } } + client.setScreen(screen); }, Component.translatable("text.snapper.delete_question"), diff --git a/src/client/resources/assets/snapper/lang/en_us.json b/src/client/resources/assets/snapper/lang/en_us.json index 9ad2729..bca78da 100644 --- a/src/client/resources/assets/snapper/lang/en_us.json +++ b/src/client/resources/assets/snapper/lang/en_us.json @@ -93,5 +93,6 @@ "text.snapper.image_size": "Image Size: %dx%d", "text.snapper.screen_size": "Screen Size: %dx%d", "text.snapper.scale_factor": "Scale Factor: %s", - "text.snapper.scale_size": "Scaled Size: %dx%d" + "text.snapper.scale_size": "Scaled Size: %dx%d", + "button.snapper.reload": "Reload" } From 89c9609eb6ab43a7eb855da5dcfe00547693981d Mon Sep 17 00:00:00 2001 From: CallMeEchoCodes Date: Thu, 26 Mar 2026 16:59:55 +1100 Subject: [PATCH 4/5] chore: unload off screen --- .../gui/screen/ScreenshotListScreen.java | 3 --- .../gui/widget/ScreenshotGridWidget.java | 11 +++++++++++ .../gui/widget/ScreenshotListWidget.java | 12 ++++++++++++ .../snapper/gui/widget/ScreenshotsWidget.java | 17 ++++++++++++++++- .../snapper/util/ScreenshotTexture.java | 4 ++-- 5 files changed, 41 insertions(+), 6 deletions(-) diff --git a/src/client/java/dev/spiritstudios/snapper/gui/screen/ScreenshotListScreen.java b/src/client/java/dev/spiritstudios/snapper/gui/screen/ScreenshotListScreen.java index 4058a91..b4e8946 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/screen/ScreenshotListScreen.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/screen/ScreenshotListScreen.java @@ -44,7 +44,6 @@ public class ScreenshotListScreen extends Screen implements ReloadableScreen { private Button deleteButton; private Button renameButton; - private Button viewButton; private Button copyButton; private Button openButton; private Button uploadButton; @@ -216,7 +215,6 @@ public void imageSelected(@Nullable ScreenshotListWidget.ScreenshotEntry screens this.deleteButton.active = hasScreenshot; this.openButton.active = hasScreenshot; this.renameButton.active = hasScreenshot; -// this.viewButton.active = hasScreenshot; this.selectedScreenshot = screenshot; this.uploadButton.active = !isOffline && hasScreenshot; } @@ -276,5 +274,4 @@ public enum ViewMode implements StringRepresentable { return name; } } - } diff --git a/src/client/java/dev/spiritstudios/snapper/gui/widget/ScreenshotGridWidget.java b/src/client/java/dev/spiritstudios/snapper/gui/widget/ScreenshotGridWidget.java index ceb27e9..6583f86 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/widget/ScreenshotGridWidget.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/widget/ScreenshotGridWidget.java @@ -5,6 +5,7 @@ import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.navigation.ScreenDirection; +import net.minecraft.client.gui.screens.LoadingDotsText; import net.minecraft.client.gui.screens.Screen; import net.minecraft.client.input.MouseButtonEvent; import net.minecraft.client.renderer.RenderPipelines; @@ -150,6 +151,16 @@ public void renderContent(GuiGraphics graphics, int mouseX, int mouseY, boolean texture.getWidth(), texture.getHeight(), texture.getWidth(), texture.getHeight() ); + } else { + String loadString = LoadingDotsText.get(Util.getMillis()); + + graphics.drawString( + minecraft.font, + loadString, + getContentX() + (getContentWidth() - minecraft.font.width(loadString)) / 2, getContentY() + getContentHeight() / 2, + CommonColors.GRAY, + false + ); } if (minecraft.options.touchscreen().get() || (isHovering && mouseX < getX() + getWidth()) || safeIsSelected(this)) { diff --git a/src/client/java/dev/spiritstudios/snapper/gui/widget/ScreenshotListWidget.java b/src/client/java/dev/spiritstudios/snapper/gui/widget/ScreenshotListWidget.java index c54b1fc..9ab5ec5 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/widget/ScreenshotListWidget.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/widget/ScreenshotListWidget.java @@ -4,6 +4,7 @@ import dev.spiritstudios.snapper.util.SnapperUtil; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.screens.LoadingDotsText; import net.minecraft.client.gui.screens.Screen; import net.minecraft.client.input.MouseButtonEvent; import net.minecraft.client.renderer.RenderPipelines; @@ -74,6 +75,17 @@ public void renderContent(GuiGraphics graphics, int mouseX, int mouseY, boolean texture.getHeight(), texture.getHeight(), texture.getWidth(), texture.getHeight() ); + } else { + String loadString = LoadingDotsText.get(Util.getMillis()); + + graphics.drawString( + minecraft.font, + loadString, + getContentX() + (16 - minecraft.font.width(loadString) / 2), + (getContentY() + getContentHeight() / 2) - minecraft.font.lineHeight / 2, + CommonColors.GRAY, + false + ); } if (minecraft.options.touchscreen().get() || isHovering) { diff --git a/src/client/java/dev/spiritstudios/snapper/gui/widget/ScreenshotsWidget.java b/src/client/java/dev/spiritstudios/snapper/gui/widget/ScreenshotsWidget.java index 306d4ff..5af1bbf 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/widget/ScreenshotsWidget.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/widget/ScreenshotsWidget.java @@ -89,6 +89,17 @@ public ScreenshotsWidget( addEntries(); } + @Override + protected void renderListItems(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) { + for (Entry entry : this.children()) { + if (entry.getY() + entry.getHeight() >= this.getY() && entry.getY() <= this.getBottom()) { + this.renderItem(guiGraphics, mouseX, mouseY, partialTick, entry); + } else if (entry instanceof ScreenshotEntry sEntry) { + sEntry.clear(); + } + } + } + @Override public void clearEntries() { for (ScreenshotTexture texture : this.textures) { @@ -114,7 +125,7 @@ protected void addEntries() { repositionEntries(); } - public void reload() { + public synchronized void reload() { clearEntries(); for (Path screenshot : ScreenshotActions.getScreenshots()) { @@ -269,6 +280,10 @@ public ScreenshotEntry(ScreenshotTexture texture) { this.creation = creation; } + public void clear() { + texture.clear(); + } + @Override public void setFocused(boolean focused) { if (focused) { diff --git a/src/client/java/dev/spiritstudios/snapper/util/ScreenshotTexture.java b/src/client/java/dev/spiritstudios/snapper/util/ScreenshotTexture.java index d153ba6..9545f6f 100644 --- a/src/client/java/dev/spiritstudios/snapper/util/ScreenshotTexture.java +++ b/src/client/java/dev/spiritstudios/snapper/util/ScreenshotTexture.java @@ -59,7 +59,7 @@ public NativeImage load() { } } - public void startLoading(Minecraft minecraft) { + public synchronized void startLoading(Minecraft minecraft) { if (!isLoaded()) { CompletableFuture .supplyAsync(this::load, Util.nonCriticalIoPool()) @@ -67,7 +67,7 @@ public void startLoading(Minecraft minecraft) { } } - public void upload(NativeImage image) { + public synchronized void upload(NativeImage image) { try { this.checkOpen(); if (this.texture == null) { From 39263e9c272e638e3b5061ea5e87bf88cd0104d2 Mon Sep 17 00:00:00 2001 From: CallMeEchoCodes Date: Thu, 26 Mar 2026 17:01:03 +1100 Subject: [PATCH 5/5] chore: launch orbital strike at deadspaceelement --- .../gui/screen/ScreenshotListScreen.java | 1 - .../snapper/gui/widget/DeadSpaceElement.java | 54 ------------------- 2 files changed, 55 deletions(-) delete mode 100644 src/client/java/dev/spiritstudios/snapper/gui/widget/DeadSpaceElement.java diff --git a/src/client/java/dev/spiritstudios/snapper/gui/screen/ScreenshotListScreen.java b/src/client/java/dev/spiritstudios/snapper/gui/screen/ScreenshotListScreen.java index b4e8946..d66bca6 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/screen/ScreenshotListScreen.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/screen/ScreenshotListScreen.java @@ -5,7 +5,6 @@ import dev.spiritstudios.snapper.Snapper; import dev.spiritstudios.snapper.SnapperConfig; import dev.spiritstudios.snapper.gui.toast.SnapperToast; -import dev.spiritstudios.snapper.gui.widget.DeadSpaceElement; import dev.spiritstudios.snapper.gui.widget.ScreenshotListWidget; import dev.spiritstudios.snapper.gui.widget.ScreenshotsWidget; import dev.spiritstudios.snapper.gui.widget.ViewModeButton; diff --git a/src/client/java/dev/spiritstudios/snapper/gui/widget/DeadSpaceElement.java b/src/client/java/dev/spiritstudios/snapper/gui/widget/DeadSpaceElement.java deleted file mode 100644 index e618ef2..0000000 --- a/src/client/java/dev/spiritstudios/snapper/gui/widget/DeadSpaceElement.java +++ /dev/null @@ -1,54 +0,0 @@ -package dev.spiritstudios.snapper.gui.widget; - -import net.minecraft.client.gui.components.AbstractWidget; -import net.minecraft.client.gui.layouts.LayoutElement; - -import java.util.function.Consumer; - -public class DeadSpaceElement implements LayoutElement { - private final int width; - private final int height; - - private int x = 0; - private int y = 0; - - public DeadSpaceElement(int width, int height) { - this.width = width; - this.height = height; - } - - @Override - public void setX(int x) { - this.x = x; - } - - @Override - public void setY(int y) { - this.y = y; - } - - @Override - public int getX() { - return x; - } - - @Override - public int getY() { - return y; - } - - @Override - public int getWidth() { - return width; - } - - @Override - public int getHeight() { - return height; - } - - @Override - public void visitWidgets(Consumer consumer) { - - } -}