diff --git a/src/main/java/moe/caa/fabric/quitconfirm/client/config/Config.java b/src/main/java/moe/caa/fabric/quitconfirm/client/config/Config.java index dbe73f5..c743e5e 100644 --- a/src/main/java/moe/caa/fabric/quitconfirm/client/config/Config.java +++ b/src/main/java/moe/caa/fabric/quitconfirm/client/config/Config.java @@ -1,18 +1,23 @@ package moe.caa.fabric.quitconfirm.client.config; import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; import moe.caa.fabric.quitconfirm.client.main.QuitConfirm; +import moe.caa.fabric.quitconfirm.client.screen.confirm.style.BaseStyle; +import moe.caa.fabric.quitconfirm.client.screen.confirm.style.ClassicStyle; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; +import java.util.function.Supplier; public class Config { - public static Config config = new Config(); + public static final Config config = new Config(); private static final Gson gson = new Gson(); private static final Path path = Path.of("config/quitconfirm.json"); - public ConfirmTypeEnum confirmTypeInFinalQuit = ConfirmTypeEnum.SCREEN; public ConfirmTypeEnum confirmTypeInSinglePlayer = ConfirmTypeEnum.TOAST; public ConfirmTypeEnum confirmTypeInMultiplayer = ConfirmTypeEnum.TOAST; @@ -29,9 +34,38 @@ public static void load() { save(); return; } - config = gson.fromJson(Files.readString(path), Config.class); + JsonElement element = JsonParser.parseString(Files.readString(path)); + JsonObject object = element.getAsJsonObject(); + if (object.has("confirmTypeInFinalQuit")) { + config.confirmTypeInFinalQuit = ConfirmTypeEnum.valueOf(object.getAsJsonPrimitive("confirmTypeInFinalQuit").getAsString()); + } + if (object.has("confirmTypeInSinglePlayer")) { + config.confirmTypeInSinglePlayer = ConfirmTypeEnum.valueOf(object.getAsJsonPrimitive("confirmTypeInSinglePlayer").getAsString()); + } + if (object.has("confirmTypeInMultiplayer")) { + config.confirmTypeInMultiplayer = ConfirmTypeEnum.valueOf(object.getAsJsonPrimitive("confirmTypeInMultiplayer").getAsString()); + } + if (object.has("enableScreenShortcutKey")) { + config.enableScreenShortcutKey = object.getAsJsonPrimitive("enableScreenShortcutKey").getAsBoolean(); + } + if (object.has("keepDarkInConfirmScreenTime")) { + config.keepDarkInConfirmScreenTime = object.getAsJsonPrimitive("keepDarkInConfirmScreenTime").getAsLong(); + } + if (object.has("confirmScreenDisplayType")) { + config.confirmScreenDisplayType = ConfirmScreenDisplayTypeEnum.valueOf(object.getAsJsonPrimitive("confirmScreenDisplayType").getAsString()); + } + if (object.has("toastConfirmDisplayTime")) { + config.toastConfirmDisplayTime = object.getAsJsonPrimitive("toastConfirmDisplayTime").getAsLong(); + } + if (object.has("toastConfirmStartAliveTime")) { + config.toastConfirmStartAliveTime = object.getAsJsonPrimitive("toastConfirmStartAliveTime").getAsLong(); + } + if (object.has("toastConfirmEndAliveTime")) { + config.toastConfirmEndAliveTime = object.getAsJsonPrimitive("toastConfirmEndAliveTime").getAsLong(); + } } catch (Exception e){ QuitConfirm.LOGGER.error("Failed to read " + path, e); + save(); } } @@ -50,13 +84,14 @@ public > T nextEnum(Class tClass, T value) { } public enum ConfirmScreenDisplayTypeEnum { - CLASSIC("经典"), - BEDROCK("基岩"), - BEDROCK_OPAQUE("基岩(不透明)"); + CLASSIC("经典", ClassicStyle::new), + BEDROCK("基岩", ClassicStyle::new); public final String displayName; + public final Supplier baseStyleSupplier; - ConfirmScreenDisplayTypeEnum(String displayName) { + ConfirmScreenDisplayTypeEnum(String displayName, Supplier baseStyleSupplier) { this.displayName = displayName; + this.baseStyleSupplier = baseStyleSupplier; } } diff --git a/src/main/java/moe/caa/fabric/quitconfirm/client/main/QuitConfirm.java b/src/main/java/moe/caa/fabric/quitconfirm/client/main/QuitConfirm.java index 8f87cb0..ed667bf 100644 --- a/src/main/java/moe/caa/fabric/quitconfirm/client/main/QuitConfirm.java +++ b/src/main/java/moe/caa/fabric/quitconfirm/client/main/QuitConfirm.java @@ -5,15 +5,16 @@ import moe.caa.fabric.quitconfirm.client.event.ClientScheduleStopEvent; import moe.caa.fabric.quitconfirm.client.event.EventResult; import moe.caa.fabric.quitconfirm.client.handle.ToastQuitHandler; +import moe.caa.fabric.quitconfirm.client.screen.confirm.ConfirmScreen; import net.fabricmc.api.ClientModInitializer; import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.screen.GameMenuScreen; +import net.minecraft.text.Text; import net.minecraft.text.TranslatableTextContent; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - public class QuitConfirm implements ClientModInitializer { public static final Logger LOGGER = LoggerFactory.getLogger("QuitConfirm"); private final ToastQuitHandler toastInFinalQuitHandler = new ToastQuitHandler("退出这个游戏,请再次操作"); @@ -28,7 +29,8 @@ public void onInitializeClient() { return toastInFinalQuitHandler.trigger(); } if (Config.config.confirmTypeInFinalQuit == Config.ConfirmTypeEnum.SCREEN) { - return toastInFinalQuitHandler.trigger(); + MinecraftClient.getInstance().setScreen(new ConfirmScreen(MinecraftClient.getInstance().currentScreen, Text.literal("退出这个游戏"), () -> MinecraftClient.getInstance().scheduleStop())); + return EventResult.CANCEL; } return EventResult.PASS; }); @@ -45,10 +47,24 @@ public void onInitializeClient() { key = ((TranslatableTextContent) button.getMessage().getContent()).getKey(); } if ("menu.returnToMenu".equals(key)) { - return toastInSinglePlayerQuitHandle.trigger(); + if (Config.config.confirmTypeInSinglePlayer == Config.ConfirmTypeEnum.TOAST) { + return toastInSinglePlayerQuitHandle.trigger(); + } + if (Config.config.confirmTypeInSinglePlayer == Config.ConfirmTypeEnum.SCREEN) { + MinecraftClient.getInstance().setScreen(new ConfirmScreen(MinecraftClient.getInstance().currentScreen, Text.literal("退出单人游戏"), button::onPress)); + return EventResult.CANCEL; + } + return EventResult.PASS; } if ("menu.disconnect".equals(key)) { - return toastInMultiplayerQuitHandle.trigger(); + if (Config.config.confirmTypeInMultiplayer == Config.ConfirmTypeEnum.TOAST) { + return toastInMultiplayerQuitHandle.trigger(); + } + if (Config.config.confirmTypeInMultiplayer == Config.ConfirmTypeEnum.SCREEN) { + MinecraftClient.getInstance().setScreen(new ConfirmScreen(MinecraftClient.getInstance().currentScreen, Text.literal("退出多人游戏"), button::onPress)); + return EventResult.CANCEL; + } + return EventResult.PASS; } return EventResult.PASS; }); diff --git a/src/main/java/moe/caa/fabric/quitconfirm/client/mixin/MixinButtonWidget.java b/src/main/java/moe/caa/fabric/quitconfirm/client/mixin/MixinButtonWidget.java index f1484d3..123a280 100644 --- a/src/main/java/moe/caa/fabric/quitconfirm/client/mixin/MixinButtonWidget.java +++ b/src/main/java/moe/caa/fabric/quitconfirm/client/mixin/MixinButtonWidget.java @@ -3,6 +3,8 @@ import moe.caa.fabric.quitconfirm.client.event.ButtonPressEvent; import moe.caa.fabric.quitconfirm.client.event.EventResult; +import moe.caa.fabric.quitconfirm.client.screen.confirm.ConfirmScreen; +import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.widget.ButtonWidget; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; @@ -13,6 +15,7 @@ public class MixinButtonWidget { @Inject(method = "onPress", at = @At("HEAD"), cancellable = true) private void onOnPress(CallbackInfo ci) { + if (MinecraftClient.getInstance().currentScreen instanceof ConfirmScreen) return; EventResult result = ButtonPressEvent.BUTTON_PRESS.invoker().onPress((ButtonWidget) (Object) this); if(result == EventResult.CANCEL){ ci.cancel(); diff --git a/src/main/java/moe/caa/fabric/quitconfirm/client/mixin/MixinMinecraftClient.java b/src/main/java/moe/caa/fabric/quitconfirm/client/mixin/MixinMinecraftClient.java index b0f4770..4ec5e4a 100644 --- a/src/main/java/moe/caa/fabric/quitconfirm/client/mixin/MixinMinecraftClient.java +++ b/src/main/java/moe/caa/fabric/quitconfirm/client/mixin/MixinMinecraftClient.java @@ -2,8 +2,11 @@ import moe.caa.fabric.quitconfirm.client.event.ClientScheduleStopEvent; import moe.caa.fabric.quitconfirm.client.event.EventResult; +import moe.caa.fabric.quitconfirm.client.screen.confirm.ConfirmScreen; import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.util.Window; +import org.jetbrains.annotations.Nullable; import org.lwjgl.glfw.GLFW; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; @@ -14,12 +17,18 @@ @Mixin(MinecraftClient.class) public class MixinMinecraftClient { - @Shadow @Final private Window window; + @Shadow + @Nullable + public Screen currentScreen; + @Shadow + @Final + private Window window; @Inject(method = "scheduleStop", at = @At("HEAD"), cancellable = true) - private void onScheduleStop(CallbackInfo ci){ + private void onScheduleStop(CallbackInfo ci) { + if (currentScreen instanceof ConfirmScreen) return; EventResult result = ClientScheduleStopEvent.CLIENT_SCHEDULE_STOP.invoker().onScheduleStop(); - if(result == EventResult.CANCEL){ + if (result == EventResult.CANCEL) { GLFW.glfwSetWindowShouldClose(this.window.getHandle(), false); ci.cancel(); } diff --git a/src/main/java/moe/caa/fabric/quitconfirm/client/screen/confirm/ConfirmScreen.java b/src/main/java/moe/caa/fabric/quitconfirm/client/screen/confirm/ConfirmScreen.java new file mode 100644 index 0000000..d4d6f0f --- /dev/null +++ b/src/main/java/moe/caa/fabric/quitconfirm/client/screen/confirm/ConfirmScreen.java @@ -0,0 +1,72 @@ +package moe.caa.fabric.quitconfirm.client.screen.confirm; + +import moe.caa.fabric.quitconfirm.client.config.Config; +import moe.caa.fabric.quitconfirm.client.screen.confirm.style.BaseStyle; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.widget.ButtonWidget; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.text.Text; + +public class ConfirmScreen extends Screen { + private final Text message; + private final ButtonWidget.PressAction onCancel; + private final ButtonWidget.PressAction onConfirm; + private final long openTime; + private final BaseStyle style = Config.config.confirmScreenDisplayType.baseStyleSupplier.get(); + private ButtonWidget cancel; + private ButtonWidget confirm; + + public ConfirmScreen(Screen parentScreen, Text message, Runnable confirm) { + this(parentScreen, message, button -> confirm.run()); + } + + public ConfirmScreen(Screen parentScreen, Text message, ButtonWidget.PressAction confirm) { + super(Text.literal("你确定?")); + openTime = System.currentTimeMillis(); + this.message = message; + onCancel = (buttonWidget) -> this.client.setScreen(parentScreen); + onConfirm = confirm; + } + + @Override + protected void init() { + initButton(); + } + + @Override + public void tick() { + if (openTime + Config.config.keepDarkInConfirmScreenTime < System.currentTimeMillis()) { + cancel.active = true; + confirm.active = true; + } + } + + private void initButton() { + confirm = style.generateConfirmButtons(this, onConfirm); + cancel = style.generateCancelButtons(this, onCancel); + + confirm.active = false; + cancel.active = false; + this.addDrawableChild(confirm); + this.addDrawableChild(cancel); + } + + @Override + public void render(MatrixStack matrices, int mouseX, int mouseY, float delta) { + style.render(this.client, this.textRenderer, this, title, message, matrices, mouseX, mouseY, delta); + super.render(matrices, mouseX, mouseY, delta); + } + + @Override + public boolean keyPressed(int keyCode, int scanCode, int modifiers) { + if (Config.config.enableScreenShortcutKey && keyCode == 257 /* ENTER */) { + onConfirm.onPress(confirm); + } + return super.keyPressed(keyCode, scanCode, modifiers); + } + + @Override + public boolean shouldCloseOnEsc() { + return Config.config.enableScreenShortcutKey; + } +} \ No newline at end of file diff --git a/src/main/java/moe/caa/fabric/quitconfirm/client/screen/confirm/style/BaseStyle.java b/src/main/java/moe/caa/fabric/quitconfirm/client/screen/confirm/style/BaseStyle.java new file mode 100644 index 0000000..fd63163 --- /dev/null +++ b/src/main/java/moe/caa/fabric/quitconfirm/client/screen/confirm/style/BaseStyle.java @@ -0,0 +1,20 @@ +package moe.caa.fabric.quitconfirm.client.screen.confirm.style; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.font.TextRenderer; +import net.minecraft.client.gui.DrawableHelper; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.widget.ButtonWidget; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.text.Text; + +public abstract class BaseStyle extends DrawableHelper { + + + public abstract ButtonWidget generateConfirmButtons(Screen screen, ButtonWidget.PressAction onConfirm); + + public abstract ButtonWidget generateCancelButtons(Screen screen, ButtonWidget.PressAction onCancel); + + public abstract void render(MinecraftClient client, TextRenderer textRenderer, Screen screen, Text title, Text message, + MatrixStack matrices, int mouseX, int mouseY, float delta); +} \ No newline at end of file diff --git a/src/main/java/moe/caa/fabric/quitconfirm/client/screen/confirm/style/ClassicStyle.java b/src/main/java/moe/caa/fabric/quitconfirm/client/screen/confirm/style/ClassicStyle.java new file mode 100644 index 0000000..990e879 --- /dev/null +++ b/src/main/java/moe/caa/fabric/quitconfirm/client/screen/confirm/style/ClassicStyle.java @@ -0,0 +1,65 @@ +package moe.caa.fabric.quitconfirm.client.screen.confirm.style; + +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.util.math.MatrixStack; +import net.minecraft.screen.ScreenTexts; +import net.minecraft.text.Text; + +public class ClassicStyle extends BaseStyle { + // 按钮宽度 + private static final int buttonWidth = 150; + // 按钮长度 + private static final int buttonHeight = 20; + // 按钮间隔 + private static final int buttonFMargin = 10; + // 按钮下边距 + private static final int buttonBMargin = 40; + // 标题上边距 + private static final int titleTMargin = 30; + + @Override + public ButtonWidget generateConfirmButtons(Screen screen, ButtonWidget.PressAction onConfirm) { + return ButtonWidget.builder(ScreenTexts.YES, onConfirm) + .dimensions(screen.width / 2 - buttonWidth - buttonFMargin, + screen.height - buttonHeight - buttonBMargin, + buttonWidth, buttonHeight).build(); + } + + @Override + public ButtonWidget generateCancelButtons(Screen screen, ButtonWidget.PressAction onCancel) { + return ButtonWidget.builder(ScreenTexts.NO, onCancel) + .dimensions(screen.width / 2 + buttonFMargin, + screen.height - buttonHeight - buttonBMargin, + buttonWidth, buttonHeight).build(); + } + + @Override + public void render(MinecraftClient client, TextRenderer textRenderer, Screen screen, Text title, Text message, + MatrixStack matrices, int mouseX, int mouseY, float delta) { + this.renderBackground(matrices, screen); + drawTextAndMessage(textRenderer, screen, title, message, matrices); + } + + public void renderBackground(MatrixStack matrices, Screen screen) { + if (MinecraftClient.getInstance().world != null) { + fillGradient(matrices, 0, 0, screen.width, screen.height, -1072689136, -804253680); + } else { + screen.renderBackgroundTexture(matrices); + } + } + + private void drawTextAndMessage(TextRenderer textRenderer, Screen screen, Text title, Text message, MatrixStack matrices) { + drawCenteredTextWithShadow(matrices, textRenderer, title, + screen.width / 2, + titleTMargin, + 16777215); + + drawCenteredTextWithShadow(matrices, textRenderer, message, + screen.width / 2, + screen.height / 2 - 30, + 10526880); + } +} \ No newline at end of file