diff --git a/fabric-screen-api-v1/src/client/java/net/fabricmc/fabric/api/client/screen/v1/ScreenEvents.java b/fabric-screen-api-v1/src/client/java/net/fabricmc/fabric/api/client/screen/v1/ScreenEvents.java index 726354e630..84490d912d 100644 --- a/fabric-screen-api-v1/src/client/java/net/fabricmc/fabric/api/client/screen/v1/ScreenEvents.java +++ b/fabric-screen-api-v1/src/client/java/net/fabricmc/fabric/api/client/screen/v1/ScreenEvents.java @@ -18,6 +18,8 @@ import java.util.Objects; +import org.jetbrains.annotations.Nullable; + import net.minecraft.client.MinecraftClient; import net.minecraft.client.gui.DrawContext; import net.minecraft.client.gui.screen.Screen; @@ -43,6 +45,26 @@ */ public final class ScreenEvents { + /** + * Called just before a new screen is set to {@link net.minecraft.client.MinecraftClient#currentScreen} in + * {@link net.minecraft.client.MinecraftClient#setScreen(Screen)}, allows for exchanging the new screen with a + * different one, or can prevent a new screen from opening, by returning the original screen. + * + *

Note that the old screen has already been removed by calling {@link Screen#removed()}, and the new screen + * will always be initialized via {@link Screen#init(MinecraftClient, int, int)}. + */ + public static final Event OPEN = EventFactory.createArrayBacked(Open.class, + callbacks -> (oldScreen, newScreen) -> { + for (Open callback : callbacks) { + Screen screen = callback.onOpen(oldScreen, newScreen); + + if (screen != newScreen) { + return screen; + } + } + + return newScreen; + }); /** * An event that is called before {@link Screen#init(MinecraftClient, int, int) a screen is initialized} to its default state. * It should be noted some methods in {@link Screens} such as a screen's {@link Screens#getTextRenderer(Screen) text renderer} may not be initialized yet, and as such their use is discouraged. @@ -159,6 +181,19 @@ public static Event afterTick(Screen screen) { return ScreenExtensions.getExtensions(screen).fabric_getAfterTickEvent(); } + @FunctionalInterface + public interface Open { + /** + * @param oldScreen the screen that is being removed, which may be {@code null} when opening the screen from + * {@link net.minecraft.client.gui.hud.InGameHud}, like + * {@link net.minecraft.client.gui.screen.GameMenuScreen} + * @param newScreen the new screen that is being set, which may be {@code null} when closing a screen and returning + * to the in-game hud + * @return the screen to be opened, by default the new screen + */ + @Nullable Screen onOpen(@Nullable Screen oldScreen, @Nullable Screen newScreen); + } + @FunctionalInterface public interface BeforeInit { void beforeInit(MinecraftClient client, Screen screen, int scaledWidth, int scaledHeight); diff --git a/fabric-screen-api-v1/src/client/java/net/fabricmc/fabric/mixin/screen/MinecraftClientMixin.java b/fabric-screen-api-v1/src/client/java/net/fabricmc/fabric/mixin/screen/MinecraftClientMixin.java index 4370746954..ef3802634f 100644 --- a/fabric-screen-api-v1/src/client/java/net/fabricmc/fabric/mixin/screen/MinecraftClientMixin.java +++ b/fabric-screen-api-v1/src/client/java/net/fabricmc/fabric/mixin/screen/MinecraftClientMixin.java @@ -24,6 +24,8 @@ 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.ModifyVariable; +import org.spongepowered.asm.mixin.injection.Slice; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import net.minecraft.client.MinecraftClient; @@ -61,6 +63,11 @@ private void onScreenRemove(@Nullable Screen screen, CallbackInfo ci) { ScreenEvents.remove(this.currentScreen).invoker().onRemove(this.currentScreen); } + @ModifyVariable(method = "setScreen", at = @At(value = "LOAD", ordinal = 0), slice = @Slice(from = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/ClientPlayerEntity;requestRespawn()V"))) + public @Nullable Screen onScreenOpen(@Nullable Screen screen) { + return ScreenEvents.OPEN.invoker().onOpen(this.currentScreen, screen); + } + @Inject(method = "stop", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/Screen;removed()V", shift = At.Shift.AFTER)) private void onScreenRemoveBecauseStopping(CallbackInfo ci) { ScreenEvents.remove(this.currentScreen).invoker().onRemove(this.currentScreen); diff --git a/fabric-screen-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/screen/ScreenTests.java b/fabric-screen-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/screen/ScreenTests.java index e242836e28..db1e267c6d 100644 --- a/fabric-screen-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/screen/ScreenTests.java +++ b/fabric-screen-api-v1/src/testmodClient/java/net/fabricmc/fabric/test/screen/ScreenTests.java @@ -26,6 +26,9 @@ import net.minecraft.client.gui.screen.Screen; import net.minecraft.client.gui.screen.TitleScreen; import net.minecraft.client.gui.screen.ingame.CreativeInventoryScreen; +import net.minecraft.client.gui.screen.option.ControlsOptionsScreen; +import net.minecraft.client.gui.screen.option.KeybindsScreen; +import net.minecraft.client.gui.screen.option.OptionsScreen; import net.minecraft.client.gui.widget.ButtonWidget; import net.minecraft.client.gui.widget.ClickableWidget; import net.minecraft.text.Text; @@ -48,6 +51,16 @@ public void onInitializeClient() { }); ScreenEvents.AFTER_INIT.register(this::afterInitScreen); + + // skip the control options screen and go directly to keybindings when coming from the main options screen + ScreenEvents.OPEN.register((oldScreen, newScreen) -> { + if (oldScreen instanceof OptionsScreen && newScreen instanceof ControlsOptionsScreen) { + // the new screen does not have a client instance set yet + return new KeybindsScreen(newScreen, Screens.getClient(oldScreen).options); + } else { + return newScreen; + } + }); } private void afterInitScreen(MinecraftClient client, Screen screen, int windowWidth, int windowHeight) {