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) {