diff --git a/README.md b/README.md index 6d5ecea..3d6f9b5 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,9 @@ Screenshots, made simple Won't support Forge -Made by Spirit Studios +Made by Spirit Studios + +As seen on ModFest 1.21 ## 📖 About @@ -16,18 +18,19 @@ Snapper includes: - A screenshot managment menu, accessible from the title screen or ingame - The ability to view latest screenshot with just a keybind - Options to rename, delete, copy, and externally open images ingame -- The ability to and view panoramic screenshots ingame +- The ability to create and view panoramic screenshots ingame +- ~~Not having to install a mod from an EXE~~ ## ❓ FAQ * Forge?
-No. Please do not ask us for a forge port. + No. Please do not ask us for a forge port. * Do you plan to / Could you add `X`?
-If you want to suggest something, join the [discord](https://discord.gg/TTmx7d2axf). + If you want to suggest something, join the [discord](https://discord.gg/TTmx7d2axf). * Can you port/backport to `X` version?
-Probably not. Multi-version projects are hard :< + Probably not. Multi-version projects are hard :< Special thanks to our original beta testers: - [Blurryface](https://blurry.gay) -- [ThinkSeal](https://github.com/thinkseal) +- [ThinkSeal](https://github.com/thinkseal) \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index d623cbb..0380de9 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,6 +1,7 @@ plugins { java - id("fabric-loom") version "1.7-SNAPSHOT" + alias(libs.plugins.fabric.loom) + alias(libs.plugins.minotaur) } class ModInfo { @@ -9,70 +10,50 @@ class ModInfo { val version = property("mod.version").toString() } -class Dependencies { - val minecraft = property("deps.minecraft").toString() - val loader = property("deps.loader").toString() - val yarn = property("deps.yarn").toString() - - val fabricApi = property("deps.fabricapi").toString() - val specter = property("deps.specter").toString() -} - - val mod = ModInfo() -val deps = Dependencies() version = mod.version group = mod.group -base.archivesName = "${mod.id}-${mod.version}" +base.archivesName = mod.id loom { splitEnvironmentSourceSets() mods.create(mod.id) { - sourceSet(sourceSets.getByName("main")) - sourceSet(sourceSets.getByName("client")) + sourceSet(sourceSets["main"]) + sourceSet(sourceSets["client"]) } } repositories { mavenCentral() - maven("https://maven.callmeecho.dev/releases/") + maven("https://maven.spiritstudios.dev/releases/") } dependencies { - minecraft("com.mojang:minecraft:${deps.minecraft}") - mappings("net.fabricmc:yarn:${deps.yarn}:v2") - modImplementation("net.fabricmc:fabric-loader:${deps.loader}") - - modImplementation("net.fabricmc.fabric-api:fabric-api:${deps.fabricApi}") + minecraft(libs.minecraft) + mappings(variantOf(libs.yarn) { classifier("v2") }) + modImplementation(libs.fabric.loader) - fun specterModule(name: String) { - include("dev.spiritstudios.specter:specter-$name:${deps.specter}") - modImplementation("dev.spiritstudios.specter:specter-$name:${deps.specter}") - } + modImplementation(libs.fabric.api); - specterModule("config") - specterModule("core") + include(libs.bundles.specter) + modImplementation(libs.bundles.specter) // TODO: Find a way to use the macOS clipboard without this. - implementation("ca.weblite:java-objc-bridge:1.0.0") + implementation(libs.objc.bridge) } tasks.processResources { - inputs.property("id", mod.id) - inputs.property("version", mod.version) - inputs.property("loader_version", deps.loader) - inputs.property("minecraft_version", deps.minecraft) - val map = mapOf( - "id" to mod.id, - "version" to mod.version, - "loader_version" to deps.loader, - "minecraft_version" to deps.minecraft + "mod_id" to mod.id, + "mod_version" to mod.version, + "fabric_loader_version" to libs.versions.fabric.loader.get(), + "minecraft_version" to libs.versions.minecraft.get() ) + inputs.properties(map) filesMatching("fabric.mod.json") { expand(map) } } @@ -83,8 +64,22 @@ java { targetCompatibility = JavaVersion.VERSION_21 } - tasks.withType { options.encoding = "UTF-8" options.release = 21 +} + +tasks.jar { from("LICENSE") { rename { "${it}_${base.archivesName}" } } } + +modrinth { + token.set(System.getenv("MODRINTH_TOKEN")) + projectId.set(mod.id) + versionNumber.set(mod.version) + uploadFile.set(tasks.remapJar) + gameVersions.addAll(libs.versions.minecraft.get(), "1.21.1") + loaders.addAll("fabric", "quilt") + syncBodyFrom.set(rootProject.file("README.md").readText()) + dependencies { + required.version("fabric-api", libs.versions.fabric.api.get()) + } } \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 6cc26fd..b9e8cce 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,13 +1,13 @@ org.gradle.jvmargs=-Xmx2G org.gradle.parallel=true -mod.version = 1.0.0 +mod.version = 1.0.1 mod.group = dev.spiritstudios mod.id = snapper deps.minecraft=1.21 -deps.loader=0.15.11 +deps.loader=0.16.5 deps.yarn=1.21+build.9 -deps.fabricapi=0.102.0+1.21 -deps.specter=1.0.2 \ No newline at end of file +deps.fabricapi=0.110.0+1.21.1 +deps.specter=1.0.6 \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000..cd187fd --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,38 @@ +[versions] +fabric_loom = "1.8-SNAPSHOT" +minotaur = "2.+" + +minecraft = "1.21" +yarn = "1.21+build.9" + +fabric_loader = "0.16.5" +fabric_api = "0.110.0+1.21.1" + +specter = "1.0.6" +objc_bridge = "1.0.0" + +[plugins] +fabric_loom = { id = "fabric-loom", version.ref = "fabric_loom" } +minotaur = { id = "com.modrinth.minotaur", version.ref = "minotaur" } + +[libraries] +minecraft = { group = "mojang", name = "minecraft", version.ref = "minecraft" } +yarn = { group = "net.fabricmc", name = "yarn", version.ref = "yarn" } + +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" } + +specter_config = { group = "dev.spiritstudios.specter", name = "specter-config", version.ref = "specter" } +specter_core = { group = "dev.spiritstudios.specter", name = "specter-core", version.ref = "specter" } +specter_serialization = { group = "dev.spiritstudios.specter", name = "specter-serialization", version.ref = "specter" } +specter_gui = { group = "dev.spiritstudios.specter", name = "specter-gui", version.ref = "specter" } + +objc_bridge = { group = "ca.weblite", name = "java-objc-bridge", version.ref = "objc_bridge" } + +[bundles] +specter = [ + "specter_serialization", + "specter_core", + "specter_config", + "specter_gui" +] \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e644113..a4b76b9 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 a441313..df97d72 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-8.8-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew old mode 100644 new mode 100755 index b740cf1..f5feea6 --- a/gradlew +++ b/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -84,7 +86,8 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum diff --git a/gradlew.bat b/gradlew.bat index 25da30d..9d21a21 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## diff --git a/src/client/java/dev/spiritstudios/snapper/Snapper.java b/src/client/java/dev/spiritstudios/snapper/Snapper.java index 06c4ba0..5646a40 100644 --- a/src/client/java/dev/spiritstudios/snapper/Snapper.java +++ b/src/client/java/dev/spiritstudios/snapper/Snapper.java @@ -1,46 +1,32 @@ package dev.spiritstudios.snapper; -import dev.spiritstudios.snapper.gui.ScreenshotScreen; -import dev.spiritstudios.snapper.gui.ScreenshotViewerScreen; -import dev.spiritstudios.snapper.util.*; -import dev.spiritstudios.specter.api.ModMenuHelper; -import dev.spiritstudios.specter.api.core.util.ClientKeybindEvents; +import dev.spiritstudios.snapper.util.MacActions; +import dev.spiritstudios.snapper.util.PlatformHelper; +import dev.spiritstudios.snapper.util.WindowsActions; +import dev.spiritstudios.specter.api.config.ModMenuHelper; import net.fabricmc.api.ClientModInitializer; -import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper; import net.fabricmc.loader.api.FabricLoader; -import net.minecraft.client.option.KeyBinding; -import net.minecraft.text.Text; +import net.minecraft.util.Identifier; import net.minecraft.util.Util; -import org.lwjgl.glfw.GLFW; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.File; -import java.util.List; - -public class Snapper implements ClientModInitializer { +public final class Snapper implements ClientModInitializer { public static final String MODID = "snapper"; public static final Logger LOGGER = LoggerFactory.getLogger(MODID); public static final boolean IS_IRIS_INSTALLED = FabricLoader.getInstance().isModLoaded("iris"); - public static final KeyBinding RECENT_SCREENSHOT_KEY = KeyBindingHelper.registerKeyBinding( - new KeyBinding( - "key.snapper.recent", - GLFW.GLFW_KEY_O, - "key.categories.misc" - )); - private static final KeyBinding SCREENSHOT_MENU_KEY = KeyBindingHelper.registerKeyBinding( - new KeyBinding( - "key.snapper.screenshot_menu", - GLFW.GLFW_KEY_V, - "key.categories.misc" - )); - private static final KeyBinding PANORAMA_KEY = KeyBindingHelper.registerKeyBinding( - new KeyBinding( - "key.snapper.panorama", - GLFW.GLFW_KEY_F8, - "key.categories.misc" - )); + + @Override + public void onInitializeClient() { + SnapperKeybindings.init(); + + ModMenuHelper.addConfig(Snapper.MODID, SnapperConfig.HOLDER.id()); + } + + public static Identifier id(String path) { + return Identifier.of(MODID, path); + } public static PlatformHelper getPlatformHelper() { return switch (Util.getOperatingSystem()) { @@ -49,42 +35,4 @@ public static PlatformHelper getPlatformHelper() { default -> new WindowsActions(); }; } - - @Override - public void onInitializeClient() { - ModMenuHelper.addConfig(Snapper.MODID, SnapperConfig.INSTANCE.getId()); - - ClientKeybindEvents.pressed(SCREENSHOT_MENU_KEY).register(client -> client.setScreen(new ScreenshotScreen(null))); - - ClientKeybindEvents.pressed(PANORAMA_KEY).register(client -> { - if (client.player == null) return; - - if (IS_IRIS_INSTALLED) { - client.player.sendMessage(Text.translatable("text.snapper.panorama_failure_iris"), true); - return; - } - - client.takePanorama(client.runDirectory, 1024, 1024); - client.player.sendMessage(Text.translatable( - "text.snapper.panorama_success", - SCREENSHOT_MENU_KEY.getBoundKeyLocalizedText() - ), true); - }); - - ClientKeybindEvents.pressed(RECENT_SCREENSHOT_KEY).register(client -> { - List screenshots = ScreenshotActions.getScreenshots(client); - if (screenshots.isEmpty()) { - if (client.player != null) - client.player.sendMessage(Text.translatable("text.snapper.screenshot_failure_open"), true); - return; - } - - File latestScreenshot = screenshots.getFirst(); - client.setScreen(new ScreenshotViewerScreen( - ScreenshotImage.of(latestScreenshot, client.getTextureManager()), - latestScreenshot, - client.currentScreen - )); - }); - } } \ 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 8605a25..abe651f 100644 --- a/src/client/java/dev/spiritstudios/snapper/SnapperConfig.java +++ b/src/client/java/dev/spiritstudios/snapper/SnapperConfig.java @@ -1,15 +1,24 @@ package dev.spiritstudios.snapper; import dev.spiritstudios.specter.api.config.Config; -import net.minecraft.util.Identifier; +import dev.spiritstudios.specter.api.config.ConfigHolder; +import dev.spiritstudios.specter.api.config.Value; -public class SnapperConfig extends Config { - public static final SnapperConfig INSTANCE = create(SnapperConfig.class); +public final class SnapperConfig extends Config { + public static final ConfigHolder HOLDER = ConfigHolder.builder( + Snapper.id("snapper"), SnapperConfig.class + ).build(); + public static final SnapperConfig INSTANCE = HOLDER.get(); - @Override - public Identifier getId() { return Identifier.of(Snapper.MODID, "snapper"); } - - public Value copyTakenScreenshot = booleanValue(false) + public final Value copyTakenScreenshot = booleanValue(false) .comment("Whether to copy screenshots to clipboard when taken.") .build(); + + public final Value showSnapperTitleScreen = booleanValue(true) + .comment("Whether to show Snapper button on title screen.") + .build(); + + public final Value showSnapperGameMenu = booleanValue(true) + .comment("Whether to show Snapper button in game menu.") + .build(); } \ No newline at end of file diff --git a/src/client/java/dev/spiritstudios/snapper/SnapperKeybindings.java b/src/client/java/dev/spiritstudios/snapper/SnapperKeybindings.java new file mode 100644 index 0000000..f7f2ec6 --- /dev/null +++ b/src/client/java/dev/spiritstudios/snapper/SnapperKeybindings.java @@ -0,0 +1,78 @@ +package dev.spiritstudios.snapper; + +import dev.spiritstudios.snapper.gui.screen.ScreenshotScreen; +import dev.spiritstudios.snapper.gui.screen.ScreenshotViewerScreen; +import dev.spiritstudios.snapper.util.ScreenshotActions; +import dev.spiritstudios.snapper.util.ScreenshotImage; +import dev.spiritstudios.specter.api.core.util.ClientKeybindEvents; +import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.option.KeyBinding; +import net.minecraft.text.Text; +import org.lwjgl.glfw.GLFW; + +import java.io.File; +import java.util.List; + +public final class SnapperKeybindings { + public static final KeyBinding PANORAMA_KEY = new KeyBinding( + "key.snapper.panorama", + GLFW.GLFW_KEY_F8, + "key.categories.misc" + ); + + public static final KeyBinding RECENT_SCREENSHOT_KEY = new KeyBinding( + "key.snapper.recent", + GLFW.GLFW_KEY_O, + "key.categories.misc" + ); + + public static final KeyBinding SCREENSHOT_MENU_KEY = new KeyBinding( + "key.snapper.screenshot_menu", + GLFW.GLFW_KEY_V, + "key.categories.misc" + ); + + public static void init() { + KeyBindingHelper.registerKeyBinding(PANORAMA_KEY); + KeyBindingHelper.registerKeyBinding(RECENT_SCREENSHOT_KEY); + KeyBindingHelper.registerKeyBinding(SCREENSHOT_MENU_KEY); + + ClientKeybindEvents.pressed(SCREENSHOT_MENU_KEY).register(client -> + client.setScreen(new ScreenshotScreen(client.currentScreen))); + + ClientKeybindEvents.pressed(PANORAMA_KEY).register(SnapperKeybindings::takePanorama); + ClientKeybindEvents.pressed(RECENT_SCREENSHOT_KEY).register(SnapperKeybindings::openRecentScreenshot); + } + + private static void takePanorama(MinecraftClient client) { + if (client.player == null) return; + + if (Snapper.IS_IRIS_INSTALLED) { + client.player.sendMessage(Text.translatable("text.snapper.panorama_failure_iris"), true); + return; + } + + client.takePanorama(client.runDirectory, 1024, 1024); + client.player.sendMessage(Text.translatable( + "text.snapper.panorama_success", + SCREENSHOT_MENU_KEY.getBoundKeyLocalizedText() + ), true); + } + + private static void openRecentScreenshot(MinecraftClient client) { + List screenshots = ScreenshotActions.getScreenshots(client); + if (screenshots.isEmpty()) { + if (client.player != null) + client.player.sendMessage(Text.translatable("text.snapper.screenshot_failure_open"), true); + return; + } + + File latestScreenshot = screenshots.getFirst(); + client.setScreen(new ScreenshotViewerScreen( + ScreenshotImage.of(latestScreenshot, client.getTextureManager()), + latestScreenshot, + client.currentScreen + )); + } +} diff --git a/src/client/java/dev/spiritstudios/snapper/gui/PanoramaViewerScreen.java b/src/client/java/dev/spiritstudios/snapper/gui/screen/PanoramaViewerScreen.java similarity index 98% rename from src/client/java/dev/spiritstudios/snapper/gui/PanoramaViewerScreen.java rename to src/client/java/dev/spiritstudios/snapper/gui/screen/PanoramaViewerScreen.java index f03476d..6b0bb72 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/PanoramaViewerScreen.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/screen/PanoramaViewerScreen.java @@ -1,4 +1,4 @@ -package dev.spiritstudios.snapper.gui; +package dev.spiritstudios.snapper.gui.screen; import dev.spiritstudios.snapper.Snapper; import dev.spiritstudios.snapper.util.ScreenshotImage; @@ -67,7 +67,7 @@ private void loadIcon(ScreenshotImage icon, String fileName, Path filePath) { try (InputStream inputStream = Files.newInputStream(filePath)) { icon.load(NativeImage.read(inputStream)); - } catch (IOException error) { + } catch (Throwable error) { Snapper.LOGGER.error("Invalid face for panorama {}", fileName, error); } } @@ -150,7 +150,7 @@ public void render(DrawContext context, int mouseX, int mouseY, float delta) { FALLBACK_PANORAMA_RENDERER_CUBE.render(context, this.width, this.height, this.backgroundAlpha, delta); context.drawCenteredTextWithShadow(textRenderer, Text.translatable("text.snapper.panorama_encourage"), this.width / 2, this.height / 2, 0xFFFFFF); } - + context.drawCenteredTextWithShadow(this.textRenderer, this.title, this.width / 2, 20, 0xffffff); } diff --git a/src/client/java/dev/spiritstudios/snapper/gui/RenameScreenshotScreen.java b/src/client/java/dev/spiritstudios/snapper/gui/screen/RenameScreenshotScreen.java similarity index 98% rename from src/client/java/dev/spiritstudios/snapper/gui/RenameScreenshotScreen.java rename to src/client/java/dev/spiritstudios/snapper/gui/screen/RenameScreenshotScreen.java index 5c2e6bf..00d7c49 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/RenameScreenshotScreen.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/screen/RenameScreenshotScreen.java @@ -1,4 +1,4 @@ -package dev.spiritstudios.snapper.gui; +package dev.spiritstudios.snapper.gui.screen; import dev.spiritstudios.snapper.util.ScreenshotActions; import net.minecraft.client.MinecraftClient; diff --git a/src/client/java/dev/spiritstudios/snapper/gui/ScreenshotScreen.java b/src/client/java/dev/spiritstudios/snapper/gui/screen/ScreenshotScreen.java similarity index 93% rename from src/client/java/dev/spiritstudios/snapper/gui/ScreenshotScreen.java rename to src/client/java/dev/spiritstudios/snapper/gui/screen/ScreenshotScreen.java index 120115a..0fcf564 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/ScreenshotScreen.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/screen/ScreenshotScreen.java @@ -1,10 +1,11 @@ -package dev.spiritstudios.snapper.gui; +package dev.spiritstudios.snapper.gui.screen; import dev.spiritstudios.snapper.Snapper; import dev.spiritstudios.snapper.SnapperConfig; import dev.spiritstudios.snapper.gui.widget.ScreenshotListWidget; import dev.spiritstudios.snapper.util.ScreenshotActions; -import dev.spiritstudios.specter.impl.config.gui.ConfigScreen; +import dev.spiritstudios.snapper.util.ScreenshotImage; +import dev.spiritstudios.specter.api.config.RootConfigScreen; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.screen.Screen; @@ -42,7 +43,6 @@ public ScreenshotScreen(Screen parent) { @Override protected void init() { if (client == null) return; - screenshotList = this.addDrawableChild(new ScreenshotListWidget( client, width, @@ -135,7 +135,8 @@ protected void init() { TextIconButtonWidget settingsButton = addDrawableChild(TextIconButtonWidget.builder( Text.translatable("config.snapper.snapper.title"), - button -> this.client.setScreen(new ConfigScreen(SnapperConfig.INSTANCE, this)), + button -> this.client.setScreen( + new RootConfigScreen(SnapperConfig.HOLDER, this)), true ).width(20).texture(SETTINGS_ICON, 15, 15).build()); @@ -184,6 +185,11 @@ public boolean keyPressed(int keyCode, int scanCode, int modifiers) { return true; } + if (keyCode == GLFW.GLFW_KEY_ENTER && selectedScreenshot != null) { + if (client == null) return false; + client.setScreen(new ScreenshotViewerScreen(selectedScreenshot.icon, selectedScreenshot.screenshot, this)); + } + return false; } diff --git a/src/client/java/dev/spiritstudios/snapper/gui/ScreenshotViewerScreen.java b/src/client/java/dev/spiritstudios/snapper/gui/screen/ScreenshotViewerScreen.java similarity index 99% rename from src/client/java/dev/spiritstudios/snapper/gui/ScreenshotViewerScreen.java rename to src/client/java/dev/spiritstudios/snapper/gui/screen/ScreenshotViewerScreen.java index 0d8dff1..c7d6fd9 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/ScreenshotViewerScreen.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/screen/ScreenshotViewerScreen.java @@ -1,4 +1,4 @@ -package dev.spiritstudios.snapper.gui; +package dev.spiritstudios.snapper.gui.screen; import com.mojang.blaze3d.systems.RenderSystem; import dev.spiritstudios.snapper.Snapper; 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 edc7454..df665aa 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/widget/ScreenshotListWidget.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/widget/ScreenshotListWidget.java @@ -2,15 +2,17 @@ import com.mojang.blaze3d.systems.RenderSystem; import dev.spiritstudios.snapper.Snapper; -import dev.spiritstudios.snapper.gui.ScreenshotScreen; -import dev.spiritstudios.snapper.gui.ScreenshotViewerScreen; +import dev.spiritstudios.snapper.gui.screen.ScreenshotScreen; +import dev.spiritstudios.snapper.gui.screen.ScreenshotViewerScreen; import dev.spiritstudios.snapper.util.ScreenshotActions; import dev.spiritstudios.snapper.util.ScreenshotImage; +import dev.spiritstudios.specter.api.core.exception.UnreachableException; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.screen.LoadingDisplay; import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.gui.widget.AlwaysSelectedEntryListWidget; +import net.minecraft.client.input.KeyCodes; import net.minecraft.client.sound.PositionedSoundInstance; import net.minecraft.client.texture.NativeImage; import net.minecraft.sound.SoundEvents; @@ -31,33 +33,35 @@ import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.time.format.FormatStyle; -import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.concurrent.CompletableFuture; - -import static dev.spiritstudios.snapper.Snapper.MODID; +import java.util.stream.Collectors; public class ScreenshotListWidget extends AlwaysSelectedEntryListWidget { - private static final Identifier VIEW_TEXTURE = Identifier.of(MODID, "screenshots/view"); - private static final Identifier VIEW_HIGHLIGHTED_TEXTURE = Identifier.of(MODID, "screenshots/view_highlighted"); + private static final Identifier VIEW_TEXTURE = Snapper.id("screenshots/view"); + private static final Identifier VIEW_HIGHLIGHTED_TEXTURE = Snapper.id("screenshots/view_highlighted"); private final Screen parent; public final CompletableFuture> loadFuture; - public ScreenshotListWidget(MinecraftClient client, int width, int height, int y, int itemHeight, ScreenshotListWidget previous, Screen parent) { + public ScreenshotListWidget(MinecraftClient client, int width, int height, int y, int itemHeight, @Nullable ScreenshotListWidget previous, Screen parent) { super(client, width, height, y, itemHeight); + this.parent = parent; this.addEntry(new LoadingEntry(client)); - if (previous != null) this.loadFuture = previous.loadFuture; - else this.loadFuture = load(client); + this.loadFuture = previous != null ? previous.loadFuture : load(client); - this.loadFuture.thenAccept((entries) -> { + this.loadFuture.thenAccept(entries -> { this.clearEntries(); entries.sort(Comparator.comparingLong(ScreenshotEntry::lastModified).reversed()); entries.forEach(this::addEntry); + + if (entries.isEmpty()) { + this.addEntry(new EmptyEntry(client)); + } }); } @@ -67,18 +71,29 @@ protected void clearEntries() { super.clearEntries(); } + @Override + public boolean keyPressed(int keyCode, int scanCode, int modifiers) { + if (KeyCodes.isToggle(keyCode)) { + Entry entry = this.getSelectedOrNull(); + if (entry instanceof ScreenshotEntry screenshotEntry) return screenshotEntry.click(); + } + + return super.keyPressed(keyCode, scanCode, modifiers); + } + public CompletableFuture> load(MinecraftClient client) { return CompletableFuture.supplyAsync(() -> { List screenshots = ScreenshotActions.getScreenshots(client); - List entries = new ArrayList<>(); - screenshots.parallelStream().forEach(file -> entries.add(new ScreenshotEntry(file, client, parent))); - return entries; + return screenshots.parallelStream() + .map(file -> new ScreenshotEntry(file, client, parent)) + .collect(Collectors.toList()); }); } + private void setEntrySelected(@Nullable ScreenshotEntry entry) { super.setSelected(entry); - ScreenshotScreen parentScreen = (ScreenshotScreen) this.parent; - parentScreen.imageSelected(entry); + if (this.parent instanceof ScreenshotScreen screenshotScreen) + screenshotScreen.imageSelected(entry); } public abstract static class Entry extends AlwaysSelectedEntryListWidget.Entry implements AutoCloseable { @@ -125,6 +140,34 @@ public void render(DrawContext context, int index, int y, int x, int entryWidth, } } + public static class EmptyEntry extends Entry implements AutoCloseable { + private static final Text EMPTY_LIST_TEXT = Text.translatable("text.snapper.empty"); + private final MinecraftClient client; + + public EmptyEntry(MinecraftClient client) { + this.client = client; + } + + @Override + public Text getNarration() { + return EMPTY_LIST_TEXT; + } + + @Override + public void render(DrawContext context, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) { + if (this.client.currentScreen == null) throw new UnreachableException(); + + context.drawText( + this.client.textRenderer, + EMPTY_LIST_TEXT, + (this.client.currentScreen.width - this.client.textRenderer.getWidth(EMPTY_LIST_TEXT)) / 2, + y + entryHeight / 2, + 0xFFFFFF, + false + ); + } + } + public class ScreenshotEntry extends Entry implements AutoCloseable { public static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter .ofLocalizedDateTime(FormatStyle.SHORT) @@ -139,7 +182,6 @@ public class ScreenshotEntry extends Entry implements AutoCloseable { private long time; public final File screenshot; - public ScreenshotEntry(File screenshot, MinecraftClient client, Screen parent) { this.client = client; this.screenParent = parent; @@ -188,21 +230,21 @@ public void render(DrawContext context, int index, int y, int x, int entryWidth, ); if (this.icon != null) { - RenderSystem.enableBlend(); - context.drawTexture( - this.icon.getTextureId(), - x, - y, - 32, - 32, - (icon.getHeight()) / 3.0F + 32, - 0, - icon.getHeight(), - icon.getHeight(), - icon.getWidth(), - icon.getHeight() - ); - RenderSystem.disableBlend(); + RenderSystem.enableBlend(); + context.drawTexture( + this.icon.getTextureId(), + x, + y, + 32, + 32, + (icon.getHeight()) / 3.0F + 32, + 0, + icon.getHeight(), + icon.getHeight(), + icon.getWidth(), + icon.getHeight() + ); + RenderSystem.disableBlend(); } if (this.client.options.getTouchscreen().getValue() || hovered) { @@ -226,7 +268,7 @@ private void loadIcon() { try (InputStream inputStream = Files.newInputStream(this.iconPath)) { this.icon.load(NativeImage.read(inputStream)); - } catch (IOException error) { + } catch (Throwable error) { Snapper.LOGGER.error("Invalid icon for screenshot {}", iconFileName, error); this.iconPath = null; } @@ -245,14 +287,17 @@ public boolean mouseClicked(double mouseX, double mouseY, int button) { if (!(mouseX - (double) ScreenshotListWidget.this.getRowLeft() <= 32.0) && Util.getMeasuringTimeMs() - this.time >= 250L) { this.time = Util.getMeasuringTimeMs(); return super.mouseClicked(mouseX, mouseY, button); - } else { - if (this.icon == null) return false; - this.client.getSoundManager().play(PositionedSoundInstance.master(SoundEvents.UI_BUTTON_CLICK, 1.0F)); - - this.client.setScreen(new ScreenshotViewerScreen(this.icon, this.screenshot, this.screenParent)); - return true; } + return click(); + } + + public boolean click() { + if (this.icon == null) return false; + this.client.getSoundManager().play(PositionedSoundInstance.master(SoundEvents.UI_BUTTON_CLICK, 1.0F)); + + this.client.setScreen(new ScreenshotViewerScreen(this.icon, this.screenshot, this.screenParent)); + return true; } @Override diff --git a/src/client/java/dev/spiritstudios/snapper/mixin/CameraMixin.java b/src/client/java/dev/spiritstudios/snapper/mixin/CameraMixin.java index d22cc86..53b4020 100644 --- a/src/client/java/dev/spiritstudios/snapper/mixin/CameraMixin.java +++ b/src/client/java/dev/spiritstudios/snapper/mixin/CameraMixin.java @@ -10,7 +10,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(Camera.class) -public class CameraMixin { +public abstract class CameraMixin { @Inject(method = "update", at = @At("HEAD"), cancellable = true) private void blockUpdateDuringPanoramaRender( BlockView area, diff --git a/src/client/java/dev/spiritstudios/snapper/mixin/GameMenuMixin.java b/src/client/java/dev/spiritstudios/snapper/mixin/GameMenuMixin.java index ab56922..4ab5a70 100644 --- a/src/client/java/dev/spiritstudios/snapper/mixin/GameMenuMixin.java +++ b/src/client/java/dev/spiritstudios/snapper/mixin/GameMenuMixin.java @@ -1,6 +1,7 @@ package dev.spiritstudios.snapper.mixin; -import dev.spiritstudios.snapper.gui.ScreenshotScreen; +import dev.spiritstudios.snapper.SnapperConfig; +import dev.spiritstudios.snapper.gui.screen.ScreenshotScreen; import net.minecraft.client.gui.screen.GameMenuScreen; import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.gui.widget.TextIconButtonWidget; @@ -15,7 +16,7 @@ import static dev.spiritstudios.snapper.Snapper.MODID; @Mixin(GameMenuScreen.class) -public class GameMenuMixin extends Screen { +public abstract class GameMenuMixin extends Screen { protected GameMenuMixin(Text title) { super(title); } @@ -24,21 +25,23 @@ protected GameMenuMixin(Text title) { private static final Identifier SNAPPER_BUTTON_ICON = Identifier.of(MODID, "screenshots/screenshot"); @Inject( - method = "init", + method = "initWidgets", at = @At("TAIL") ) - protected void head(CallbackInfo ci) { - this.addDrawableChild( - TextIconButtonWidget.builder( - Text.translatable("button.snapper.screenshots"), - button -> { - if (this.client == null) - return; + protected void initWidgets(CallbackInfo ci) { + if (SnapperConfig.INSTANCE.showSnapperGameMenu.get()) { + this.addDrawableChild( + TextIconButtonWidget.builder( + Text.translatable("button.snapper.screenshots"), + button -> { + if (this.client == null) + return; - this.client.setScreen(new ScreenshotScreen(new GameMenuScreen(true))); - }, - true - ).width(20).texture(SNAPPER_BUTTON_ICON, 15, 15).build() - ).setPosition(this.width / 2 - 130, height / 4 + 32); + this.client.setScreen(new ScreenshotScreen(new GameMenuScreen(true))); + }, + true + ).width(20).texture(SNAPPER_BUTTON_ICON, 15, 15).build() + ).setPosition(this.width / 2 - 130, height / 4 + 32); + } } } diff --git a/src/client/java/dev/spiritstudios/snapper/mixin/KeyboardMixin.java b/src/client/java/dev/spiritstudios/snapper/mixin/KeyboardMixin.java index 9b0fead..a57420f 100644 --- a/src/client/java/dev/spiritstudios/snapper/mixin/KeyboardMixin.java +++ b/src/client/java/dev/spiritstudios/snapper/mixin/KeyboardMixin.java @@ -1,7 +1,7 @@ package dev.spiritstudios.snapper.mixin; -import dev.spiritstudios.snapper.Snapper; import dev.spiritstudios.snapper.SnapperConfig; +import dev.spiritstudios.snapper.SnapperKeybindings; import net.minecraft.client.Keyboard; import net.minecraft.client.MinecraftClient; import net.minecraft.text.Text; @@ -11,7 +11,7 @@ import org.spongepowered.asm.mixin.Shadow; @Mixin(Keyboard.class) -public class KeyboardMixin { +public abstract class KeyboardMixin { @Shadow @Final private MinecraftClient client; @@ -27,7 +27,7 @@ private void method_1464(Text text) { "text.snapper.screenshot_instructions_copy" : "text.snapper.screenshot_instructions", text, - Snapper.RECENT_SCREENSHOT_KEY.getBoundKeyLocalizedText() + SnapperKeybindings.RECENT_SCREENSHOT_KEY.getBoundKeyLocalizedText() ), false); } } diff --git a/src/client/java/dev/spiritstudios/snapper/mixin/MinecraftClientMixin.java b/src/client/java/dev/spiritstudios/snapper/mixin/MinecraftClientMixin.java index e622bb1..41945d2 100644 --- a/src/client/java/dev/spiritstudios/snapper/mixin/MinecraftClientMixin.java +++ b/src/client/java/dev/spiritstudios/snapper/mixin/MinecraftClientMixin.java @@ -24,7 +24,7 @@ import java.util.function.Consumer; @Mixin(MinecraftClient.class) -public class MinecraftClientMixin { +public abstract class MinecraftClientMixin { @Final @Shadow public static boolean IS_SYSTEM_MAC; diff --git a/src/client/java/dev/spiritstudios/snapper/mixin/ScreenshotRecorderMixin.java b/src/client/java/dev/spiritstudios/snapper/mixin/ScreenshotRecorderMixin.java index ba3823a..60054da 100644 --- a/src/client/java/dev/spiritstudios/snapper/mixin/ScreenshotRecorderMixin.java +++ b/src/client/java/dev/spiritstudios/snapper/mixin/ScreenshotRecorderMixin.java @@ -18,7 +18,7 @@ import java.util.function.Consumer; @Mixin(ScreenshotRecorder.class) -public class ScreenshotRecorderMixin { +public abstract class ScreenshotRecorderMixin { /** * @author hama * @reason check if panorama file exists before writing to it diff --git a/src/client/java/dev/spiritstudios/snapper/mixin/TitleScreenMixin.java b/src/client/java/dev/spiritstudios/snapper/mixin/TitleScreenMixin.java index 9ad6501..9f227ab 100644 --- a/src/client/java/dev/spiritstudios/snapper/mixin/TitleScreenMixin.java +++ b/src/client/java/dev/spiritstudios/snapper/mixin/TitleScreenMixin.java @@ -1,6 +1,7 @@ package dev.spiritstudios.snapper.mixin; -import dev.spiritstudios.snapper.gui.ScreenshotScreen; +import dev.spiritstudios.snapper.SnapperConfig; +import dev.spiritstudios.snapper.gui.screen.ScreenshotScreen; import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.gui.screen.TitleScreen; import net.minecraft.client.gui.widget.TextIconButtonWidget; @@ -32,12 +33,14 @@ protected void init(CallbackInfo ci) { int y = this.height / 4 + 48; int spacingY = 24; - this.addDrawableChild( - TextIconButtonWidget.builder( - Text.translatable("button.snapper.screenshots"), - button -> this.client.setScreen(new ScreenshotScreen((TitleScreen) ((Object) this))), - true - ).width(20).texture(SNAPPER_BUTTON_ICON, 15, 15).build() - ).setPosition(this.width / 2 - 124, y + spacingY); + if (SnapperConfig.INSTANCE.showSnapperTitleScreen.get()) { + this.addDrawableChild( + TextIconButtonWidget.builder( + Text.translatable("button.snapper.screenshots"), + button -> this.client.setScreen(new ScreenshotScreen((TitleScreen) ((Object) this))), + true + ).width(20).texture(SNAPPER_BUTTON_ICON, 15, 15).build() + ).setPosition(this.width / 2 - 124, y + spacingY); + } } } diff --git a/src/client/java/dev/spiritstudios/snapper/util/ScreenshotImage.java b/src/client/java/dev/spiritstudios/snapper/util/ScreenshotImage.java index 75c1f05..be2b774 100644 --- a/src/client/java/dev/spiritstudios/snapper/util/ScreenshotImage.java +++ b/src/client/java/dev/spiritstudios/snapper/util/ScreenshotImage.java @@ -1,5 +1,6 @@ package dev.spiritstudios.snapper.util; +import com.mojang.blaze3d.systems.RenderSystem; import dev.spiritstudios.snapper.Snapper; import net.minecraft.client.texture.NativeImage; import net.minecraft.client.texture.NativeImageBackedTexture; @@ -9,7 +10,6 @@ import org.jetbrains.annotations.Nullable; import java.io.File; -import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; @@ -68,22 +68,33 @@ private void loadIcon(Path path) { try (InputStream inputStream = Files.newInputStream(path)) { this.load(NativeImage.read(inputStream)); - } catch (IOException error) { + } catch (Throwable error) { Snapper.LOGGER.error("Invalid icon for screenshot {}", new File(String.valueOf(path)).getName(), error); } }); } public void load(NativeImage image) { - this.assertOpen(); - if (image != null) { - if (this.texture == null) this.texture = new NativeImageBackedTexture(image); - else { - this.texture.setImage(image); - this.texture.upload(); + Runnable load = () -> { + try { + this.assertOpen(); + if (image != null) { + if (this.texture == null) this.texture = new NativeImageBackedTexture(image); + else { + this.texture.setImage(image); + this.texture.upload(); + } + this.textureManager.registerTexture(this.id, this.texture); + } + } catch (Throwable e) { + image.close(); + this.destroy(); + throw e; } - this.textureManager.registerTexture(this.id, this.texture); - } + }; + + if (RenderSystem.isOnRenderThread()) load.run(); + else RenderSystem.recordRenderCall(load::run); } /* diff --git a/src/client/resources/assets/snapper/lang/en_us.json b/src/client/resources/assets/snapper/lang/en_us.json index 171414e..76d5c82 100644 --- a/src/client/resources/assets/snapper/lang/en_us.json +++ b/src/client/resources/assets/snapper/lang/en_us.json @@ -2,7 +2,7 @@ "key.snapper.panorama": "Take Panoramic Screenshots", "key.snapper.huge": "Take Huge Screenshot", "key.snapper.screenshot_menu": "View Screenshots", - "key.snapper.recent": "Open latest screenshot", + "key.snapper.recent": "Open Latest Screenshot", "menu.snapper.screenshotmenu": "Screenshots", "menu.snapper.viewermenu": "Viewer", "menu.snapper.panorama": "View Panorama", @@ -17,6 +17,7 @@ "text.snapper.created": "Created", "text.snapper.generic": "Screenshot", "text.snapper.loading": "Loading Screenshots", + "text.snapper.empty": "Take a screenshot in-game to see it here!", "text.snapper.panorama_encourage": "Take a panorama in-game to see it here!", "text.snapper.panorama_success": "Panorama saved. View by pressing %s", "text.snapper.panorama_failure_iris": "Due to a bug in Iris, panoramas cannot be taken while it's installed.", @@ -28,9 +29,14 @@ "text.snapper.rename_invalid_png": "Name must end with '.png'", "text.snapper.screenshot_instructions": "%s. View by pressing %s", "text.snapper.screenshot_instructions_copy": "%s. Automagically copied to clipboard.", - "text.snapper.screenshot_failure_open": "Take a screenshot in order to view it", + "text.snapper.screenshot_failure_open": "Take a screenshot in order to view it!", "panorama.snapper.failure": "Couldn't save panorama: %s", "panorama.snapper.success": "Saved screenshot as %s", "config.snapper.snapper.title": "Snapper Settings", - "config.snapper.snapper.copyTakenScreenshot": "Copy screenshot to clipboard when taken" + "config.snapper.snapper.copyTakenScreenshot": "Auto-Clipboard", + "config.snapper.snapper.copyTakenScreenshot.tooltip": "Copy screenshot to clipboard when taken", + "config.snapper.snapper.showSnapperTitleScreen": "Title Screen Button", + "config.snapper.snapper.showSnapperTitleScreen.tooltip": "Show Snapper button on title screen", + "config.snapper.snapper.showSnapperGameMenu": "Game Menu Button", + "config.snapper.snapper.showSnapperGameMenu.tooltip": "Show Snapper button in game menu" } \ No newline at end of file diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 2f773c1..45c0f8d 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -1,7 +1,7 @@ { "schemaVersion": 1, "id": "snapper", - "version": "${version}", + "version": "${mod_version}", "name": "Snapper", "description": "Screenshots, made simple.", "authors": [ @@ -24,7 +24,7 @@ "snapper.mixins.json" ], "depends": { - "fabricloader": ">=${loader_version}", + "fabricloader": ">=${fabric_loader_version}", "minecraft": "~${minecraft_version}-", "fabric-api": "*", "java": ">=21",