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 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 c7a740b..8d03b4c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,9 +5,7 @@ plugins { alias(libs.plugins.modpublish) } -val mappingsAttribute = Attribute.of("net.minecraft.mappings", String::class.java) - -val modVersion = "1.1.1" +val modVersion = "1.1.2" 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 = listOf( + "-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 { jvmArguments.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) - include(libs.greenhouse.config) + runtimeOnly(libs.greenhouse.config.fabric) + include(libs.greenhouse.config.fabric) - 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 @@ -126,7 +128,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..fc17e13 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,31 +1,29 @@ [versions] -fabric_loom = "1.15-SNAPSHOT" -modpublish = "1.1.0" +fabric_loom = "1.17-SNAPSHOT" +modpublish = "2.0.0" -minecraft = "1.21.10" -parchment = "2025.10.12" +minecraft = "26.1.2" -fabric_loader = "0.18.1" -fabric_api = "0.138.3+1.21.10" +fabric_loader = "0.18.4" +fabric_api = "0.151.0+26.1.2" -greenhouse_config = "3.0.0-beta.4+1.21.10" -modmenu = "16.0.0" +greenhouse_config = "3.0.0-beta.5+26.1.x" +modmenu = "18.0.0-beta.1" 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.10", 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" } 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 1b33c55..d997cfc 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 23449a2..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.2.1-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 23d15a9..0262dcb 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. @@ -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/. @@ -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 978dd6d..7e3fdea 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; @@ -11,14 +11,16 @@ 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(); - SnapperKeybindings.init(); - ClientLifecycleEvents.CLIENT_STOPPING.register(client -> ScreenshotUploading.close()); + SnapperKeyMappings.init(); + ClientLifecycleEvents.CLIENT_STOPPING.register(_ -> 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/SnapperKeyMappings.java similarity index 82% rename from src/client/java/dev/spiritstudios/snapper/SnapperKeybindings.java rename to src/client/java/dev/spiritstudios/snapper/SnapperKeyMappings.java index b89a644..b25999c 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,35 +15,35 @@ import java.nio.file.Path; import java.util.List; -public final class SnapperKeybindings { - public static final KeyMapping.Category SNAPPER = KeyMapping.Category.register(Snapper.id(Snapper.MOD_ID)); +public final class SnapperKeyMappings { + 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() { - 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)); }); } @@ -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, @@ -90,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/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 91c0901..b53bb3e 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/overlay/ExternalDialogOverlay.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/overlay/ExternalDialogOverlay.java @@ -2,47 +2,47 @@ 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; -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(); + private final Minecraft minecraft = 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) { - if (this.client.screen != null) { - this.client.screen.renderWithTooltipAndSubtitles(graphics, mouseX, mouseY, partialTick); + public void extractRenderState(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float 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(), 32, 32 ); - graphics.drawCenteredString( - client.font, + graphics.centeredText( + 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 08d8c1e..d3cba0b 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; @@ -137,14 +138,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 +164,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 +250,17 @@ 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) { + Minecraft.getInstance().submit(reloadableScreen::reload); + if (config.viewMode != viewMode) { + reloadableScreen.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..5e4a429 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/screen/PanoramaViewerScreen.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/screen/PanoramaViewerScreen.java @@ -4,16 +4,17 @@ import dev.spiritstudios.snapper.util.DynamicCubemapTexture; import dev.spiritstudios.snapper.util.SafeFiles; import dev.spiritstudios.snapper.util.SnapperUtil; -import net.minecraft.Util; +import net.fabricmc.fabric.api.client.rendering.v1.RenderStateDataKey; +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.ResourceLocation; +import net.minecraft.resources.Identifier; import net.minecraft.util.CommonColors; import org.jetbrains.annotations.Nullable; @@ -23,16 +24,18 @@ import java.util.stream.Stream; public class PanoramaViewerScreen extends Screen { - protected static final ResourceLocation ID = Snapper.id("screenshots/panorama"); - protected static final CubeMap PANORAMA_RENDERER = new CubeMap(ID); + public static final RenderStateDataKey SNAPPER_PANORAMA = RenderStateDataKey.create(() -> "Snapper Panorama"); + + public static final Identifier TEXTURE_ID = Snapper.id("screenshots/panorama"); - private final PanoramaRenderer rotatingPanoramaRenderer = new PanoramaRenderer(PANORAMA_RENDERER); private final DynamicCubemapTexture texture; private final String title; private final Screen parent; - protected PanoramaViewerScreen(String title, Screen parent) { + private float spin = 0.0F; + + public PanoramaViewerScreen(String title, Screen parent) { super(Component.translatable("menu.snapper.viewer_menu")); this.title = title; this.parent = parent; @@ -40,14 +43,12 @@ 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); } } @Nullable private DynamicCubemapTexture getTexture() { - assert minecraft != null; - Path panoramaDir = SnapperUtil.getConfiguredScreenshotDirectory().resolve("panorama"); if (!SnapperUtil.panoramaPresent(panoramaDir)) return null; @@ -56,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; + }) ? new DynamicCubemapTexture(TEXTURE_ID, panoramaDir) : null; } catch (IOException | NullPointerException e) { Snapper.LOGGER.error("Failed to list the contents of directory", e); return null; @@ -66,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(); } @@ -82,8 +83,8 @@ protected void init() { return; } - Path panoramaPath = Path.of(Minecraft.getInstance().gameDirectory.getPath(), "screenshots", "panorama"); - addRenderableWidget(Button.builder(Component.translatable("button.snapper.folder"), button -> { + 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()); @@ -94,10 +95,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 +110,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 c7d94d9..c27b010 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/screen/PrivacyNoticeScreen.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/screen/PrivacyNoticeScreen.java @@ -30,9 +30,10 @@ 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.GuiGraphicsExtractor; import net.minecraft.client.gui.components.Button; import net.minecraft.client.gui.components.MultiLineLabel; import net.minecraft.client.gui.screens.Screen; @@ -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 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, + width / 2, getMessageY(), + 10, + graphics.textRenderer() + ); } @Override @@ -84,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/gui/screen/ReloadableScreen.java b/src/client/java/dev/spiritstudios/snapper/gui/screen/ReloadableScreen.java new file mode 100644 index 0000000..0b54e1c --- /dev/null +++ b/src/client/java/dev/spiritstudios/snapper/gui/screen/ReloadableScreen.java @@ -0,0 +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 02b6280..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,69 +4,51 @@ 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; +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"); - - 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"); - +public class ScreenshotListScreen extends Screen implements ReloadableScreen { private final Screen parent; - private final boolean isOffline; private ScreenshotsWidget screenshots = null; - private Button deleteButton; - private Button renameButton; - private Button viewButton; - private Button copyButton; - private Button openButton; - private Button uploadButton; - private SpriteIconButton viewModeButton; + private SnapperButtonBar bar; + 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() { + @Override + public synchronized void recreateList() { if (screenshots != null) { this.removeWidget(screenshots); } @@ -79,183 +61,68 @@ 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; - - recreateList(); - - int secondRowButtonWidth = 100; - - Button folderButton = addRenderableWidget(Button.builder( - Component.translatable("button.snapper.folder"), - button -> Util.getPlatform().openPath(SnapperUtil.getConfiguredScreenshotDirectory()) - ).width(secondRowButtonWidth).build()); - - - this.openButton = addRenderableWidget(Button.builder( - Component.translatable("button.snapper.open"), - button -> { - if (selectedScreenshot != null) { - Util.getPlatform().openPath(selectedScreenshot.icon.getPath()); - } - } - ).width(secondRowButtonWidth).build()); - - Button doneButton = addRenderableWidget(Button.builder( - CommonComponents.GUI_DONE, - button -> this.onClose() - ).width(secondRowButtonWidth).build()); - - int firstRowButtonWidth = 58; - - this.deleteButton = addRenderableWidget(Button.builder( - Component.translatable("button.snapper.delete"), - button -> { - if (selectedScreenshot != null) { - ScreenshotActions.deleteScreenshot(selectedScreenshot.icon.getPath(), this); - } - } - ).width(firstRowButtonWidth).build()); - - this.renameButton = addRenderableWidget(Button.builder( - Component.translatable("button.snapper.rename"), - button -> { - if (this.selectedScreenshot != null) { - minecraft.setScreen(new ScreenshotRenameScreen(this.selectedScreenshot.icon.getPath(), this)); - } - } - ).width(firstRowButtonWidth).build()); - - this.copyButton = addRenderableWidget(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 - )); - } - } - ).width(firstRowButtonWidth).build()); - - this.uploadButton = addRenderableWidget(Button.builder(Component.translatable("button.snapper.upload"), button -> { - if (selectedScreenshot == null) return; - - button.active = false; - ScreenshotUploading.upload(selectedScreenshot.icon.getPath()) - .thenRun(() -> button.active = true); - }).width(firstRowButtonWidth).build()); - - if (isOffline) { - this.uploadButton.setTooltip(Tooltip.create(Component.translatable("button.snapper.upload.tooltip"))); - } + this.layout.addTitleHeader(this.title, this.font); - LinearLayout verticalButtonLayout = LinearLayout.vertical() - .spacing(4); + this.bar = new SnapperButtonBar( + this, + this.layout, + () -> this.selectedScreenshot != null ? this.selectedScreenshot.texture : null, + true, + this::toggleGrid, + this::reload + ); - 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); - - 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) { boolean hasScreenshot = screenshot != null; - this.copyButton.active = hasScreenshot; - this.deleteButton.active = hasScreenshot; - this.openButton.active = hasScreenshot; - this.renameButton.active = hasScreenshot; - this.viewButton.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() { 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 +135,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 +149,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..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); @@ -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..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,210 +1,121 @@ package dev.spiritstudios.snapper.gui.screen; -import dev.spiritstudios.snapper.Snapper; +import dev.spiritstudios.snapper.gui.SnapperButtonBar; 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.GuiGraphicsExtractor; 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 boolean shouldRecreateParent = 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(), true); - 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() { - this.client.setScreen(this.parent); - } - - @Override - protected void init() { - - int firstRowButtonWidth = 74; - - // OPEN FOLDER - - Button folderButton = addRenderableWidget(Button.builder( - Component.translatable("button.snapper.folder"), - button -> Util.getPlatform().openFile(new File(client.gameDirectory, "screenshots")) - ) - .width(100) - .build() - ); - - // OPEN IMAGE EXTERNALLY - - Button openButton = addRenderableWidget(Button.builder( - Component.translatable("button.snapper.open"), - button -> Util.getPlatform().openPath(this.iconPath) - ).width(100).build()); - - // EXIT PAGE - - Button doneButton = addRenderableWidget(Button.builder( - CommonComponents.GUI_DONE, - button -> this.onClose() - ).width(100).build()); - - // DELETE SCREENSHOT - - Button deleteButton = addRenderableWidget(Button.builder( - Component.translatable("button.snapper.delete"), - button -> ScreenshotActions.deleteScreenshot(this.screenshot, this.parent) - ).width(firstRowButtonWidth).build()); - - // RENAME SCREENSHOT - - Button renameButton = addRenderableWidget(Button.builder( - Component.translatable("button.snapper.rename"), - button -> { - if (this.screenshot != null) - client.setScreen(new ScreenshotRenameScreen(this.screenshot, this.parent)); - } - ).width(firstRowButtonWidth).build()); - - // COPY SCREENSHOT - - Button copyButton = addRenderableWidget(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); - } - ).width(firstRowButtonWidth).build()); - - if (SnapperUtil.isOfflineAccount()) { - uploadButton.active = false; - uploadButton.setTooltip(Tooltip.create(Component.translatable("button.snapper.upload.tooltip"))); + if (!(parent instanceof ScreenshotListScreen listScreen)) { + this.texture.close(); } - LinearLayout verticalButtonLayout = LinearLayout.vertical().spacing(4); + if (parent instanceof ReloadableScreen reloadable) { + if (shouldReloadParent) reloadable.reload(); + if (shouldRecreateParent) reloadable.recreateList(); + } - EqualSpacingLayout firstRowWidget = verticalButtonLayout.addChild(new EqualSpacingLayout( - 308, - 20, - EqualSpacingLayout.Orientation.HORIZONTAL) - ); + this.client.setScreen(this.parent); + } - firstRowWidget.addChild(deleteButton); - firstRowWidget.addChild(renameButton); - firstRowWidget.addChild(copyButton); - firstRowWidget.addChild(uploadButton); + @Override + protected void repositionElements() { + this.layout.arrangeElements(); + } - EqualSpacingLayout secondRowWidget = verticalButtonLayout.addChild(new EqualSpacingLayout( - 308, - 20, - EqualSpacingLayout.Orientation.HORIZONTAL) + @Override + protected void init() { + this.layout.addTitleHeader(this.title, this.font); + + new SnapperButtonBar( + this, + this.layout, + () -> this.texture, + false, + null, + null ); - 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 extractRenderState(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float a) { + super.extractRenderState(graphics, mouseX, mouseY, a); + + 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.centeredText( this.font, "Screenshot %d/%d".formatted(screenshotIndex + 1, screenshots.size()), this.width / 2, @@ -215,15 +126,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.centeredText( 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.centeredText( this.font, Component.translatable("text.snapper.screen_size", this.width, this.height), this.width / 2, @@ -231,14 +142,14 @@ public void render(GuiGraphics context, int mouseX, int mouseY, float delta) { CommonColors.WHITE ); - context.drawCenteredString(this.font, + graphics.centeredText(this.font, Component.translatable("text.snapper.scale_factor", scaleFactor), this.width / 2, 60, CommonColors.WHITE ); - context.drawCenteredString( + graphics.centeredText( this.font, Component.translatable("text.snapper.scale_size", finalWidth, finalHeight), this.width / 2, @@ -248,44 +159,47 @@ public void render(GuiGraphics context, int mouseX, int mouseY, float delta) { } } - private void drawMenuBackground(GuiGraphics context) { - context.blit( + private void drawMenuBackground(GuiGraphicsExtractor 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(GuiGraphicsExtractor 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; + } + + @Override + public void recreateList() { + this.shouldRecreateParent = 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..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,12 +3,12 @@ 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; 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; @@ -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); } } @@ -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/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/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/gui/widget/FolderSelectWidget.java b/src/client/java/dev/spiritstudios/snapper/gui/widget/FolderSelectWidget.java index 624c0b7..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,14 +5,14 @@ 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; 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 @@ -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 24b5909..2932458 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.GuiGraphicsExtractor; 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) { @@ -132,21 +133,33 @@ 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, false); + 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.text( + minecraft.font, + loadString, + getContentX() + (getContentWidth() - minecraft.font.width(loadString)) / 2, getContentY() + getContentHeight() / 2, + CommonColors.GRAY, + false ); } @@ -163,14 +176,14 @@ 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, 32, 32 ); - graphics.drawString( + graphics.text( minecraft.font, SnapperUtil.clipText(minecraft.font, fileName, getContentWidth() - 5), getContentX() + 5, @@ -179,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, @@ -188,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 62e241c..cfb9cf3 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.GuiGraphicsExtractor; +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,23 +34,20 @@ 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) { - graphics.drawString( + public void extractContent(GuiGraphicsExtractor graphics, int mouseX, int mouseY, boolean isHovering, float partialTick) { + texture.startLoading(minecraft, false); + + graphics.text( minecraft.font, SnapperUtil.clipText(minecraft.font, fileName, getContentWidth() - 32 - 6), getContentX() + 32 + 3, getContentY() + 1, @@ -62,23 +55,36 @@ public void renderContent(GuiGraphics graphics, int mouseX, int mouseY, boolean false ); - graphics.drawString( + graphics.text( 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.text( + 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..a26ff68 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/widget/ScreenshotsWidget.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/widget/ScreenshotsWidget.java @@ -8,17 +8,17 @@ 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.GuiGraphicsExtractor; import net.minecraft.client.gui.components.ObjectSelectionList; import net.minecraft.client.gui.screens.LoadingDotsText; 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,83 @@ 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 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.extractItem(graphics, mouseX, mouseY, partialTick, entry); + } else if (entry instanceof ScreenshotEntry sEntry) { + if (sEntry.texture.isLoaded()) { + 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 +157,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() { @@ -164,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, @@ -178,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, @@ -204,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, @@ -216,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, @@ -232,9 +246,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 +256,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 +272,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 +300,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..f1efbad --- /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.GuiGraphicsExtractor; +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 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; + + 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 deleted file mode 100644 index 348e132..0000000 --- a/src/client/java/dev/spiritstudios/snapper/mixin/CameraMixin.java +++ /dev/null @@ -1,25 +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 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( - BlockGetter level, - Entity entity, - boolean detached, - boolean thirdPersonReverse, - float partialTick, - CallbackInfo ci - ) { - if (Minecraft.getInstance().gameRenderer.isPanoramicMode() && detached) ci.cancel(); - } -} 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 new file mode 100644 index 0000000..7771261 --- /dev/null +++ b/src/client/java/dev/spiritstudios/snapper/mixin/GuiRendererMixin.java @@ -0,0 +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/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 464e383..f43aad3 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; @@ -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 6aabaa8..0bbdf5c 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", @@ -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 110c8eb..835e08e 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); @@ -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/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/java/dev/spiritstudios/snapper/util/DynamicCubemapTexture.java b/src/client/java/dev/spiritstudios/snapper/util/DynamicCubemapTexture.java index 0020af5..afb2ff8 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,15 +48,11 @@ 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) { - 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..fe1c8b7 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; @@ -14,42 +15,36 @@ 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 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 +52,87 @@ 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(); + 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)) { + CURRENTLY_LOADING.incrementAndGet(); + + CompletableFuture + .supplyAsync(this::load, Util.nonCriticalIoPool()) + .thenAcceptAsync(this::upload, minecraft) + .whenComplete((_, _) -> { + CURRENTLY_LOADING.decrementAndGet(); + }); + } } - 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 void close() { + this.clear(); + this.closed = true; } - public ResourceLocation getTextureId() { - return this.texture != null ? this.id : UNKNOWN_SERVER; + public boolean isLoaded() { + return this.loaded; } - public boolean loaded() { - return texture != null; + public boolean isClosed() { + return this.closed; } - public Path getPath() { - return path; + private void checkOpen() { + if (this.closed) { + throw new IllegalStateException("ScreenshotTexture already closed"); + } } - public void close() { - this.destroy(); + 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..683cf5c 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; @@ -157,7 +157,6 @@ private CompletableFuture> request(URI url, byte[] rawBody, ); return this.client.sendAsync(builder.build(), HttpResponse.BodyHandlers.ofString()); - } @Override 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/client/resources/snapper.mixins.json b/src/client/resources/snapper.mixins.json index ccddfaf..0539220 100644 --- a/src/client/resources/snapper.mixins.json +++ b/src/client/resources/snapper.mixins.json @@ -1,17 +1,16 @@ { "required": true, "package": "dev.spiritstudios.snapper.mixin", - "compatibilityLevel": "JAVA_21", + "compatibilityLevel": "JAVA_25", "client": [ - "CameraMixin", "InGameHudMixin", "KeyboardHandlerMixin", "MinecraftMixin", "PauseScreenMixin", - "ScreenshotRecorderMixin", + "ScreenshotMixin", "TitleScreenMixin", - "accessor.AbstractSelectionListAccessor", - "accessor.AbstractSliderButtonAccessor", + "CubeMapMixin", + "GuiRendererMixin", "accessor.CameraAccessor" ], "injectors": { diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 8998d6a..1b78c46 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.10", + "minecraft": "~26.1.2", "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