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
-
+
+
+
## 📖 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",