diff --git a/build.gradle.kts b/build.gradle.kts index 9be3e3c..5394ec6 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -29,7 +29,9 @@ loom { } repositories { + mavenLocal() // TODO: Remove me after Greenhouse Config releases. mavenCentral() + maven("https://maven.parchmentmc.org/") maven("https://maven.spiritstudios.dev/releases/") maven("https://moehreag.duckdns.org/maven/releases") { content { @@ -37,11 +39,15 @@ repositories { includeGroup("io.github.axolotlclient.AxolotlClient-config") } } + maven("https://maven.greenhouse.lgbt/releases/") } dependencies { minecraft(libs.minecraft) - mappings(variantOf(libs.yarn) { classifier("v2") }) + mappings(loom.layered { + officialMojangMappings() + parchment(libs.parchment) + }) modImplementation(libs.fabric.loader) modImplementation(libs.fabric.api) @@ -49,6 +55,9 @@ dependencies { include(libs.bundles.specter) modImplementation(libs.bundles.specter) + include(libs.greenhouse.config) + modImplementation(libs.greenhouse.config) + implementation(libs.objc.bridge) } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9dbf30e..9a51a7f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,38 +1,37 @@ [versions] -fabric_loom = "1.13-SNAPSHOT" +fabric_loom = "1.14-SNAPSHOT" minotaur = "2.+" minecraft = "1.21.10" -yarn = "1.21.10+build.2" +parchment = "2025.10.12" fabric_loader = "0.17.3" fabric_api = "0.138.3+1.21.10" specter = "1.3.2" +greenhouse_config = "3.0.0-beta.1+1.21.10" objc_bridge = "1.0.0" [plugins] -fabric_loom = { id = "fabric-loom", version.ref = "fabric_loom" } +fabric_loom = { id = "net.fabricmc.fabric-loom-remap", 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" } +parchment = { module = "org.parchmentmc.data: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" } -specter_config = { group = "dev.spiritstudios.specter", name = "config", version.ref = "specter" } specter_core = { group = "dev.spiritstudios.specter", name = "core", version.ref = "specter" } -specter_serialization = { group = "dev.spiritstudios.specter", name = "serialization", version.ref = "specter" } specter_gui = { group = "dev.spiritstudios.specter", name = "gui", version.ref = "specter" } +greenhouse_config = { group = "lgbt.greenhouse.config", name = "greenhouseconfig-fabric", version.ref = "greenhouse_config" } + 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.properties b/gradle/wrapper/gradle-wrapper.properties index ca025c8..23449a2 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.14-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/src/client/java/dev/spiritstudios/snapper/Snapper.java b/src/client/java/dev/spiritstudios/snapper/Snapper.java index 43e3036..caa81bd 100644 --- a/src/client/java/dev/spiritstudios/snapper/Snapper.java +++ b/src/client/java/dev/spiritstudios/snapper/Snapper.java @@ -3,17 +3,13 @@ import dev.spiritstudios.snapper.util.PlatformHelper; import dev.spiritstudios.snapper.util.actions.GeneralPlatformActions; import dev.spiritstudios.snapper.util.actions.MacPlatformActions; -import dev.spiritstudios.snapper.util.config.DirectoryConfigUtil; import dev.spiritstudios.snapper.util.uploading.ScreenshotUploading; -import dev.spiritstudios.specter.api.config.client.ConfigScreenWidgets; -import dev.spiritstudios.specter.api.config.client.ModMenuHelper; import net.fabricmc.api.ClientModInitializer; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents; -import net.minecraft.util.Identifier; +import net.minecraft.resources.ResourceLocation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.nio.file.Path; import java.util.Locale; public final class Snapper implements ClientModInitializer { @@ -23,16 +19,13 @@ public final class Snapper implements ClientModInitializer { @Override public void onInitializeClient() { - ConfigScreenWidgets.add(Path.class, DirectoryConfigUtil.PATH_WIDGET_FACTORY); + SnapperConfig.init(); SnapperKeybindings.init(); - - ModMenuHelper.addConfig(Snapper.MODID, SnapperConfig.HOLDER.id()); - ClientLifecycleEvents.CLIENT_STOPPING.register(client -> ScreenshotUploading.close()); } - public static Identifier id(String path) { - return Identifier.of(MODID, path); + public static ResourceLocation id(String path) { + return ResourceLocation.fromNamespaceAndPath(MODID, path); } public static PlatformHelper getPlatformHelper() { diff --git a/src/client/java/dev/spiritstudios/snapper/SnapperConfig.java b/src/client/java/dev/spiritstudios/snapper/SnapperConfig.java index 9964342..999ceed 100644 --- a/src/client/java/dev/spiritstudios/snapper/SnapperConfig.java +++ b/src/client/java/dev/spiritstudios/snapper/SnapperConfig.java @@ -1,51 +1,226 @@ package dev.spiritstudios.snapper; +import com.mojang.serialization.Codec; import dev.spiritstudios.snapper.gui.screen.ScreenshotScreen; import dev.spiritstudios.snapper.util.SnapperUtil; import dev.spiritstudios.snapper.util.config.DirectoryConfigUtil; import dev.spiritstudios.snapper.util.uploading.AxolotlClientApi; -import dev.spiritstudios.specter.api.config.Config; -import dev.spiritstudios.specter.api.config.ConfigHolder; -import dev.spiritstudios.specter.api.config.Value; +import lgbt.greenhouse.config.api.v3.GreenhouseConfigSide; +import lgbt.greenhouse.config.api.v3.config.GreenhouseConfigHolder; +import lgbt.greenhouse.config.api.v3.dfu.builder.DataFixerBuilderFunctions; +import lgbt.greenhouse.config.api.v3.dfu.builder.schema.TypeTemplateBuilder; +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 java.nio.file.Path; +import java.util.Map; -public final class SnapperConfig extends Config { - public static final ConfigHolder HOLDER = ConfigHolder.builder( - Snapper.id("snapper"), SnapperConfig.class - ).build(); +public record SnapperConfig(boolean copyTakenScreenshot, + SnapperButton snapperButton, + ScreenshotScreen.ViewMode viewMode, + SnapperUtil.PanoramaSize panoramaDimensions, + CustomScreenshotFolder customScreenshotPath, + AxolotlClient axolotlClient) { + public static final GreenhouseConfigHolder HOLDER = GreenhouseConfigHolder.register( + SnapperConfig.class, + "snapper", + 1, + GreenhouseConfigJsonCLang.INSTANCE, + GreenhouseConfigSide.CLIENT, + configBuilder -> configBuilder + .withValue( + "copy_taken_screenshot", + "Whether to copy screenshots to the clipboard when taken.", + Codec.BOOL, + false, + SnapperConfig::copyTakenScreenshot + ).withMapValue( + SnapperButton.class, + "snapper_button", + "Settings relating to the Snapper Button", + SnapperConfig::snapperButton, + mapBuilder -> mapBuilder + .withValue( + "show_on_title_screen", + "Whether to show Snapper button on title screen.", + Codec.BOOL, + true, + SnapperButton::showOnTitleScreen + ).withValue( + "show_in_game_menu", + "Whether to show Snapper button in game menu.", + Codec.BOOL, + true, + SnapperButton::showInGameMenu + ) + ).withValue( + "view_mode", + """ + Whether to show the screenshot menu in a grid or a list. + May be either 'grid' or 'list'.""", + ScreenshotScreen.ViewMode.CODEC, + ScreenshotScreen.ViewMode.GRID, + SnapperConfig::viewMode + ).withValue( + "panorama_size", + """ + Dimensions of individual panorama images when saved. + May be 1024, 2048, or 4096.""", + SnapperUtil.PanoramaSize.CODEC, + SnapperUtil.PanoramaSize.ONE_THOUSAND_TWENTY_FOUR, + SnapperConfig::panoramaDimensions + ).withMapValue( + CustomScreenshotFolder.class, + "custom_screenshot_path", + "Settings relating to the custom screenshot path", + SnapperConfig::customScreenshotPath, + mapBuilder -> mapBuilder + .withValue( + "enabled", + "Whether to use a custom screenshot path instead of Minecraft's default", + Codec.BOOL, + false, + CustomScreenshotFolder::enabled + ).withValue( + "path", + "The path to use if custom screenshot folders are enabled.", + DirectoryConfigUtil.PATH_CODEC, + SnapperUtil.UNIFIED_FOLDER, + CustomScreenshotFolder::path + ) + ).withMapValue( + AxolotlClient.class, + "axolotl_client", + "Settings relating to Axolotl Client.", + SnapperConfig::axolotlClient, + mapBuilder -> mapBuilder + .withValue( + "terms_status", + """ + Whether the terms of AxolotlClient have been accepted. + These terms must be accepted to share screenshots via AxolotlClient's image host. + May be 'accept', 'deny', or 'unset'.""", + AxolotlClientApi.TermsAcceptance.CODEC, + AxolotlClientApi.TermsAcceptance.UNSET, + AxolotlClient::termsStatus + ) + ), + dataFixerBuilder -> dataFixerBuilder + .withSchema( + 0, + schemaBuilder -> + schemaBuilder + .withField("copyTakenScreenshot", TypeTemplateBuilder.BOOL) + .withField("showSnapperTitleScreen", TypeTemplateBuilder.BOOL) + .withField("showSnapperGameMenu", TypeTemplateBuilder.BOOL) + .withField("viewMode", TypeTemplateBuilder.STRING) + .withField("termsAccepted", TypeTemplateBuilder.STRING) + .withField("panoramaDimensions", TypeTemplateBuilder.STRING) + .withField("useCustomScreenshotFolder", TypeTemplateBuilder.BOOL) + .withField("customScreenshotFolder", TypeTemplateBuilder.STRING) + ).withPreviousLang(0, GreenhouseConfigJsonLang.INSTANCE) + .withSchemaAndFixes( + 1, + DataFixerBuilderFunctions.create( + builder -> builder + .withField("copy_taken_screenshot", TypeTemplateBuilder.BOOL) + .withField("snapper_button", TypeTemplateBuilder.map( + mapBuilder -> mapBuilder + .withField("show_on_title_screen", TypeTemplateBuilder.BOOL) + .withField("show_in_game_menu", TypeTemplateBuilder.BOOL) + )) + .withField("view_mode", TypeTemplateBuilder.STRING) + .withField("panorama_dimensions", TypeTemplateBuilder.STRING) + .withField("custom_screenshot_path", TypeTemplateBuilder.map( + mapBuilder -> mapBuilder + .withField("enabled", TypeTemplateBuilder.BOOL) + .withField("path", TypeTemplateBuilder.STRING) + )) + .withField("axolotl_client", TypeTemplateBuilder.map( + mapBuilder -> mapBuilder + .withField("terms_status", TypeTemplateBuilder.STRING) + )) + .noInheritance(), + schema -> GreenhouseConfigRelocateFieldsFix.create( + schema, + Map.of( + "copyTakenScreenshot", "copy_taken_screenshot", + "showSnapperTitleScreen", "snapper_button.show_on_title_screen", + "showSnapperGameMenu", "snapper_button.show_in_game_menu", + "viewMode", "view_mode", + "panoramaDimensions", "panorama_dimensions", + "useCustomScreenshotFolder", "custom_screenshot_path.enabled", + "customScreenshotFolder", "custom_screenshot_path.path", + "termsAccepted", "axolotl_client.terms_status" + ) + ) + ) + ) + ); - public static final SnapperConfig INSTANCE = HOLDER.get(); + public record SnapperButton(boolean showOnTitleScreen, boolean showInGameMenu) {} + public record CustomScreenshotFolder(boolean enabled, Path path) {} + public record AxolotlClient(AxolotlClientApi.TermsAcceptance termsStatus) {} - public final Value copyTakenScreenshot = booleanValue(false) - .comment("Whether to copy screenshots to clipboard when taken.") - .build(); + public static void init() {} + public static Mutable mutable() { + return new Mutable(); + } - public final Value showSnapperTitleScreen = booleanValue(true) - .comment("Whether to show Snapper button on title screen.") - .build(); + public static class Mutable { + // Root + public boolean copyTakenScreenshot; + public ScreenshotScreen.ViewMode viewMode; + public SnapperUtil.PanoramaSize panoramaDimensions; - public final Value showSnapperGameMenu = booleanValue(true) - .comment("Whether to show Snapper button in game menu.") - .build(); + // Snapper Button + public boolean showOnTitleScreen; + public boolean showInGameMenu; - public final Value viewMode = enumValue(ScreenshotScreen.ViewMode.GRID, ScreenshotScreen.ViewMode.class) - .comment("Whether to show screenshot menu with grid or list.") - .build(); + // Custom Screenshot Folder + public boolean enabled; + public Path path; - public final Value termsAccepted = enumValue(AxolotlClientApi.TermsAcceptance.UNSET, AxolotlClientApi.TermsAcceptance.class) - .comment("Whether the terms of AxolotlClient have been accepted.") - .build(); + // Axolotl Client + public AxolotlClientApi.TermsAcceptance termsAccepted; - public final Value panoramaDimensions = enumValue(SnapperUtil.PanoramaSize.ONE_THOUSAND_TWENTY_FOUR, SnapperUtil.PanoramaSize.class) - .comment("Dimensions of individual panorama images when saved.") - .build(); + public Mutable() { + SnapperConfig config = HOLDER.get(); + copyTakenScreenshot = config.copyTakenScreenshot; + viewMode = config.viewMode; + panoramaDimensions = config.panoramaDimensions; - public final Value useCustomScreenshotFolder = booleanValue(false) - .comment("Whether to use a custom screenshot folder instead of Minecraft's default") - .build(); + showOnTitleScreen = config.snapperButton.showOnTitleScreen; + showInGameMenu = config.snapperButton.showInGameMenu; - public final Value customScreenshotFolder = value(SnapperUtil.UNIFIED_FOLDER, DirectoryConfigUtil.PATH_CODEC) - .comment("What folder to use if custom screenshot folders are enabled.") - .build(); -} + enabled = config.customScreenshotPath.enabled; + path = config.customScreenshotPath.path; + + termsAccepted = config.axolotlClient.termsStatus; + } + + public void save() { + HOLDER.save(build(), null); + } + + private SnapperConfig build() { + return new SnapperConfig( + copyTakenScreenshot, + new SnapperButton( + showOnTitleScreen, + showInGameMenu + ), + viewMode, + panoramaDimensions, + new CustomScreenshotFolder( + enabled, + path + ), + new AxolotlClient( + termsAccepted + ) + ); + } + } +} \ 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 index a60e14b..12f5917 100644 --- a/src/client/java/dev/spiritstudios/snapper/SnapperKeybindings.java +++ b/src/client/java/dev/spiritstudios/snapper/SnapperKeybindings.java @@ -3,34 +3,34 @@ import dev.spiritstudios.snapper.gui.screen.ScreenshotScreen; import dev.spiritstudios.snapper.gui.screen.ScreenshotViewerScreen; import dev.spiritstudios.snapper.gui.toast.SnapperToast; -import dev.spiritstudios.snapper.util.DynamicTexture; +import dev.spiritstudios.snapper.util.ScreenshotTexture; import dev.spiritstudios.snapper.util.ScreenshotActions; import dev.spiritstudios.specter.api.core.client.event.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 net.minecraft.client.KeyMapping; +import net.minecraft.client.Minecraft; +import net.minecraft.network.chat.Component; import org.lwjgl.glfw.GLFW; import java.nio.file.Path; import java.util.List; public final class SnapperKeybindings { - public static final KeyBinding.Category SNAPPER = KeyBinding.Category.create(Snapper.id("snapper")); + public static final KeyMapping.Category SNAPPER = KeyMapping.Category.register(Snapper.id("snapper")); - public static final KeyBinding PANORAMA_KEY = new KeyBinding( + public static final KeyMapping PANORAMA_KEY = new KeyMapping( "key.snapper.panorama", GLFW.GLFW_KEY_F8, SNAPPER ); - public static final KeyBinding RECENT_SCREENSHOT_KEY = new KeyBinding( + public static final KeyMapping RECENT_SCREENSHOT_KEY = new KeyMapping( "key.snapper.recent", GLFW.GLFW_KEY_O, SNAPPER ); - public static final KeyBinding SCREENSHOT_MENU_KEY = new KeyBinding( + public static final KeyMapping SCREENSHOT_MENU_KEY = new KeyMapping( "key.snapper.screenshot_menu", GLFW.GLFW_KEY_V, SNAPPER @@ -42,52 +42,52 @@ public static void init() { KeyBindingHelper.registerKeyBinding(SCREENSHOT_MENU_KEY); ClientKeybindEvents.pressed(SCREENSHOT_MENU_KEY).register(client -> - client.setScreen(new ScreenshotScreen(client.currentScreen))); + client.setScreen(new ScreenshotScreen(client.screen))); ClientKeybindEvents.pressed(PANORAMA_KEY).register(SnapperKeybindings::takePanorama); ClientKeybindEvents.pressed(RECENT_SCREENSHOT_KEY).register(SnapperKeybindings::openRecentScreenshot); } - private static void takePanorama(MinecraftClient client) { + private static void takePanorama(Minecraft client) { if (client.player == null) return; - client.takePanorama(client.runDirectory); + client.grabPanoramixScreenshot(client.gameDirectory); SnapperToast.push( SnapperToast.Type.PANORAMA, - Text.translatable("toast.snapper.panorama.created"), - Text.translatable( + Component.translatable("toast.snapper.panorama.created"), + Component.translatable( "toast.snapper.panorama.created.description", - SCREENSHOT_MENU_KEY.getBoundKeyLocalizedText() + SCREENSHOT_MENU_KEY.getTranslatedKeyMessage() ) ); } - private static void openRecentScreenshot(MinecraftClient client) { + private static void openRecentScreenshot(Minecraft client) { List screenshots = ScreenshotActions.getScreenshots(); if (screenshots.isEmpty()) { SnapperToast.push( SnapperToast.Type.SCREENSHOT, - Text.translatable("toast.snapper.screenshot.recent.failure"), - Text.translatable("toast.snapper.screenshot.recent.failure.not_exist") + Component.translatable("toast.snapper.screenshot.recent.failure"), + Component.translatable("toast.snapper.screenshot.recent.failure.not_exist") ); return; } Path latestPath = screenshots.getFirst(); - DynamicTexture.createScreenshot(client.getTextureManager(), latestPath) + ScreenshotTexture.createScreenshot(client.getTextureManager(), latestPath) .ifPresentOrElse( image -> { client.setScreen(new ScreenshotViewerScreen( image, latestPath, - client.currentScreen + client.screen )); image.load(); }, () -> SnapperToast.push( SnapperToast.Type.DENY, - Text.translatable("toast.snapper.screenshot.recent.failure"), - Text.translatable("toast.snapper.screenshot.recent.failure.generic") + Component.translatable("toast.snapper.screenshot.recent.failure"), + Component.translatable("toast.snapper.screenshot.recent.failure.generic") ) ); 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 e1efe48..795a72c 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/overlay/ExternalDialogOverlay.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/overlay/ExternalDialogOverlay.java @@ -1,51 +1,51 @@ package dev.spiritstudios.snapper.gui.overlay; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.gl.RenderPipelines; -import net.minecraft.client.gui.DrawContext; -import net.minecraft.client.gui.screen.Overlay; -import net.minecraft.client.util.InputUtil; -import net.minecraft.text.Text; -import net.minecraft.util.Colors; -import net.minecraft.util.Identifier; +import com.mojang.blaze3d.platform.InputConstants; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphics; +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.util.CommonColors; public class ExternalDialogOverlay extends Overlay { - private final MinecraftClient client = MinecraftClient.getInstance(); + private final Minecraft client = Minecraft.getInstance(); - public static final Identifier MENU_BACKGROUND_TEXTURE = Identifier.ofVanilla("textures/gui/menu_background.png"); - private static final Identifier INWORLD_MENU_BACKGROUND_TEXTURE = Identifier.ofVanilla("textures/gui/inworld_menu_background.png"); + 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"); @Override - public boolean pausesGame() { + public boolean isPauseScreen() { return false; } @Override - public void render(DrawContext context, int mouseX, int mouseY, float delta) { - if (this.client.currentScreen != null) { - this.client.currentScreen.renderBackground(context, mouseX, mouseY, delta); - this.client.currentScreen.render(context, mouseX, mouseY, delta); + public void render(GuiGraphics context, int mouseX, int mouseY, float delta) { + if (this.client.screen != null) { + this.client.screen.renderBackground(context, mouseX, mouseY, delta); + this.client.screen.render(context, mouseX, mouseY, delta); } - this.client.gameRenderer.renderBlur(); + this.client.gameRenderer.processBlurEffect(); - context.drawTexture( + context.blit( RenderPipelines.GUI_TEXTURED, - this.client.world == null ? MENU_BACKGROUND_TEXTURE : INWORLD_MENU_BACKGROUND_TEXTURE, + this.client.level == null ? MENU_BACKGROUND_TEXTURE : INWORLD_MENU_BACKGROUND_TEXTURE, 0, 0, 0, 0, - context.getScaledWindowWidth(), context.getScaledWindowHeight(), + context.guiWidth(), context.guiHeight(), 32, 32 ); - context.drawCenteredTextWithShadow( - client.textRenderer, - Text.translatable("overlay.snapper.external_dialog.folder"), - context.getScaledWindowWidth() / 2, context.getScaledWindowHeight() / 2, - Colors.WHITE + context.drawCenteredString( + client.font, + Component.translatable("overlay.snapper.external_dialog.folder"), + context.guiWidth() / 2, context.guiHeight() / 2, + CommonColors.WHITE ); - if (InputUtil.isKeyPressed(client.getWindow(), InputUtil.GLFW_KEY_ESCAPE)) close(); + if (InputConstants.isKeyDown(client.getWindow(), InputConstants.KEY_ESCAPE)) close(); } public void close() { 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 d45ecb9..190214e 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/screen/PanoramaViewerScreen.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/screen/PanoramaViewerScreen.java @@ -4,17 +4,17 @@ import dev.spiritstudios.snapper.util.DynamicCubemapTexture; import dev.spiritstudios.snapper.util.SafeFiles; import dev.spiritstudios.snapper.util.SnapperUtil; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.gui.CubeMapRenderer; -import net.minecraft.client.gui.DrawContext; -import net.minecraft.client.gui.RotatingCubeMapRenderer; -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.gui.widget.ButtonWidget; -import net.minecraft.screen.ScreenTexts; -import net.minecraft.text.Text; -import net.minecraft.util.Colors; -import net.minecraft.util.Identifier; -import net.minecraft.util.Util; +import net.minecraft.Util; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.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.util.CommonColors; import org.jetbrains.annotations.Nullable; import java.io.IOException; @@ -23,30 +23,28 @@ import java.util.stream.Stream; public class PanoramaViewerScreen extends Screen { - protected static final Identifier ID = Snapper.id("screenshots/panorama"); - protected static final CubeMapRenderer PANORAMA_RENDERER = new CubeMapRenderer(ID); + protected static final ResourceLocation ID = Snapper.id("screenshots/panorama"); + protected static final CubeMap PANORAMA_RENDERER = new CubeMap(ID); - private final RotatingCubeMapRenderer rotatingPanoramaRenderer = new RotatingCubeMapRenderer(PANORAMA_RENDERER); + 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) { - super(Text.translatable("menu.snapper.viewer_menu")); + super(Component.translatable("menu.snapper.viewer_menu")); this.title = title; this.parent = parent; - this.client = MinecraftClient.getInstance(); - assert client != null; this.texture = this.getTexture(); if (texture != null) { - client.getTextureManager().registerTexture(ID, texture); + Minecraft.getInstance().getTextureManager().registerAndLoad(ID, texture); } } @Nullable private DynamicCubemapTexture getTexture() { - assert client != null; + assert minecraft != null; Path panoramaDir = SnapperUtil.getConfiguredScreenshotDirectory().resolve("panorama"); if (!SnapperUtil.panoramaPresent(panoramaDir)) return null; @@ -65,55 +63,51 @@ private DynamicCubemapTexture getTexture() { } @Override - public void close() { - assert client != null; - + public void onClose() { if (texture != null) { - client.getTextureManager().destroyTexture(ID); + Minecraft.getInstance().getTextureManager().release(ID); texture.close(); } - client.setScreen(this.parent); + Minecraft.getInstance().setScreen(this.parent); } @Override protected void init() { // This is called whenever the window is resized. - assert client != null; - if (this.texture == null) { Snapper.LOGGER.error("No panorama found"); - close(); + onClose(); return; } - Path panoramaPath = Path.of(client.runDirectory.getPath(), "screenshots", "panorama"); - addDrawableChild(ButtonWidget.builder(Text.translatable("button.snapper.folder"), button -> { - Util.getOperatingSystem().open(panoramaPath); - }).dimensions(width / 2 - 150 - 4, height - 32, 150, 20).build()); + Path panoramaPath = Path.of(Minecraft.getInstance().gameDirectory.getPath(), "screenshots", "panorama"); + addRenderableWidget(Button.builder(Component.translatable("button.snapper.folder"), button -> { + Util.getPlatform().openPath(panoramaPath); + }).bounds(width / 2 - 150 - 4, height - 32, 150, 20).build()); - addDrawableChild(ButtonWidget.builder( - ScreenTexts.DONE, - button -> this.close() - ).dimensions(width / 2 + 4, height - 32, 150, 20).build()); + addRenderableWidget(Button.builder( + CommonComponents.GUI_DONE, + button -> this.onClose() + ).bounds(width / 2 + 4, height - 32, 150, 20).build()); } @Override - public void render(DrawContext context, int mouseX, int mouseY, float delta) { + public void render(GuiGraphics context, int mouseX, int mouseY, float delta) { rotatingPanoramaRenderer.render(context, this.width, this.height, true); - context.drawCenteredTextWithShadow( - this.textRenderer, + context.drawCenteredString( + this.font, this.title, this.width / 2, 20, - Colors.WHITE + CommonColors.WHITE ); super.render(context, mouseX, mouseY, delta); } @Override - public void renderBackground(DrawContext context, int mouseX, int mouseY, float delta) { + public void renderBackground(GuiGraphics context, int mouseX, int mouseY, float delta) { } } 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 3cbb437..feb1ad7 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/screen/PrivacyNoticeScreen.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/screen/PrivacyNoticeScreen.java @@ -28,19 +28,17 @@ package dev.spiritstudios.snapper.gui.screen; -import dev.spiritstudios.snapper.SnapperConfig; -import dev.spiritstudios.snapper.util.uploading.AxolotlClientApi; -import net.minecraft.client.font.MultilineText; -import net.minecraft.client.gui.DrawContext; -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.gui.widget.ButtonWidget; -import net.minecraft.screen.ScreenTexts; -import net.minecraft.text.Text; -import net.minecraft.util.Util; -import net.minecraft.util.math.MathHelper; +import net.minecraft.Util; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.components.MultiLineLabel; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.CommonComponents; +import net.minecraft.network.chat.Component; +import net.minecraft.util.Mth; import java.net.URI; -import java.util.Objects; import java.util.function.Consumer; public class PrivacyNoticeScreen extends Screen { @@ -48,65 +46,61 @@ public class PrivacyNoticeScreen extends Screen { private final Screen parent; private final Consumer accepted; - private MultilineText message; + private MultiLineLabel message; public PrivacyNoticeScreen(Screen parent, Consumer accepted) { - super(Text.translatable("snapper.privacy_notice")); + super(Component.translatable("snapper.privacy_notice")); this.parent = parent; this.accepted = accepted; } @Override - public void render(DrawContext context, int mouseX, int mouseY, float delta) { + public void render(GuiGraphics context, int mouseX, int mouseY, float delta) { super.render(context, mouseX, mouseY, delta); - context.drawCenteredTextWithShadow(this.textRenderer, this.title, this.width / 2, getTitleY(), -1); - message.draw(context, MultilineText.Alignment.CENTER, width / 2, getMessageY(), 10, true, 0xFFFFFF); + context.drawCenteredString(this.font, this.title, this.width / 2, getTitleY(), -1); + message.render(context, MultiLineLabel.Align.CENTER, width / 2, getMessageY(), 10, true, 0xFFFFFF); } @Override - public Text getNarratedTitle() { - return ScreenTexts.joinSentences(super.getNarratedTitle(), - Text.translatable("snapper.privacy_notice.description")); + public Component getNarrationMessage() { + return CommonComponents.joinForNarration(super.getNarrationMessage(), + Component.translatable("snapper.privacy_notice.description")); } @Override protected void init() { - Objects.requireNonNull(client); + message = MultiLineLabel.create(Minecraft.getInstance().font, + Component.translatable("snapper.privacy_notice.description"), width - 50); - message = MultilineText.create(client.textRenderer, - Text.translatable("snapper.privacy_notice.description"), width - 50); - - int y = MathHelper.clamp(this.getMessageY() + this.getMessagesHeight() + 20, this.height / 6 + 96, this.height - 24); + int y = Mth.clamp(this.getMessageY() + this.getMessagesHeight() + 20, this.height / 6 + 96, this.height - 24); this.addButtons(y); } private void addButtons(int y) { - Objects.requireNonNull(client); - int buttonWidth = 120; - addDrawableChild(ButtonWidget.builder(Text.translatable("snapper.privacy_notice.view_terms"), buttonWidget -> - Util.getOperatingSystem().open(TERMS_URI)).dimensions(width / 2 - (buttonWidth / 2) - buttonWidth - 5, y, buttonWidth, 20).build()); + addRenderableWidget(Button.builder(Component.translatable("snapper.privacy_notice.view_terms"), buttonWidget -> + Util.getPlatform().openUri(TERMS_URI)).bounds(width / 2 - (buttonWidth / 2) - buttonWidth - 5, y, buttonWidth, 20).build()); - addDrawableChild(ButtonWidget.builder(Text.translatable("snapper.privacy_notice.accept"), buttonWidget -> { - client.setScreen(parent); - SnapperConfig.INSTANCE.termsAccepted.set(AxolotlClientApi.TermsAcceptance.ACCEPTED); - SnapperConfig.HOLDER.save(); + addRenderableWidget(Button.builder(Component.translatable("snapper.privacy_notice.accept"), buttonWidget -> { + Minecraft.getInstance().setScreen(parent); +// SnapperConfig.INSTANCE.termsStatus.set(AxolotlClientApi.TermsAcceptance.ACCEPTED); +// SnapperConfig.HOLDER.save(); accepted.accept(true); - }).dimensions(width / 2 - (buttonWidth / 2), y, buttonWidth, 20).build()); + }).bounds(width / 2 - (buttonWidth / 2), y, buttonWidth, 20).build()); - addDrawableChild(ButtonWidget.builder(Text.translatable("snapper.privacy_notice.deny"), buttonWidget -> { - client.setScreen(parent); - SnapperConfig.INSTANCE.termsAccepted.set(AxolotlClientApi.TermsAcceptance.DENIED); - SnapperConfig.HOLDER.save(); + addRenderableWidget(Button.builder(Component.translatable("snapper.privacy_notice.deny"), buttonWidget -> { + Minecraft.getInstance().setScreen(parent); +// SnapperConfig.INSTANCE.termsStatus.set(AxolotlClientApi.TermsAcceptance.DENIED); +// SnapperConfig.HOLDER.save(); accepted.accept(false); - }).dimensions(width / 2 - (buttonWidth / 2) + buttonWidth + 5, y, buttonWidth, 20).build()); + }).bounds(width / 2 - (buttonWidth / 2) + buttonWidth + 5, y, buttonWidth, 20).build()); } private int getTitleY() { int i = (this.height - this.getMessagesHeight()) / 2; - return MathHelper.clamp(i - 20 - 9, 10, 80); + return Mth.clamp(i - 20 - 9, 10, 80); } private int getMessageY() { 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 ed7494a..4997397 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/screen/ScreenshotRenameScreen.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/screen/ScreenshotRenameScreen.java @@ -1,50 +1,50 @@ package dev.spiritstudios.snapper.gui.screen; import dev.spiritstudios.snapper.util.ScreenshotActions; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.font.TextRenderer; -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.gui.widget.ButtonWidget; -import net.minecraft.client.gui.widget.TextFieldWidget; -import net.minecraft.client.gui.widget.TextWidget; -import net.minecraft.screen.ScreenTexts; -import net.minecraft.text.Text; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.components.EditBox; +import net.minecraft.client.gui.components.StringWidget; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.CommonComponents; +import net.minecraft.network.chat.Component; import java.nio.file.Path; public class ScreenshotRenameScreen extends Screen { private final Path screenshot; - private final TextFieldWidget renameInput; - private final Text RENAME_INPUT_TEXT = Text.translatable("text.snapper.rename_input"); - private final MinecraftClient client = MinecraftClient.getInstance(); - private final TextRenderer textRenderer = client.textRenderer; + private final EditBox renameInput; + private final Component RENAME_INPUT_TEXT = Component.translatable("text.snapper.rename_input"); + private final Minecraft client = Minecraft.getInstance(); + private final Font textRenderer = client.font; private final Screen parent; protected ScreenshotRenameScreen(Path screenshot, Screen parent) { - super(Text.translatable("text.snapper.rename")); + super(Component.translatable("text.snapper.rename")); this.screenshot = screenshot; - this.renameInput = new TextFieldWidget(textRenderer, 200, 20, RENAME_INPUT_TEXT); + this.renameInput = new EditBox(textRenderer, 200, 20, RENAME_INPUT_TEXT); this.parent = parent; } @Override protected void init() { - this.addDrawableChild(new TextWidget(RENAME_INPUT_TEXT, textRenderer)) - .setPosition(this.width / 2 - textRenderer.getWidth(RENAME_INPUT_TEXT) / 2, this.height / 2 - 20); + this.addRenderableWidget(new StringWidget(RENAME_INPUT_TEXT, textRenderer)) + .setPosition(this.width / 2 - textRenderer.width(RENAME_INPUT_TEXT) / 2, this.height / 2 - 20); - this.addDrawableChild(this.renameInput).setPosition(this.width / 2 - 100, this.height / 2); + this.addRenderableWidget(this.renameInput).setPosition(this.width / 2 - 100, this.height / 2); - this.renameInput.setText(this.screenshot.getFileName().toString()); + this.renameInput.setValue(this.screenshot.getFileName().toString()); this.renameInput.setMaxLength(255); - this.addDrawableChild(ButtonWidget.builder( - Text.translatable("button.snapper.rename"), - button -> this.renameScreenshot(this.renameInput.getText()) - ).dimensions(width / 2 - 150 - 4, height - 32, 150, 20).build()); - - this.addDrawableChild(ButtonWidget.builder( - ScreenTexts.CANCEL, - button -> this.close() - ).dimensions(width / 2 + 4, height - 32, 150, 20).build()); + this.addRenderableWidget(Button.builder( + Component.translatable("button.snapper.rename"), + button -> this.renameScreenshot(this.renameInput.getValue()) + ).bounds(width / 2 - 150 - 4, height - 32, 150, 20).build()); + + this.addRenderableWidget(Button.builder( + CommonComponents.GUI_CANCEL, + button -> this.onClose() + ).bounds(width / 2 + 4, height - 32, 150, 20).build()); } private void renameScreenshot(String newName) { @@ -55,7 +55,7 @@ private void renameScreenshot(String newName) { } @Override - public void close() { + public void onClose() { this.client.setScreen(this.parent); } } diff --git a/src/client/java/dev/spiritstudios/snapper/gui/screen/ScreenshotScreen.java b/src/client/java/dev/spiritstudios/snapper/gui/screen/ScreenshotScreen.java index 8f02c81..ea304c5 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/screen/ScreenshotScreen.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/screen/ScreenshotScreen.java @@ -1,5 +1,7 @@ package dev.spiritstudios.snapper.gui.screen; +import com.mojang.blaze3d.platform.InputConstants; +import com.mojang.serialization.Codec; import dev.spiritstudios.snapper.Snapper; import dev.spiritstudios.snapper.SnapperConfig; import dev.spiritstudios.snapper.gui.toast.SnapperToast; @@ -7,59 +9,63 @@ import dev.spiritstudios.snapper.util.ScreenshotActions; import dev.spiritstudios.snapper.util.SnapperUtil; import dev.spiritstudios.snapper.util.uploading.ScreenshotUploading; -import dev.spiritstudios.specter.api.config.client.RootConfigScreen; -import net.minecraft.client.gui.DrawContext; -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.gui.tooltip.Tooltip; -import net.minecraft.client.gui.widget.*; -import net.minecraft.client.input.KeyInput; -import net.minecraft.client.util.InputUtil; -import net.minecraft.screen.ScreenTexts; -import net.minecraft.text.Text; -import net.minecraft.util.Colors; -import net.minecraft.util.Identifier; -import net.minecraft.util.Util; +import net.minecraft.Util; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.components.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.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.util.StringRepresentable; import org.jetbrains.annotations.Nullable; import java.nio.file.Path; +// TODO: Config Screen. public class ScreenshotScreen extends Screen { - private static final Identifier PANORAMA_BUTTON_ICON = Snapper.id("screenshots/panorama"); - private static final Identifier PANORAMA_BUTTON_DISABLED_ICON = Snapper.id("screenshots/panorama_disabled"); + private static final ResourceLocation PANORAMA_BUTTON_ICON = Snapper.id("screenshots/panorama"); + private static final ResourceLocation PANORAMA_BUTTON_DISABLED_ICON = Snapper.id("screenshots/panorama_disabled"); - private static final Identifier SETTINGS_ICON = Snapper.id("screenshots/settings"); + private static final ResourceLocation SETTINGS_ICON = Snapper.id("screenshots/settings"); - private static final Identifier VIEW_MODE_ICON_LIST = Snapper.id("screenshots/show_list"); + private static final ResourceLocation VIEW_MODE_ICON_LIST = Snapper.id("screenshots/show_list"); - private static final Identifier VIEW_MODE_ICON_GRID = Snapper.id("screenshots/show_grid"); + private static final ResourceLocation VIEW_MODE_ICON_GRID = Snapper.id("screenshots/show_grid"); private final Screen parent; private final boolean isOffline; private ScreenshotListWidget screenshotList; - private ButtonWidget deleteButton; - private ButtonWidget renameButton; - private ButtonWidget viewButton; - private ButtonWidget copyButton; - private ButtonWidget openButton; - private ButtonWidget uploadButton; - private TextIconButtonWidget viewModeButton; + private Button deleteButton; + private Button renameButton; + private Button viewButton; + private Button copyButton; + private Button openButton; + private Button uploadButton; + private SpriteIconButton viewModeButton; private @Nullable ScreenshotListWidget.ScreenshotEntry selectedScreenshot = null; private boolean showGrid; public ScreenshotScreen(Screen parent) { - super(Text.translatable("menu.snapper.screenshot_menu")); + super(Component.translatable("menu.snapper.screenshot_menu")); this.parent = parent; - this.showGrid = SnapperConfig.INSTANCE.viewMode.get().equals(ViewMode.GRID); + this.showGrid = SnapperConfig.HOLDER.get().viewMode().equals(ViewMode.GRID); this.isOffline = SnapperUtil.isOfflineAccount(); } @Override protected void init() { - if (client == null) return; - screenshotList = this.addDrawableChild(new ScreenshotListWidget( - client, + screenshotList = this.addRenderableWidget(new ScreenshotListWidget( + Minecraft.getInstance(), width, height - 48 - 68, 48, @@ -70,30 +76,30 @@ protected void init() { int secondRowButtonWidth = 100; - ButtonWidget folderButton = addDrawableChild(ButtonWidget.builder( - Text.translatable("button.snapper.folder"), - button -> Util.getOperatingSystem().open(SnapperUtil.getConfiguredScreenshotDirectory()) + Button folderButton = addRenderableWidget(Button.builder( + Component.translatable("button.snapper.folder"), + button -> Util.getPlatform().openPath(SnapperUtil.getConfiguredScreenshotDirectory()) ).width(secondRowButtonWidth).build()); - this.openButton = addDrawableChild(ButtonWidget.builder( - Text.translatable("button.snapper.open"), + this.openButton = addRenderableWidget(Button.builder( + Component.translatable("button.snapper.open"), button -> { if (selectedScreenshot != null) { - Util.getOperatingSystem().open(selectedScreenshot.icon.getPath()); + Util.getPlatform().openPath(selectedScreenshot.icon.getPath()); } } ).width(secondRowButtonWidth).build()); - ButtonWidget doneButton = addDrawableChild(ButtonWidget.builder( - ScreenTexts.DONE, - button -> this.close() + Button doneButton = addRenderableWidget(Button.builder( + CommonComponents.GUI_DONE, + button -> this.onClose() ).width(secondRowButtonWidth).build()); int firstRowButtonWidth = 58; - this.deleteButton = addDrawableChild(ButtonWidget.builder( - Text.translatable("button.snapper.delete"), + this.deleteButton = addRenderableWidget(Button.builder( + Component.translatable("button.snapper.delete"), button -> { if (selectedScreenshot != null) { ScreenshotActions.deleteScreenshot(selectedScreenshot.icon.getPath(), this); @@ -101,17 +107,17 @@ protected void init() { } ).width(firstRowButtonWidth).build()); - this.renameButton = addDrawableChild(ButtonWidget.builder( - Text.translatable("button.snapper.rename"), + this.renameButton = addRenderableWidget(Button.builder( + Component.translatable("button.snapper.rename"), button -> { if (this.selectedScreenshot != null) { - client.setScreen(new ScreenshotRenameScreen(this.selectedScreenshot.icon.getPath(), this)); + minecraft.setScreen(new ScreenshotRenameScreen(this.selectedScreenshot.icon.getPath(), this)); } } ).width(firstRowButtonWidth).build()); - this.copyButton = addDrawableChild(ButtonWidget.builder( - Text.translatable("button.snapper.copy"), + this.copyButton = addRenderableWidget(Button.builder( + Component.translatable("button.snapper.copy"), button -> { if (selectedScreenshot != null) { Snapper.getPlatformHelper().copyScreenshot(selectedScreenshot.icon.getPath()); @@ -119,11 +125,11 @@ protected void init() { } ).width(firstRowButtonWidth).build()); - this.viewButton = addDrawableChild(ButtonWidget.builder( - Text.translatable("button.snapper.view"), + this.viewButton = addRenderableWidget(Button.builder( + Component.translatable("button.snapper.view"), button -> { if (selectedScreenshot != null) { - this.client.setScreen(new ScreenshotViewerScreen( + this.minecraft.setScreen(new ScreenshotViewerScreen( selectedScreenshot.icon, selectedScreenshot.icon.getPath(), selectedScreenshot.screenParent @@ -132,7 +138,7 @@ protected void init() { } ).width(firstRowButtonWidth).build()); - this.uploadButton = addDrawableChild(ButtonWidget.builder(Text.translatable("button.snapper.upload"), button -> { + this.uploadButton = addRenderableWidget(Button.builder(Component.translatable("button.snapper.upload"), button -> { if (selectedScreenshot == null) return; button.active = false; @@ -141,68 +147,68 @@ protected void init() { }).width(firstRowButtonWidth).build()); if (isOffline) { - this.uploadButton.setTooltip(Tooltip.of(Text.translatable("button.snapper.upload.tooltip"))); + this.uploadButton.setTooltip(Tooltip.create(Component.translatable("button.snapper.upload.tooltip"))); } - DirectionalLayoutWidget verticalButtonLayout = DirectionalLayoutWidget.vertical() + LinearLayout verticalButtonLayout = LinearLayout.vertical() .spacing(4); - AxisGridWidget firstRowWidget = verticalButtonLayout.add(new AxisGridWidget( + EqualSpacingLayout firstRowWidget = verticalButtonLayout.addChild(new EqualSpacingLayout( 308, 20, - AxisGridWidget.DisplayAxis.HORIZONTAL + EqualSpacingLayout.Orientation.HORIZONTAL )); - firstRowWidget.add(this.deleteButton); - firstRowWidget.add(this.renameButton); - firstRowWidget.add(this.copyButton); - firstRowWidget.add(this.viewButton); - firstRowWidget.add(this.uploadButton); + firstRowWidget.addChild(this.deleteButton); + firstRowWidget.addChild(this.renameButton); + firstRowWidget.addChild(this.copyButton); + firstRowWidget.addChild(this.viewButton); + firstRowWidget.addChild(this.uploadButton); - AxisGridWidget secondRowWidget = verticalButtonLayout.add(new AxisGridWidget( + EqualSpacingLayout secondRowWidget = verticalButtonLayout.addChild(new EqualSpacingLayout( 308, 20, - AxisGridWidget.DisplayAxis.HORIZONTAL + EqualSpacingLayout.Orientation.HORIZONTAL )); - secondRowWidget.add(folderButton); - secondRowWidget.add(this.openButton); - secondRowWidget.add(doneButton); + secondRowWidget.addChild(folderButton); + secondRowWidget.addChild(this.openButton); + secondRowWidget.addChild(doneButton); - verticalButtonLayout.refreshPositions(); - SimplePositioningWidget.setPos(verticalButtonLayout, 0, this.height - 66, this.width, 64); + verticalButtonLayout.arrangeElements(); + FrameLayout.centerInRectangle(verticalButtonLayout, 0, this.height - 66, this.width, 64); - TextIconButtonWidget settingsButton = addDrawableChild(TextIconButtonWidget.builder( - Text.translatable("config.snapper.snapper.title"), - button -> this.client.setScreen( - new RootConfigScreen(SnapperConfig.HOLDER, new ScreenshotScreen(this.parent))), - true - ).width(20).texture(SETTINGS_ICON, 15, 15).build()); +// SpriteIconButton settingsButton = addRenderableWidget(SpriteIconButton.builder( +// Component.translatable("config.snapper.snapper.title"), +// button -> this.minecraft.setScreen( +// new RootConfigScreen(SnapperConfig.HOLDER, new ScreenshotScreen(this.parent))), +// true +// ).width(20).sprite(SETTINGS_ICON, 15, 15).build()); - settingsButton.setPosition(width / 2 - 178, height - 32); +// settingsButton.setPosition(width / 2 - 178, height - 32); - this.viewModeButton = addDrawableChild(TextIconButtonWidget.builder( - Text.translatable("config.snapper.snapper.viewMode"), + this.viewModeButton = addRenderableWidget(SpriteIconButton.builder( + Component.translatable("config.snapper.snapper.viewMode"), button -> this.toggleGrid(), true - ).width(20).texture(showGrid ? VIEW_MODE_ICON_LIST : VIEW_MODE_ICON_GRID, 15, 15).build()); + ).width(20).sprite(showGrid ? VIEW_MODE_ICON_LIST : VIEW_MODE_ICON_GRID, 15, 15).build()); viewModeButton.setPosition(width / 2 - 178, height - 56); Path panoramaDir = SnapperUtil.getConfiguredScreenshotDirectory().resolve("panorama"); boolean hasPanorama = SnapperUtil.panoramaPresent(panoramaDir); - TextIconButtonWidget panoramaButton = addDrawableChild(TextIconButtonWidget.builder( - Text.translatable("button.snapper.screenshots"), - button -> this.client.setScreen(new PanoramaViewerScreen(Text.translatable("menu.snapper.panorama").getString(), this)), + 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).texture(hasPanorama ? PANORAMA_BUTTON_ICON : PANORAMA_BUTTON_DISABLED_ICON, 15, 15).build()); + ).width(20).sprite(hasPanorama ? PANORAMA_BUTTON_ICON : PANORAMA_BUTTON_DISABLED_ICON, 15, 15).build()); panoramaButton.active = hasPanorama; panoramaButton.setPosition(width / 2 + 158, height - 32); - panoramaButton.setTooltip(Tooltip.of(Text.translatable(hasPanorama ? + panoramaButton.setTooltip(Tooltip.create(Component.translatable(hasPanorama ? "button.snapper.panorama.tooltip" : "text.snapper.panorama_encourage"))); @@ -223,37 +229,37 @@ public void imageSelected(@Nullable ScreenshotListWidget.ScreenshotEntry screens public void toggleGrid() { screenshotList.toggleGrid(); - screenshotList.refreshScroll(); + screenshotList.refreshScrollAmount(); this.showGrid = !this.showGrid; - remove(this.viewModeButton); - this.viewModeButton = addDrawableChild(TextIconButtonWidget.builder( - Text.translatable("config.snapper.snapper.viewMode"), + removeWidget(this.viewModeButton); + this.viewModeButton = addRenderableWidget(SpriteIconButton.builder( + Component.translatable("config.snapper.snapper.viewMode"), button -> this.toggleGrid(), true - ).width(20).texture(showGrid ? VIEW_MODE_ICON_LIST : VIEW_MODE_ICON_GRID, 15, 15).build()); + ).width(20).sprite(showGrid ? VIEW_MODE_ICON_LIST : VIEW_MODE_ICON_GRID, 15, 15).build()); viewModeButton.setPosition(width / 2 - 178, height - 56); } @Override - public boolean keyPressed(KeyInput input) { - if (client == null) return false; + public boolean keyPressed(KeyEvent input) { + if (minecraft == null) return false; - if (input.key() == InputUtil.GLFW_KEY_F5) { - client.setScreen(new ScreenshotScreen(this.parent)); + if (input.key() == InputConstants.KEY_F5) { + minecraft.setScreen(new ScreenshotScreen(this.parent)); return true; } if (selectedScreenshot == null) return false; - if ((input.modifiers() & InputUtil.GLFW_MOD_CONTROL) != 0 && input.key() == InputUtil.GLFW_KEY_C) { + if ((input.modifiers() & InputConstants.MOD_CONTROL) != 0 && input.key() == InputConstants.KEY_C) { Snapper.getPlatformHelper().copyScreenshot(selectedScreenshot.icon.getPath()); - SnapperToast.push(SnapperToast.Type.SCREENSHOT, Text.translatable("toast.snapper.screenshot.copy"), null); + SnapperToast.push(SnapperToast.Type.SCREENSHOT, Component.translatable("toast.snapper.screenshot.copy"), null); return true; } - if (input.key() == InputUtil.GLFW_KEY_ENTER) { - client.setScreen(new ScreenshotViewerScreen(selectedScreenshot.icon, selectedScreenshot.icon.getPath(), this)); + if (input.key() == InputConstants.KEY_RETURN) { + minecraft.setScreen(new ScreenshotViewerScreen(selectedScreenshot.icon, selectedScreenshot.icon.getPath(), this)); return true; } @@ -261,19 +267,32 @@ public boolean keyPressed(KeyInput input) { } @Override - public void close() { - SnapperConfig.HOLDER.save(); - super.close(); + public void onClose() { +// SnapperConfig.HOLDER.save(); + super.onClose(); } @Override - public void render(DrawContext context, int mouseX, int mouseY, float delta) { - super.render(context, mouseX, mouseY, delta); - context.drawCenteredTextWithShadow(this.textRenderer, this.title, this.width / 2, 20, Colors.WHITE); + public void render(GuiGraphics conComponent, int mouseX, int mouseY, float delta) { + super.render(conComponent, mouseX, mouseY, delta); + conComponent.drawCenteredString(this.font, this.title, this.width / 2, 20, CommonColors.WHITE); } - public enum ViewMode { - LIST, - GRID + public enum ViewMode implements StringRepresentable { + LIST("list"), + GRID("grid"); + + public static final Codec CODEC = StringRepresentable.fromEnum(ViewMode::values); + + private final String name; + + ViewMode(String name) { + this.name = name; + } + + @Override + public String getSerializedName() { + return name; + } } } diff --git a/src/client/java/dev/spiritstudios/snapper/gui/screen/ScreenshotViewerScreen.java b/src/client/java/dev/spiritstudios/snapper/gui/screen/ScreenshotViewerScreen.java index 1889ad0..3a1f313 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/screen/ScreenshotViewerScreen.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/screen/ScreenshotViewerScreen.java @@ -1,25 +1,26 @@ package dev.spiritstudios.snapper.gui.screen; import dev.spiritstudios.snapper.Snapper; -import dev.spiritstudios.snapper.util.DynamicTexture; +import dev.spiritstudios.snapper.util.ScreenshotTexture; import dev.spiritstudios.snapper.util.ScreenshotActions; import dev.spiritstudios.snapper.util.SnapperUtil; import dev.spiritstudios.snapper.util.uploading.ScreenshotUploading; import net.fabricmc.loader.api.FabricLoader; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.gl.RenderPipelines; -import net.minecraft.client.gui.DrawContext; -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.client.gui.tooltip.Tooltip; -import net.minecraft.client.gui.widget.AxisGridWidget; -import net.minecraft.client.gui.widget.ButtonWidget; -import net.minecraft.client.gui.widget.DirectionalLayoutWidget; -import net.minecraft.client.gui.widget.SimplePositioningWidget; -import net.minecraft.screen.ScreenTexts; -import net.minecraft.text.Text; -import net.minecraft.util.Colors; -import net.minecraft.util.Identifier; -import net.minecraft.util.Util; +import net.minecraft.Util; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.components.Tooltip; +import net.minecraft.client.gui.layouts.EqualSpacingLayout; +import net.minecraft.client.gui.layouts.FrameLayout; +import net.minecraft.client.gui.layouts.HeaderAndFooterLayout; +import net.minecraft.client.gui.layouts.LinearLayout; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.renderer.RenderPipelines; +import net.minecraft.network.chat.CommonComponents; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.CommonColors; import org.jetbrains.annotations.Nullable; import javax.imageio.ImageIO; @@ -32,11 +33,11 @@ import java.util.List; public class ScreenshotViewerScreen extends Screen { - private static final Identifier MENU_DECOR_BACKGROUND_TEXTURE = Identifier.ofVanilla("textures/gui/menu_list_background.png"); - private static final Identifier INWORLD_MENU_DECOR_BACKGROUND_TEXTURE = Identifier.ofVanilla("textures/gui/inworld_menu_list_background.png"); + private static final ResourceLocation MENU_DECOR_BACKGROUND_TEXTURE = ResourceLocation.withDefaultNamespace("textures/gui/menu_list_background.png"); + private static final ResourceLocation INWORLD_MENU_DECOR_BACKGROUND_TEXTURE = ResourceLocation.withDefaultNamespace("textures/gui/inworld_menu_list_background.png"); - private final MinecraftClient client = MinecraftClient.getInstance(); - private final DynamicTexture image; + private final Minecraft client = Minecraft.getInstance(); + private final ScreenshotTexture image; private final String title; private final int imageWidth; private final int imageHeight; @@ -45,13 +46,14 @@ public class ScreenshotViewerScreen extends Screen { private final @Nullable List screenshots; private final int screenshotIndex; private final Path iconPath; + private final HeaderAndFooterLayout layout = new HeaderAndFooterLayout(this); - public ScreenshotViewerScreen(DynamicTexture icon, Path screenshot, Screen parent) { + public ScreenshotViewerScreen(ScreenshotTexture icon, Path screenshot, Screen parent) { this(icon, screenshot, parent, null); } - public ScreenshotViewerScreen(DynamicTexture icon, Path iconPath, Screen parent, @Nullable List screenshots) { - super(Text.translatable("menu.snapper.viewer_menu")); + public ScreenshotViewerScreen(ScreenshotTexture icon, Path iconPath, Screen parent, @Nullable List screenshots) { + super(Component.translatable("menu.snapper.viewer_menu")); this.parent = parent; this.iconPath = iconPath; @@ -77,7 +79,7 @@ public ScreenshotViewerScreen(DynamicTexture icon, Path iconPath, Screen parent, } @Override - public void close() { + public void onClose() { this.client.setScreen(this.parent); } @@ -88,9 +90,9 @@ protected void init() { // OPEN FOLDER - ButtonWidget folderButton = addDrawableChild(ButtonWidget.builder( - Text.translatable("button.snapper.folder"), - button -> Util.getOperatingSystem().open(new File(client.runDirectory, "screenshots")) + Button folderButton = addRenderableWidget(Button.builder( + Component.translatable("button.snapper.folder"), + button -> Util.getPlatform().openFile(new File(client.gameDirectory, "screenshots")) ) .width(100) .build() @@ -98,29 +100,29 @@ protected void init() { // OPEN IMAGE EXTERNALLY - ButtonWidget openButton = addDrawableChild(ButtonWidget.builder( - Text.translatable("button.snapper.open"), - button -> Util.getOperatingSystem().open(this.iconPath) + Button openButton = addRenderableWidget(Button.builder( + Component.translatable("button.snapper.open"), + button -> Util.getPlatform().openPath(this.iconPath) ).width(100).build()); // EXIT PAGE - ButtonWidget doneButton = addDrawableChild(ButtonWidget.builder( - ScreenTexts.DONE, - button -> this.close() + Button doneButton = addRenderableWidget(Button.builder( + CommonComponents.GUI_DONE, + button -> this.onClose() ).width(100).build()); // DELETE SCREENSHOT - ButtonWidget deleteButton = addDrawableChild(ButtonWidget.builder( - Text.translatable("button.snapper.delete"), + Button deleteButton = addRenderableWidget(Button.builder( + Component.translatable("button.snapper.delete"), button -> ScreenshotActions.deleteScreenshot(this.screenshot, this.parent) ).width(firstRowButtonWidth).build()); // RENAME SCREENSHOT - ButtonWidget renameButton = addDrawableChild(ButtonWidget.builder( - Text.translatable("button.snapper.rename"), + Button renameButton = addRenderableWidget(Button.builder( + Component.translatable("button.snapper.rename"), button -> { if (this.screenshot != null) client.setScreen(new ScreenshotRenameScreen(this.screenshot, this.parent)); @@ -129,15 +131,15 @@ protected void init() { // COPY SCREENSHOT - ButtonWidget copyButton = addDrawableChild(ButtonWidget.builder( - Text.translatable("button.snapper.copy"), + Button copyButton = addRenderableWidget(Button.builder( + Component.translatable("button.snapper.copy"), button -> Snapper.getPlatformHelper().copyScreenshot(this.screenshot) ).width(firstRowButtonWidth).build()); // UPLOAD SCREENSHOT - ButtonWidget uploadButton = addDrawableChild(ButtonWidget.builder( - Text.translatable("button.snapper.upload"), + Button uploadButton = addRenderableWidget(Button.builder( + Component.translatable("button.snapper.upload"), button -> { button.active = false; ScreenshotUploading.upload(iconPath).thenRun(() -> button.active = true); @@ -146,112 +148,115 @@ protected void init() { if (SnapperUtil.isOfflineAccount()) { uploadButton.active = false; - uploadButton.setTooltip(Tooltip.of(Text.translatable("button.snapper.upload.tooltip"))); + uploadButton.setTooltip(Tooltip.create(Component.translatable("button.snapper.upload.tooltip"))); } - DirectionalLayoutWidget verticalButtonLayout = DirectionalLayoutWidget.vertical().spacing(4); + LinearLayout verticalButtonLayout = LinearLayout.vertical().spacing(4); - AxisGridWidget firstRowWidget = verticalButtonLayout.add(new AxisGridWidget( + EqualSpacingLayout firstRowWidget = verticalButtonLayout.addChild(new EqualSpacingLayout( 308, 20, - AxisGridWidget.DisplayAxis.HORIZONTAL) + EqualSpacingLayout.Orientation.HORIZONTAL) ); - firstRowWidget.add(deleteButton); - firstRowWidget.add(renameButton); - firstRowWidget.add(copyButton); - firstRowWidget.add(uploadButton); + firstRowWidget.addChild(deleteButton); + firstRowWidget.addChild(renameButton); + firstRowWidget.addChild(copyButton); + firstRowWidget.addChild(uploadButton); - AxisGridWidget secondRowWidget = verticalButtonLayout.add(new AxisGridWidget( + EqualSpacingLayout secondRowWidget = verticalButtonLayout.addChild(new EqualSpacingLayout( 308, 20, - AxisGridWidget.DisplayAxis.HORIZONTAL) + EqualSpacingLayout.Orientation.HORIZONTAL) ); - secondRowWidget.add(folderButton); - secondRowWidget.add(openButton); - secondRowWidget.add(doneButton); + secondRowWidget.addChild(folderButton); + secondRowWidget.addChild(openButton); + secondRowWidget.addChild(doneButton); - verticalButtonLayout.refreshPositions(); - SimplePositioningWidget.setPos(verticalButtonLayout, 0, this.height - 66, this.width, 64); + verticalButtonLayout.arrangeElements(); + FrameLayout.centerInRectangle(verticalButtonLayout, 0, this.height - 66, this.width, 64); + + layout.setHeaderHeight(46); + layout.setFooterHeight(height - 68); } @Override - public void render(DrawContext context, int mouseX, int mouseY, float delta) { + public void render(GuiGraphics context, int mouseX, int mouseY, float delta) { super.render(context, mouseX, mouseY, delta); this.drawMenuBackground(context); this.drawHeaderAndFooterSeparators(context); - context.drawCenteredTextWithShadow(this.textRenderer, this.title, this.width / 2, 20, Colors.WHITE); + context.drawCenteredString(this.font, this.title, this.width / 2, 20, CommonColors.WHITE); - int finalHeight = this.height - 48 - 68; + int finalHeight = this.height - 50 - 68; float scaleFactor = (float) finalHeight / imageHeight; int finalWidth = (int) (imageWidth * scaleFactor); - context.drawTexture( + context.blit( RenderPipelines.GUI_TEXTURED, this.image.getTextureId(), - (this.width / 2) - (finalWidth / 2), this.height - 68 - finalHeight, + (this.width / 2) - (finalWidth / 2), this.height - 70 - finalHeight, 0, 0, finalWidth, finalHeight, finalWidth, finalHeight ); if (screenshotIndex != -1 && screenshots != null) { - context.drawCenteredTextWithShadow( - this.textRenderer, + context.drawCenteredString( + this.font, "Screenshot %d/%d".formatted(screenshotIndex + 1, screenshots.size()), this.width / 2, 30, - Colors.WHITE + CommonColors.WHITE ); } if (FabricLoader.getInstance().isDevelopmentEnvironment()) renderDebugInfo(context); } - private void renderDebugInfo(DrawContext context) { - context.getMatrices().pushMatrix(); - int finalHeight = this.height - 48 - 48; + private void renderDebugInfo(GuiGraphics context) { + context.pose().pushMatrix(); + int finalHeight = this.height - 50 - 68; float scaleFactor = (float) finalHeight / imageHeight; int finalWidth = (int) (imageWidth * scaleFactor); - context.drawCenteredTextWithShadow( - this.textRenderer, + context.drawCenteredString( + this.font, "Image Size: %dx%d".formatted(imageWidth, imageHeight), this.width / 2, 40, - Colors.WHITE + CommonColors.WHITE ); - context.drawCenteredTextWithShadow( - this.textRenderer, + context.drawCenteredString( + this.font, "Screen Size: %dx%d".formatted(this.width, this.height), this.width / 2, 50, - Colors.WHITE + CommonColors.WHITE ); - context.drawCenteredTextWithShadow(this.textRenderer, + context.drawCenteredString(this.font, "Scale Factor: %s".formatted(scaleFactor), this.width / 2, 60, - Colors.WHITE + CommonColors.WHITE ); - context.drawCenteredTextWithShadow( - this.textRenderer, + context.drawCenteredString( + this.font, "Scaled Size: %dx%d".formatted(finalWidth, finalHeight), this.width / 2, 70, - Colors.WHITE + CommonColors.WHITE ); } - private void drawMenuBackground(DrawContext context) { - context.drawTexture( + private void drawMenuBackground(GuiGraphics context) { + context.blit( RenderPipelines.GUI_TEXTURED, - this.client.world == null ? + this.client.level == null ? MENU_DECOR_BACKGROUND_TEXTURE : INWORLD_MENU_DECOR_BACKGROUND_TEXTURE, 0, @@ -265,24 +270,24 @@ private void drawMenuBackground(DrawContext context) { ); } - private void drawHeaderAndFooterSeparators(DrawContext context) { - context.drawTexture( + private void drawHeaderAndFooterSeparators(GuiGraphics context) { + context.blit( RenderPipelines.GUI_TEXTURED, - this.client.world == null ? - Screen.HEADER_SEPARATOR_TEXTURE : - Screen.INWORLD_HEADER_SEPARATOR_TEXTURE, - 0, 48 - 2, + this.client.level == null ? + Screen.HEADER_SEPARATOR : + Screen.INWORLD_HEADER_SEPARATOR, + 0, layout.getHeaderHeight(), 0, 0, width, 2, 32, 2 ); - context.drawTexture( + context.blit( RenderPipelines.GUI_TEXTURED, - this.client.world == null ? - Screen.FOOTER_SEPARATOR_TEXTURE : - Screen.INWORLD_FOOTER_SEPARATOR_TEXTURE, - 0, height - 68, + this.client.level == null ? + Screen.FOOTER_SEPARATOR : + Screen.INWORLD_FOOTER_SEPARATOR, + 0, this.layout.getFooterHeight() - 2, 0, 0, width, 2, 32, 2 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 0b9b234..c87f974 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/toast/SnapperToast.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/toast/SnapperToast.java @@ -1,47 +1,47 @@ package dev.spiritstudios.snapper.gui.toast; import dev.spiritstudios.snapper.Snapper; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.font.TextRenderer; -import net.minecraft.client.gl.RenderPipelines; -import net.minecraft.client.gui.DrawContext; -import net.minecraft.client.toast.Toast; -import net.minecraft.client.toast.ToastManager; -import net.minecraft.text.OrderedText; -import net.minecraft.text.Text; -import net.minecraft.util.Colors; -import net.minecraft.util.Identifier; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.GuiGraphics; +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.util.CommonColors; +import net.minecraft.util.FormattedCharSequence; import java.util.List; public class SnapperToast implements Toast { - private static final Identifier TEXTURE = Snapper.id("push/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 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 int VISIBILITY_DURATION = 5000; private static final int WIDTH = 256; private static final int LINE_HEIGHT = 12; private static final int PADDING = 10; private final Type type; - private final Text title; - private final List lines; + private final Component title; + private final List lines; private Visibility visibility; - public SnapperToast(Type type, Text title, Text description) { + public SnapperToast(Type type, Component title, Component description) { this.type = type; this.visibility = Visibility.HIDE; this.title = title; - MinecraftClient client = MinecraftClient.getInstance(); - TextRenderer textRenderer = client.textRenderer; - this.lines = textRenderer.wrapLines(description, WIDTH - PADDING * 3 - 16); + Minecraft minecraft = Minecraft.getInstance(); + Font textRenderer = minecraft.font; + this.lines = textRenderer.split(description, WIDTH - PADDING * 3 - 16); } - public static void push(Type type, Text title, Text description) { - MinecraftClient.getInstance().getToastManager().add( + public static void push(Type type, Component title, Component description) { + Minecraft.getInstance().getToastManager().addToast( new SnapperToast( type, title, @@ -51,11 +51,11 @@ public static void push(Type type, Text title, Text description) { } @Override - public Visibility getVisibility() { + public Visibility getWantedVisibility() { return this.visibility; } - public Visibility setVisibility(Visibility visibility) { + public Visibility setWantedVisibility(Visibility visibility) { return this.visibility = visibility; } @@ -65,26 +65,26 @@ public void update(ToastManager manager, long time) { } @Override - public void draw(DrawContext context, TextRenderer textRenderer, long startTime) { - context.drawGuiTexture(RenderPipelines.GUI_TEXTURED, TEXTURE, 0, 0, this.getWidth(), this.getHeight()); - context.drawGuiTexture(RenderPipelines.GUI_TEXTURED, getCurrentTexture(), PADDING, PADDING, 16, 16); + 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); - context.drawText(textRenderer, title, PADDING * 2 + 14, this.lines.isEmpty() ? 12 : 7, Colors.YELLOW, false); + context.drawString(font, title, PADDING * 2 + 12, this.lines.isEmpty() ? 12 : 7, CommonColors.YELLOW, false); for (int i = 0; i < this.lines.size(); ++i) { - context.drawText(textRenderer, this.lines.get(i), PADDING * 2 + 14, 18 + i * 12, Colors.WHITE, false); + context.drawString(font, this.lines.get(i), PADDING * 2 + 12, 18 + i * 12, CommonColors.WHITE, false); } } - public int getWidth() { + public int width() { return WIDTH; } - public int getHeight() { + public int height() { return PADDING * 2 + Math.max(this.lines.size(), 1) * LINE_HEIGHT; } - private Identifier getCurrentTexture() { + private ResourceLocation getCurrentComponenture() { return switch (type) { case UPLOAD -> UPLOAD_ICON; case PANORAMA -> PANORAMA_ICON; diff --git a/src/client/java/dev/spiritstudios/snapper/gui/widget/FolderSelectWidget.java b/src/client/java/dev/spiritstudios/snapper/gui/widget/FolderSelectWidget.java index 661a5a8..f94bed3 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/widget/FolderSelectWidget.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/widget/FolderSelectWidget.java @@ -3,23 +3,17 @@ import dev.spiritstudios.snapper.Snapper; import dev.spiritstudios.snapper.gui.overlay.ExternalDialogOverlay; import dev.spiritstudios.snapper.util.config.DirectoryConfigUtil; -import dev.spiritstudios.specter.api.config.Value; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.gui.Click; -import net.minecraft.client.gui.DrawContext; -import net.minecraft.client.gui.Drawable; -import net.minecraft.client.gui.ParentElement; -import net.minecraft.client.gui.screen.narration.NarrationMessageBuilder; -import net.minecraft.client.gui.tooltip.Tooltip; -import net.minecraft.client.gui.widget.ClickableWidget; -import net.minecraft.client.gui.widget.ContainerWidget; -import net.minecraft.client.gui.widget.TextFieldWidget; -import net.minecraft.client.gui.widget.TextIconButtonWidget; -import net.minecraft.screen.ScreenTexts; -import net.minecraft.text.Text; -import net.minecraft.util.Colors; -import net.minecraft.util.Formatting; -import net.minecraft.util.Identifier; +import net.minecraft.ChatFormatting; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphics; +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.util.CommonColors; import org.jetbrains.annotations.Nullable; import java.nio.file.Files; @@ -28,37 +22,37 @@ import java.util.List; import java.util.function.Consumer; -public class FolderSelectWidget extends ContainerWidget implements ParentElement { - private static final Identifier FOLDER_ICON = Snapper.id("screenshots/folder"); - private static final Identifier RESET_ICON = Snapper.id("screenshots/reset"); +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 int BUTTON_WIDTH = 25; // Includes padding - private final Value value; + private final PathFunctions value; - private final TextFieldWidget textInput; - private final TextIconButtonWidget fileDialogButton; - private final TextIconButtonWidget resetButton; + private final EditBox textInput; + private final SpriteIconButton fileDialogButton; + private final SpriteIconButton resetButton; - public FolderSelectWidget(int x, int y, int width, int height, Value value, String placeholderKey) { - super(x, y, width, height, ScreenTexts.EMPTY); - this.value = value; + public FolderSelectWidget(int x, int y, int width, int height, PathFunctions pathFunctions, String placeholderKey) { + super(x, y, width, height, CommonComponents.EMPTY); + this.value = pathFunctions; this.active = false; - MinecraftClient client = MinecraftClient.getInstance(); + Minecraft client = Minecraft.getInstance(); - this.textInput = new TextFieldWidget( - client.textRenderer, + this.textInput = new EditBox( + client.font, BUTTON_WIDTH * 2, 0, width - (BUTTON_WIDTH * 2), 20, - Text.of(value.get().toString()) + Component.literal(pathFunctions.get().toString()) ); - this.textInput.setPlaceholder(Text.translatableWithFallback(placeholderKey, "").formatted(Formatting.DARK_GRAY)); + this.textInput.setHint(Component.translatableWithFallback(placeholderKey, "").withStyle(ChatFormatting.DARK_GRAY)); this.textInput.setMaxLength(4096); // Unix maximum path length, shorter on windows (I think it may have been 240) - this.textInput.setText(value.get().toString()); - this.textInput.setChangedListener(content -> { + this.textInput.setValue(pathFunctions.get().toString()); + this.textInput.setResponder(content -> { Path path; try { @@ -68,22 +62,22 @@ public FolderSelectWidget(int x, int y, int width, int height, Value value } if (path == null || !Files.exists(path) || !Files.isDirectory(path)) { - this.textInput.setEditableColor(Colors.RED); + this.textInput.setTextColor(CommonColors.RED); } else { - this.textInput.setEditableColor(Colors.WHITE); - value.set(path); + this.textInput.setTextColor(CommonColors.WHITE); + pathFunctions.set(path); } }); - this.textInput.setTooltip(Tooltip.of(Text.translatable("config.snapper.snapper.customScreenshotFolder.input"))); + this.textInput.setTooltip(Tooltip.create(Component.translatable("config.snapper.snapper.customScreenshotFolder.input"))); - this.fileDialogButton = TextIconButtonWidget.builder( - Text.translatable("config.snapper.snapper.customScreenshotFolder.select"), + this.fileDialogButton = SpriteIconButton.builder( + Component.translatable("config.snapper.snapper.customScreenshotFolder.select"), button -> { ExternalDialogOverlay overlay = new ExternalDialogOverlay(); client.setOverlay(overlay); DirectoryConfigUtil.openFolderSelect( - Text.translatable("prompt.snapper.folder_select") + Component.translatable("prompt.snapper.folder_select") .getString() ) .thenAccept(path -> { @@ -94,24 +88,24 @@ public FolderSelectWidget(int x, int y, int width, int height, Value value true ) .width(20) - .texture(FOLDER_ICON, 15, 15) + .sprite(FOLDER_ICON, 15, 15) .build(); - this.fileDialogButton.setTooltip(Tooltip.of(Text.translatable("config.snapper.snapper.customScreenshotFolder.select"))); + this.fileDialogButton.setTooltip(Tooltip.create(Component.translatable("config.snapper.snapper.customScreenshotFolder.select"))); - this.resetButton = TextIconButtonWidget.builder( - Text.translatable("config.snapper.snapper.customScreenshotFolder.reset"), + this.resetButton = SpriteIconButton.builder( + Component.translatable("config.snapper.snapper.customScreenshotFolder.reset"), button -> { value.reset(); - textInput.setText(value.get().toString()); + textInput.setValue(value.get().toString()); }, true - ).width(20).texture(RESET_ICON, 15, 15).build(); - this.resetButton.setTooltip(Tooltip.of(Text.translatable("config.snapper.snapper.customScreenshotFolder.reset"))); + ).width(20).sprite(RESET_ICON, 15, 15).build(); + this.resetButton.setTooltip(Tooltip.create(Component.translatable("config.snapper.snapper.customScreenshotFolder.reset"))); resetButton.setX(BUTTON_WIDTH); } @Override - public List children() { + public List children() { return List.of( this.fileDialogButton, this.resetButton, this.textInput ); @@ -149,23 +143,23 @@ private void valueFromSelectDialog(@Nullable Path value) { if (Files.exists(value)) { this.value.set(value); - this.textInput.setText(this.value.get().toString()); + this.textInput.setValue(this.value.get().toString()); } } @Override - public boolean mouseClicked(Click click, boolean doubled) { + public boolean mouseClicked(MouseButtonEvent click, boolean doubled) { int clicksRan = 0; - for (ClickableWidget child : this.children()) { + for (AbstractWidget child : this.children()) { if (child.isHovered()) { clicksRan += 1; - this.playDownSound(MinecraftClient.getInstance().getSoundManager()); + this.playDownSound(Minecraft.getInstance().getSoundManager()); if (child == textInput) { textInput.setFocused(true); this.setFocused(textInput); - textInput.setFocusUnlocked(true); + textInput.setFocused(true); } child.onClick(click, doubled); @@ -176,31 +170,31 @@ public boolean mouseClicked(Click click, boolean doubled) { } @Override - protected void renderWidget(DrawContext context, int mouseX, int mouseY, float deltaTicks) { - for (Drawable drawable : this.children()) { + protected void renderWidget(GuiGraphics context, int mouseX, int mouseY, float deltaTicks) { + for (Renderable drawable : this.children()) { drawable.render(context, mouseX, mouseY, deltaTicks); } } @Override - protected void appendClickableNarrations(NarrationMessageBuilder builder) { - fileDialogButton.appendClickableNarrations(builder); - resetButton.appendClickableNarrations(builder); - textInput.appendClickableNarrations(builder); + protected void updateWidgetNarration(NarrationElementOutput builder) { + fileDialogButton.updateWidgetNarration(builder); + resetButton.updateWidgetNarration(builder); + textInput.updateWidgetNarration(builder); } @Override - public void forEachChild(Consumer consumer) { + public void visitWidgets(Consumer consumer) { this.children().forEach(consumer); } @Override - protected int getContentsHeightWithPadding() { + protected int contentHeight() { return 20; } @Override - protected double getDeltaYPerScroll() { + protected double scrollRate() { return 20 / 2f; } @@ -211,4 +205,10 @@ public boolean isMouseOver(double mouseX, double mouseY) { this.active = false; return hovered; } + + public static abstract class PathFunctions { + public abstract Path get(); + public abstract void set(Path path); + public abstract void reset(); + } } 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 dd9eeec..a5485e7 100644 --- a/src/client/java/dev/spiritstudios/snapper/gui/widget/ScreenshotListWidget.java +++ b/src/client/java/dev/spiritstudios/snapper/gui/widget/ScreenshotListWidget.java @@ -1,31 +1,30 @@ package dev.spiritstudios.snapper.gui.widget; +import com.mojang.blaze3d.platform.InputConstants; import dev.spiritstudios.snapper.Snapper; import dev.spiritstudios.snapper.SnapperConfig; import dev.spiritstudios.snapper.gui.screen.ScreenshotScreen; import dev.spiritstudios.snapper.gui.screen.ScreenshotViewerScreen; -import dev.spiritstudios.snapper.mixin.accessor.EntryListWidgetAccessor; -import dev.spiritstudios.snapper.util.DynamicTexture; +import dev.spiritstudios.snapper.mixin.accessor.AbstractSelectionListAccessor; +import dev.spiritstudios.snapper.util.ScreenshotTexture; import dev.spiritstudios.snapper.util.SafeFiles; import dev.spiritstudios.snapper.util.ScreenshotActions; import dev.spiritstudios.snapper.util.SnapperUtil; import dev.spiritstudios.specter.api.core.exception.UnreachableException; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.gl.RenderPipelines; -import net.minecraft.client.gui.Click; -import net.minecraft.client.gui.DrawContext; -import net.minecraft.client.gui.navigation.NavigationDirection; -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.KeyInput; -import net.minecraft.client.util.InputUtil; -import net.minecraft.text.Text; -import net.minecraft.util.Colors; -import net.minecraft.util.Identifier; -import net.minecraft.util.StringHelper; -import net.minecraft.util.Util; -import net.minecraft.util.math.MathHelper; +import net.minecraft.Util; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.ObjectSelectionList; +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.KeyEvent; +import net.minecraft.client.input.MouseButtonEvent; +import net.minecraft.client.renderer.RenderPipelines; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.CommonColors; +import net.minecraft.util.StringUtil; import org.jetbrains.annotations.Nullable; import java.io.IOException; @@ -43,11 +42,11 @@ import java.util.concurrent.CompletableFuture; import java.util.function.Predicate; -public class ScreenshotListWidget extends AlwaysSelectedEntryListWidget { - private static final Identifier VIEW_SPRITE = Snapper.id("screenshots/view"); - private static final Identifier VIEW_HIGHLIGHTED_SPRITE = Snapper.id("screenshots/view_highlighted"); +public class ScreenshotListWidget extends ObjectSelectionList { + private static final ResourceLocation VIEW_SPRITE = Snapper.id("screenshots/view"); + private static final ResourceLocation VIEW_HIGHLIGHTED_SPRITE = Snapper.id("screenshots/view_highlighted"); - private static final Identifier GRID_SELECTION_BACKGROUND_TEXTURE = Snapper.id("textures/gui/grid_selection_background.png"); + private static final ResourceLocation GRID_SELECTION_BACKGROUND_TEXTURE = Snapper.id("textures/gui/grid_selection_background.png"); private final Screen parent; @@ -60,7 +59,7 @@ public class ScreenshotListWidget extends AlwaysSelectedEntryListWidget> load(MinecraftClient client) { + public CompletableFuture> load(Minecraft client) { return CompletableFuture.supplyAsync(() -> { List screenshots = ScreenshotActions.getScreenshots(); return screenshots.parallelStream() - .flatMap(path -> DynamicTexture.createScreenshot(client.getTextureManager(), path).stream()) + .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); @@ -131,8 +133,8 @@ private void setEntrySelected(@Nullable ScreenshotEntry entry) { } private int getColumnCount() { - if (client.currentScreen != null) { - int width = client.currentScreen.width; + if (minecraft.screen != null) { + int width = minecraft.screen.width; if (width < 480) { return 2; @@ -151,28 +153,36 @@ public int getRowWidth() { } @Override - protected void renderList(DrawContext context, int mouseX, int mouseY, float delta) { + public void repositionEntries() { + int entryCount = this.getItemCount(); if (showGrid) { - int rowLeft = this.getRowLeft(); //FIXME MATH + int rowTop = this.getY() + 2 - (int)this.scrollAmount(); + + int rowLeft = this.getRowLeft(); int rowWidth = this.getRowWidth(); - int entryHeight = this.itemHeight - 4; + int entryHeight = this.defaultEntryHeight - 4; int entryWidth = GRID_ENTRY_WIDTH; - int entryCount = this.getEntryCount(); int spacing = (rowWidth - (getColumnCount() * entryWidth)) / (getColumnCount() - 1); for (int index = 0; index < entryCount; index++) { - int rowTop = this.getRowTop(index); - int rowBottom = this.getRowBottom(index); int colIndex = index % getColumnCount(); int leftOffset = colIndex * (entryWidth + spacing); - if (rowBottom >= this.getY() && rowTop <= this.getBottom()) { - //this.renderEntry(context, mouseX, mouseY, delta, index, rowLeft + leftOffset, rowTop, entryWidth, entryHeight); //TODO MATH - this.renderEntry(context, mouseX, mouseY, delta, this.children().get(index)); + ScreenshotListWidget.Entry entry = this.children().get(index); + entry.setY(rowTop); + entry.setX(rowLeft + leftOffset); + entry.setWidth(entryWidth); + entry.setHeight(entryHeight); + + if (colIndex == getColumnCount() - 1) { + rowTop += entry.getHeight(); } } } else { - super.renderList(context, mouseX, mouseY, delta); + super.repositionEntries(); + for (var entry : this.children()) { + entry.setHeight(defaultEntryHeight); + } } } @@ -182,58 +192,43 @@ public int getRowTop(int index) { } @Override - protected void drawSelectionHighlight(DrawContext context, Entry entry, int color) { + protected void renderSelection(GuiGraphics context, Entry entry, int color) { // let elements handle it } @Override - public int getMaxScrollY() { - int totalRows = (getEntryCount() / getColumnCount()) + (getEntryCount() % getColumnCount() > 0 ? 1 : 0); - return showGrid ? Math.max(0, totalRows * itemHeight - this.height + 4) : super.getMaxScrollY(); + public int maxScrollAmount() { + int totalColumns = (getItemCount() / getColumnCount()) + (getItemCount() % getColumnCount() > 0 ? 1 : 0); + return showGrid ? Math.max(0, totalColumns * defaultEntryHeight - defaultEntryHeight - this.height + 17) : super.maxScrollAmount(); } @Override - protected int getContentsHeightWithPadding() { - if (!this.showGrid) return super.getContentsHeightWithPadding(); - int totalRows = (getEntryCount() / getColumnCount()) + (getEntryCount() % getColumnCount() > 0 ? 1 : 0); - //return totalRows * this.itemHeight + this.headerHeight + 4; - return totalRows * this.itemHeight + 4; // TODO HEADERHEIGHT WAS KILLED + protected int contentHeight() { + if (!this.showGrid) return super.contentHeight(); + int totalRows = (getItemCount() / getColumnCount()) + (getItemCount() % getColumnCount() > 0 ? 1 : 0); + return totalRows * this.defaultEntryHeight + 4; } public void toggleGrid() { this.showGrid = !this.showGrid; - ((EntryListWidgetAccessor) this).setItemHeight(this.showGrid ? this.gridItemHeight : this.listItemHeight); + ((AbstractSelectionListAccessor) this).setDefaultEntryHeight(this.showGrid ? this.gridItemHeight : this.listItemHeight); for (var entry : this.children()) if (entry instanceof ScreenshotEntry sc) sc.setShowGrid(this.showGrid); - SnapperConfig.INSTANCE.viewMode.set(this.showGrid ? ScreenshotScreen.ViewMode.GRID : ScreenshotScreen.ViewMode.LIST); - } - - @Override - protected @Nullable Entry getEntryAtPosition(double x, double y) { - if (!showGrid) return super.getEntryAtPosition(x, y); - - int rowWidth = this.getRowWidth(); - int relX = MathHelper.floor(x - this.getRowLeft()); - //int relY = MathHelper.floor(y - (double) this.getY()) - this.headerHeight; - int relY = MathHelper.floor(y - (double) this.getY()); // FIXME HEADERHEIGHT WAS KILLED + SnapperConfig.Mutable mutable = SnapperConfig.mutable(); + mutable.viewMode = this.showGrid ? ScreenshotScreen.ViewMode.GRID : ScreenshotScreen.ViewMode.LIST; + mutable.save(); - if (relX < 0 || relX > rowWidth || relY < 0 || relY > getBottom()) return null; - - int rowIndex = (relY + (int) this.getScrollY()) / this.itemHeight; - int colIndex = MathHelper.floor(((float) relX / (float) rowWidth) * (float) getColumnCount()); - int entryIndex = rowIndex * getColumnCount() + colIndex; - - return entryIndex >= 0 && entryIndex < getEntryCount() ? this.children().get(entryIndex) : null; + repositionEntries(); } - public abstract static class Entry extends AlwaysSelectedEntryListWidget.Entry implements AutoCloseable { + public abstract static class Entry extends ObjectSelectionList.Entry implements AutoCloseable { public void close() { } } @Override - protected @Nullable Entry getNeighboringEntry(NavigationDirection direction, Predicate predicate, @Nullable Entry selected) { - if (!showGrid) return super.getNeighboringEntry(direction, predicate, selected); + protected @Nullable Entry nextEntry(ScreenDirection direction, Predicate predicate, @Nullable Entry selected) { + if (!showGrid) return super.nextEntry(direction, predicate, selected); int offset = switch (direction) { case LEFT -> -1; case RIGHT -> 1; @@ -241,10 +236,10 @@ public void close() { case DOWN -> getColumnCount(); }; - if (getEntryCount() > 0) { + if (getItemCount() > 0) { int entryIndex; if (selected == null) { - entryIndex = offset > 0 ? 0 : getEntryCount() - 1; + entryIndex = offset > 0 ? 0 : getItemCount() - 1; } else { entryIndex = this.children().indexOf(selected) + offset; } @@ -261,77 +256,77 @@ public void close() { } public static class LoadingEntry extends Entry implements AutoCloseable { - private static final Text LOADING_LIST_TEXT = Text.translatable("text.snapper.loading"); - private final MinecraftClient client; + private static final Component LOADING_LIST_TEXT = Component.translatable("text.snapper.loading"); + private final Minecraft client; - public LoadingEntry(MinecraftClient client) { + public LoadingEntry(Minecraft client) { this.client = client; } @Override - public Text getNarration() { + public Component getNarration() { return LOADING_LIST_TEXT; } @Override - public void render(DrawContext context, int mouseX, int mouseY, boolean hovered, float deltaTicks) { - if (this.client.currentScreen == null) throw new UnreachableException(); + public void renderContent(GuiGraphics context, int mouseX, int mouseY, boolean isHovering, float partialTick) { + if (this.client.screen == null) throw new UnreachableException(); - context.drawText( - this.client.textRenderer, + context.drawString( + this.client.font, LOADING_LIST_TEXT, - (this.client.currentScreen.width - this.client.textRenderer.getWidth(LOADING_LIST_TEXT)) / 2, + (this.client.screen.width - this.client.font.width(LOADING_LIST_TEXT)) / 2, getY() + (getHeight() - 9) / 2, - Colors.WHITE, + CommonColors.WHITE, false ); - String loadString = LoadingDisplay.get(Util.getMeasuringTimeMs()); + String loadString = LoadingDotsText.get(Util.getMillis()); - context.drawText( - this.client.textRenderer, + context.drawString( + this.client.font, loadString, - (this.client.currentScreen.width - this.client.textRenderer.getWidth(loadString)) / 2, + (this.client.screen.width - this.client.font.width(loadString)) / 2, getY() + (getHeight() - 9) / 2 + 9, - Colors.GRAY, + CommonColors.GRAY, false ); } } public static class EmptyEntry extends Entry implements AutoCloseable { - private static final Text EMPTY_LIST_TEXT = Text.translatable("text.snapper.empty"); - private static final Text EMPTY_CUSTOM_LIST_TEXT = Text.translatable("text.snapper.empty.custom"); - private final MinecraftClient client; + private static final Component EMPTY_LIST_TEXT = Component.translatable("text.snapper.empty"); + private static final Component EMPTY_CUSTOM_LIST_TEXT = Component.translatable("text.snapper.empty.custom"); + private final Minecraft minecraft; - public EmptyEntry(MinecraftClient client) { - this.client = client; + public EmptyEntry(Minecraft minecraft) { + this.minecraft = minecraft; } @Override - public Text getNarration() { + public Component getNarration() { return EMPTY_LIST_TEXT; } @Override - public void render(DrawContext context, int mouseX, int mouseY, boolean hovered, float deltaTicks) { - if (this.client.currentScreen == null) throw new UnreachableException(); + public void renderContent(GuiGraphics context, int mouseX, int mouseY, boolean isHovering, float partialTick) { + if (this.minecraft.screen == null) throw new UnreachableException(); - context.drawText( - this.client.textRenderer, + context.drawString( + this.minecraft.font, EMPTY_LIST_TEXT, - (this.client.currentScreen.width - this.client.textRenderer.getWidth(EMPTY_LIST_TEXT)) / 2, + (this.minecraft.screen.width - this.minecraft.font.width(EMPTY_LIST_TEXT)) / 2, getY() + getHeight() / 2, - Colors.WHITE, + CommonColors.WHITE, false ); - context.drawText( - this.client.textRenderer, + context.drawString( + this.minecraft.font, EMPTY_CUSTOM_LIST_TEXT, - (this.client.currentScreen.width - this.client.textRenderer.getWidth(EMPTY_CUSTOM_LIST_TEXT)) / 2, + (this.minecraft.screen.width - this.minecraft.font.width(EMPTY_CUSTOM_LIST_TEXT)) / 2, getY() + getHeight() / 2 + 10, - Colors.WHITE, + CommonColors.WHITE, false ); } @@ -343,8 +338,8 @@ public class ScreenshotEntry extends Entry implements AutoCloseable { .withZone(ZoneId.systemDefault()); public final FileTime lastModified; - private final MinecraftClient client; - public final DynamicTexture icon; + private final Minecraft client; + public final ScreenshotTexture icon; public final String iconFileName; public final Screen screenParent; private long time; @@ -353,7 +348,7 @@ public class ScreenshotEntry extends Entry implements AutoCloseable { private boolean clickThroughHovered = false; private final int index; - public ScreenshotEntry(DynamicTexture icon, MinecraftClient client, Screen parent, List screenshots) { + public ScreenshotEntry(ScreenshotTexture icon, Minecraft client, Screen parent, List screenshots) { this.showGrid = ScreenshotListWidget.this.showGrid; this.client = client; this.screenParent = parent; @@ -369,20 +364,20 @@ public void setShowGrid(boolean showGrid) { } private boolean safeIsSelected(Entry entry) { - @Nullable Entry nullableSelected = getSelectedOrNull(); + @Nullable Entry nullableSelected = getSelected(); return (nullableSelected != null && nullableSelected.equals(entry)); } @Override - public void render(DrawContext context, int mouseX, int mouseY, boolean hovered, float deltaTicks) { + public void renderContent(GuiGraphics context, int mouseX, int mouseY, boolean hovered, float tickDelta) { if (this.showGrid) { - renderGrid(context, mouseX, mouseY, hovered, deltaTicks); + renderGrid(context, mouseX, mouseY, hovered, tickDelta); return; } - renderList(context, mouseX, mouseY, hovered, deltaTicks); + renderList(context, mouseX, mouseY, hovered, tickDelta); } - public void renderList(DrawContext context, int mouseX, int mouseY, boolean hovered, float tickDelta) { + public void renderList(GuiGraphics context, int mouseX, int mouseY, boolean hovered, float tickDelta) { String fileName = this.iconFileName; String creationString = "undefined"; @@ -394,83 +389,83 @@ public void renderList(DrawContext context, int mouseX, int mouseY, boolean hove } if (creationTime != -1L) - creationString = Text.translatable("text.snapper.created").getString() + " " + DATE_FORMAT.format(Instant.ofEpochMilli(creationTime)); + creationString = Component.translatable("text.snapper.created").getString() + " " + DATE_FORMAT.format(Instant.ofEpochMilli(creationTime)); - if (StringHelper.isEmpty(fileName)) - fileName = Text.translatable("text.snapper.generic") + " " + (this.index + 1); + if (StringUtil.isNullOrEmpty(fileName)) + fileName = Component.translatable("text.snapper.generic") + " " + (this.index + 1); - context.drawText( - this.client.textRenderer, - truncateFileName(fileName, getWidth() - 32 - 6, 29), - getX() + 32 + 3, getY() + 1, - Colors.WHITE, + context.drawString( + this.client.font, + truncateFileName(fileName, getContentWidth() - 32 - 6, 29), + getContentX() + 32 + 3, getContentY() + 1, + CommonColors.WHITE, false ); - context.drawText( - this.client.textRenderer, + context.drawString( + this.client.font, creationString, - getX() + 35, getY() + 12, - Colors.GRAY, + getContentX() + 35, getContentY() + 12, + CommonColors.GRAY, false ); if (icon.loaded()) { - context.drawTexture( + context.blit( RenderPipelines.GUI_TEXTURED, this.icon.getTextureId(), - getX(), getY(), + getContentX(), getContentY(), (icon.getHeight()) / 3.0f + 32, 0, - getHeight(), getHeight(), + getContentHeight(), getContentHeight(), icon.getHeight(), icon.getHeight(), icon.getWidth(), icon.getHeight() ); } - if (this.client.options.getTouchscreen().getValue() || hovered) { - context.fill(getX(), getY(), getX() + 32, getY() + 32, 0xA0909090); - context.drawGuiTexture( + if (this.client.options.touchscreen().get() || hovered) { + context.fill(getContentX(), getContentY(), getContentX() + 32, getContentY() + 32, 0xA0909090); + context.blitSprite( RenderPipelines.GUI_TEXTURED, - mouseX - getX() < 32 && this.icon.loaded() ? + mouseX - getContentX() < 32 && this.icon.loaded() ? ScreenshotListWidget.VIEW_HIGHLIGHTED_SPRITE : ScreenshotListWidget.VIEW_SPRITE, - getX(), getY(), + getContentX(), getContentY(), 32, 32 ); } } - public void renderGrid(DrawContext context, int mouseX, int mouseY, boolean hovered, float tickDelta) { - int centreX = getX() + getWidth() / 2; - int centreY = getY() + getHeight() / 2; + public void renderGrid(GuiGraphics context, int mouseX, int mouseY, boolean hovered, float tickDelta) { + 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()) { - context.drawTexture( + context.blit( RenderPipelines.GUI_TEXTURED, this.icon.getTextureId(), - getX(), getY(), + getContentX(), getContentY(), 0, 0, - getWidth(), getHeight(), + getContentWidth(), getContentHeight(), icon.getWidth(), icon.getHeight(), icon.getWidth(), icon.getHeight() ); } - if (this.client.options.getTouchscreen().getValue() || (hovered && mouseX < getX() + getWidth()) || safeIsSelected(this)) { + if (this.client.options.touchscreen().get() || (hovered && mouseX < getX() + getWidth()) || safeIsSelected(this)) { renderMetadata(context, mouseX, mouseY, hovered, tickDelta); } } - public void renderMetadata(DrawContext context, int mouseX, int mouseY, boolean hovered, float deltaTick) { + public void renderMetadata(GuiGraphics context, int mouseX, int mouseY, boolean hovered, float deltaTick) { String fileName = this.iconFileName; int centreX = getX() + getWidth() / 2; int centreY = getY() + getHeight() / 2; - if (StringHelper.isEmpty(fileName)) - fileName = Text.translatable("text.snapper.generic") + " " + (this.index + 1); + if (StringUtil.isNullOrEmpty(fileName)) + fileName = Component.translatable("text.snapper.generic") + " " + (this.index + 1); String creationString = "undefined"; long creationTime = 0; @@ -483,17 +478,17 @@ public void renderMetadata(DrawContext context, int mouseX, int mouseY, boolean if (creationTime != -1L) creationString = DATE_FORMAT.format(Instant.ofEpochMilli(creationTime)); - context.drawTexture( + context.blit( RenderPipelines.GUI_TEXTURED, GRID_SELECTION_BACKGROUND_TEXTURE, - getX(), getY(), + getContentX(), getContentY(), 0, 0, - getWidth(), getHeight(), + getContentWidth(), getContentHeight(), 16, 16 ); - context.drawGuiTexture( + context.blitSprite( RenderPipelines.GUI_TEXTURED, clickThroughHovered && icon.loaded() ? ScreenshotListWidget.VIEW_HIGHLIGHTED_SPRITE : ScreenshotListWidget.VIEW_SPRITE, @@ -503,49 +498,41 @@ public void renderMetadata(DrawContext context, int mouseX, int mouseY, boolean 32 ); - context.drawText( - this.client.textRenderer, - truncateFileName(fileName, getWidth(), 24), - getX() + 5, - getY() + 6, - Colors.WHITE, + context.drawString( + this.client.font, + truncateFileName(fileName, getContentWidth(), 24), + getContentX() + 5, + getContentY() + 6, + CommonColors.WHITE, true ); - context.drawText( - this.client.textRenderer, - Text.translatable("text.snapper.created"), - getX() + 5, - getY() + getHeight() - 22, - Colors.LIGHT_GRAY, + context.drawString( + this.client.font, + Component.translatable("text.snapper.created"), + getContentX() + 5, + getContentY() + getContentHeight() - 22, + CommonColors.LIGHT_GRAY, true ); - context.drawText( - this.client.textRenderer, + context.drawString( + this.client.font, creationString, - getX() + 5, - getY() + getHeight() - 12, - Colors.LIGHT_GRAY, + getContentX() + 5, + getContentY() + getContentHeight() - 12, + CommonColors.LIGHT_GRAY, true ); } public String truncateFileName(String fileName, int maxWidth, int truncateLength) { String truncatedName = fileName; - if (this.client.textRenderer.getWidth(truncatedName) > maxWidth) + if (this.client.font.width(truncatedName) > maxWidth) truncatedName = truncatedName.substring(0, Math.min(fileName.length(), truncateLength)) + "..."; return truncatedName; } - /*@Override //FIXME BORDER KILLED - public void drawBorder(DrawContext context, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta) { - if (isSelectedEntry(index)) { - context.fill(x - 2, y - 2, x + entryWidth + 2, y + entryHeight + 2, -1); - context.fill(x - 1, y - 1, x + entryWidth + 1, y + entryHeight + 1, -16777216); - } - }*/ - @Override public void setFocused(boolean focused) { if (focused) { @@ -555,12 +542,12 @@ public void setFocused(boolean focused) { } @Override - public Text getNarration() { - return Text.literal(this.iconFileName); + public Component getNarration() { + return Component.literal(this.iconFileName); } @Override - public boolean mouseClicked(Click click, boolean doubled) { + public boolean mouseClicked(MouseButtonEvent click, boolean doubled) { ScreenshotListWidget.this.setEntrySelected(this); boolean clickThrough = @@ -572,8 +559,8 @@ public boolean mouseClicked(Click click, boolean doubled) { this.showGrid && clickThroughHovered ); - if (!clickThrough && Util.getMeasuringTimeMs() - this.time >= 250L) { - this.time = Util.getMeasuringTimeMs(); + if (!clickThrough && Util.getMillis() - this.time >= 250L) { + this.time = Util.getMillis(); return super.mouseClicked(click, doubled); } @@ -582,7 +569,7 @@ public boolean mouseClicked(Click click, boolean doubled) { public boolean click() { if (this.icon == null) return false; - playClickSound(this.client.getSoundManager()); + playButtonClickSound(this.client.getSoundManager()); this.client.setScreen(new ScreenshotViewerScreen(this.icon, icon.getPath(), this.screenParent, this.screenshots)); return true; } diff --git a/src/client/java/dev/spiritstudios/snapper/mixin/CameraMixin.java b/src/client/java/dev/spiritstudios/snapper/mixin/CameraMixin.java index 53b4020..348e132 100644 --- a/src/client/java/dev/spiritstudios/snapper/mixin/CameraMixin.java +++ b/src/client/java/dev/spiritstudios/snapper/mixin/CameraMixin.java @@ -1,9 +1,9 @@ package dev.spiritstudios.snapper.mixin; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.render.Camera; -import net.minecraft.entity.Entity; -import net.minecraft.world.BlockView; +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; @@ -11,15 +11,15 @@ @Mixin(Camera.class) public abstract class CameraMixin { - @Inject(method = "update", at = @At("HEAD"), cancellable = true) + @Inject(method = "setup", at = @At("HEAD"), cancellable = true) private void blockUpdateDuringPanoramaRender( - BlockView area, - Entity focusedEntity, - boolean thirdPerson, - boolean inverseView, - float tickDelta, + BlockGetter level, + Entity entity, + boolean detached, + boolean thirdPersonReverse, + float partialTick, CallbackInfo ci ) { - if (MinecraftClient.getInstance().gameRenderer.isRenderingPanorama() && thirdPerson) ci.cancel(); + if (Minecraft.getInstance().gameRenderer.isPanoramicMode() && detached) ci.cancel(); } } diff --git a/src/client/java/dev/spiritstudios/snapper/mixin/GameMenuMixin.java b/src/client/java/dev/spiritstudios/snapper/mixin/GameMenuMixin.java deleted file mode 100644 index 1f62a16..0000000 --- a/src/client/java/dev/spiritstudios/snapper/mixin/GameMenuMixin.java +++ /dev/null @@ -1,46 +0,0 @@ -package dev.spiritstudios.snapper.mixin; - -import dev.spiritstudios.snapper.Snapper; -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; -import net.minecraft.text.Text; -import net.minecraft.util.Identifier; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Unique; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -@Mixin(GameMenuScreen.class) -public abstract class GameMenuMixin extends Screen { - protected GameMenuMixin(Text title) { - super(title); - } - - @Unique - private static final Identifier SNAPPER_BUTTON_ICON = Snapper.id("screenshots/screenshot"); - - @Inject( - method = "initWidgets", - at = @At("TAIL") - ) - 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); - } - } -} diff --git a/src/client/java/dev/spiritstudios/snapper/mixin/InGameHudMixin.java b/src/client/java/dev/spiritstudios/snapper/mixin/InGameHudMixin.java index 2926e87..62d9477 100644 --- a/src/client/java/dev/spiritstudios/snapper/mixin/InGameHudMixin.java +++ b/src/client/java/dev/spiritstudios/snapper/mixin/InGameHudMixin.java @@ -4,27 +4,26 @@ import com.llamalad7.mixinextras.expression.Expression; import com.llamalad7.mixinextras.injector.ModifyExpressionValue; import dev.spiritstudios.snapper.gui.screen.PanoramaViewerScreen; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.gui.hud.InGameHud; -import net.minecraft.client.gui.screen.world.LevelLoadingScreen; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Gui; +import net.minecraft.client.gui.screens.LevelLoadingScreen; 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; -@Mixin(InGameHud.class) +@Mixin(Gui.class) public class InGameHudMixin { - @Shadow @Final - private MinecraftClient client; + private Minecraft minecraft; @Definition(id = "LevelLoadingScreen", type = LevelLoadingScreen.class) - @Definition(id = "client", field = "Lnet/minecraft/client/gui/hud/InGameHud;client:Lnet/minecraft/client/MinecraftClient;") - @Definition(id = "currentScreen", field = "Lnet/minecraft/client/MinecraftClient;currentScreen:Lnet/minecraft/client/gui/screen/Screen;") - @Expression("(this.client.currentScreen instanceof LevelLoadingScreen)") + @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")) private boolean cancelRenderingHudInPanoramaScreen(boolean original) { - return original || client.currentScreen instanceof PanoramaViewerScreen; + 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 new file mode 100644 index 0000000..628dfc3 --- /dev/null +++ b/src/client/java/dev/spiritstudios/snapper/mixin/KeyboardHandlerMixin.java @@ -0,0 +1,49 @@ +package dev.spiritstudios.snapper.mixin; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +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.gui.toast.SnapperToast; +import net.minecraft.client.KeyboardHandler; +import net.minecraft.client.Minecraft; +import net.minecraft.network.chat.Component; +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 java.io.File; +import java.util.function.Consumer; + +@Mixin(KeyboardHandler.class) +public abstract class KeyboardHandlerMixin { + @Shadow + @Final + 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) { + original.call( + gameDirectory, + renderTarget, + (Consumer) message -> { + // Execute on the render thread. + Minecraft.getInstance().execute(() -> { + // Lovely tree of decisions to decide what instructions make sense. <3 Lynn + String inGameDeterminedDescription = minecraft.screen == null ? "toast.snapper.screenshot.created.description" + : "toast.snapper.screenshot.created.description_in_menu"; + String copyDeterminedDescription = SnapperConfig.HOLDER.get().copyTakenScreenshot() ? + "toast.snapper.screenshot.created.description_copy" : inGameDeterminedDescription; + + SnapperToast.push( + SnapperToast.Type.SCREENSHOT, + Component.translatable("toast.snapper.screenshot.created"), + Component.translatable(copyDeterminedDescription, message, SnapperKeybindings.RECENT_SCREENSHOT_KEY.getTranslatedKeyMessage()) + ); + }); + } + ); + } +} diff --git a/src/client/java/dev/spiritstudios/snapper/mixin/KeyboardMixin.java b/src/client/java/dev/spiritstudios/snapper/mixin/KeyboardMixin.java deleted file mode 100644 index 0a012e8..0000000 --- a/src/client/java/dev/spiritstudios/snapper/mixin/KeyboardMixin.java +++ /dev/null @@ -1,38 +0,0 @@ -package dev.spiritstudios.snapper.mixin; - -import dev.spiritstudios.snapper.SnapperConfig; -import dev.spiritstudios.snapper.SnapperKeybindings; -import dev.spiritstudios.snapper.gui.toast.SnapperToast; -import net.minecraft.client.Keyboard; -import net.minecraft.client.MinecraftClient; -import net.minecraft.text.Text; -import org.spongepowered.asm.mixin.Final; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Overwrite; -import org.spongepowered.asm.mixin.Shadow; - -@Mixin(Keyboard.class) -public abstract class KeyboardMixin { - @Shadow - @Final - private MinecraftClient client; - - /** - * @author CallMeEcho & WorldWidePixel - * @reason Change message logic to show a push instead of chat message - */ - @Overwrite - private void method_1464(Text text) { - // Lovely tree of decisions to decide what instructions make sense. <3 Lynn - String inGameDeterminedDescription = client.currentScreen == null ? "push.snapper.screenshot.created.description" - : "push.snapper.screenshot.created.description_in_menu"; - String copyDeterminedDescription = SnapperConfig.INSTANCE.copyTakenScreenshot.get() ? - "push.snapper.screenshot.created.description_copy" : inGameDeterminedDescription; - - SnapperToast.push( - SnapperToast.Type.SCREENSHOT, - Text.translatable("toast.snapper.screenshot.created"), - Text.translatable(copyDeterminedDescription, text, SnapperKeybindings.RECENT_SCREENSHOT_KEY.getBoundKeyLocalizedText()) - ); - } -} diff --git a/src/client/java/dev/spiritstudios/snapper/mixin/MinecraftClientMixin.java b/src/client/java/dev/spiritstudios/snapper/mixin/MinecraftClientMixin.java deleted file mode 100644 index cec8597..0000000 --- a/src/client/java/dev/spiritstudios/snapper/mixin/MinecraftClientMixin.java +++ /dev/null @@ -1,88 +0,0 @@ -package dev.spiritstudios.snapper.mixin; - -import com.llamalad7.mixinextras.injector.wrapoperation.Operation; -import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; -import com.llamalad7.mixinextras.sugar.Share; -import com.llamalad7.mixinextras.sugar.ref.LocalFloatRef; -import dev.spiritstudios.snapper.SnapperConfig; -import dev.spiritstudios.snapper.mixin.accessor.CameraAccessor; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.RunArgs; -import net.minecraft.client.gl.Framebuffer; -import net.minecraft.client.network.ClientPlayerEntity; -import net.minecraft.client.option.GameOptions; -import net.minecraft.client.render.GameRenderer; -import net.minecraft.text.Text; -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.Constant; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.ModifyConstant; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; - -import java.io.File; -import java.util.function.Consumer; - -@Mixin(MinecraftClient.class) -public abstract class MinecraftClientMixin { - @Final - @Shadow - public static boolean IS_SYSTEM_MAC; - - @Final - @Shadow - public GameOptions options; - - @Final - @Shadow - public GameRenderer gameRenderer; - - @WrapOperation( - method = "takePanorama", - at = @At( - value = "INVOKE", - target = "Lnet/minecraft/client/util/ScreenshotRecorder;saveScreenshot(Ljava/io/File;Ljava/lang/String;Lnet/minecraft/client/gl/Framebuffer;ILjava/util/function/Consumer;)V") - ) - private void saveScreenshot(File gameDirectory, String fileName, Framebuffer framebuffer, int downscaleFactor, Consumer messageReceiver, Operation original) { - fileName = "panorama/" + fileName; - original.call(gameDirectory, fileName, framebuffer, downscaleFactor, messageReceiver); - } - - @Inject( - method = "", - at = @At("TAIL") - ) - private void init(RunArgs args, CallbackInfo ci) { - if (!IS_SYSTEM_MAC) System.setProperty("java.awt.headless", "false"); - } - - @WrapOperation( - method = "takePanorama", - at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/ClientPlayerEntity;setYaw(F)V") - ) - private void captureSetYaw(ClientPlayerEntity player, float value, Operation op, @Share("yaw")LocalFloatRef yaw) { - if (!this.options.getPerspective().isFirstPerson()) yaw.set(value); - else op.call(player, value); - } - - @WrapOperation( - method = "takePanorama", - at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/ClientPlayerEntity;setPitch(F)V") - ) - private void applyThirdPersonCameraRotation(ClientPlayerEntity player, float value, Operation op, @Share("yaw")LocalFloatRef yaw) { - if (!this.options.getPerspective().isFirstPerson()) - ((CameraAccessor) this.gameRenderer.getCamera()).invokeSetRotation(yaw.get(), value); - else op.call(player, value); - } - - @ModifyConstant( - method = "takePanorama", - constant = @Constant(intValue = 4096) - ) - private int configurablePanoramaSize(int original) { - return SnapperConfig.INSTANCE.panoramaDimensions.get().size() * 4; - } - -} diff --git a/src/client/java/dev/spiritstudios/snapper/mixin/MinecraftMixin.java b/src/client/java/dev/spiritstudios/snapper/mixin/MinecraftMixin.java new file mode 100644 index 0000000..a908d0f --- /dev/null +++ b/src/client/java/dev/spiritstudios/snapper/mixin/MinecraftMixin.java @@ -0,0 +1,85 @@ +package dev.spiritstudios.snapper.mixin; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import com.llamalad7.mixinextras.sugar.Share; +import com.llamalad7.mixinextras.sugar.ref.LocalFloatRef; +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.client.Minecraft; +import net.minecraft.client.Options; +import net.minecraft.client.main.GameConfig; +import net.minecraft.client.player.LocalPlayer; +import net.minecraft.client.renderer.GameRenderer; +import net.minecraft.network.chat.Component; +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.Constant; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.ModifyConstant; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.io.File; +import java.util.function.Consumer; + +@Mixin(Minecraft.class) +public abstract class MinecraftMixin { + @Final + @Shadow + public Options options; + + @Final + @Shadow + public GameRenderer gameRenderer; + + @WrapOperation( + method = "grabPanoramixScreenshot", + at = @At( + 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); + } + + @Inject( + method = "", + at = @At("TAIL") + ) + private void init(GameConfig args, CallbackInfo ci) { + if (!Util.getPlatform().equals(Util.OS.OSX)) System.setProperty("java.awt.headless", "false"); + } + + @WrapOperation( + method = "grabPanoramixScreenshot", + at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/LocalPlayer;setYRot(F)V") + ) + private void captureSetYaw(LocalPlayer player, float value, Operation op, @Share("yaw")LocalFloatRef yaw) { + if (!this.options.getCameraType().isFirstPerson()) yaw.set(value); + else op.call(player, value); + } + + @WrapOperation( + method = "grabPanoramixScreenshot", + at = @At(value = "INVOKE", target = "Lnet/minecraft/client/player/LocalPlayer;setYRot(F)V") + ) + private void applyThirdPersonCameraRotation(LocalPlayer player, float value, Operation op, @Share("yaw")LocalFloatRef yaw) { + if (!this.options.getCameraType().isFirstPerson()) + ((CameraAccessor) this.gameRenderer.getMainCamera()).invokeSetRotation(yaw.get(), value); + else op.call(player, value); + } + + @ModifyConstant( + method = "grabPanoramixScreenshot", + constant = @Constant(intValue = 4096) + ) + private int configurablePanoramaSize(int original) { + return SnapperConfig.HOLDER.get().panoramaDimensions().size() * 4; + } + +} diff --git a/src/client/java/dev/spiritstudios/snapper/mixin/PauseScreenMixin.java b/src/client/java/dev/spiritstudios/snapper/mixin/PauseScreenMixin.java new file mode 100644 index 0000000..a646113 --- /dev/null +++ b/src/client/java/dev/spiritstudios/snapper/mixin/PauseScreenMixin.java @@ -0,0 +1,44 @@ +package dev.spiritstudios.snapper.mixin; + +import dev.spiritstudios.snapper.Snapper; +import dev.spiritstudios.snapper.SnapperConfig; +import dev.spiritstudios.snapper.gui.screen.ScreenshotScreen; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.components.SpriteIconButton; +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 org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(PauseScreen.class) +public abstract class PauseScreenMixin extends Screen { + protected PauseScreenMixin(Component title) { + super(title); + } + + @Unique + private static final ResourceLocation SNAPPER_BUTTON_ICON = Snapper.id("screenshots/screenshot"); + + @Inject( + method = "init", + at = @At("TAIL") + ) + protected void initWidgets(CallbackInfo ci) { + if (SnapperConfig.HOLDER.get().snapperButton().showInGameMenu()) { + this.addRenderableWidget( + SpriteIconButton.builder( + Component.translatable("button.snapper.screenshots"), + button -> { + Minecraft.getInstance().setScreen(new ScreenshotScreen(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/ScreenshotRecorderMixin.java index 30bbc3d..d540207 100644 --- a/src/client/java/dev/spiritstudios/snapper/mixin/ScreenshotRecorderMixin.java +++ b/src/client/java/dev/spiritstudios/snapper/mixin/ScreenshotRecorderMixin.java @@ -2,12 +2,12 @@ import com.llamalad7.mixinextras.injector.wrapmethod.WrapMethod; 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 net.minecraft.client.gl.Framebuffer; -import net.minecraft.client.texture.NativeImage; -import net.minecraft.client.util.ScreenshotRecorder; -import net.minecraft.text.Text; +import net.minecraft.client.Screenshot; +import net.minecraft.network.chat.Component; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; @@ -19,7 +19,7 @@ import java.nio.file.Files; import java.util.function.Consumer; -@Mixin(ScreenshotRecorder.class) +@Mixin(Screenshot.class) public abstract class ScreenshotRecorderMixin { /** * @author hama @@ -28,19 +28,19 @@ public abstract class ScreenshotRecorderMixin { @SuppressWarnings("ResultOfMethodCallIgnored") @Inject( method = "method_22691", - at = @At(value = "INVOKE", target = "Lnet/minecraft/client/texture/NativeImage;writeTo(Ljava/io/File;)V") + 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 { + private static void lookBeforeYouLeap(NativeImage nativeImage, File screenshotFile, Consumer messageReceiver, CallbackInfo ci) throws IOException { screenshotFile.getParentFile().mkdirs(); screenshotFile.createNewFile(); } @Inject( method = "method_22691", - at = @At(value = "INVOKE", target = "Lnet/minecraft/text/Text;literal(Ljava/lang/String;)Lnet/minecraft/text/MutableText;", shift = At.Shift.AFTER) + 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.INSTANCE.copyTakenScreenshot.get()) { + private static void saveWrittenFileToClipboard(NativeImage nativeImage, File screenshotFile, Consumer messageReceiver, CallbackInfo ci) { + if (!screenshotFile.getAbsolutePath().contains("/panorama/") && SnapperConfig.HOLDER.get().copyTakenScreenshot()) { Snapper.getPlatformHelper().copyScreenshot(screenshotFile.toPath()); } } @@ -50,19 +50,19 @@ private static void saveWrittenFileToClipboard(NativeImage nativeImage, File scr * @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", - at = @At(value = "INVOKE", target = "Lnet/minecraft/text/Text;translatable(Ljava/lang/String;[Ljava/lang/Object;)Lnet/minecraft/text/MutableText;", ordinal = 0)) + 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 "push.snapper.screenshot.created.success"; + return "toast.snapper.screenshot.created.success"; } - @WrapMethod(method = "saveScreenshot(Ljava/io/File;Ljava/lang/String;Lnet/minecraft/client/gl/Framebuffer;ILjava/util/function/Consumer;)V") - private static void getConfiguredGameDirectory(File gameDirectory, String fileName, Framebuffer framebuffer, int downscaleFactor, Consumer messageReceiver, Operation original) { + @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) { original.call( - SnapperConfig.INSTANCE.useCustomScreenshotFolder.get() && Files.exists(SnapperConfig.INSTANCE.customScreenshotFolder.get()) ? - SnapperConfig.INSTANCE.customScreenshotFolder.get().toFile() : + SnapperConfig.HOLDER.get().customScreenshotPath().enabled() && Files.exists(SnapperConfig.HOLDER.get().customScreenshotPath().path()) ? + SnapperConfig.HOLDER.get().customScreenshotPath().path().toFile() : gameDirectory, fileName, - framebuffer, + renderTarget, downscaleFactor, messageReceiver ); diff --git a/src/client/java/dev/spiritstudios/snapper/mixin/TitleScreenMixin.java b/src/client/java/dev/spiritstudios/snapper/mixin/TitleScreenMixin.java index a1dbe7a..5416cea 100644 --- a/src/client/java/dev/spiritstudios/snapper/mixin/TitleScreenMixin.java +++ b/src/client/java/dev/spiritstudios/snapper/mixin/TitleScreenMixin.java @@ -3,11 +3,11 @@ import dev.spiritstudios.snapper.Snapper; 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; -import net.minecraft.text.Text; -import net.minecraft.util.Identifier; +import net.minecraft.client.gui.components.SpriteIconButton; +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 org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; @@ -19,9 +19,9 @@ @Mixin(TitleScreen.class) public abstract class TitleScreenMixin extends Screen { @Unique - private static final Identifier SNAPPER_BUTTON_ICON = Snapper.id("screenshots/screenshot"); + private static final ResourceLocation SNAPPER_BUTTON_ICON = Snapper.id("screenshots/screenshot"); - protected TitleScreenMixin(Text title) { + protected TitleScreenMixin(Component title) { super(title); } @@ -30,18 +30,18 @@ protected TitleScreenMixin(Text title) { at = @At("HEAD") ) protected void init(CallbackInfo ci) { - if (SnapperConfig.INSTANCE.showSnapperTitleScreen.get()) { - Objects.requireNonNull(client); + if (SnapperConfig.HOLDER.get().snapperButton().showOnTitleScreen()) { + Objects.requireNonNull(minecraft); 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))), + this.addRenderableWidget( + SpriteIconButton.builder( + Component.translatable("button.snapper.screenshots"), + button -> this.minecraft.setScreen(new ScreenshotScreen((TitleScreen) ((Object) this))), true - ).width(20).texture(SNAPPER_BUTTON_ICON, 15, 15).build() + ).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/EntryListWidgetAccessor.java b/src/client/java/dev/spiritstudios/snapper/mixin/accessor/AbstractSelectionListAccessor.java similarity index 52% rename from src/client/java/dev/spiritstudios/snapper/mixin/accessor/EntryListWidgetAccessor.java rename to src/client/java/dev/spiritstudios/snapper/mixin/accessor/AbstractSelectionListAccessor.java index 4876a00..9359141 100644 --- a/src/client/java/dev/spiritstudios/snapper/mixin/accessor/EntryListWidgetAccessor.java +++ b/src/client/java/dev/spiritstudios/snapper/mixin/accessor/AbstractSelectionListAccessor.java @@ -1,13 +1,13 @@ package dev.spiritstudios.snapper.mixin.accessor; -import net.minecraft.client.gui.widget.EntryListWidget; +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(EntryListWidget.class) -public interface EntryListWidgetAccessor { +@Mixin(AbstractSelectionList.class) +public interface AbstractSelectionListAccessor { @Mutable @Accessor - void setItemHeight(int height); + void setDefaultEntryHeight(int height); } diff --git a/src/client/java/dev/spiritstudios/snapper/mixin/accessor/CameraAccessor.java b/src/client/java/dev/spiritstudios/snapper/mixin/accessor/CameraAccessor.java index 222e6e3..162dd4c 100644 --- a/src/client/java/dev/spiritstudios/snapper/mixin/accessor/CameraAccessor.java +++ b/src/client/java/dev/spiritstudios/snapper/mixin/accessor/CameraAccessor.java @@ -1,6 +1,6 @@ package dev.spiritstudios.snapper.mixin.accessor; -import net.minecraft.client.render.Camera; +import net.minecraft.client.Camera; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.gen.Invoker; diff --git a/src/client/java/dev/spiritstudios/snapper/util/DynamicCubemapTexture.java b/src/client/java/dev/spiritstudios/snapper/util/DynamicCubemapTexture.java index a10d17a..0020af5 100644 --- a/src/client/java/dev/spiritstudios/snapper/util/DynamicCubemapTexture.java +++ b/src/client/java/dev/spiritstudios/snapper/util/DynamicCubemapTexture.java @@ -1,12 +1,12 @@ package dev.spiritstudios.snapper.util; +import com.mojang.blaze3d.platform.NativeImage; import dev.spiritstudios.snapper.Snapper; -import net.minecraft.client.resource.metadata.TextureResourceMetadata; -import net.minecraft.client.texture.CubemapTexture; -import net.minecraft.client.texture.NativeImage; -import net.minecraft.client.texture.TextureContents; -import net.minecraft.resource.ResourceManager; -import net.minecraft.util.Identifier; +import net.minecraft.client.renderer.texture.CubeMapTexture; +import net.minecraft.client.renderer.texture.TextureContents; +import net.minecraft.client.resources.metadata.texture.TextureMetadataSection; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.resources.ResourceManager; import java.io.IOException; import java.io.InputStream; @@ -14,11 +14,11 @@ import java.nio.file.Path; import java.util.Optional; -public class DynamicCubemapTexture extends CubemapTexture { +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(Identifier id, Path path) { + public DynamicCubemapTexture(ResourceLocation id, Path path) { super(id); this.path = path; } @@ -37,7 +37,7 @@ public TextureContents loadContents(ResourceManager resourceManager) throws IOEx try (InputStream panoramaStream = Files.newInputStream(path.resolve("panorama" + TEXTURE_SUFFIXES[i]))) { NativeImage panoramaImage = NativeImage.read(panoramaStream); if (panoramaImage.getWidth() != width || panoramaImage.getHeight() != height) { - Snapper.LOGGER.error("Image dimensions of panorama '{}' sides do not match: part 0 is {}x{}, but part {} is {}x{}", getId(), width, height, i, panoramaImage.getWidth(), panoramaImage.getHeight()); + Snapper.LOGGER.error("Image dimensions of panorama '{}' sides do not match: part 0 is {}x{}, but part {} is {}x{}", getTexture(), width, height, i, panoramaImage.getWidth(), panoramaImage.getHeight()); baseImage.close(); throw new IOException(); } @@ -47,12 +47,12 @@ public TextureContents loadContents(ResourceManager resourceManager) throws IOEx } baseImage.close(); - contents = new TextureContents(image, new TextureResourceMetadata(true, false)); + contents = new TextureContents(image, new TextureMetadataSection(true, false)); } return contents; } - public static Optional createPanorama(Identifier id, Path path) { + 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/DynamicTexture.java b/src/client/java/dev/spiritstudios/snapper/util/DynamicTexture.java deleted file mode 100644 index d8783f4..0000000 --- a/src/client/java/dev/spiritstudios/snapper/util/DynamicTexture.java +++ /dev/null @@ -1,95 +0,0 @@ -package dev.spiritstudios.snapper.util; - -import dev.spiritstudios.snapper.Snapper; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.texture.NativeImage; -import net.minecraft.client.texture.NativeImageBackedTexture; -import net.minecraft.client.texture.TextureManager; -import net.minecraft.util.Identifier; -import net.minecraft.util.Util; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Optional; -import java.util.concurrent.CompletableFuture; - -public class DynamicTexture implements AutoCloseable { - private static final Identifier UNKNOWN_SERVER = Identifier.ofVanilla("textures/misc/unknown_server.png"); - - private final TextureManager textureManager; - private final Identifier id; - private final Path path; - - private final NativeImage image; - private NativeImageBackedTexture texture; - - private DynamicTexture(TextureManager textureManager, Identifier id, Path path) throws IOException { - this.textureManager = textureManager; - this.id = id; - - this.path = path; - - try (InputStream stream = Files.newInputStream(path)) { - this.image = NativeImage.read(stream); - } - } - - public CompletableFuture load() { - return MinecraftClient.getInstance().submit(() -> { - this.texture = new NativeImageBackedTexture(this.id::toString, this.image); - this.textureManager.registerTexture(this.id, this.texture); - }); - } - - public static Optional createScreenshot(TextureManager textureManager, Path path) { - try { - return Optional.of(new DynamicTexture( - textureManager, - Snapper.id( - "screenshots/" + Util.replaceInvalidChars(path.getFileName().toString(), Identifier::isPathCharacterValid) + "/icon" - ), - path - )); - } catch (IOException e) { - return Optional.empty(); - } - } - - /* - * Must be called on render thread - */ - public void enableFiltering() { - this.texture.setFilter(true, true); - } - - public void destroy() { - this.textureManager.destroyTexture(this.id); - this.texture.close(); - } - - public int getWidth() { - return this.texture != null && this.texture.getImage() != null ? this.texture.getImage().getWidth() : 64; - } - - public int getHeight() { - return this.texture != null && this.texture.getImage() != null ? this.texture.getImage().getHeight() : 64; - } - - public Identifier getTextureId() { - return this.texture != null ? this.id : UNKNOWN_SERVER; - } - - public boolean loaded() { - return texture != null; - } - - public Path getPath() { - return path; - } - - public void close() { - this.destroy(); - } -} diff --git a/src/client/java/dev/spiritstudios/snapper/util/ScreenshotActions.java b/src/client/java/dev/spiritstudios/snapper/util/ScreenshotActions.java index ac78a35..f1712bc 100644 --- a/src/client/java/dev/spiritstudios/snapper/util/ScreenshotActions.java +++ b/src/client/java/dev/spiritstudios/snapper/util/ScreenshotActions.java @@ -1,12 +1,12 @@ package dev.spiritstudios.snapper.util; import dev.spiritstudios.snapper.Snapper; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.gui.screen.ConfirmScreen; -import net.minecraft.client.gui.screen.ProgressScreen; -import net.minecraft.client.gui.screen.Screen; -import net.minecraft.screen.ScreenTexts; -import net.minecraft.text.Text; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screens.ConfirmScreen; +import net.minecraft.client.gui.screens.ProgressScreen; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.CommonComponents; +import net.minecraft.network.chat.Component; import java.io.IOException; import java.nio.file.Files; @@ -20,7 +20,7 @@ public class ScreenshotActions { public static void deleteScreenshot(Path path, Screen screen) { if (!Files.exists(path)) return; - MinecraftClient client = MinecraftClient.getInstance(); + Minecraft client = Minecraft.getInstance(); client.setScreen( new ConfirmScreen( confirmed -> { @@ -34,10 +34,10 @@ public static void deleteScreenshot(Path path, Screen screen) { } client.setScreen(screen); }, - Text.translatable("text.snapper.delete_question"), - Text.translatable("text.snapper.delete_warning", path.getFileName()), - Text.translatable("button.snapper.delete"), - ScreenTexts.CANCEL + Component.translatable("text.snapper.delete_question"), + Component.translatable("text.snapper.delete_warning", path.getFileName()), + Component.translatable("button.snapper.delete"), + CommonComponents.GUI_CANCEL ) ); } diff --git a/src/client/java/dev/spiritstudios/snapper/util/ScreenshotTexture.java b/src/client/java/dev/spiritstudios/snapper/util/ScreenshotTexture.java new file mode 100644 index 0000000..3070105 --- /dev/null +++ b/src/client/java/dev/spiritstudios/snapper/util/ScreenshotTexture.java @@ -0,0 +1,95 @@ +package dev.spiritstudios.snapper.util; + +import com.mojang.blaze3d.platform.NativeImage; +import dev.spiritstudios.snapper.Snapper; +import net.minecraft.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 java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +public class ScreenshotTexture implements AutoCloseable { + private static final ResourceLocation UNKNOWN_SERVER = ResourceLocation.withDefaultNamespace("textures/misc/unknown_server.png"); + + private final TextureManager textureManager; + private final ResourceLocation id; + private final Path path; + + private final NativeImage image; + private DynamicTexture texture; + + private ScreenshotTexture(TextureManager textureManager, ResourceLocation id, Path path) throws IOException { + this.textureManager = textureManager; + this.id = id; + + 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); + }); + } + + public static Optional createScreenshot(TextureManager textureManager, Path path) { + try { + return Optional.of(new ScreenshotTexture( + textureManager, + Snapper.id( + "screenshots/" + Util.sanitizeName(path.getFileName().toString(), ResourceLocation::validPathChar) + "/icon" + ), + path + )); + } catch (IOException e) { + return Optional.empty(); + } + } + + /* + * Must be called on render thread + */ + public void enableFiltering() { + this.texture.setFilter(true, true); + } + + public void destroy() { + this.textureManager.release(this.id); + this.texture.close(); + } + + 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 ResourceLocation getTextureId() { + return this.texture != null ? this.id : UNKNOWN_SERVER; + } + + public boolean loaded() { + return texture != null; + } + + public Path getPath() { + return path; + } + + public void close() { + this.destroy(); + } +} diff --git a/src/client/java/dev/spiritstudios/snapper/util/SnapperUtil.java b/src/client/java/dev/spiritstudios/snapper/util/SnapperUtil.java index c94e702..644c3e5 100644 --- a/src/client/java/dev/spiritstudios/snapper/util/SnapperUtil.java +++ b/src/client/java/dev/spiritstudios/snapper/util/SnapperUtil.java @@ -1,20 +1,24 @@ package dev.spiritstudios.snapper.util; +import com.mojang.serialization.Codec; +import com.mojang.serialization.DataResult; import dev.spiritstudios.snapper.Snapper; import dev.spiritstudios.snapper.SnapperConfig; -import net.minecraft.client.MinecraftClient; -import net.minecraft.util.Util; +import net.minecraft.Util; +import net.minecraft.client.Minecraft; import org.apache.commons.lang3.SystemProperties; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Arrays; +import java.util.stream.Collectors; public final class SnapperUtil { // Helper things. Please order alphabetically. <3 Lynn public static Path getConfiguredScreenshotDirectory() { - if (SnapperConfig.INSTANCE.useCustomScreenshotFolder.get()) { - Path customPath = SnapperConfig.INSTANCE.customScreenshotFolder.get().resolve("screenshots"); + if (SnapperConfig.HOLDER.get().customScreenshotPath().enabled()) { + Path customPath = SnapperConfig.HOLDER.get().customScreenshotPath().path().resolve("screenshots"); if (!SafeFiles.createDirectories(customPath)) { Snapper.LOGGER.error("Failed to create directories of configured custom screenshot folder"); @@ -23,7 +27,7 @@ public static Path getConfiguredScreenshotDirectory() { return customPath; } - return MinecraftClient.getInstance().runDirectory.toPath().resolve("screenshots"); + return Minecraft.getInstance().gameDirectory.toPath().resolve("screenshots"); } public static boolean inBoundingBox(int x, int y, int w, int h, double mouseX, double mouseY) { @@ -31,7 +35,7 @@ public static boolean inBoundingBox(int x, int y, int w, int h, double mouseX, d } public static boolean isOfflineAccount() { - return MinecraftClient.getInstance().getSession().getAccessToken().length() < 400; + return Minecraft.getInstance().getUser().getAccessToken().length() < 400; } public static boolean panoramaPresent(Path path) { @@ -47,6 +51,20 @@ public static boolean panoramaPresent(Path path) { public enum PanoramaSize { ONE_THOUSAND_TWENTY_FOUR(1024), TWO_THOUSAND_FORTY_EIGHT(2048), FOUR_THOUSAND_NINETY_SIX(4096); + public static final Codec CODEC = Codec.INT.comapFlatMap( + i -> { + for (PanoramaSize size : PanoramaSize.values()) { + if (i == size.size) { + return DataResult.success(size); + } + } + return DataResult.error(() -> "Invalid panorama size, must be one of " + Arrays.stream(PanoramaSize.values()) + .map(panoramaSize -> Integer.toString(panoramaSize.size)) + .collect(Collectors.joining(",")) + ); + }, + PanoramaSize::size + ); private final int size; PanoramaSize(int size) { @@ -58,7 +76,7 @@ public int size() { } } - public static final Path UNIFIED_FOLDER = switch (Util.getOperatingSystem()) { + public static final Path UNIFIED_FOLDER = switch (Util.getPlatform()) { case WINDOWS -> Path.of(System.getenv("APPDATA"), ".snapper"); case OSX -> Path.of(SystemProperties.getUserHome(), "Library", "Application Support", "snapper"); default -> Path.of(SystemProperties.getUserHome(), ".snapper"); diff --git a/src/client/java/dev/spiritstudios/snapper/util/config/DirectoryConfigUtil.java b/src/client/java/dev/spiritstudios/snapper/util/config/DirectoryConfigUtil.java index b663628..81c9fd3 100644 --- a/src/client/java/dev/spiritstudios/snapper/util/config/DirectoryConfigUtil.java +++ b/src/client/java/dev/spiritstudios/snapper/util/config/DirectoryConfigUtil.java @@ -3,9 +3,8 @@ import com.mojang.serialization.Codec; import com.mojang.serialization.DataResult; import dev.spiritstudios.snapper.gui.widget.FolderSelectWidget; -import dev.spiritstudios.specter.api.config.Value; import joptsimple.internal.Strings; -import net.minecraft.client.gui.widget.ClickableWidget; +import net.minecraft.client.gui.components.AbstractWidget; import org.apache.commons.lang3.SystemProperties; import org.lwjgl.util.tinyfd.TinyFileDialogs; @@ -48,11 +47,11 @@ public static CompletableFuture> openFolderSelect(String title) { }); } - public static final BiFunction, String, ? extends ClickableWidget> PATH_WIDGET_FACTORY = (configValue, id) -> { - @SuppressWarnings("unchecked") Value value = (Value) configValue; - - return new FolderSelectWidget(0, 0, 10, 10, value, "%s.placeholder".formatted(configValue.translationKey(id))); - }; +// public static final BiFunction, String, ? extends AbstractWidget> PATH_WIDGET_FACTORY = (configValue, id) -> { +// @SuppressWarnings("unchecked") Value value = (Value) configValue; +// +// return new FolderSelectWidget(0, 0, 10, 10, value, "%s.placeholder".formatted(configValue.translationKey(id))); +// }; public static String escapePath(String path) { return path.replace("\\", "\\\\"); diff --git a/src/client/java/dev/spiritstudios/snapper/util/uploading/AxolotlAuthentication.java b/src/client/java/dev/spiritstudios/snapper/util/uploading/AxolotlAuthentication.java index 3f17cff..09c27eb 100644 --- a/src/client/java/dev/spiritstudios/snapper/util/uploading/AxolotlAuthentication.java +++ b/src/client/java/dev/spiritstudios/snapper/util/uploading/AxolotlAuthentication.java @@ -2,14 +2,14 @@ import com.mojang.serialization.Codec; import com.mojang.serialization.codecs.RecordCodecBuilder; -import net.minecraft.util.Uuids; +import net.minecraft.core.UUIDUtil; import java.util.UUID; public record AxolotlAuthentication(String username, UUID uuid, String accessToken) { public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( Codec.STRING.fieldOf("username").forGetter(AxolotlAuthentication::username), - Uuids.CODEC.fieldOf("uuid").forGetter(AxolotlAuthentication::uuid), + UUIDUtil.CODEC.fieldOf("uuid").forGetter(AxolotlAuthentication::uuid), Codec.STRING.fieldOf("access_token").forGetter(AxolotlAuthentication::accessToken) ).apply(instance, AxolotlAuthentication::new)); } 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 bdde7b0..56307ca 100644 --- a/src/client/java/dev/spiritstudios/snapper/util/uploading/AxolotlClientApi.java +++ b/src/client/java/dev/spiritstudios/snapper/util/uploading/AxolotlClientApi.java @@ -3,17 +3,20 @@ import com.google.gson.JsonParser; import com.mojang.authlib.exceptions.AuthenticationException; import com.mojang.authlib.minecraft.MinecraftSessionService; +import com.mojang.serialization.Codec; import com.mojang.serialization.JsonOps; import dev.spiritstudios.snapper.Snapper; import dev.spiritstudios.snapper.SnapperConfig; import dev.spiritstudios.snapper.gui.toast.SnapperToast; -import net.minecraft.client.MinecraftClient; -import net.minecraft.client.session.Session; -import net.minecraft.text.Text; -import net.minecraft.util.Util; +import net.minecraft.Util; +import net.minecraft.client.Minecraft; +import net.minecraft.client.User; +import net.minecraft.network.chat.Component; +import net.minecraft.util.StringRepresentable; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.lang3.RandomStringUtils; import org.jetbrains.annotations.Nullable; +import org.jspecify.annotations.NonNull; import java.io.Closeable; import java.io.IOException; @@ -31,17 +34,30 @@ import java.util.concurrent.CompletableFuture; public class AxolotlClientApi implements Closeable { - public enum TermsAcceptance { - ACCEPTED, - DENIED, - UNSET + public enum TermsAcceptance implements StringRepresentable { + ACCEPTED("accepted"), + DENIED("denied"), + UNSET("unset"); + + public static final Codec CODEC = StringRepresentable.fromEnum(TermsAcceptance::values); + + private final String name; + + TermsAcceptance(String name) { + this.name = name; + } + + @Override + public @NonNull String getSerializedName() { + return name; + } } private static final String BASE_URL = "https://api.axolotlclient.com/v1/"; private final HttpClient client = HttpClient.newBuilder() .followRedirects(HttpClient.Redirect.NORMAL) - .executor(Util.getIoWorkerExecutor()) + .executor(Util.ioPool()) .build(); private Instant authTime = Instant.EPOCH; @@ -56,8 +72,8 @@ public CompletableFuture uploadImage(Path image) { } SnapperToast.push(SnapperToast.Type.UPLOAD, - Text.translatable("toast.snapper.upload.in_progress"), - Text.translatable("toast.snapper.upload.in_progress.description")); + Component.translatable("toast.snapper.upload.in_progress"), + Component.translatable("toast.snapper.upload.in_progress.description")); return authenticate() .thenCompose(ignored -> post("image/" + image.getFileName().toString(), bytes)) @@ -75,17 +91,17 @@ private CompletableFuture authenticate() { if (authTime.plus(24, ChronoUnit.HOURS).isAfter(Instant.now())) return CompletableFuture.completedFuture(null); - Session session = MinecraftClient.getInstance().getSession(); - MinecraftSessionService sessionService = MinecraftClient.getInstance().getApiServices().sessionService(); + User session = Minecraft.getInstance().getUser(); + MinecraftSessionService sessionService = Minecraft.getInstance().services().sessionService(); String serverId = new BigInteger(DigestUtils.sha1(RandomStringUtils.insecure().next(40).getBytes(StandardCharsets.UTF_8))).toString(16); try { - sessionService.joinServer(session.getUuidOrNull(), session.getAccessToken(), serverId); + sessionService.joinServer(session.getProfileId(), session.getAccessToken(), serverId); } catch (AuthenticationException e) { return CompletableFuture.failedFuture(e); } - return this.get("authenticate", Map.of("username", session.getUsername(), "server_id", serverId)) + return this.get("authenticate", Map.of("username", session.getName(), "server_id", serverId)) .thenApply(response -> AxolotlAuthentication.CODEC.parse(JsonOps.INSTANCE, JsonParser.parseString(response.body())) .getOrThrow()) @@ -104,7 +120,7 @@ public CompletableFuture> post(String route, byte[] rawBody } private CompletableFuture> request(String route, Map query, byte[] rawBody, String method) { - if (SnapperConfig.INSTANCE.termsAccepted.get() != TermsAcceptance.ACCEPTED) + if (SnapperConfig.HOLDER.get().axolotlClient().termsStatus() != TermsAcceptance.ACCEPTED) return CompletableFuture.failedFuture(new IllegalStateException("Terms not accepted")); StringBuilder url = new StringBuilder(BASE_URL); diff --git a/src/client/java/dev/spiritstudios/snapper/util/uploading/ScreenshotUploading.java b/src/client/java/dev/spiritstudios/snapper/util/uploading/ScreenshotUploading.java index 7b8f372..c66fb1e 100644 --- a/src/client/java/dev/spiritstudios/snapper/util/uploading/ScreenshotUploading.java +++ b/src/client/java/dev/spiritstudios/snapper/util/uploading/ScreenshotUploading.java @@ -6,8 +6,8 @@ import dev.spiritstudios.snapper.gui.toast.SnapperToast; import dev.spiritstudios.snapper.util.SnapperUtil; import net.fabricmc.loader.api.FabricLoader; -import net.minecraft.client.MinecraftClient; -import net.minecraft.text.Text; +import net.minecraft.client.Minecraft; +import net.minecraft.network.chat.Component; import java.nio.file.Path; import java.util.concurrent.CompletableFuture; @@ -24,28 +24,28 @@ public static CompletableFuture upload(Path image) { if (SnapperUtil.isOfflineAccount()) { SnapperToast.push( SnapperToast.Type.DENY, - Text.translatable("toast.snapper.upload.axolotlclient.api_disabled"), - Text.translatable("toast.snapper.upload.offline") + Component.translatable("toast.snapper.upload.axolotlclient.api_disabled"), + Component.translatable("toast.snapper.upload.offline") ); return CompletableFuture.failedFuture(new IllegalStateException("Minecraft is currently running in offline mode.")); } - if (SnapperConfig.INSTANCE.termsAccepted.get() == AxolotlClientApi.TermsAcceptance.UNSET) { - MinecraftClient client = MinecraftClient.getInstance(); + if (SnapperConfig.HOLDER.get().axolotlClient().termsStatus() == AxolotlClientApi.TermsAcceptance.UNSET) { + Minecraft client = Minecraft.getInstance(); CompletableFuture success = new CompletableFuture<>(); - client.setScreen(new PrivacyNoticeScreen(client.currentScreen, v -> { + client.setScreen(new PrivacyNoticeScreen(client.screen, v -> { if (v) upload(image).thenAccept(success::complete); })); return success; } - if (SnapperConfig.INSTANCE.termsAccepted.get() != AxolotlClientApi.TermsAcceptance.ACCEPTED) { + if (SnapperConfig.HOLDER.get().axolotlClient().termsStatus() != AxolotlClientApi.TermsAcceptance.ACCEPTED) { SnapperToast.push( SnapperToast.Type.UPLOAD, - Text.translatable("toast.snapper.upload.failure"), - Text.translatable("toast.snapper.upload.axolotlclient.api_disabled") + Component.translatable("toast.snapper.upload.failure"), + Component.translatable("toast.snapper.upload.axolotlclient.api_disabled") ); return CompletableFuture.failedFuture(new IllegalStateException("AxolotlClient API is disabled.")); } @@ -59,22 +59,22 @@ private static void imageUploaded(String imageId) { if (imageId == null) { SnapperToast.push( SnapperToast.Type.DENY, - Text.translatable("toast.snapper.upload.failure"), - Text.translatable("toast.snapper.upload.failure.generic") + Component.translatable("toast.snapper.upload.failure"), + Component.translatable("toast.snapper.upload.failure.generic") ); return; } - MinecraftClient client = MinecraftClient.getInstance(); + Minecraft client = Minecraft.getInstance(); String snapperUrl = SNAPPER_WEB_URL.formatted(imageId); Snapper.LOGGER.info("Uploaded screenshot to: {}", snapperUrl); - client.keyboard.setClipboard(snapperUrl); + client.keyboardHandler.setClipboard(snapperUrl); SnapperToast.push( SnapperToast.Type.UPLOAD, - Text.translatable("toast.snapper.upload.success"), - Text.translatable("toast.snapper.upload.success.description", snapperUrl) + Component.translatable("toast.snapper.upload.success"), + Component.translatable("toast.snapper.upload.success.description", snapperUrl) ); } diff --git a/src/client/resources/snapper.mixins.json b/src/client/resources/snapper.mixins.json index 4330c5b..6f84cad 100644 --- a/src/client/resources/snapper.mixins.json +++ b/src/client/resources/snapper.mixins.json @@ -4,14 +4,14 @@ "compatibilityLevel": "JAVA_21", "client": [ "CameraMixin", - "GameMenuMixin", - "KeyboardMixin", + "PauseScreenMixin", + "KeyboardHandlerMixin", "InGameHudMixin", - "MinecraftClientMixin", + "MinecraftMixin", "ScreenshotRecorderMixin", "TitleScreenMixin", "accessor.CameraAccessor", - "accessor.EntryListWidgetAccessor" + "accessor.AbstractSelectionListAccessor" ], "injectors": { "defaultRequire": 1 diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index 07c52b5..cfcc53f 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -9,6 +9,9 @@ "CallMeEcho", "hama" ], + "contributors": [ + "ChrysanthCow" + ], "contact": { "homepage": "https://spiritstudios.dev", "sources": "https://github.com/SpiritGameStudios/Snapper", @@ -31,7 +34,7 @@ "minecraft": "${minecraft_version}", "fabric-api": "*", "java": ">=21", - "specter-config": "*" + "greenhouseconfig": ">=3.0.0-" }, "custom": { "modmenu": { diff --git a/src/main/resources/snapper.accesswidener b/src/main/resources/snapper.accesswidener index 66736c7..a13cb7b 100644 --- a/src/main/resources/snapper.accesswidener +++ b/src/main/resources/snapper.accesswidener @@ -1,2 +1,4 @@ accessWidener v2 named -extendable method net/minecraft/client/gui/widget/EntryListWidget getEntryAtPosition (DD)Lnet/minecraft/client/gui/widget/EntryListWidget$Entry; \ No newline at end of file +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