From a18fa3962c145646e6d3a499954a98df563e300c Mon Sep 17 00:00:00 2001 From: CallMeEchoCodes Date: Sun, 15 Mar 2026 15:44:10 +1100 Subject: [PATCH 01/15] 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 02/15] 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 03/15] 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 04/15] 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 05/15] 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) { - - } -} From 444d5b46855115fbab35c8f6e8fac82f2f1b1a0a Mon Sep 17 00:00:00 2001 From: CallMeEchoCodes Date: Sat, 6 Jun 2026 13:35:09 +1000 Subject: [PATCH 06/15] 26.1 port (init) --- build.gradle.kts | 52 +++++++++--------- gradle/libs.versions.toml | 16 +++--- gradle/wrapper/gradle-wrapper.jar | Bin 43764 -> 45633 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 5 +- gradlew.bat | 3 +- .../dev/spiritstudios/snapper/Snapper.java | 2 +- ...ybindings.java => SnapperKeyMappings.java} | 14 ++--- .../gui/overlay/ExternalDialogOverlay.java | 8 +-- .../snapper/gui/screen/ConfigScreen.java | 3 +- .../gui/screen/PanoramaViewerScreen.java | 30 ++++++---- .../gui/screen/PrivacyNoticeScreen.java | 8 +-- .../gui/screen/ScreenshotViewerScreen.java | 20 +++---- .../snapper/gui/toast/SnapperToast.java | 12 ++-- .../snapper/gui/widget/ConfigList.java | 10 ++-- .../gui/widget/FolderSelectWidget.java | 8 +-- .../gui/widget/ScreenshotGridWidget.java | 12 ++-- .../gui/widget/ScreenshotListWidget.java | 10 ++-- .../snapper/gui/widget/ScreenshotsWidget.java | 22 ++++---- .../snapper/gui/widget/ViewModeButton.java | 6 +- .../snapper/mixin/CameraMixin.java | 21 ------- .../snapper/mixin/GuiRendererMixin.java | 9 +++ .../snapper/mixin/InGameHudMixin.java | 2 +- .../snapper/mixin/KeyboardHandlerMixin.java | 10 ++-- .../snapper/mixin/MinecraftMixin.java | 8 +-- .../snapper/mixin/PauseScreenMixin.java | 4 +- ...ecorderMixin.java => ScreenshotMixin.java} | 31 +++++------ .../snapper/mixin/TitleScreenMixin.java | 2 +- src/client/resources/snapper.mixins.json | 8 ++- src/main/resources/fabric.mod.json | 5 +- src/main/resources/snapper.classtweaker | 4 +- 31 files changed, 169 insertions(+), 178 deletions(-) rename src/client/java/dev/spiritstudios/snapper/{SnapperKeybindings.java => SnapperKeyMappings.java} (89%) delete mode 100644 src/client/java/dev/spiritstudios/snapper/mixin/CameraMixin.java create mode 100644 src/client/java/dev/spiritstudios/snapper/mixin/GuiRendererMixin.java rename src/client/java/dev/spiritstudios/snapper/mixin/{ScreenshotRecorderMixin.java => ScreenshotMixin.java} (67%) diff --git a/build.gradle.kts b/build.gradle.kts index f61e119..29c6582 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,8 +5,6 @@ plugins { alias(libs.plugins.modpublish) } -val mappingsAttribute = Attribute.of("net.minecraft.mappings", String::class.java) - val modVersion = "1.1.1" val modId = "snapper" val modName = "Snapper" @@ -21,11 +19,6 @@ version = "$modVersion+${libs.versions.minecraft.get()}" @Suppress("UnstableApiUsage") repositories { - maven("https://maven.parchmentmc.org/") { - name = "ParchmentMC" - content { includeGroupAndSubgroups("org.parchmentmc") } - } - maven("https://maven.terraformersmc.com/") { name = "Terraformers" content { includeGroupAndSubgroups("com.terraformersmc") } @@ -49,6 +42,22 @@ repositories { mavenCentral() } +val debugArgs = arrayOf( + "-enableassertions", + + // Mixin debugging, should make failures happen quicker + "-Dmixin.debug.verify=true", + "-Dmixin.debug.strict=true", + "-Dmixin.debug.countInjections=true", + + // Memory Usage Optimization + "-XX:+UseZGC", + "-XX:+UseCompactObjectHeaders", + "-XX:+UseStringDeduplication", + + "-XX:+AlwaysPreTouch" // Apparently makes startup faster +) + loom { runtimeOnlyLog4j = true @@ -60,29 +69,22 @@ loom { } accessWidenerPath = file("src/main/resources/snapper.classtweaker") + + runs.configureEach { vmArgs.addAll(debugArgs) } } dependencies { minecraft(libs.minecraft) - @Suppress("UnstableApiUsage") - mappings( - loom.layered { - officialMojangMappings() - parchment(libs.parchment) - } - ) - modImplementation(libs.fabric.loader) - modImplementation(libs.fabric.api) + implementation(libs.fabric.loader) + implementation(libs.fabric.api) - modCompileOnlyApi(libs.greenhouse.config.api) { - attributes { attribute(mappingsAttribute, "intermediary") } - } + compileOnlyApi(libs.greenhouse.config.api) - modRuntimeOnly(libs.greenhouse.config) + runtimeOnly(libs.greenhouse.config) include(libs.greenhouse.config) - modCompileOnly(libs.modmenu) + compileOnly(libs.modmenu) implementation(libs.objc.bridge) } @@ -101,13 +103,13 @@ tasks.processResources { java { withSourcesJar() - sourceCompatibility = JavaVersion.VERSION_21 - targetCompatibility = JavaVersion.VERSION_21 + sourceCompatibility = JavaVersion.VERSION_25 + targetCompatibility = JavaVersion.VERSION_25 } tasks.withType { options.encoding = "UTF-8" - options.release = 21 + options.release = 25 } tasks.jar { @@ -115,7 +117,7 @@ tasks.jar { } publishMods { - file = tasks.remapJar.get().archiveFile + file = tasks.jar.get().archiveFile modLoaders.add("fabric") version = modVersion diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0e34c81..b8f7eae 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,25 +1,23 @@ [versions] -fabric_loom = "1.15-SNAPSHOT" +fabric_loom = "1.16-SNAPSHOT" modpublish = "1.1.0" -minecraft = "1.21.11" -parchment = "2025.12.20" +minecraft = "26.1.2" -fabric_loader = "0.18.1" -fabric_api = "0.141.3+1.21.11" +fabric_loader = "0.18.4" +fabric_api = "0.147.0+26.1.2" -greenhouse_config = "3.0.0-beta.4+1.21.11" -modmenu = "17.0.0-beta.2" +greenhouse_config = "3.0.0-beta.5+26.1.x" +modmenu = "18.0.0-alpha.8" objc_bridge = "1.0.0" [plugins] -fabric_loom = { id = "net.fabricmc.fabric-loom-remap", version.ref = "fabric_loom" } +fabric_loom = { id = "net.fabricmc.fabric-loom", version.ref = "fabric_loom" } 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.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/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 1b33c55baabb587c669f562ae36f953de2481846..f8e1ee3125fe0768e9a76ee977ac089eb657005e 100644 GIT binary patch delta 37442 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

WNp5~7YD4``2J+2^JLXSDAn1#GQa=Wm~d2R({ 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 delta 35599 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 ORzXA2f%7X@nSTIfr%7r6 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 23449a2..dbc3ce4 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.0-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 23d15a9..adff685 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright © 2015-2021 the original authors. +# Copyright © 2015 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -114,7 +114,6 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -172,7 +171,6 @@ fi # For Cygwin or MSYS, switch paths to Windows format before running java if "$cygwin" || "$msys" ; then APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) JAVACMD=$( cygpath --unix "$JAVACMD" ) @@ -212,7 +210,6 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" diff --git a/gradlew.bat b/gradlew.bat index db3a6ac..c4bdd3a 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -70,11 +70,10 @@ goto fail :execute @rem Setup the command line -set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell diff --git a/src/client/java/dev/spiritstudios/snapper/Snapper.java b/src/client/java/dev/spiritstudios/snapper/Snapper.java index 0ed1289..24ecfe2 100644 --- a/src/client/java/dev/spiritstudios/snapper/Snapper.java +++ b/src/client/java/dev/spiritstudios/snapper/Snapper.java @@ -14,7 +14,7 @@ public final class Snapper implements ClientModInitializer { @Override public void onInitializeClient() { SnapperConfig.init(); - SnapperKeybindings.init(); + SnapperKeyMappings.init(); ClientLifecycleEvents.CLIENT_STOPPING.register(client -> ScreenshotUploading.close()); } diff --git a/src/client/java/dev/spiritstudios/snapper/SnapperKeybindings.java b/src/client/java/dev/spiritstudios/snapper/SnapperKeyMappings.java similarity index 89% rename from src/client/java/dev/spiritstudios/snapper/SnapperKeybindings.java rename to src/client/java/dev/spiritstudios/snapper/SnapperKeyMappings.java index 02b38b5..f86cefc 100644 --- a/src/client/java/dev/spiritstudios/snapper/SnapperKeybindings.java +++ b/src/client/java/dev/spiritstudios/snapper/SnapperKeyMappings.java @@ -6,7 +6,7 @@ import dev.spiritstudios.snapper.util.ScreenshotTexture; import dev.spiritstudios.snapper.util.ScreenshotActions; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; -import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper; +import net.fabricmc.fabric.api.client.keymapping.v1.KeyMappingHelper; import net.minecraft.client.KeyMapping; import net.minecraft.client.Minecraft; import net.minecraft.network.chat.Component; @@ -15,7 +15,7 @@ import java.nio.file.Path; import java.util.List; -public final class SnapperKeybindings { +public final class SnapperKeyMappings { public static final KeyMapping.Category SNAPPER = KeyMapping.Category.register(Snapper.id(Snapper.MOD_ID)); public static final KeyMapping PANORAMA_KEY = new KeyMapping( @@ -37,13 +37,13 @@ public final class SnapperKeybindings { ); public static void init() { - KeyBindingHelper.registerKeyBinding(PANORAMA_KEY); - KeyBindingHelper.registerKeyBinding(RECENT_SCREENSHOT_KEY); - KeyBindingHelper.registerKeyBinding(SCREENSHOT_MENU_KEY); + KeyMappingHelper.registerKeyMapping(PANORAMA_KEY); + KeyMappingHelper.registerKeyMapping(RECENT_SCREENSHOT_KEY); + KeyMappingHelper.registerKeyMapping(SCREENSHOT_MENU_KEY); ClientTickEvents.END_CLIENT_TICK.register(client -> { - while (PANORAMA_KEY.consumeClick()) SnapperKeybindings.takePanorama(client); - while (RECENT_SCREENSHOT_KEY.consumeClick()) SnapperKeybindings.openRecentScreenshot(client); + while (PANORAMA_KEY.consumeClick()) SnapperKeyMappings.takePanorama(client); + while (RECENT_SCREENSHOT_KEY.consumeClick()) SnapperKeyMappings.openRecentScreenshot(client); while (SCREENSHOT_MENU_KEY.consumeClick()) client.setScreen(new ScreenshotListScreen(client.screen)); }); } 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 beca903..007f27a 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/overlay/ExternalDialogOverlay.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/overlay/ExternalDialogOverlay.java @@ -2,7 +2,7 @@ import com.mojang.blaze3d.platform.InputConstants; import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.GuiGraphicsExtractor; import net.minecraft.client.gui.screens.Overlay; import net.minecraft.client.renderer.RenderPipelines; import net.minecraft.network.chat.Component; @@ -16,9 +16,9 @@ public class ExternalDialogOverlay extends Overlay { 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) { + public void extractRenderState(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float a) { if (this.client.screen != null) { - this.client.screen.renderWithTooltipAndSubtitles(graphics, mouseX, mouseY, partialTick); + this.client.screen.extractRenderStateWithTooltipAndSubtitles(graphics, mouseX, mouseY, a); } graphics.nextStratum(); @@ -32,7 +32,7 @@ public void render(GuiGraphics graphics, int mouseX, int mouseY, float partialTi 32, 32 ); - graphics.drawCenteredString( + graphics.centeredText( client.font, Component.translatable("overlay.snapper.external_dialog.folder"), graphics.guiWidth() / 2, graphics.guiHeight() / 2, 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 3f0b45e..d5571cf 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/screen/ConfigScreen.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/screen/ConfigScreen.java @@ -6,6 +6,7 @@ import dev.spiritstudios.snapper.gui.widget.FolderSelectWidget; import dev.spiritstudios.snapper.util.SnapperUtil; import dev.spiritstudios.snapper.util.uploading.AxolotlClientApi; +import net.minecraft.client.Minecraft; import net.minecraft.client.gui.components.*; import net.minecraft.client.gui.layouts.HeaderAndFooterLayout; import net.minecraft.client.gui.screens.Screen; @@ -253,7 +254,7 @@ public void onClose() { config.saveAsync().thenRun(() -> { if (lastScreen instanceof ReloadableScreen reloadableScreen) { - reloadableScreen.reload(); + Minecraft.getInstance().submit(reloadableScreen::reload); } if (lastScreen instanceof ScreenshotListScreen screenshotsScreen && config.viewMode != viewMode) { 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 9f0e2ac..fdd88de 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/screen/PanoramaViewerScreen.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/screen/PanoramaViewerScreen.java @@ -4,13 +4,15 @@ import dev.spiritstudios.snapper.util.DynamicCubemapTexture; import dev.spiritstudios.snapper.util.SafeFiles; import dev.spiritstudios.snapper.util.SnapperUtil; +import net.fabricmc.fabric.api.client.rendering.v1.RenderStateDataKey; +import net.minecraft.client.renderer.RenderPipelines; +import net.minecraft.client.renderer.state.gui.PanoramaRenderState; +import net.minecraft.util.Mth; import net.minecraft.util.Util; import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.GuiGraphicsExtractor; import net.minecraft.client.gui.components.Button; import net.minecraft.client.gui.screens.Screen; -import net.minecraft.client.renderer.CubeMap; -import net.minecraft.client.renderer.PanoramaRenderer; import net.minecraft.network.chat.CommonComponents; import net.minecraft.network.chat.Component; import net.minecraft.resources.Identifier; @@ -23,15 +25,17 @@ import java.util.stream.Stream; public class PanoramaViewerScreen extends Screen { + public static final RenderStateDataKey SNAPPER_PANORAMA = RenderStateDataKey.create(() -> "Snapper 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); private final DynamicCubemapTexture texture; private final String title; private final Screen parent; + private float spin = 0.0F; + protected PanoramaViewerScreen(String title, Screen parent) { super(Component.translatable("menu.snapper.viewer_menu")); this.title = title; @@ -46,8 +50,6 @@ protected PanoramaViewerScreen(String title, Screen parent) { @Nullable private DynamicCubemapTexture getTexture() { - assert minecraft != null; - Path panoramaDir = SnapperUtil.getConfiguredScreenshotDirectory().resolve("panorama"); if (!SnapperUtil.panoramaPresent(panoramaDir)) return null; @@ -94,10 +96,14 @@ protected void init() { } @Override - public void render(GuiGraphics context, int mouseX, int mouseY, float delta) { - rotatingPanoramaRenderer.render(context, this.width, this.height, true); + public void extractRenderState(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float a) { + Minecraft minecraft = Minecraft.getInstance(); + float delta = (float) ((double) a * minecraft.gameRenderer.getGameRenderState().optionsRenderState.panoramaSpeed); + this.spin = Mth.wrapDegrees(this.spin + delta * 0.1F); + + minecraft.gameRenderer.getGameRenderState().guiRenderState.setData(SNAPPER_PANORAMA, new PanoramaRenderState(-this.spin)); - context.drawCenteredString( + graphics.centeredText( this.font, this.title, this.width / 2, @@ -105,10 +111,10 @@ public void render(GuiGraphics context, int mouseX, int mouseY, float delta) { CommonColors.WHITE ); - super.render(context, mouseX, mouseY, delta); + super.extractRenderState(graphics, mouseX, mouseY, a); } @Override - public void renderBackground(GuiGraphics context, int mouseX, int mouseY, float delta) { + public void extractBackground(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float a) { } } 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 c0e027c..4357e14 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/screen/PrivacyNoticeScreen.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/screen/PrivacyNoticeScreen.java @@ -33,7 +33,7 @@ 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.GuiGraphicsExtractor; import net.minecraft.client.gui.components.Button; import net.minecraft.client.gui.components.MultiLineLabel; import net.minecraft.client.gui.screens.Screen; @@ -60,9 +60,9 @@ public PrivacyNoticeScreen(Screen parent, Consumer accepted) { } @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, getTitleY(), CommonColors.WHITE); + public void extractRenderState(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float a) { + super.extractRenderState(graphics, mouseX, mouseY, a); + graphics.centeredText(this.font, this.title, this.width / 2, getTitleY(), CommonColors.WHITE); message.visitLines( TextAlignment.CENTER, 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 015beb1..71facb2 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.uploading.ScreenshotUploading; import net.fabricmc.loader.api.FabricLoader; import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.GuiGraphicsExtractor; import net.minecraft.client.gui.components.Button; import net.minecraft.client.gui.components.Tooltip; import net.minecraft.client.gui.layouts.HeaderAndFooterLayout; @@ -140,8 +140,8 @@ protected void init() { } @Override - public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { - super.render(graphics, mouseX, mouseY, delta); + public void extractRenderState(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float a) { + super.extractRenderState(graphics, mouseX, mouseY, a); this.drawMenuBackground(graphics); this.drawHeaderAndFooterSeparators(graphics); @@ -162,7 +162,7 @@ public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { } if (screenshotIndex != -1 && screenshots != null) { - graphics.drawCenteredString( + graphics.centeredText( this.font, "Screenshot %d/%d".formatted(screenshotIndex + 1, screenshots.size()), this.width / 2, @@ -173,7 +173,7 @@ public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { // TODO: Maybe add an option to the debug menu to turn this off if (FabricLoader.getInstance().isDevelopmentEnvironment()) { - graphics.drawCenteredString( + graphics.centeredText( this.font, Component.translatable("text.snapper.image_size", texture.getWidth(), texture.getHeight()), this.width / 2, @@ -181,7 +181,7 @@ public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { CommonColors.WHITE ); - graphics.drawCenteredString( + graphics.centeredText( this.font, Component.translatable("text.snapper.screen_size", this.width, this.height), this.width / 2, @@ -189,14 +189,14 @@ public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { CommonColors.WHITE ); - graphics.drawCenteredString(this.font, + graphics.centeredText(this.font, Component.translatable("text.snapper.scale_factor", scaleFactor), this.width / 2, 60, CommonColors.WHITE ); - graphics.drawCenteredString( + graphics.centeredText( this.font, Component.translatable("text.snapper.scale_size", finalWidth, finalHeight), this.width / 2, @@ -206,7 +206,7 @@ public void render(GuiGraphics graphics, int mouseX, int mouseY, float delta) { } } - private void drawMenuBackground(GuiGraphics graphics) { + private void drawMenuBackground(GuiGraphicsExtractor graphics) { graphics.blit( RenderPipelines.GUI_TEXTURED, this.minecraft.level == null ? MENU_LIST_BACKGROUND : INWORLD_MENU_LIST_BACKGROUND, @@ -220,7 +220,7 @@ private void drawMenuBackground(GuiGraphics graphics) { ); } - private void drawHeaderAndFooterSeparators(GuiGraphics graphics) { + private void drawHeaderAndFooterSeparators(GuiGraphicsExtractor graphics) { graphics.blit( RenderPipelines.GUI_TEXTURED, this.minecraft.level == null ? Screen.HEADER_SEPARATOR : Screen.INWORLD_HEADER_SEPARATOR, 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 331f472..d1c4200 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/toast/SnapperToast.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/toast/SnapperToast.java @@ -3,7 +3,7 @@ import dev.spiritstudios.snapper.Snapper; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.Font; -import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.GuiGraphicsExtractor; import net.minecraft.client.gui.components.toasts.Toast; import net.minecraft.client.gui.components.toasts.ToastManager; import net.minecraft.client.renderer.RenderPipelines; @@ -66,14 +66,14 @@ public void update(ToastManager manager, long time) { } @Override - public void render(GuiGraphics context, Font font, long startTime) { - context.blitSprite(RenderPipelines.GUI_TEXTURED, TEXTURE, 0, 0, this.width(), this.height()); - context.blitSprite(RenderPipelines.GUI_TEXTURED, getCurrentComponenture(), PADDING, PADDING, 16, 16); + public void extractRenderState(GuiGraphicsExtractor graphics, Font font, long startTime) { + graphics.blitSprite(RenderPipelines.GUI_TEXTURED, TEXTURE, 0, 0, this.width(), this.height()); + graphics.blitSprite(RenderPipelines.GUI_TEXTURED, getCurrentComponenture(), PADDING, PADDING, 16, 16); - context.drawString(font, title, PADDING * 2 + 12, this.lines.isEmpty() ? 12 : 7, CommonColors.YELLOW, false); + graphics.text(font, title, PADDING * 2 + 12, this.lines.isEmpty() ? 12 : 7, CommonColors.YELLOW, false); for (int i = 0; i < this.lines.size(); ++i) { - context.drawString(font, this.lines.get(i), PADDING * 2 + 12, 18 + i * 12, CommonColors.WHITE, false); + graphics.text(font, this.lines.get(i), PADDING * 2 + 12, 18 + i * 12, CommonColors.WHITE, false); } } diff --git a/src/client/java/dev/spiritstudios/snapper/gui/widget/ConfigList.java b/src/client/java/dev/spiritstudios/snapper/gui/widget/ConfigList.java index 4fc6fe2..afcd11c 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/widget/ConfigList.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/widget/ConfigList.java @@ -2,7 +2,7 @@ import dev.spiritstudios.snapper.gui.screen.ConfigScreen; import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.GuiGraphicsExtractor; import net.minecraft.client.gui.components.AbstractWidget; import net.minecraft.client.gui.components.ContainerObjectSelectionList; import net.minecraft.client.gui.components.StringWidget; @@ -83,13 +83,13 @@ public static Entry small(AbstractWidget leftOption, @Nullable AbstractWidget ri } @Override - public void renderContent(GuiGraphics guiGraphics, int mouseX, int mouseY, boolean isHovering, float partialTick) { + public void extractContent(GuiGraphicsExtractor graphics, int mouseX, int mouseY, boolean hovered, float a) { int xOffset = 0; int x = this.screen.width / 2 - 155; for (AbstractWidget abstractWidget : this.children) { abstractWidget.setPosition(x + xOffset, this.getContentY()); - abstractWidget.render(guiGraphics, mouseX, mouseY, partialTick); + abstractWidget.extractRenderState(graphics, mouseX, mouseY, a); xOffset += X_OFFSET; } } @@ -122,9 +122,9 @@ protected HeaderEntry(final Screen screen, final Component text, final int paddi } @Override - public void renderContent(final GuiGraphics graphics, final int mouseX, final int mouseY, final boolean hovered, final float a) { + public void extractContent(final GuiGraphicsExtractor graphics, final int mouseX, final int mouseY, final boolean hovered, final float a) { this.widget.setPosition(this.screen.width / 2 - 155, this.getContentY() + this.paddingTop); - this.widget.render(graphics, mouseX, mouseY, a); + this.widget.extractRenderState(graphics, mouseX, mouseY, a); } public @NonNull List children() { 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 51b20bb..14e6136 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/widget/FolderSelectWidget.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/widget/FolderSelectWidget.java @@ -5,7 +5,7 @@ import dev.spiritstudios.snapper.util.DirectoryConfigUtil; import net.minecraft.ChatFormatting; import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.GuiGraphicsExtractor; import net.minecraft.client.gui.components.*; import net.minecraft.client.gui.components.events.ContainerEventHandler; import net.minecraft.client.gui.narration.NarrationElementOutput; @@ -36,7 +36,7 @@ public class FolderSelectWidget extends AbstractContainerWidget implements Conta private final SpriteIconButton resetButton; public FolderSelectWidget(int x, int y, int width, int height, PathFunctions pathFunctions, String placeholderKey) { - super(x, y, width, height, CommonComponents.EMPTY); + super(x, y, width, height, CommonComponents.EMPTY, AbstractScrollArea.defaultSettings(0)); this.value = pathFunctions; this.active = false; @@ -185,9 +185,9 @@ public boolean mouseClicked(MouseButtonEvent click, boolean doubled) { } @Override - protected void renderWidget(GuiGraphics context, int mouseX, int mouseY, float deltaTicks) { + protected void extractWidgetRenderState(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float a) { for (Renderable drawable : this.children()) { - drawable.render(context, mouseX, mouseY, deltaTicks); + drawable.extractRenderState(graphics, mouseX, mouseY, a); } } 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 6583f86..bfa27c0 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/widget/ScreenshotGridWidget.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/widget/ScreenshotGridWidget.java @@ -3,7 +3,7 @@ import dev.spiritstudios.snapper.util.ScreenshotTexture; import dev.spiritstudios.snapper.util.SnapperUtil; import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.GuiGraphicsExtractor; import net.minecraft.client.gui.navigation.ScreenDirection; import net.minecraft.client.gui.screens.LoadingDotsText; import net.minecraft.client.gui.screens.Screen; @@ -133,7 +133,7 @@ private boolean safeIsSelected(Entry entry) { } @Override - public void renderContent(GuiGraphics graphics, int mouseX, int mouseY, boolean isHovering, float partialTick) { + public void extractContent(GuiGraphicsExtractor graphics, int mouseX, int mouseY, boolean isHovering, float partialTick) { texture.startLoading(minecraft); int centreX = getContentX() + getContentWidth() / 2; @@ -154,7 +154,7 @@ public void renderContent(GuiGraphics graphics, int mouseX, int mouseY, boolean } else { String loadString = LoadingDotsText.get(Util.getMillis()); - graphics.drawString( + graphics.text( minecraft.font, loadString, getContentX() + (getContentWidth() - minecraft.font.width(loadString)) / 2, getContentY() + getContentHeight() / 2, @@ -183,7 +183,7 @@ public void renderContent(GuiGraphics graphics, int mouseX, int mouseY, boolean 32, 32 ); - graphics.drawString( + graphics.text( minecraft.font, SnapperUtil.clipText(minecraft.font, fileName, getContentWidth() - 5), getContentX() + 5, @@ -192,7 +192,7 @@ public void renderContent(GuiGraphics graphics, int mouseX, int mouseY, boolean true ); - graphics.drawString( + graphics.text( minecraft.font, Component.translatable("text.snapper.created"), getContentX() + 5, @@ -201,7 +201,7 @@ public void renderContent(GuiGraphics graphics, int mouseX, int mouseY, boolean true ); - graphics.drawString( + graphics.text( minecraft.font, SnapperUtil.clipText(minecraft.font, creation, getContentWidth() - 5), getContentX() + 5, 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 9ab5ec5..61a422e 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/widget/ScreenshotListWidget.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/widget/ScreenshotListWidget.java @@ -3,7 +3,7 @@ import dev.spiritstudios.snapper.util.ScreenshotTexture; import dev.spiritstudios.snapper.util.SnapperUtil; import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.GuiGraphicsExtractor; import net.minecraft.client.gui.screens.LoadingDotsText; import net.minecraft.client.gui.screens.Screen; import net.minecraft.client.input.MouseButtonEvent; @@ -44,10 +44,10 @@ public ListScreenshotEntry(ScreenshotTexture texture) { } @Override - public void renderContent(GuiGraphics graphics, int mouseX, int mouseY, boolean isHovering, float partialTick) { + public void extractContent(GuiGraphicsExtractor graphics, int mouseX, int mouseY, boolean isHovering, float partialTick) { texture.startLoading(minecraft); - graphics.drawString( + graphics.text( minecraft.font, SnapperUtil.clipText(minecraft.font, fileName, getContentWidth() - 32 - 6), getContentX() + 32 + 3, getContentY() + 1, @@ -55,7 +55,7 @@ public void renderContent(GuiGraphics graphics, int mouseX, int mouseY, boolean false ); - graphics.drawString( + graphics.text( minecraft.font, Component.translatable("text.snapper.created") .append(CommonComponents.space()) @@ -78,7 +78,7 @@ public void renderContent(GuiGraphics graphics, int mouseX, int mouseY, boolean } else { String loadString = LoadingDotsText.get(Util.getMillis()); - graphics.drawString( + graphics.text( minecraft.font, loadString, getContentX() + (16 - minecraft.font.width(loadString) / 2), 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 5af1bbf..a26ff68 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/widget/ScreenshotsWidget.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/widget/ScreenshotsWidget.java @@ -9,7 +9,7 @@ import dev.spiritstudios.snapper.util.ScreenshotActions; import dev.spiritstudios.snapper.util.ScreenshotTexture; import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.GuiGraphicsExtractor; import net.minecraft.client.gui.components.ObjectSelectionList; import net.minecraft.client.gui.screens.LoadingDotsText; import net.minecraft.client.gui.screens.Screen; @@ -90,12 +90,14 @@ public ScreenshotsWidget( } @Override - protected void renderListItems(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) { + protected void extractListItems(GuiGraphicsExtractor graphics, 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); + this.extractItem(graphics, mouseX, mouseY, partialTick, entry); } else if (entry instanceof ScreenshotEntry sEntry) { - sEntry.clear(); + if (sEntry.texture.isLoaded()) { + sEntry.clear(); + } } } } @@ -176,10 +178,10 @@ public LoadingEntry(Minecraft client) { } @Override - public void renderContent(GuiGraphics context, int mouseX, int mouseY, boolean isHovering, float partialTick) { + public void extractContent(GuiGraphicsExtractor context, int mouseX, int mouseY, boolean isHovering, float partialTick) { if (this.client.screen == null) throw new IllegalStateException(); - context.drawString( + context.text( this.client.font, LOADING_LIST_TEXT, (this.client.screen.width - this.client.font.width(LOADING_LIST_TEXT)) / 2, @@ -190,7 +192,7 @@ public void renderContent(GuiGraphics context, int mouseX, int mouseY, boolean i String loadString = LoadingDotsText.get(Util.getMillis()); - context.drawString( + context.text( this.client.font, loadString, (this.client.screen.width - this.client.font.width(loadString)) / 2, @@ -216,10 +218,10 @@ public EmptyEntry(Minecraft minecraft) { } @Override - public void renderContent(GuiGraphics context, int mouseX, int mouseY, boolean isHovering, float partialTick) { + public void extractContent(GuiGraphicsExtractor context, int mouseX, int mouseY, boolean isHovering, float partialTick) { if (this.minecraft.screen == null) throw new IllegalStateException(); - context.drawString( + context.text( this.minecraft.font, EMPTY_LIST_TEXT, (this.minecraft.screen.width - this.minecraft.font.width(EMPTY_LIST_TEXT)) / 2, @@ -228,7 +230,7 @@ public void renderContent(GuiGraphics context, int mouseX, int mouseY, boolean i false ); - context.drawString( + context.text( this.minecraft.font, EMPTY_CUSTOM_LIST_TEXT, (this.minecraft.screen.width - this.minecraft.font.width(EMPTY_CUSTOM_LIST_TEXT)) / 2, diff --git a/src/client/java/dev/spiritstudios/snapper/gui/widget/ViewModeButton.java b/src/client/java/dev/spiritstudios/snapper/gui/widget/ViewModeButton.java index 224650a..f1efbad 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/widget/ViewModeButton.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/widget/ViewModeButton.java @@ -2,7 +2,7 @@ import dev.spiritstudios.snapper.Snapper; import dev.spiritstudios.snapper.SnapperConfig; -import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.GuiGraphicsExtractor; import net.minecraft.client.gui.components.Button; import net.minecraft.client.gui.components.WidgetSprites; import net.minecraft.client.renderer.RenderPipelines; @@ -20,8 +20,8 @@ public ViewModeButton(OnPress onPress, @Nullable CreateNarration createNarration } @Override - protected void renderContents(GuiGraphics graphics, int mouseX, int mouseY, float partialTick) { - this.renderDefaultSprite(graphics); + protected void extractContents(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float a) { + this.extractDefaultSprite(graphics); int x = this.getX() + this.getWidth() / 2 - SPRITE_SIZE / 2; int y = this.getY() + this.getHeight() / 2 - SPRITE_SIZE / 2; diff --git a/src/client/java/dev/spiritstudios/snapper/mixin/CameraMixin.java b/src/client/java/dev/spiritstudios/snapper/mixin/CameraMixin.java deleted file mode 100644 index e012cfc..0000000 --- a/src/client/java/dev/spiritstudios/snapper/mixin/CameraMixin.java +++ /dev/null @@ -1,21 +0,0 @@ -package dev.spiritstudios.snapper.mixin; - -import net.minecraft.client.Camera; -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; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -@Mixin(Camera.class) -public abstract class CameraMixin { - @Inject(method = "setup", at = @At("HEAD"), cancellable = true) - private void blockUpdateDuringPanoramaRender( - 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/GuiRendererMixin.java b/src/client/java/dev/spiritstudios/snapper/mixin/GuiRendererMixin.java new file mode 100644 index 0000000..191ec4c --- /dev/null +++ b/src/client/java/dev/spiritstudios/snapper/mixin/GuiRendererMixin.java @@ -0,0 +1,9 @@ +package dev.spiritstudios.snapper.mixin; + +import net.minecraft.client.gui.render.GuiRenderer; +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(GuiRenderer.class) +public class GuiRendererMixin { + +} diff --git a/src/client/java/dev/spiritstudios/snapper/mixin/InGameHudMixin.java b/src/client/java/dev/spiritstudios/snapper/mixin/InGameHudMixin.java index 62d9477..21e0984 100644 --- a/src/client/java/dev/spiritstudios/snapper/mixin/InGameHudMixin.java +++ b/src/client/java/dev/spiritstudios/snapper/mixin/InGameHudMixin.java @@ -22,7 +22,7 @@ public class InGameHudMixin { @Definition(id = "minecraft", field = "Lnet/minecraft/client/gui/Gui;minecraft:Lnet/minecraft/client/Minecraft;") @Definition(id = "screen", field = "Lnet/minecraft/client/Minecraft;screen:Lnet/minecraft/client/gui/screens/Screen;") @Expression("(this.minecraft.screen instanceof LevelLoadingScreen)") - @ModifyExpressionValue(method = "render", at = @At(value = "MIXINEXTRAS:EXPRESSION")) + @ModifyExpressionValue(method = "extractRenderState", at = @At(value = "MIXINEXTRAS:EXPRESSION")) private boolean cancelRenderingHudInPanoramaScreen(boolean original) { return original || minecraft.screen instanceof PanoramaViewerScreen; } diff --git a/src/client/java/dev/spiritstudios/snapper/mixin/KeyboardHandlerMixin.java b/src/client/java/dev/spiritstudios/snapper/mixin/KeyboardHandlerMixin.java index 628dfc3..fe35fbd 100644 --- a/src/client/java/dev/spiritstudios/snapper/mixin/KeyboardHandlerMixin.java +++ b/src/client/java/dev/spiritstudios/snapper/mixin/KeyboardHandlerMixin.java @@ -4,7 +4,7 @@ import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import com.mojang.blaze3d.pipeline.RenderTarget; import dev.spiritstudios.snapper.SnapperConfig; -import dev.spiritstudios.snapper.SnapperKeybindings; +import dev.spiritstudios.snapper.SnapperKeyMappings; import dev.spiritstudios.snapper.gui.toast.SnapperToast; import net.minecraft.client.KeyboardHandler; import net.minecraft.client.Minecraft; @@ -24,10 +24,10 @@ public abstract class KeyboardHandlerMixin { private Minecraft minecraft; @WrapOperation(method = "keyPress", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/Screenshot;grab(Ljava/io/File;Lcom/mojang/blaze3d/pipeline/RenderTarget;Ljava/util/function/Consumer;)V")) - private void showDebugChat(File gameDirectory, RenderTarget renderTarget, Consumer messageConsumer, Operation original) { + private void showDebugChat(File workDir, RenderTarget target, Consumer callback, Operation original) { original.call( - gameDirectory, - renderTarget, + workDir, + target, (Consumer) message -> { // Execute on the render thread. Minecraft.getInstance().execute(() -> { @@ -40,7 +40,7 @@ private void showDebugChat(File gameDirectory, RenderTarget renderTarget, Consum SnapperToast.push( SnapperToast.Type.SCREENSHOT, Component.translatable("toast.snapper.screenshot.created"), - Component.translatable(copyDeterminedDescription, message, SnapperKeybindings.RECENT_SCREENSHOT_KEY.getTranslatedKeyMessage()) + Component.translatable(copyDeterminedDescription, message, SnapperKeyMappings.RECENT_SCREENSHOT_KEY.getTranslatedKeyMessage()) ); }); } diff --git a/src/client/java/dev/spiritstudios/snapper/mixin/MinecraftMixin.java b/src/client/java/dev/spiritstudios/snapper/mixin/MinecraftMixin.java index 7765b12..f43aad3 100644 --- a/src/client/java/dev/spiritstudios/snapper/mixin/MinecraftMixin.java +++ b/src/client/java/dev/spiritstudios/snapper/mixin/MinecraftMixin.java @@ -42,16 +42,16 @@ public abstract class MinecraftMixin { value = "INVOKE", target = "Lnet/minecraft/client/Screenshot;grab(Ljava/io/File;Ljava/lang/String;Lcom/mojang/blaze3d/pipeline/RenderTarget;ILjava/util/function/Consumer;)V") ) - private void saveScreenshot(File gameDirectory, String fileName, RenderTarget renderTarget, int downscaleFactor, Consumer messageReceiver, Operation original) { - fileName = "panorama/" + fileName; - original.call(gameDirectory, fileName, renderTarget, downscaleFactor, messageReceiver); + private void saveScreenshot(File workDir, String forceName, RenderTarget target, int downscaleFactor, Consumer callback, Operation original) { + forceName = "panorama/" + forceName; + original.call(workDir, forceName, target, downscaleFactor, callback); } @Inject( method = "", at = @At("TAIL") ) - private void init(GameConfig args, CallbackInfo ci) { + private void init(GameConfig gameConfig, CallbackInfo ci) { if (Util.getPlatform() != Util.OS.OSX) System.setProperty("java.awt.headless", "false"); } diff --git a/src/client/java/dev/spiritstudios/snapper/mixin/PauseScreenMixin.java b/src/client/java/dev/spiritstudios/snapper/mixin/PauseScreenMixin.java index d93496d..0bbdf5c 100644 --- a/src/client/java/dev/spiritstudios/snapper/mixin/PauseScreenMixin.java +++ b/src/client/java/dev/spiritstudios/snapper/mixin/PauseScreenMixin.java @@ -33,9 +33,7 @@ protected void initWidgets(CallbackInfo ci) { this.addRenderableWidget( SpriteIconButton.builder( Component.translatable("button.snapper.screenshots"), - button -> { - Minecraft.getInstance().setScreen(new ScreenshotListScreen(new PauseScreen(true))); - }, + _ -> Minecraft.getInstance().setScreen(new ScreenshotListScreen(new PauseScreen(true))), true ).width(20).sprite(SNAPPER_BUTTON_ICON, 15, 15).build() ).setPosition(this.width / 2 - 130, height / 4 + 32); diff --git a/src/client/java/dev/spiritstudios/snapper/mixin/ScreenshotRecorderMixin.java b/src/client/java/dev/spiritstudios/snapper/mixin/ScreenshotMixin.java similarity index 67% rename from src/client/java/dev/spiritstudios/snapper/mixin/ScreenshotRecorderMixin.java rename to src/client/java/dev/spiritstudios/snapper/mixin/ScreenshotMixin.java index 818df58..93a702a 100644 --- a/src/client/java/dev/spiritstudios/snapper/mixin/ScreenshotRecorderMixin.java +++ b/src/client/java/dev/spiritstudios/snapper/mixin/ScreenshotMixin.java @@ -4,7 +4,6 @@ import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import com.mojang.blaze3d.pipeline.RenderTarget; import com.mojang.blaze3d.platform.NativeImage; -import dev.spiritstudios.snapper.Snapper; import dev.spiritstudios.snapper.SnapperConfig; import dev.spiritstudios.snapper.util.PlatformHelper; import net.minecraft.client.Screenshot; @@ -21,28 +20,28 @@ import java.util.function.Consumer; @Mixin(Screenshot.class) -public abstract class ScreenshotRecorderMixin { +public abstract class ScreenshotMixin { /** * @author hama * @reason check if panorama file exists before writing to it */ @SuppressWarnings("ResultOfMethodCallIgnored") @Inject( - method = "method_22691", + method = "lambda$grab$1", at = @At(value = "INVOKE", target = "Lcom/mojang/blaze3d/platform/NativeImage;writeToFile(Ljava/io/File;)V") ) - private static void lookBeforeYouLeap(NativeImage nativeImage, File screenshotFile, Consumer messageReceiver, CallbackInfo ci) throws IOException { - screenshotFile.getParentFile().mkdirs(); - screenshotFile.createNewFile(); + private static void lookBeforeYouLeap(NativeImage image, File file, Consumer callback, CallbackInfo ci) throws IOException { + file.getParentFile().mkdirs(); + file.createNewFile(); } @Inject( - method = "method_22691", + method = "lambda$grab$1", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/chat/Component;literal(Ljava/lang/String;)Lnet/minecraft/network/chat/MutableComponent;", shift = At.Shift.AFTER) ) - private static void saveWrittenFileToClipboard(NativeImage nativeImage, File screenshotFile, Consumer messageReceiver, CallbackInfo ci) { - if (!screenshotFile.getAbsolutePath().contains("/panorama/") && SnapperConfig.HOLDER.get().copyTakenScreenshot()) { - PlatformHelper.INSTANCE.copyScreenshot(screenshotFile.toPath()); + private static void saveWrittenFileToClipboard(NativeImage image, File file, Consumer callback, CallbackInfo ci) { + if (!file.getAbsolutePath().contains("/panorama/") && SnapperConfig.HOLDER.get().copyTakenScreenshot()) { + PlatformHelper.INSTANCE.copyScreenshot(file.toPath()); } } @@ -50,22 +49,22 @@ private static void saveWrittenFileToClipboard(NativeImage nativeImage, File scr * @author WorldWidePixel * @reason Okay, I know this is weird but it's so we can use our own wrapper text for the push. */ - @ModifyArg(method = "method_22691", + @ModifyArg(method = "lambda$grab$1", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/chat/Component;translatable(Ljava/lang/String;[Ljava/lang/Object;)Lnet/minecraft/network/chat/MutableComponent;", ordinal = 0)) private static String changeSuccessTranslation(String existing) { return "toast.snapper.screenshot.created.success"; } @WrapMethod(method = "grab(Ljava/io/File;Ljava/lang/String;Lcom/mojang/blaze3d/pipeline/RenderTarget;ILjava/util/function/Consumer;)V") - private static void getConfiguredGameDirectory(File gameDirectory, String fileName, RenderTarget renderTarget, int downscaleFactor, Consumer messageReceiver, Operation original) { + private static void getConfiguredGameDirectory(File workDir, String forceName, RenderTarget target, int downscaleFactor, Consumer callback, Operation original) { original.call( SnapperConfig.HOLDER.get().customScreenshotPath().enabled() && Files.exists(SnapperConfig.HOLDER.get().customScreenshotPath().path()) ? SnapperConfig.HOLDER.get().customScreenshotPath().path().toFile() : - gameDirectory, - fileName, - renderTarget, + workDir, + forceName, + target, downscaleFactor, - messageReceiver + callback ); } } diff --git a/src/client/java/dev/spiritstudios/snapper/mixin/TitleScreenMixin.java b/src/client/java/dev/spiritstudios/snapper/mixin/TitleScreenMixin.java index aa86054..835e08e 100644 --- a/src/client/java/dev/spiritstudios/snapper/mixin/TitleScreenMixin.java +++ b/src/client/java/dev/spiritstudios/snapper/mixin/TitleScreenMixin.java @@ -39,7 +39,7 @@ protected void init(CallbackInfo ci) { this.addRenderableWidget( SpriteIconButton.builder( Component.translatable("button.snapper.screenshots"), - button -> this.minecraft.setScreen(new ScreenshotListScreen((TitleScreen) ((Object) this))), + _ -> this.minecraft.setScreen(new ScreenshotListScreen((TitleScreen) ((Object) this))), true ).width(20).sprite(SNAPPER_BUTTON_ICON, 15, 15).build() ).setPosition(this.width / 2 - 124, y + spacingY); diff --git a/src/client/resources/snapper.mixins.json b/src/client/resources/snapper.mixins.json index ccddfaf..73cf9fe 100644 --- a/src/client/resources/snapper.mixins.json +++ b/src/client/resources/snapper.mixins.json @@ -3,12 +3,11 @@ "package": "dev.spiritstudios.snapper.mixin", "compatibilityLevel": "JAVA_21", "client": [ - "CameraMixin", "InGameHudMixin", "KeyboardHandlerMixin", "MinecraftMixin", "PauseScreenMixin", - "ScreenshotRecorderMixin", + "ScreenshotMixin", "TitleScreenMixin", "accessor.AbstractSelectionListAccessor", "accessor.AbstractSliderButtonAccessor", @@ -22,5 +21,8 @@ }, "mixinextras": { "minVersion": "0.5.0" - } + }, + "mixins": [ + "GuiRendererMixin" + ] } \ No newline at end of file diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index c36e497..109eb56 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -39,10 +39,9 @@ ], "depends": { "fabricloader": ">=${loader_version}", - "minecraft": "1.21.11", + "minecraft": "~26.1", "fabric-api": "*", - "java": ">=21", - "greenhouseconfig": ">=3.0.0-" + "java": ">=25" }, "custom": { "modmenu": { diff --git a/src/main/resources/snapper.classtweaker b/src/main/resources/snapper.classtweaker index 4c22e2e..3e8ef07 100644 --- a/src/main/resources/snapper.classtweaker +++ b/src/main/resources/snapper.classtweaker @@ -1,4 +1,4 @@ -classTweaker v1 named -extendable method net/minecraft/client/gui/components/AbstractSelectionList getEntryAtPosition (DD)Lnet/minecraft/client/gui/components/AbstractSelectionList$Entry; +classTweaker v1 official +extendable method net/minecraft/client/gui/components/AbstractSelectionList getEntryAtPosition (DD)Lnet/minecraft/client/gui/components/AbstractSelectionList$Entry; accessible method net/minecraft/client/gui/components/AbstractSelectionList repositionEntries ()V extendable method net/minecraft/client/gui/components/AbstractSelectionList repositionEntries ()V \ No newline at end of file From 60565c70930aefa6e9022a0ae0fd30d35c4da7a1 Mon Sep 17 00:00:00 2001 From: CallMeEchoCodes Date: Sat, 6 Jun 2026 13:42:33 +1000 Subject: [PATCH 07/15] Add cap to the amount of images loaded at once --- .../java/dev/spiritstudios/snapper/Snapper.java | 2 +- .../spiritstudios/snapper/SnapperKeyMappings.java | 1 - .../snapper/gui/screen/ScreenshotListScreen.java | 4 ++-- .../snapper/gui/screen/ScreenshotViewerScreen.java | 2 +- .../snapper/gui/widget/ScreenshotGridWidget.java | 2 +- .../snapper/gui/widget/ScreenshotListWidget.java | 2 +- .../snapper/util/ScreenshotTexture.java | 12 ++++++++++-- 7 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/client/java/dev/spiritstudios/snapper/Snapper.java b/src/client/java/dev/spiritstudios/snapper/Snapper.java index 24ecfe2..5ee4ede 100644 --- a/src/client/java/dev/spiritstudios/snapper/Snapper.java +++ b/src/client/java/dev/spiritstudios/snapper/Snapper.java @@ -15,7 +15,7 @@ public final class Snapper implements ClientModInitializer { public void onInitializeClient() { SnapperConfig.init(); SnapperKeyMappings.init(); - ClientLifecycleEvents.CLIENT_STOPPING.register(client -> ScreenshotUploading.close()); + ClientLifecycleEvents.CLIENT_STOPPING.register(_ -> ScreenshotUploading.close()); } public static Identifier id(String path) { diff --git a/src/client/java/dev/spiritstudios/snapper/SnapperKeyMappings.java b/src/client/java/dev/spiritstudios/snapper/SnapperKeyMappings.java index f86cefc..ff5ac1a 100644 --- a/src/client/java/dev/spiritstudios/snapper/SnapperKeyMappings.java +++ b/src/client/java/dev/spiritstudios/snapper/SnapperKeyMappings.java @@ -88,6 +88,5 @@ private static void openRecentScreenshot(Minecraft client) { Component.translatable("toast.snapper.screenshot.recent.failure.generic") ) ); - } } 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 d66bca6..8fb6a2b 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/screen/ScreenshotListScreen.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/screen/ScreenshotListScreen.java @@ -122,7 +122,7 @@ protected void init() { bottomRow.addChild(Button.builder( CommonComponents.GUI_DONE, - button -> this.onClose() + _ -> this.onClose() ).width(bottomButtonWidth).build()); Path panoramaDir = SnapperUtil.getConfiguredScreenshotDirectory().resolve("panorama"); boolean hasPanorama = SnapperUtil.panoramaPresent(panoramaDir); @@ -156,7 +156,7 @@ protected void init() { this.renameButton = topRow.addChild(Button.builder( Component.translatable("button.snapper.rename"), - button -> { + _ -> { if (this.selectedScreenshot != null) { minecraft.setScreen(new ScreenshotRenameScreen(this.selectedScreenshot.texture.path, this)); } 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 71facb2..7fbcecf 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/screen/ScreenshotViewerScreen.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/screen/ScreenshotViewerScreen.java @@ -47,7 +47,7 @@ public ScreenshotViewerScreen(ScreenshotTexture texture, Screen parent, @Nullabl this.parent = parent; this.texture = texture; - texture.startLoading(Minecraft.getInstance()); + texture.startLoading(Minecraft.getInstance(), true); this.screenshots = screenshots; 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 bfa27c0..2932458 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/widget/ScreenshotGridWidget.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/widget/ScreenshotGridWidget.java @@ -134,7 +134,7 @@ private boolean safeIsSelected(Entry entry) { @Override public void extractContent(GuiGraphicsExtractor graphics, int mouseX, int mouseY, boolean isHovering, float partialTick) { - texture.startLoading(minecraft); + texture.startLoading(minecraft, false); int centreX = getContentX() + getContentWidth() / 2; int centreY = getContentY() + getContentHeight() / 2; 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 61a422e..cfb9cf3 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/widget/ScreenshotListWidget.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/widget/ScreenshotListWidget.java @@ -45,7 +45,7 @@ public ListScreenshotEntry(ScreenshotTexture texture) { @Override public void extractContent(GuiGraphicsExtractor graphics, int mouseX, int mouseY, boolean isHovering, float partialTick) { - texture.startLoading(minecraft); + texture.startLoading(minecraft, false); graphics.text( minecraft.font, diff --git a/src/client/java/dev/spiritstudios/snapper/util/ScreenshotTexture.java b/src/client/java/dev/spiritstudios/snapper/util/ScreenshotTexture.java index 9545f6f..c552784 100644 --- a/src/client/java/dev/spiritstudios/snapper/util/ScreenshotTexture.java +++ b/src/client/java/dev/spiritstudios/snapper/util/ScreenshotTexture.java @@ -15,10 +15,13 @@ import java.nio.file.Path; import java.util.Optional; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicInteger; public class ScreenshotTexture implements AutoCloseable { private static final Identifier UNKNOWN_SERVER = Identifier.withDefaultNamespace("textures/misc/unknown_server.png"); + private static final AtomicInteger CURRENTLY_LOADING = new AtomicInteger(); + private final TextureManager textureManager; private final Identifier textureLocation; public final Path path; @@ -59,8 +62,12 @@ public NativeImage load() { } } - public synchronized void startLoading(Minecraft minecraft) { - if (!isLoaded()) { + private static final int MAX_LOADING = 10; + + public synchronized void startLoading(Minecraft minecraft, boolean force) { + if (!isLoaded() && (CURRENTLY_LOADING.get() < MAX_LOADING || force)) { + CURRENTLY_LOADING.incrementAndGet(); + CompletableFuture .supplyAsync(this::load, Util.nonCriticalIoPool()) .thenAcceptAsync(this::upload, minecraft); @@ -79,6 +86,7 @@ public synchronized void upload(NativeImage image) { this.textureManager.register(this.textureLocation, this.texture); loaded = true; + CURRENTLY_LOADING.decrementAndGet(); } catch (Throwable throwable) { image.close(); this.clear(); From 28af8f2005a04f5e41abd68d72a65d822ed54bb4 Mon Sep 17 00:00:00 2001 From: CallMeEchoCodes Date: Sat, 6 Jun 2026 14:04:04 +1000 Subject: [PATCH 08/15] Add panorama rendering --- .../dev/spiritstudios/snapper/Snapper.java | 2 ++ .../gui/screen/PanoramaViewerScreen.java | 9 +++--- .../snapper/mixin/CubeMapMixin.java | 23 +++++++++++++++ .../snapper/mixin/GuiRendererMixin.java | 28 +++++++++++++++++++ .../snapper/util/ScreenshotTexture.java | 1 - src/client/resources/snapper.mixins.json | 1 + 6 files changed, 58 insertions(+), 6 deletions(-) create mode 100644 src/client/java/dev/spiritstudios/snapper/mixin/CubeMapMixin.java diff --git a/src/client/java/dev/spiritstudios/snapper/Snapper.java b/src/client/java/dev/spiritstudios/snapper/Snapper.java index 5ee4ede..7e3fdea 100644 --- a/src/client/java/dev/spiritstudios/snapper/Snapper.java +++ b/src/client/java/dev/spiritstudios/snapper/Snapper.java @@ -11,6 +11,8 @@ public final class Snapper implements ClientModInitializer { public static final String MOD_ID = "snapper"; public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID); + public static final ScopedValue CUBEMAP_TEXTURE = ScopedValue.newInstance(); + @Override public void onInitializeClient() { SnapperConfig.init(); 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 fdd88de..f6fb6d6 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/screen/PanoramaViewerScreen.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/screen/PanoramaViewerScreen.java @@ -5,7 +5,6 @@ import dev.spiritstudios.snapper.util.SafeFiles; import dev.spiritstudios.snapper.util.SnapperUtil; import net.fabricmc.fabric.api.client.rendering.v1.RenderStateDataKey; -import net.minecraft.client.renderer.RenderPipelines; import net.minecraft.client.renderer.state.gui.PanoramaRenderState; import net.minecraft.util.Mth; import net.minecraft.util.Util; @@ -27,7 +26,7 @@ public class PanoramaViewerScreen extends Screen { public static final RenderStateDataKey SNAPPER_PANORAMA = RenderStateDataKey.create(() -> "Snapper Panorama"); - protected static final Identifier ID = Snapper.id("screenshots/panorama"); + public static final Identifier TEXTURE_ID = Snapper.id("screenshots/panorama"); private final DynamicCubemapTexture texture; @@ -44,7 +43,7 @@ protected PanoramaViewerScreen(String title, Screen parent) { if (texture != null) { // TODO: May be worth doing texture loading here off-thread as not to cause a freeze - Minecraft.getInstance().getTextureManager().registerAndLoad(ID, texture); + Minecraft.getInstance().getTextureManager().registerAndLoad(TEXTURE_ID, texture); } } @@ -58,7 +57,7 @@ private DynamicCubemapTexture getTexture() { if (Files.isDirectory(path)) return false; return SafeFiles.isContentType(path, "image/png", ".png"); - }) ? DynamicCubemapTexture.createPanorama(ID, panoramaDir).orElse(null) : null; + }) ? DynamicCubemapTexture.createPanorama(TEXTURE_ID, panoramaDir).orElse(null) : null; } catch (IOException | NullPointerException e) { Snapper.LOGGER.error("Failed to list the contents of directory", e); return null; @@ -68,7 +67,7 @@ private DynamicCubemapTexture getTexture() { @Override public void onClose() { if (texture != null) { - Minecraft.getInstance().getTextureManager().release(ID); + Minecraft.getInstance().getTextureManager().release(TEXTURE_ID); texture.close(); } diff --git a/src/client/java/dev/spiritstudios/snapper/mixin/CubeMapMixin.java b/src/client/java/dev/spiritstudios/snapper/mixin/CubeMapMixin.java new file mode 100644 index 0000000..3d6c27b --- /dev/null +++ b/src/client/java/dev/spiritstudios/snapper/mixin/CubeMapMixin.java @@ -0,0 +1,23 @@ +package dev.spiritstudios.snapper.mixin; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import dev.spiritstudios.snapper.Snapper; +import net.minecraft.client.renderer.CubeMap; +import net.minecraft.client.renderer.texture.AbstractTexture; +import net.minecraft.client.renderer.texture.TextureManager; +import net.minecraft.resources.Identifier; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +@Mixin(CubeMap.class) +public class CubeMapMixin { + @WrapOperation(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/renderer/texture/TextureManager;getTexture(Lnet/minecraft/resources/Identifier;)Lnet/minecraft/client/renderer/texture/AbstractTexture;")) + private AbstractTexture getCustomTexture(TextureManager instance, Identifier location, Operation original) { + if (Snapper.CUBEMAP_TEXTURE.isBound()) { + return original.call(instance, Snapper.CUBEMAP_TEXTURE.get()); + } else { + return original.call(instance, location); + } + } +} diff --git a/src/client/java/dev/spiritstudios/snapper/mixin/GuiRendererMixin.java b/src/client/java/dev/spiritstudios/snapper/mixin/GuiRendererMixin.java index 191ec4c..7771261 100644 --- a/src/client/java/dev/spiritstudios/snapper/mixin/GuiRendererMixin.java +++ b/src/client/java/dev/spiritstudios/snapper/mixin/GuiRendererMixin.java @@ -1,9 +1,37 @@ package dev.spiritstudios.snapper.mixin; +import com.mojang.blaze3d.buffers.GpuBufferSlice; +import dev.spiritstudios.snapper.Snapper; +import dev.spiritstudios.snapper.gui.screen.PanoramaViewerScreen; import net.minecraft.client.gui.render.GuiRenderer; +import net.minecraft.client.renderer.CubeMap; +import net.minecraft.client.renderer.state.gui.GuiRenderState; +import net.minecraft.client.renderer.state.gui.PanoramaRenderState; +import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(GuiRenderer.class) public class GuiRendererMixin { + @Shadow + @Final + private GuiRenderState renderState; + @Shadow + @Final + private CubeMap cubeMap; + + @Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/profiling/ProfilerFiller;push(Ljava/lang/String;)V", ordinal = 0)) + private void renderSnapperPanorama(GpuBufferSlice fogBuffer, CallbackInfo ci) { + PanoramaRenderState pano = renderState.getData(PanoramaViewerScreen.SNAPPER_PANORAMA); + + if (pano != null) { + ScopedValue.where(Snapper.CUBEMAP_TEXTURE, PanoramaViewerScreen.TEXTURE_ID).run(() -> { + this.cubeMap.render(10.0F, pano.spin()); + }); + } + } } diff --git a/src/client/java/dev/spiritstudios/snapper/util/ScreenshotTexture.java b/src/client/java/dev/spiritstudios/snapper/util/ScreenshotTexture.java index c552784..91f7ecb 100644 --- a/src/client/java/dev/spiritstudios/snapper/util/ScreenshotTexture.java +++ b/src/client/java/dev/spiritstudios/snapper/util/ScreenshotTexture.java @@ -94,7 +94,6 @@ public synchronized void upload(NativeImage image) { } } - public void clear() { this.checkOpen(); if (this.texture != null) { diff --git a/src/client/resources/snapper.mixins.json b/src/client/resources/snapper.mixins.json index 73cf9fe..8890849 100644 --- a/src/client/resources/snapper.mixins.json +++ b/src/client/resources/snapper.mixins.json @@ -23,6 +23,7 @@ "minVersion": "0.5.0" }, "mixins": [ + "CubeMapMixin", "GuiRendererMixin" ] } \ No newline at end of file From d057122bdab99706119c77c58a232c6d73851021 Mon Sep 17 00:00:00 2001 From: CallMeEchoCodes Date: Fri, 19 Jun 2026 12:06:47 +1000 Subject: [PATCH 09/15] chore: update deps --- build.gradle.kts | 8 ++++---- gradle/libs.versions.toml | 10 +++++----- gradle/wrapper/gradle-wrapper.jar | Bin 45633 -> 48966 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 2 +- src/client/resources/snapper.mixins.json | 10 ++++------ src/main/resources/fabric.mod.json | 2 +- 7 files changed, 16 insertions(+), 18 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 29c6582..e70185b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -42,7 +42,7 @@ repositories { mavenCentral() } -val debugArgs = arrayOf( +val debugArgs = listOf( "-enableassertions", // Mixin debugging, should make failures happen quicker @@ -70,7 +70,7 @@ loom { accessWidenerPath = file("src/main/resources/snapper.classtweaker") - runs.configureEach { vmArgs.addAll(debugArgs) } + runs.configureEach { jvmArguments.addAll(debugArgs) } } dependencies { @@ -81,8 +81,8 @@ dependencies { compileOnlyApi(libs.greenhouse.config.api) - runtimeOnly(libs.greenhouse.config) - include(libs.greenhouse.config) + runtimeOnly(libs.greenhouse.config.fabric) + include(libs.greenhouse.config.fabric) compileOnly(libs.modmenu) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b8f7eae..fc17e13 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,14 +1,14 @@ [versions] -fabric_loom = "1.16-SNAPSHOT" -modpublish = "1.1.0" +fabric_loom = "1.17-SNAPSHOT" +modpublish = "2.0.0" minecraft = "26.1.2" fabric_loader = "0.18.4" -fabric_api = "0.147.0+26.1.2" +fabric_api = "0.151.0+26.1.2" greenhouse_config = "3.0.0-beta.5+26.1.x" -modmenu = "18.0.0-alpha.8" +modmenu = "18.0.0-beta.1" objc_bridge = "1.0.0" @@ -23,7 +23,7 @@ fabric_loader = { group = "net.fabricmc", name = "fabric-loader", version.ref = fabric_api = { group = "net.fabricmc.fabric-api", name = "fabric-api", version.ref = "fabric_api" } greenhouse_config_api = { group = "lgbt.greenhouse.config", name = "greenhouse-config-api", version.ref = "greenhouse_config" } -greenhouse_config = { group = "lgbt.greenhouse.config", name = "greenhouse-config-fabric", version.ref = "greenhouse_config" } +greenhouse_config_fabric = { group = "lgbt.greenhouse.config", name = "greenhouse-config-fabric", version.ref = "greenhouse_config" } modmenu = { group = "com.terraformersmc", name = "modmenu", version.ref = "modmenu" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index f8e1ee3125fe0768e9a76ee977ac089eb657005e..d997cfc60f4cff0e7451d19d49a82fa986695d07 100644 GIT binary patch delta 39855 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 N2q(zEpY4qk{{xkq;s5{u diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index dbc3ce4..1a70468 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.0-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index adff685..0262dcb 100755 --- a/gradlew +++ b/gradlew @@ -57,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/b631911858264c0b6e4d6603d677ff5218766cee/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. diff --git a/src/client/resources/snapper.mixins.json b/src/client/resources/snapper.mixins.json index 8890849..2f03885 100644 --- a/src/client/resources/snapper.mixins.json +++ b/src/client/resources/snapper.mixins.json @@ -1,7 +1,7 @@ { "required": true, "package": "dev.spiritstudios.snapper.mixin", - "compatibilityLevel": "JAVA_21", + "compatibilityLevel": "JAVA_25", "client": [ "InGameHudMixin", "KeyboardHandlerMixin", @@ -9,6 +9,8 @@ "PauseScreenMixin", "ScreenshotMixin", "TitleScreenMixin", + "CubeMapMixin", + "GuiRendererMixin", "accessor.AbstractSelectionListAccessor", "accessor.AbstractSliderButtonAccessor", "accessor.CameraAccessor" @@ -21,9 +23,5 @@ }, "mixinextras": { "minVersion": "0.5.0" - }, - "mixins": [ - "CubeMapMixin", - "GuiRendererMixin" - ] + } } \ No newline at end of file diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 109eb56..1b78c46 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -39,7 +39,7 @@ ], "depends": { "fabricloader": ">=${loader_version}", - "minecraft": "~26.1", + "minecraft": "~26.1.2", "fabric-api": "*", "java": ">=25" }, From d23294e58730862ebd851fb554f8bd5167fb821c Mon Sep 17 00:00:00 2001 From: CallMeEchoCodes Date: Fri, 19 Jun 2026 12:09:49 +1000 Subject: [PATCH 10/15] Remove unused accessors --- .../accessor/AbstractSelectionListAccessor.java | 13 ------------- .../accessor/AbstractSliderButtonAccessor.java | 14 -------------- src/client/resources/snapper.mixins.json | 2 -- 3 files changed, 29 deletions(-) delete mode 100644 src/client/java/dev/spiritstudios/snapper/mixin/accessor/AbstractSelectionListAccessor.java delete mode 100644 src/client/java/dev/spiritstudios/snapper/mixin/accessor/AbstractSliderButtonAccessor.java diff --git a/src/client/java/dev/spiritstudios/snapper/mixin/accessor/AbstractSelectionListAccessor.java b/src/client/java/dev/spiritstudios/snapper/mixin/accessor/AbstractSelectionListAccessor.java deleted file mode 100644 index 9359141..0000000 --- a/src/client/java/dev/spiritstudios/snapper/mixin/accessor/AbstractSelectionListAccessor.java +++ /dev/null @@ -1,13 +0,0 @@ -package dev.spiritstudios.snapper.mixin.accessor; - -import net.minecraft.client.gui.components.AbstractSelectionList; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Mutable; -import org.spongepowered.asm.mixin.gen.Accessor; - -@Mixin(AbstractSelectionList.class) -public interface AbstractSelectionListAccessor { - @Mutable - @Accessor - void setDefaultEntryHeight(int height); -} diff --git a/src/client/java/dev/spiritstudios/snapper/mixin/accessor/AbstractSliderButtonAccessor.java b/src/client/java/dev/spiritstudios/snapper/mixin/accessor/AbstractSliderButtonAccessor.java deleted file mode 100644 index 804f4f5..0000000 --- a/src/client/java/dev/spiritstudios/snapper/mixin/accessor/AbstractSliderButtonAccessor.java +++ /dev/null @@ -1,14 +0,0 @@ -package dev.spiritstudios.snapper.mixin.accessor; - -import net.minecraft.client.gui.components.AbstractSliderButton; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.gen.Accessor; - -@Mixin(AbstractSliderButton.class) -public interface AbstractSliderButtonAccessor { - @Accessor - boolean getCanChangeValue(); - - @Accessor - void setCanChangeValue(boolean value); -} diff --git a/src/client/resources/snapper.mixins.json b/src/client/resources/snapper.mixins.json index 2f03885..0539220 100644 --- a/src/client/resources/snapper.mixins.json +++ b/src/client/resources/snapper.mixins.json @@ -11,8 +11,6 @@ "TitleScreenMixin", "CubeMapMixin", "GuiRendererMixin", - "accessor.AbstractSelectionListAccessor", - "accessor.AbstractSliderButtonAccessor", "accessor.CameraAccessor" ], "injectors": { From 193c0a1695699ea7d8b70611770ebd939125f895 Mon Sep 17 00:00:00 2001 From: CallMeEchoCodes Date: Fri, 19 Jun 2026 12:50:57 +1000 Subject: [PATCH 11/15] De-duplicate button bar code --- .../snapper/SnapperKeyMappings.java | 8 +- .../snapper/gui/SnapperButtonBar.java | 181 ++++++++++++++++++ .../gui/overlay/ExternalDialogOverlay.java | 14 +- .../snapper/gui/screen/ConfigScreen.java | 7 +- .../gui/screen/PanoramaViewerScreen.java | 4 +- .../snapper/gui/screen/ReloadableScreen.java | 1 + .../gui/screen/ScreenshotListScreen.java | 137 ++----------- .../gui/screen/ScreenshotRenameScreen.java | 2 +- .../gui/screen/ScreenshotViewerScreen.java | 82 ++------ .../gui/widget/ConfigSliderWidget.java | 4 - .../snapper/util/DynamicCubemapTexture.java | 7 - 11 files changed, 236 insertions(+), 211 deletions(-) create mode 100644 src/client/java/dev/spiritstudios/snapper/gui/SnapperButtonBar.java diff --git a/src/client/java/dev/spiritstudios/snapper/SnapperKeyMappings.java b/src/client/java/dev/spiritstudios/snapper/SnapperKeyMappings.java index ff5ac1a..b25999c 100644 --- a/src/client/java/dev/spiritstudios/snapper/SnapperKeyMappings.java +++ b/src/client/java/dev/spiritstudios/snapper/SnapperKeyMappings.java @@ -16,24 +16,24 @@ import java.util.List; public final class SnapperKeyMappings { - public static final KeyMapping.Category SNAPPER = KeyMapping.Category.register(Snapper.id(Snapper.MOD_ID)); + public static final KeyMapping.Category SNAPPER_CATEGORY = KeyMapping.Category.register(Snapper.id(Snapper.MOD_ID)); public static final KeyMapping PANORAMA_KEY = new KeyMapping( "key.snapper.panorama", GLFW.GLFW_KEY_F8, - SNAPPER + SNAPPER_CATEGORY ); public static final KeyMapping RECENT_SCREENSHOT_KEY = new KeyMapping( "key.snapper.recent", GLFW.GLFW_KEY_O, - SNAPPER + SNAPPER_CATEGORY ); public static final KeyMapping SCREENSHOT_MENU_KEY = new KeyMapping( "key.snapper.screenshot_menu", GLFW.GLFW_KEY_V, - SNAPPER + SNAPPER_CATEGORY ); public static void init() { diff --git a/src/client/java/dev/spiritstudios/snapper/gui/SnapperButtonBar.java b/src/client/java/dev/spiritstudios/snapper/gui/SnapperButtonBar.java new file mode 100644 index 0000000..2f5525e --- /dev/null +++ b/src/client/java/dev/spiritstudios/snapper/gui/SnapperButtonBar.java @@ -0,0 +1,181 @@ +package dev.spiritstudios.snapper.gui; + +import com.ibm.icu.impl.coll.BOCSU; +import dev.spiritstudios.snapper.Snapper; +import dev.spiritstudios.snapper.gui.screen.ConfigScreen; +import dev.spiritstudios.snapper.gui.screen.PanoramaViewerScreen; +import dev.spiritstudios.snapper.gui.screen.ScreenshotRenameScreen; +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.ScreenshotTexture; +import dev.spiritstudios.snapper.util.SnapperUtil; +import dev.spiritstudios.snapper.util.uploading.ScreenshotUploading; +import net.minecraft.client.Minecraft; +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.HeaderAndFooterLayout; +import net.minecraft.client.gui.layouts.LinearLayout; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.CommonComponents; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.Identifier; +import net.minecraft.util.Util; +import org.jspecify.annotations.Nullable; + +import java.nio.file.Path; +import java.util.function.Supplier; + +public class SnapperButtonBar { + 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"); + + public final Button deleteButton; + public final Button renameButton; + public final Button copyButton; + public final Button openButton; + public final Button uploadButton; + + public SnapperButtonBar( + Screen screen, + HeaderAndFooterLayout layout, + Supplier<@Nullable ScreenshotTexture> getTexture, + boolean enablePanoramaButton, + @Nullable Runnable toggleGrid, + @Nullable Runnable reload + ) { + Minecraft minecraft = Minecraft.getInstance(); + + final int hSpacing = 4; + + final int buttonWidth = 74; + final int bottomButtonWidth = 100; + + LinearLayout vertical = layout.addToFooter(LinearLayout.vertical().spacing(4)); + vertical.defaultCellSetting().alignHorizontallyCenter(); + + LinearLayout topRow = vertical.addChild(LinearLayout.horizontal().spacing(hSpacing)); + LinearLayout bottomRow = vertical.addChild(LinearLayout.horizontal().spacing(hSpacing)); + + bottomRow.addChild(SpriteIconButton.builder( + Component.translatable("config.snapper.title"), + _ -> minecraft.setScreen(new ConfigScreen(screen)), + true + ).width(20).sprite(SETTINGS_ICON, 15, 15).build()); + + bottomRow.addChild(Button.builder( + Component.translatable("button.snapper.folder"), + _ -> Util.getPlatform().openPath(SnapperUtil.getConfiguredScreenshotDirectory()) + ).width(bottomButtonWidth).build()); + + this.openButton = bottomRow.addChild(Button.builder( + Component.translatable("button.snapper.open"), + _ -> { + ScreenshotTexture selected = getTexture.get(); + + if (selected != null) { + Util.getPlatform().openPath(selected.path); + } + } + ).width(bottomButtonWidth).build()); + + bottomRow.addChild(Button.builder( + CommonComponents.GUI_DONE, + _ -> screen.onClose() + ).width(bottomButtonWidth).build()); + Path panoramaDir = SnapperUtil.getConfiguredScreenshotDirectory().resolve("panorama"); + + boolean hasPanorama = enablePanoramaButton && SnapperUtil.panoramaPresent(panoramaDir); + SpriteIconButton panoramaButton = bottomRow.addChild( + SpriteIconButton.builder( + Component.translatable("button.snapper.screenshots"), + _ -> minecraft.setScreen(new PanoramaViewerScreen(Component.translatable("menu.snapper.panorama").getString(), screen)), + true + ).width(20).sprite(hasPanorama ? PANORAMA_BUTTON_ICON : PANORAMA_BUTTON_DISABLED_ICON, 15, 15).build()); + + panoramaButton.active = hasPanorama; + + if (enablePanoramaButton) { + panoramaButton.setTooltip(Tooltip.create(Component.translatable(hasPanorama ? + "button.snapper.panorama.tooltip" : + "text.snapper.panorama_encourage"))); + } + + ViewModeButton viewModeButton = new ViewModeButton( + _ -> { + if (toggleGrid != null) toggleGrid.run(); + }, + null + ); + + viewModeButton.active = toggleGrid != null; + + topRow.addChild(viewModeButton); + + this.deleteButton = topRow.addChild(Button.builder( + Component.translatable("button.snapper.delete"), + _ -> { + ScreenshotTexture selected = getTexture.get(); + + if (selected != null) { + ScreenshotActions.deleteScreenshot(selected.path, screen); + } + } + ).width(buttonWidth).build()); + + this.renameButton = topRow.addChild(Button.builder( + Component.translatable("button.snapper.rename"), + _ -> { + ScreenshotTexture selected = getTexture.get(); + + if (selected != null) { + minecraft.setScreen(new ScreenshotRenameScreen(selected.path, screen)); + } + } + ).width(buttonWidth).build()); + + this.copyButton = topRow.addChild(Button.builder( + Component.translatable("button.snapper.copy"), + _ -> { + ScreenshotTexture selected = getTexture.get(); + + if (selected != null) { + PlatformHelper.INSTANCE.copyScreenshot(selected.path); + } + } + ).width(buttonWidth).build()); + + this.uploadButton = topRow.addChild(Button.builder(Component.translatable("button.snapper.upload"), button -> { + ScreenshotTexture selected = getTexture.get(); + + if (selected == null) return; + + button.active = false; + ScreenshotUploading.upload(selected.path) + .thenRun(() -> button.active = true); + }).width(buttonWidth).build()); + + if (SnapperUtil.isOfflineAccount()) { + this.uploadButton.active = false; + this.uploadButton.setTooltip(Tooltip.create(Component.translatable("button.snapper.upload.tooltip"))); + } + + var reloadButton = SpriteIconButton.builder( + Component.translatable("button.snapper.reload"), + _ -> { + if (reload != null) reload.run(); + }, + true + ).width(20).sprite(RELOAD_ICON, 15, 15).build(); + + reloadButton.active = reload != null; + + topRow.addChild(reloadButton); + } +} 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 007f27a..b53bb3e 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/overlay/ExternalDialogOverlay.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/overlay/ExternalDialogOverlay.java @@ -10,22 +10,22 @@ import net.minecraft.util.CommonColors; public class ExternalDialogOverlay extends Overlay { - private final Minecraft client = Minecraft.getInstance(); + private final Minecraft minecraft = Minecraft.getInstance(); 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 extractRenderState(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float a) { - if (this.client.screen != null) { - this.client.screen.extractRenderStateWithTooltipAndSubtitles(graphics, mouseX, mouseY, a); + if (this.minecraft.screen != null) { + this.minecraft.screen.extractRenderStateWithTooltipAndSubtitles(graphics, mouseX, mouseY, a); } graphics.nextStratum(); graphics.blit( RenderPipelines.GUI_TEXTURED, - this.client.level == null ? MENU_BACKGROUND_TEXTURE : INWORLD_MENU_BACKGROUND_TEXTURE, + this.minecraft.level == null ? MENU_BACKGROUND_TEXTURE : INWORLD_MENU_BACKGROUND_TEXTURE, 0, 0, 0, 0, graphics.guiWidth(), graphics.guiHeight(), @@ -33,16 +33,16 @@ public void extractRenderState(GuiGraphicsExtractor graphics, int mouseX, int mo ); graphics.centeredText( - client.font, + minecraft.font, Component.translatable("overlay.snapper.external_dialog.folder"), graphics.guiWidth() / 2, graphics.guiHeight() / 2, CommonColors.WHITE ); - if (InputConstants.isKeyDown(client.getWindow(), InputConstants.KEY_ESCAPE)) close(); + if (InputConstants.isKeyDown(minecraft.getWindow(), InputConstants.KEY_ESCAPE)) close(); } public void close() { - this.client.setOverlay(null); + this.minecraft.setOverlay(null); } } 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 d5571cf..d3cba0b 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/screen/ConfigScreen.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/screen/ConfigScreen.java @@ -255,10 +255,9 @@ public void onClose() { config.saveAsync().thenRun(() -> { if (lastScreen instanceof ReloadableScreen reloadableScreen) { Minecraft.getInstance().submit(reloadableScreen::reload); - } - - if (lastScreen instanceof ScreenshotListScreen screenshotsScreen && config.viewMode != viewMode) { - screenshotsScreen.recreateList(); + if (config.viewMode != viewMode) { + reloadableScreen.recreateList(); + } } }); 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 f6fb6d6..d18c4f5 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/screen/PanoramaViewerScreen.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/screen/PanoramaViewerScreen.java @@ -35,7 +35,7 @@ public class PanoramaViewerScreen extends Screen { private float spin = 0.0F; - protected PanoramaViewerScreen(String title, Screen parent) { + public PanoramaViewerScreen(String title, Screen parent) { super(Component.translatable("menu.snapper.viewer_menu")); this.title = title; this.parent = parent; @@ -57,7 +57,7 @@ private DynamicCubemapTexture getTexture() { if (Files.isDirectory(path)) return false; return SafeFiles.isContentType(path, "image/png", ".png"); - }) ? DynamicCubemapTexture.createPanorama(TEXTURE_ID, panoramaDir).orElse(null) : null; + }) ? new DynamicCubemapTexture(TEXTURE_ID, panoramaDir) : null; } catch (IOException | NullPointerException e) { Snapper.LOGGER.error("Failed to list the contents of directory", e); return null; diff --git a/src/client/java/dev/spiritstudios/snapper/gui/screen/ReloadableScreen.java b/src/client/java/dev/spiritstudios/snapper/gui/screen/ReloadableScreen.java index ec2f543..0b54e1c 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/screen/ReloadableScreen.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/screen/ReloadableScreen.java @@ -1,5 +1,6 @@ package dev.spiritstudios.snapper.gui.screen; public interface ReloadableScreen { + void recreateList(); 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 8fb6a2b..9677b7f 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/screen/ScreenshotListScreen.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/screen/ScreenshotListScreen.java @@ -4,6 +4,7 @@ import com.mojang.serialization.Codec; import dev.spiritstudios.snapper.Snapper; import dev.spiritstudios.snapper.SnapperConfig; +import dev.spiritstudios.snapper.gui.SnapperButtonBar; import dev.spiritstudios.snapper.gui.toast.SnapperToast; import dev.spiritstudios.snapper.gui.widget.ScreenshotListWidget; import dev.spiritstudios.snapper.gui.widget.ScreenshotsWidget; @@ -30,22 +31,11 @@ import java.nio.file.Path; 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; private ScreenshotsWidget screenshots = null; - private Button deleteButton; - private Button renameButton; - private Button copyButton; - private Button openButton; - private Button uploadButton; + private SnapperButtonBar bar; private @Nullable ScreenshotListWidget.ScreenshotEntry selectedScreenshot = null; @@ -54,10 +44,10 @@ public class ScreenshotListScreen extends Screen implements ReloadableScreen { public ScreenshotListScreen(Screen parent) { super(Component.translatable("menu.snapper.screenshot_menu")); this.parent = parent; - this.isOffline = SnapperUtil.isOfflineAccount(); this.recreateList(); } + @Override public synchronized void recreateList() { if (screenshots != null) { this.removeWidget(screenshots); @@ -88,108 +78,14 @@ protected void repositionElements() { protected void init() { 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(); - - LinearLayout topRow = vertical.addChild(LinearLayout.horizontal().spacing(hSpacing)); - LinearLayout bottomRow = vertical.addChild(LinearLayout.horizontal().spacing(hSpacing)); - - 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(bottomButtonWidth).build()); - - this.openButton = bottomRow.addChild(Button.builder( - Component.translatable("button.snapper.open"), - button -> { - if (selectedScreenshot != null) { - Util.getPlatform().openPath(selectedScreenshot.texture.path); - } - } - ).width(bottomButtonWidth).build()); - - bottomRow.addChild(Button.builder( - CommonComponents.GUI_DONE, - _ -> this.onClose() - ).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()); - - panoramaButton.active = hasPanorama; - - panoramaButton.setTooltip(Tooltip.create(Component.translatable(hasPanorama ? - "button.snapper.panorama.tooltip" : - "text.snapper.panorama_encourage"))); - - 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.texture.path, this); - } - } - ).width(buttonWidth).build()); - - this.renameButton = topRow.addChild(Button.builder( - Component.translatable("button.snapper.rename"), - _ -> { - if (this.selectedScreenshot != null) { - minecraft.setScreen(new ScreenshotRenameScreen(this.selectedScreenshot.texture.path, this)); - } - } - ).width(buttonWidth).build()); - - this.copyButton = topRow.addChild(Button.builder( - Component.translatable("button.snapper.copy"), - button -> { - if (selectedScreenshot != null) { - PlatformHelper.INSTANCE.copyScreenshot(selectedScreenshot.texture.path); - } - } - ).width(buttonWidth).build()); - - this.uploadButton = topRow.addChild(Button.builder(Component.translatable("button.snapper.upload"), button -> { - if (selectedScreenshot == null) return; - - button.active = false; - ScreenshotUploading.upload(selectedScreenshot.texture.path) - .thenRun(() -> button.active = true); - }).width(buttonWidth).build()); - - if (isOffline) { - this.uploadButton.setTooltip(Tooltip.create(Component.translatable("button.snapper.upload.tooltip"))); - } - - topRow.addChild( - SpriteIconButton.builder( - Component.translatable("button.snapper.reload"), - button -> this.reload(), - true - ).width(20).sprite(RELOAD_ICON, 15, 15).build()); + this.bar = new SnapperButtonBar( + this, + this.layout, + () -> this.selectedScreenshot != null ? this.selectedScreenshot.texture : null, + true, + this::toggleGrid, + this::reload + ); this.imageSelected(selectedScreenshot); @@ -210,12 +106,13 @@ public void onClose() { public void imageSelected(@Nullable ScreenshotListWidget.ScreenshotEntry screenshot) { boolean hasScreenshot = screenshot != null; - this.copyButton.active = hasScreenshot; - this.deleteButton.active = hasScreenshot; - this.openButton.active = hasScreenshot; - this.renameButton.active = hasScreenshot; + this.bar.copyButton.active = hasScreenshot; + this.bar.deleteButton.active = hasScreenshot; + this.bar.openButton.active = hasScreenshot; + this.bar.renameButton.active = hasScreenshot; + this.bar.uploadButton.active = !SnapperUtil.isOfflineAccount() && hasScreenshot; + this.selectedScreenshot = screenshot; - this.uploadButton.active = !isOffline && hasScreenshot; } public void toggleGrid() { 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 6e96b2b..93d8c6a 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/screen/ScreenshotRenameScreen.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/screen/ScreenshotRenameScreen.java @@ -20,7 +20,7 @@ public class ScreenshotRenameScreen extends Screen { private final Font textRenderer = client.font; private final Screen parent; - protected ScreenshotRenameScreen(Path screenshot, Screen parent) { + public ScreenshotRenameScreen(Path screenshot, Screen parent) { super(Component.translatable("text.snapper.rename")); this.screenshot = screenshot; this.renameInput = new EditBox(textRenderer, 200, 20, RENAME_INPUT_TEXT); 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 7fbcecf..642f9fb 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/screen/ScreenshotViewerScreen.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/screen/ScreenshotViewerScreen.java @@ -1,5 +1,6 @@ package dev.spiritstudios.snapper.gui.screen; +import dev.spiritstudios.snapper.gui.SnapperButtonBar; import dev.spiritstudios.snapper.util.PlatformHelper; import dev.spiritstudios.snapper.util.ScreenshotActions; import dev.spiritstudios.snapper.util.ScreenshotTexture; @@ -37,6 +38,7 @@ public class ScreenshotViewerScreen extends Screen implements ReloadableScreen { private final HeaderAndFooterLayout layout = new HeaderAndFooterLayout(this, 33, 60); public boolean shouldReloadParent = false; + public boolean shouldRecreateParent = false; public ScreenshotViewerScreen(ScreenshotTexture texture, Screen parent) { this(texture, parent, null); @@ -58,8 +60,11 @@ public ScreenshotViewerScreen(ScreenshotTexture texture, Screen parent, @Nullabl public void onClose() { if (!(parent instanceof ScreenshotListScreen listScreen)) { this.texture.close(); - } else if (shouldReloadParent) { - listScreen.getScreenshots().reload(); + } + + if (parent instanceof ReloadableScreen reloadable) { + if (shouldReloadParent) reloadable.reload(); + if (shouldRecreateParent) reloadable.recreateList(); } this.client.setScreen(this.parent); @@ -72,68 +77,16 @@ protected void repositionElements() { @Override protected void init() { - // TODO: Dedupe code from here and ScreenshotListScreen 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(); - - LinearLayout topRow = vertical.addChild(LinearLayout.horizontal().spacing(hSpacing)); - LinearLayout bottomRow = vertical.addChild(LinearLayout.horizontal().spacing(hSpacing)); - - bottomRow.addChild(Button.builder( - Component.translatable("button.snapper.folder"), - button -> Util.getPlatform().openPath(SnapperUtil.getConfiguredScreenshotDirectory()) - ).width(bottomButtonWidth).build()); - - bottomRow.addChild(Button.builder( - Component.translatable("button.snapper.open"), - button -> { - Util.getPlatform().openPath(this.texture.path); - } - ).width(bottomButtonWidth).build()); - - bottomRow.addChild(Button.builder( - CommonComponents.GUI_DONE, - button -> this.onClose() - ).width(bottomButtonWidth).build()); - - topRow.addChild(Button.builder( - Component.translatable("button.snapper.delete"), - button -> { - ScreenshotActions.deleteScreenshot(this.texture.path, parent); - } - ).width(buttonWidth).build()); - - topRow.addChild(Button.builder( - Component.translatable("button.snapper.rename"), - button -> { - minecraft.setScreen(new ScreenshotRenameScreen(this.texture.path, this)); - } - ).width(buttonWidth).build()); - - topRow.addChild(Button.builder( - Component.translatable("button.snapper.copy"), - button -> { - PlatformHelper.INSTANCE.copyScreenshot(this.texture.path); - } - ).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"))); - } + new SnapperButtonBar( + this, + this.layout, + () -> this.texture, + false, + null, + null + ); this.layout.visitWidgets(this::addRenderableWidget); this.repositionElements(); @@ -244,4 +197,9 @@ private void drawHeaderAndFooterSeparators(GuiGraphicsExtractor graphics) { public void reload() { this.shouldReloadParent = true; } + + @Override + public void recreateList() { + this.shouldRecreateParent = true; + } } diff --git a/src/client/java/dev/spiritstudios/snapper/gui/widget/ConfigSliderWidget.java b/src/client/java/dev/spiritstudios/snapper/gui/widget/ConfigSliderWidget.java index a2df146..c6040ef 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/widget/ConfigSliderWidget.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/widget/ConfigSliderWidget.java @@ -1,16 +1,12 @@ package dev.spiritstudios.snapper.gui.widget; -import dev.spiritstudios.snapper.mixin.accessor.AbstractSliderButtonAccessor; -import net.minecraft.client.OptionInstance; import net.minecraft.client.gui.components.AbstractSliderButton; import net.minecraft.client.gui.components.Tooltip; -import net.minecraft.client.input.KeyEvent; import net.minecraft.client.input.MouseButtonEvent; import net.minecraft.network.chat.CommonComponents; import net.minecraft.network.chat.Component; import org.jspecify.annotations.Nullable; -import java.util.Optional; import java.util.function.Consumer; import java.util.function.DoubleFunction; import java.util.function.Function; diff --git a/src/client/java/dev/spiritstudios/snapper/util/DynamicCubemapTexture.java b/src/client/java/dev/spiritstudios/snapper/util/DynamicCubemapTexture.java index 7ba31e7..afb2ff8 100644 --- a/src/client/java/dev/spiritstudios/snapper/util/DynamicCubemapTexture.java +++ b/src/client/java/dev/spiritstudios/snapper/util/DynamicCubemapTexture.java @@ -55,11 +55,4 @@ public TextureContents loadContents(ResourceManager resourceManager) throws IOEx } return contents; } - - public static Optional createPanorama(Identifier id, Path path) { - return Optional.of(new DynamicCubemapTexture( - id, - path - )); - } } From a561f9682f38d48e9fe70d87f199454351cc6a1a Mon Sep 17 00:00:00 2001 From: CallMeEchoCodes Date: Fri, 19 Jun 2026 12:54:00 +1000 Subject: [PATCH 12/15] Run workflow on all branches, update workflow deps --- .github/workflows/build.yml | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 674cd33..d77a138 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,9 +1,6 @@ name: Build on: push: - branches: - - main - - dev pull_request: workflow_dispatch: @@ -11,15 +8,15 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: gradle/actions/wrapper-validation@v4 - - uses: actions/setup-java@v4 + - uses: actions/checkout@v6 + - uses: gradle/actions/wrapper-validation@v6 + - uses: actions/setup-java@v5 with: - java-version: '21' + java-version: '25' distribution: 'microsoft' - run: chmod +x ./gradlew - run: ./gradlew build - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v7 with: name: Artifacts path: build/libs/ \ No newline at end of file From aed56dd592305c148f407ff84c7c648ff06cddde Mon Sep 17 00:00:00 2001 From: CallMeEchoCodes Date: Sun, 21 Jun 2026 14:22:11 +1000 Subject: [PATCH 13/15] Fix loading stall if texture load failed --- .../snapper/gui/screen/PanoramaViewerScreen.java | 2 +- .../spiritstudios/snapper/util/ScreenshotTexture.java | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) 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 d18c4f5..6e81e3e 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/screen/PanoramaViewerScreen.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/screen/PanoramaViewerScreen.java @@ -84,7 +84,7 @@ protected void init() { } Path panoramaPath = Path.of(Minecraft.getInstance().gameDirectory.getPath(), "screenshots", "panorama"); - addRenderableWidget(Button.builder(Component.translatable("button.snapper.folder"), button -> { + addRenderableWidget(Button.builder(Component.translatable("button.snapper.folder"), _ -> { Util.getPlatform().openPath(panoramaPath); }).bounds(width / 2 - 150 - 4, height - 32, 150, 20).build()); diff --git a/src/client/java/dev/spiritstudios/snapper/util/ScreenshotTexture.java b/src/client/java/dev/spiritstudios/snapper/util/ScreenshotTexture.java index 91f7ecb..fe1c8b7 100644 --- a/src/client/java/dev/spiritstudios/snapper/util/ScreenshotTexture.java +++ b/src/client/java/dev/spiritstudios/snapper/util/ScreenshotTexture.java @@ -20,8 +20,6 @@ public class ScreenshotTexture implements AutoCloseable { private static final Identifier UNKNOWN_SERVER = Identifier.withDefaultNamespace("textures/misc/unknown_server.png"); - private static final AtomicInteger CURRENTLY_LOADING = new AtomicInteger(); - private final TextureManager textureManager; private final Identifier textureLocation; public final Path path; @@ -63,6 +61,7 @@ public NativeImage load() { } private static final int MAX_LOADING = 10; + private static final AtomicInteger CURRENTLY_LOADING = new AtomicInteger(); public synchronized void startLoading(Minecraft minecraft, boolean force) { if (!isLoaded() && (CURRENTLY_LOADING.get() < MAX_LOADING || force)) { @@ -70,7 +69,10 @@ public synchronized void startLoading(Minecraft minecraft, boolean force) { CompletableFuture .supplyAsync(this::load, Util.nonCriticalIoPool()) - .thenAcceptAsync(this::upload, minecraft); + .thenAcceptAsync(this::upload, minecraft) + .whenComplete((_, _) -> { + CURRENTLY_LOADING.decrementAndGet(); + }); } } @@ -86,7 +88,6 @@ public synchronized void upload(NativeImage image) { this.textureManager.register(this.textureLocation, this.texture); loaded = true; - CURRENTLY_LOADING.decrementAndGet(); } catch (Throwable throwable) { image.close(); this.clear(); From f40dcd2bc25da9b4086793a383248126800eec3a Mon Sep 17 00:00:00 2001 From: CallMeEchoCodes Date: Sun, 21 Jun 2026 14:25:37 +1000 Subject: [PATCH 14/15] Fix Panorama Viewer "Open Folder" button opening the wrong path --- .../spiritstudios/snapper/gui/screen/PanoramaViewerScreen.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 6e81e3e..5e4a429 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/screen/PanoramaViewerScreen.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/screen/PanoramaViewerScreen.java @@ -83,7 +83,7 @@ protected void init() { return; } - Path panoramaPath = Path.of(Minecraft.getInstance().gameDirectory.getPath(), "screenshots", "panorama"); + Path panoramaPath = SnapperUtil.getConfiguredScreenshotDirectory().resolve("screenshots", "panorama"); addRenderableWidget(Button.builder(Component.translatable("button.snapper.folder"), _ -> { Util.getPlatform().openPath(panoramaPath); }).bounds(width / 2 - 150 - 4, height - 32, 150, 20).build()); From e2d3bb35a5bde44cae72c9d9755c0cf464f379fa Mon Sep 17 00:00:00 2001 From: CallMeEchoCodes Date: Sat, 4 Jul 2026 23:20:43 +1000 Subject: [PATCH 15/15] feat: 1.1.2 --- CHANGELOG.md | 14 +++++--------- build.gradle.kts | 2 +- .../snapper/gui/screen/PrivacyNoticeScreen.java | 6 +++--- .../snapper/util/uploading/AxolotlClientApi.java | 1 - 4 files changed, 9 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6462c15..78ff9dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,9 @@ -## Snapper 1.1.1 +## Snapper 1.1.2 ### Features -- 1.21.10 support -- Switch to Greenhouse Config -- Add German translations -- Replaced last of the overlays with toasts +- 26.1 support +- Parallel image loading (much faster!) +- Reload Button ### Bug Fixes -- Scrollbar math is now correct -- Fixed hell mode -- Fixed a few weird things with platform detection -- We fixed so many bugs that I cannot list them all +- Many many bugfixes related to reloading \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index e70185b..8d03b4c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,7 +5,7 @@ plugins { alias(libs.plugins.modpublish) } -val modVersion = "1.1.1" +val modVersion = "1.1.2" val modId = "snapper" val modName = "Snapper" 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 4357e14..c27b010 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/screen/PrivacyNoticeScreen.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/screen/PrivacyNoticeScreen.java @@ -91,16 +91,16 @@ protected void init() { private void addButtons(int y) { int buttonWidth = 120; - addRenderableWidget(Button.builder(Component.translatable("snapper.privacy_notice.view_terms"), buttonWidget -> + addRenderableWidget(Button.builder(Component.translatable("snapper.privacy_notice.view_terms"), _ -> Util.getPlatform().openUri(TERMS_URI)).bounds(width / 2 - (buttonWidth / 2) - buttonWidth - 5, y, buttonWidth, 20).build()); - addRenderableWidget(Button.builder(Component.translatable("snapper.privacy_notice.accept"), buttonWidget -> { + addRenderableWidget(Button.builder(Component.translatable("snapper.privacy_notice.accept"), _ -> { Minecraft.getInstance().setScreen(parent); SnapperConfig.editAsync(m -> m.termsAccepted = AxolotlClientApi.TermsAcceptance.ACCEPTED); accepted.accept(true); }).bounds(width / 2 - (buttonWidth / 2), y, buttonWidth, 20).build()); - addRenderableWidget(Button.builder(Component.translatable("snapper.privacy_notice.deny"), buttonWidget -> { + addRenderableWidget(Button.builder(Component.translatable("snapper.privacy_notice.deny"), _ -> { Minecraft.getInstance().setScreen(parent); SnapperConfig.editAsync(m -> m.termsAccepted = AxolotlClientApi.TermsAcceptance.DENIED); accepted.accept(false); 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 af9bdaa..683cf5c 100644 --- a/src/client/java/dev/spiritstudios/snapper/util/uploading/AxolotlClientApi.java +++ b/src/client/java/dev/spiritstudios/snapper/util/uploading/AxolotlClientApi.java @@ -157,7 +157,6 @@ private CompletableFuture> request(URI url, byte[] rawBody, ); return this.client.sendAsync(builder.build(), HttpResponse.BodyHandlers.ofString()); - } @Override