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/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/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..3f0b45e 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, @@ -249,13 +249,18 @@ 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 ReloadableScreen reloadableScreen) { + reloadableScreen.reload(); + } + + if (lastScreen instanceof ScreenshotListScreen screenshotsScreen && config.viewMode != viewMode) { + screenshotsScreen.recreateList(); } }); + + minecraft.setScreen(lastScreen); } } 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/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 02b6280..d66bca6 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/screen/ScreenshotListScreen.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/screen/ScreenshotListScreen.java @@ -7,39 +7,34 @@ import dev.spiritstudios.snapper.gui.toast.SnapperToast; 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.Util; -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.ResourceLocation; -import net.minecraft.util.CommonColors; +import net.minecraft.resources.Identifier; 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"); +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 ResourceLocation SETTINGS_ICON = Snapper.id("screenshots/settings"); - - private static final ResourceLocation 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 SETTINGS_ICON = Snapper.id("screenshots/settings"); + private static final Identifier RELOAD_ICON = Snapper.id("screenshots/reset"); private final Screen parent; private final boolean isOffline; @@ -48,25 +43,22 @@ public class ScreenshotListScreen extends Screen { private Button deleteButton; private Button renameButton; - private Button viewButton; 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,160 +71,141 @@ 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() { - assert minecraft != null; + this.layout.addTitleHeader(this.title, this.font); - recreateList(); + 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"))); + + topRow.addChild(new ViewModeButton( + button -> this.toggleGrid(), + null + )); - this.deleteButton = addRenderableWidget(Button.builder( + 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()); + PlatformHelper.INSTANCE.copyScreenshot(selectedScreenshot.texture.path); } } - ).width(firstRowButtonWidth).build()); + ).width(buttonWidth).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 - )); - } - } - ).width(firstRowButtonWidth).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(); - - Path panoramaDir = SnapperUtil.getConfiguredScreenshotDirectory().resolve("panorama"); - boolean hasPanorama = SnapperUtil.panoramaPresent(panoramaDir); + topRow.addChild( + SpriteIconButton.builder( + Component.translatable("button.snapper.reload"), + button -> this.reload(), + true + ).width(20).sprite(RELOAD_ICON, 15, 15).build()); - 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()); + this.imageSelected(selectedScreenshot); - panoramaButton.active = hasPanorama; - panoramaButton.setPosition(width / 2 + 158, height - 32); + this.layout.visitWidgets(this::addRenderableWidget); - panoramaButton.setTooltip(Tooltip.create(Component.translatable(hasPanorama ? - "button.snapper.panorama.tooltip" : - "text.snapper.panorama_encourage"))); + this.repositionElements(); + } + public ScreenshotsWidget getScreenshots() { + return screenshots; + } - this.imageSelected(selectedScreenshot); + @Override + public void onClose() { + super.onClose(); + screenshots.clearEntries(); } public void imageSelected(@Nullable ScreenshotListWidget.ScreenshotEntry screenshot) { @@ -241,7 +214,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; } @@ -249,13 +221,11 @@ 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 public boolean keyPressed(KeyEvent input) { - assert minecraft != null; - if (super.keyPressed(input)) { return true; } @@ -268,13 +238,13 @@ 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; } @@ -282,9 +252,8 @@ public boolean keyPressed(KeyEvent input) { } @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 void reload() { + screenshots.reload(); } public enum ViewMode implements StringRepresentable { 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 2ec0567..015beb1 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/screen/ScreenshotViewerScreen.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/screen/ScreenshotViewerScreen.java @@ -1,210 +1,168 @@ 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; 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; 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 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 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"); +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"); 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 boolean shouldReloadParent = false; - 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.imageWidth = image != null ? image.getWidth() : 0; - this.imageHeight = image != null ? image.getHeight() : 0; + this.texture = texture; + texture.startLoading(Minecraft.getInstance()); - 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 listScreen)) { + this.texture.close(); + } else if (shouldReloadParent) { + listScreen.getScreenshots().reload(); + } + 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()); - - // COPY SCREENSHOT + ).width(buttonWidth).build()); - 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.getTextureId(), - (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 +173,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 +189,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,44 +206,42 @@ 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 ); } + + @Override + public void reload() { + this.shouldReloadParent = true; + } } 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..6583f86 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/widget/ScreenshotGridWidget.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/widget/ScreenshotGridWidget.java @@ -2,15 +2,16 @@ import dev.spiritstudios.snapper.util.ScreenshotTexture; import dev.spiritstudios.snapper.util.SnapperUtil; -import net.minecraft.Util; 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; 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 +118,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 +134,32 @@ 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.getTextureId(), + 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() + ); + } 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 ); } @@ -163,7 +176,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 62e241c..9ab5ec5 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,18 @@ import dev.spiritstudios.snapper.util.ScreenshotTexture; import dev.spiritstudios.snapper.util.SnapperUtil; -import net.minecraft.Util; 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; +import net.minecraft.network.chat.CommonComponents; import net.minecraft.network.chat.Component; import net.minecraft.util.CommonColors; -import net.minecraft.util.StringUtil; +import net.minecraft.util.Util; 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, @@ -38,22 +34,19 @@ public void repositionEntries() { } @Override - protected void renderSelection(GuiGraphics context, Entry entry, int color) { - // let elements handle it - } - - @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), @@ -64,21 +57,34 @@ public void renderContent(GuiGraphics graphics, int mouseX, int mouseY, boolean graphics.drawString( minecraft.font, - creation, + 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.getTextureId(), + 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() + ); + } 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 ); } @@ -86,7 +92,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 5f74db1..5af1bbf 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; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.components.ObjectSelectionList; @@ -16,9 +15,10 @@ 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 net.minecraft.util.Util; import org.jetbrains.annotations.Nullable; import org.jspecify.annotations.NonNull; @@ -31,18 +31,21 @@ 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 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; + protected List textures; + + @Override + public void removeEntry(Entry entry) { + super.removeEntry(entry); + } public static ScreenshotsWidget create( Minecraft client, @@ -58,50 +61,81 @@ 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)); + + if (previous == null) { + this.textures = new ArrayList<>(); - this.loadFuture = previous != null ? previous.loadFuture : load(client); + for (Path screenshot : ScreenshotActions.getScreenshots()) { + textures.add(ScreenshotTexture.createScreenshot( + this.minecraft.getTextureManager(), + screenshot + ).orElseThrow()); + } + } else { + this.textures = previous.textures; + } - this.loadFuture.thenAccept(textures -> { - this.clearEntries(); + super.clearEntries(); + addEntries(); + } - if (textures.isEmpty()) { - this.addEntry(new ScreenshotsWidget.EmptyEntry(client)); - } else { - for (ScreenshotTexture texture : textures) { - addEntry(createEntry(texture)); - } + @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(); } + } + } - repositionEntries(); - }).exceptionally(ex -> { - clearEntries(); - // TODO: Error Entry - this.addEntry(new ScreenshotsWidget.EmptyEntry(client)); + @Override + public void clearEntries() { + for (ScreenshotTexture texture : this.textures) { + if (!texture.isClosed()) { + texture.close(); + } + } - repositionEntries(); + textures.clear(); - Snapper.LOGGER.error("Failed to load textures", ex); + super.clearEntries(); + } - return null; - }); + protected void addEntries() { + if (textures.isEmpty()) { + addEntry(new EmptyEntry(minecraft)); + } else { + for (ScreenshotTexture texture : textures) { + addEntry(createEntry(texture)); + } + } repositionEntries(); } - @Override - protected void clearEntries() { - this.children().forEach(Entry::close); - super.clearEntries(); + public synchronized void reload() { + clearEntries(); + + for (Path screenshot : ScreenshotActions.getScreenshots()) { + textures.add(ScreenshotTexture.createScreenshot( + this.minecraft.getTextureManager(), + screenshot + ).orElseThrow()); + } + + addEntries(); } protected void setEntrySelected(@Nullable ScreenshotEntry entry) { @@ -121,29 +155,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 +244,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 +254,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,15 +270,20 @@ 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; } + public void clear() { + texture.clear(); + } + @Override public void setFocused(boolean focused) { if (focused) { @@ -282,19 +298,9 @@ 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(); - } } } 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/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/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/java/dev/spiritstudios/snapper/util/ScreenshotTexture.java b/src/client/java/dev/spiritstudios/snapper/util/ScreenshotTexture.java index 3070105..9545f6f 100644 --- a/src/client/java/dev/spiritstudios/snapper/util/ScreenshotTexture.java +++ b/src/client/java/dev/spiritstudios/snapper/util/ScreenshotTexture.java @@ -1,12 +1,13 @@ 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; +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,40 +17,33 @@ 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 Path path; + private final Identifier textureLocation; + public final Path path; - private final NativeImage image; + private boolean loaded; private DynamicTexture texture; + private boolean closed; - 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; - - try (InputStream stream = Files.newInputStream(path)) { - this.image = NativeImage.read(stream); - } - } - - public CompletableFuture load() { - return Minecraft.getInstance().submit(() -> { - this.texture = new DynamicTexture(this.id::toString, this.image); - this.textureManager.register(this.id, 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(), ResourceLocation::validPathChar) + "/icon" - ), + Snapper.id( + "screenshots/" + Util.sanitizeName(name, Identifier::validPathChar) + "/" + Hashing.sha1().hashUnencodedChars(name) + ), path )); } catch (IOException e) { @@ -57,39 +51,80 @@ public static Optional createScreenshot(TextureManager textur } } - /* - * Must be called on render thread - */ - public void enableFiltering() { - this.texture.setFilter(true, true); + public NativeImage load() { + try (InputStream stream = Files.newInputStream(path)) { + return NativeImage.read(stream); + } catch (IOException e) { + throw new RuntimeException(e); + } } - public void destroy() { - this.textureManager.release(this.id); - this.texture.close(); + public synchronized void startLoading(Minecraft minecraft) { + if (!isLoaded()) { + CompletableFuture + .supplyAsync(this::load, Util.nonCriticalIoPool()) + .thenAcceptAsync(this::upload, minecraft); + } } - public int getWidth() { - return this.texture != null ? this.texture.getTexture().getWidth(0) : 64; + public synchronized 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 int getHeight() { - return this.texture != null ? this.texture.getTexture().getHeight(0) : 64; + + public void clear() { + this.checkOpen(); + if (this.texture != null) { + this.textureManager.release(this.textureLocation); + this.texture.close(); + this.texture = null; + this.loaded = false; + } } - public ResourceLocation getTextureId() { - return this.texture != null ? this.id : UNKNOWN_SERVER; + public void close() { + this.clear(); + this.closed = true; } - public boolean loaded() { - return texture != null; + public boolean isLoaded() { + return this.loaded; } - public Path getPath() { - return path; + public boolean isClosed() { + return this.closed; } - public void close() { - this.destroy(); + 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; } } 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/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" } 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-"