From 9af2c302f9ce68ea22dced98962d3e1c206bb745 Mon Sep 17 00:00:00 2001
From: i509VCB <git@i509.me>
Date: Mon, 25 Jan 2021 12:17:17 -0600
Subject: [PATCH] Screen API v1 (#645)

* Implement screen api

* Actually update to 20w27a

* Split render and tick events to before and after

* Rename and update

* A bit of javadoc and profiler name fixes

* Add functional interface annotations, move render and ticking events to actual FabricScreen instance.

* Add after screen resize event

* Implement key and mouse click/press and release events.

* Move keyboard and mouse events to their own pojos

* Init and resize are the same thing. Maybe Screen#init needs a rename

* Add mouse scroll events

* checkstyle

* Refresh event instances after init

* Before init is nessecary to listen to addition/removal of child elements

* Polish up the javadoc and do a rename to the public api interface.

* Mappings updates on testmod

* javadoc formatting again

* Rework screen api design to be more ergonomic.

* Add remove event, some javadoc

* Add allow phase

* Module dependencies

* Fix null ticking when no screen is open

* Refer to GLFW constants in mouse click/release events

* Keyboard event GLFW constant javadoc

* Remove redundant qualifier

* Some docs, degetterifying

* Because global go brr add screen params back around

* Add module lifecycle to FMJ

(cherry picked from commit 8e23c1d877bafbf6c12607be5c8450b4824be329)
---
 fabric-screen-api-v1/build.gradle             |   6 +
 .../api/client/screen/v1/ScreenEvents.java    | 209 ++++++++++++++
 .../screen/v1/ScreenKeyboardEvents.java       | 203 +++++++++++++
 .../client/screen/v1/ScreenMouseEvents.java   | 271 ++++++++++++++++++
 .../fabric/api/client/screen/v1/Screens.java  |  83 ++++++
 .../api/client/screen/v1/package-info.java    |  30 ++
 .../fabric/impl/client/screen/ButtonList.java | 104 +++++++
 .../client/screen/ScreenEventFactory.java     | 221 ++++++++++++++
 .../impl/client/screen/ScreenExtensions.java  |  86 ++++++
 .../mixin/screen/GameRendererMixin.java       |  62 ++++
 .../fabric/mixin/screen/KeyboardMixin.java    |  73 +++++
 .../mixin/screen/MinecraftClientMixin.java    |  84 ++++++
 .../fabric/mixin/screen/MouseMixin.java       | 141 +++++++++
 .../fabric/mixin/screen/ScreenAccessor.java   |  37 +++
 .../fabric/mixin/screen/ScreenMixin.java      | 248 ++++++++++++++++
 .../assets/fabric-screen-api-v1/icon.png      | Bin 0 -> 1579 bytes
 .../fabric-screen-api-v1.mixins.json          |  18 ++
 .../src/main/resources/fabric.mod.json        |  29 ++
 .../fabric/test/screen/ScreenTests.java       |  96 +++++++
 .../fabric/test/screen/SoundButton.java       |  47 +++
 .../fabric/test/screen/StopSoundButton.java   |  52 ++++
 .../src/testmod/resources/fabric.mod.json     |  16 ++
 settings.gradle                               |   1 +
 23 files changed, 2117 insertions(+)
 create mode 100644 fabric-screen-api-v1/build.gradle
 create mode 100644 fabric-screen-api-v1/src/main/java/net/fabricmc/fabric/api/client/screen/v1/ScreenEvents.java
 create mode 100644 fabric-screen-api-v1/src/main/java/net/fabricmc/fabric/api/client/screen/v1/ScreenKeyboardEvents.java
 create mode 100644 fabric-screen-api-v1/src/main/java/net/fabricmc/fabric/api/client/screen/v1/ScreenMouseEvents.java
 create mode 100644 fabric-screen-api-v1/src/main/java/net/fabricmc/fabric/api/client/screen/v1/Screens.java
 create mode 100644 fabric-screen-api-v1/src/main/java/net/fabricmc/fabric/api/client/screen/v1/package-info.java
 create mode 100644 fabric-screen-api-v1/src/main/java/net/fabricmc/fabric/impl/client/screen/ButtonList.java
 create mode 100644 fabric-screen-api-v1/src/main/java/net/fabricmc/fabric/impl/client/screen/ScreenEventFactory.java
 create mode 100644 fabric-screen-api-v1/src/main/java/net/fabricmc/fabric/impl/client/screen/ScreenExtensions.java
 create mode 100644 fabric-screen-api-v1/src/main/java/net/fabricmc/fabric/mixin/screen/GameRendererMixin.java
 create mode 100644 fabric-screen-api-v1/src/main/java/net/fabricmc/fabric/mixin/screen/KeyboardMixin.java
 create mode 100644 fabric-screen-api-v1/src/main/java/net/fabricmc/fabric/mixin/screen/MinecraftClientMixin.java
 create mode 100644 fabric-screen-api-v1/src/main/java/net/fabricmc/fabric/mixin/screen/MouseMixin.java
 create mode 100644 fabric-screen-api-v1/src/main/java/net/fabricmc/fabric/mixin/screen/ScreenAccessor.java
 create mode 100644 fabric-screen-api-v1/src/main/java/net/fabricmc/fabric/mixin/screen/ScreenMixin.java
 create mode 100644 fabric-screen-api-v1/src/main/resources/assets/fabric-screen-api-v1/icon.png
 create mode 100644 fabric-screen-api-v1/src/main/resources/fabric-screen-api-v1.mixins.json
 create mode 100644 fabric-screen-api-v1/src/main/resources/fabric.mod.json
 create mode 100644 fabric-screen-api-v1/src/testmod/java/net/fabricmc/fabric/test/screen/ScreenTests.java
 create mode 100644 fabric-screen-api-v1/src/testmod/java/net/fabricmc/fabric/test/screen/SoundButton.java
 create mode 100644 fabric-screen-api-v1/src/testmod/java/net/fabricmc/fabric/test/screen/StopSoundButton.java
 create mode 100644 fabric-screen-api-v1/src/testmod/resources/fabric.mod.json

diff --git a/fabric-screen-api-v1/build.gradle b/fabric-screen-api-v1/build.gradle
new file mode 100644
index 000000000..314e8930f
--- /dev/null
+++ b/fabric-screen-api-v1/build.gradle
@@ -0,0 +1,6 @@
+archivesBaseName = "fabric-screen-api-v1"
+version = getSubprojectVersion(project, "1.0.0")
+
+moduleDependencies(project, [
+		'fabric-api-base'
+])
diff --git a/fabric-screen-api-v1/src/main/java/net/fabricmc/fabric/api/client/screen/v1/ScreenEvents.java b/fabric-screen-api-v1/src/main/java/net/fabricmc/fabric/api/client/screen/v1/ScreenEvents.java
new file mode 100644
index 000000000..4acd331cb
--- /dev/null
+++ b/fabric-screen-api-v1/src/main/java/net/fabricmc/fabric/api/client/screen/v1/ScreenEvents.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (c) 2016, 2017, 2018, 2019 FabricMC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.fabricmc.fabric.api.client.screen.v1;
+
+import java.util.Objects;
+
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gui.screen.Screen;
+import net.minecraft.client.gui.screen.TickableElement;
+import net.minecraft.client.util.math.MatrixStack;
+
+import net.fabricmc.api.EnvType;
+import net.fabricmc.api.Environment;
+import net.fabricmc.fabric.api.event.Event;
+import net.fabricmc.fabric.api.event.EventFactory;
+import net.fabricmc.fabric.impl.client.screen.ScreenExtensions;
+
+/**
+ * Holds events related to {@link Screen}s.
+ *
+ * <p>Some events require a screen instance in order to obtain an event instance.
+ * The events that require a screen instance can be identified by the use of a method passing a screen instance.
+ * All events in {@link ScreenKeyboardEvents} and {@link ScreenMouseEvents} require a screen instance.
+ * This registration model is used since a screen being (re)initialized will reset the screen to it's default state, therefore reverting all changes a mod developer may have applied to a screen.
+ * Furthermore this design was chosen to reduce the amount of wasted iterations of events as a mod developer would only need to register screen events for rendering, ticking, keyboards and mice if needed on a per instance basis.
+ *
+ * <p>The primary entrypoint into a screen is when it is being opened, this is signified by an event {@link ScreenEvents#BEFORE_INIT before} and {@link ScreenEvents#AFTER_INIT after} initialization of the screen.
+ *
+ * @see Screens
+ * @see ScreenKeyboardEvents
+ * @see ScreenMouseEvents
+ */
+@Environment(EnvType.CLIENT)
+public final class ScreenEvents {
+	/**
+	 * An event that is called before {@link Screen#init(MinecraftClient, int, int) a screen is initialized} to it's default state.
+	 * It should be noted some of the 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.
+	 *
+	 * <!--<p>Typically this event is used to register screen events such as listening to when child elements are added to the screen. ------ Uncomment when child add/remove event is added for elements-->
+	 * You can still use {@link ScreenEvents#AFTER_INIT} to register events such as keyboard and mouse events.
+	 *
+	 * <p>The {@link ScreenExtensions} provided by the {@code info} parameter may be used to register tick, render events, keyboard, mouse, additional and removal of child elements (including buttons).
+	 * For example, to register an event on inventory like screens after render, the following code could be used:
+	 * <pre>{@code
+	 * &#64;Override
+	 * public void onInitializeClient() {
+	 * 	ScreenEvents.BEFORE_INIT.register((client, screen, scaledWidth, scaledHeight) -> {
+	 * 		if (screen instanceof AbstractInventoryScreen) {
+	 * 			ScreenEvents.getAfterRenderEvent(screen).register((matrices, mouseX, mouseY, tickDelta) -> {
+	 * 				...
+	 * 			});
+	 * 		}
+	 * 	});
+	 * }
+	 * }</pre>
+	 *
+	 * <p>This event indicates a screen has been resized, and therefore is being re-initialized.
+	 * This event can also indicate that the previous screen has been changed.
+	 * @see ScreenEvents#AFTER_INIT
+	 */
+	public static final Event<BeforeInit> BEFORE_INIT = EventFactory.createArrayBacked(BeforeInit.class, callbacks -> (client, screen, scaledWidth, scaledHeight) -> {
+		for (BeforeInit callback : callbacks) {
+			callback.beforeInit(client, screen, scaledWidth, scaledHeight);
+		}
+	});
+
+	/**
+	 * An event that is called after {@link Screen#init(MinecraftClient, int, int) a screen is initialized} to it's default state.
+	 *
+	 * <p>Typically this event is used to modify a screen after the screen has been initialized.
+	 * Modifications such as changing sizes of buttons, removing buttons and adding/removing child elements to the screen can be done safely using this event.
+	 *
+	 * <p>For example, to add a button to the title screen, the following code could be used:
+	 * <pre>{@code
+	 * ScreenEvents.AFTER_INIT.register((client, screen, scaledWidth, scaledHeight) -> {
+	 * 	if (screen instanceof TitleScreen) {
+	 * 		Screens.getButtons(screen).add(new ButtonWidget(...));
+	 * 	}
+	 * });
+	 * }</pre>
+	 *
+	 * <p>Note that by adding an element to a screen, the element is not automatically {@link net.minecraft.client.gui.screen.TickableElement ticked} or {@link net.minecraft.client.gui.Drawable drawn}.
+	 * Unless the element is button, you need to call the specific {@link TickableElement#tick() tick} and {@link net.minecraft.client.gui.Drawable#render(MatrixStack, int, int, float) render} methods in the corresponding screen events.
+	 *
+	 * <p>This event can also indicate that the previous screen has been closed.
+	 * @see ScreenEvents#BEFORE_INIT
+	 */
+	public static final Event<AfterInit> AFTER_INIT = EventFactory.createArrayBacked(AfterInit.class, callbacks -> (client, screen, scaledWidth, scaledHeight) -> {
+		for (AfterInit callback : callbacks) {
+			callback.afterInit(client, screen, scaledWidth, scaledHeight);
+		}
+	});
+
+	/**
+	 * An event that is called after {@link Screen#removed()} is called.
+	 * This event signifies that the screen is now closed.
+	 *
+	 * <p>This event is typically used to undo any screen specific state changes such as setting the keyboard to receive {@link net.minecraft.client.Keyboard#setRepeatEvents(boolean) repeat events} or terminate threads spawned by a screen.
+	 * This event may precede initialization events {@link ScreenEvents#BEFORE_INIT} but there is no guarantee that event will be called immediately afterwards.
+	 */
+	public static Event<Remove> remove(Screen screen) {
+		Objects.requireNonNull(screen, "Screen cannot be null");
+
+		return ScreenExtensions.getExtensions(screen).fabric_getRemoveEvent();
+	}
+
+	/**
+	 * An event that is called before a screen is rendered.
+	 *
+	 * @return the event
+	 */
+	public static Event<BeforeRender> beforeRender(Screen screen) {
+		Objects.requireNonNull(screen, "Screen cannot be null");
+
+		return ScreenExtensions.getExtensions(screen).fabric_getBeforeRenderEvent();
+	}
+
+	/**
+	 * An event that is called after a screen is rendered.
+	 *
+	 * @return the event
+	 */
+	public static Event<AfterRender> afterRender(Screen screen) {
+		Objects.requireNonNull(screen, "Screen cannot be null");
+
+		return ScreenExtensions.getExtensions(screen).fabric_getAfterRenderEvent();
+	}
+
+	/**
+	 * An event that is called before a screen is ticked.
+	 *
+	 * @return the event
+	 */
+	public static Event<BeforeTick> beforeTick(Screen screen) {
+		Objects.requireNonNull(screen, "Screen cannot be null");
+
+		return ScreenExtensions.getExtensions(screen).fabric_getBeforeTickEvent();
+	}
+
+	/**
+	 * An event that is called after a screen is ticked.
+	 *
+	 * @return the event
+	 */
+	public static Event<AfterTick> afterTick(Screen screen) {
+		Objects.requireNonNull(screen, "Screen cannot be null");
+
+		return ScreenExtensions.getExtensions(screen).fabric_getAfterTickEvent();
+	}
+
+	@Environment(EnvType.CLIENT)
+	@FunctionalInterface
+	public interface BeforeInit {
+		void beforeInit(MinecraftClient client, Screen screen, int scaledWidth, int scaledHeight);
+	}
+
+	@Environment(EnvType.CLIENT)
+	@FunctionalInterface
+	public interface AfterInit {
+		void afterInit(MinecraftClient client, Screen screen, int scaledWidth, int scaledHeight);
+	}
+
+	@Environment(EnvType.CLIENT)
+	@FunctionalInterface
+	public interface Remove {
+		void onRemove(Screen screen);
+	}
+
+	@Environment(EnvType.CLIENT)
+	@FunctionalInterface
+	public interface BeforeRender {
+		void beforeRender(Screen screen, MatrixStack matrices, int mouseX, int mouseY, float tickDelta);
+	}
+
+	@Environment(EnvType.CLIENT)
+	@FunctionalInterface
+	public interface AfterRender {
+		void afterRender(Screen screen, MatrixStack matrices, int mouseX, int mouseY, float tickDelta);
+	}
+
+	@Environment(EnvType.CLIENT)
+	@FunctionalInterface
+	public interface BeforeTick {
+		void beforeTick(Screen screen);
+	}
+
+	@Environment(EnvType.CLIENT)
+	@FunctionalInterface
+	public interface AfterTick {
+		void afterTick(Screen screen);
+	}
+
+	private ScreenEvents() {
+	}
+}
diff --git a/fabric-screen-api-v1/src/main/java/net/fabricmc/fabric/api/client/screen/v1/ScreenKeyboardEvents.java b/fabric-screen-api-v1/src/main/java/net/fabricmc/fabric/api/client/screen/v1/ScreenKeyboardEvents.java
new file mode 100644
index 000000000..26636602c
--- /dev/null
+++ b/fabric-screen-api-v1/src/main/java/net/fabricmc/fabric/api/client/screen/v1/ScreenKeyboardEvents.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (c) 2016, 2017, 2018, 2019 FabricMC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.fabricmc.fabric.api.client.screen.v1;
+
+import java.util.Objects;
+
+import net.minecraft.client.gui.screen.Screen;
+
+import net.fabricmc.api.EnvType;
+import net.fabricmc.api.Environment;
+import net.fabricmc.fabric.api.event.Event;
+import net.fabricmc.fabric.impl.client.screen.ScreenExtensions;
+
+/**
+ * Events related to use of the keyboard in a {@link Screen}.
+ *
+ * <p>All of these events work on top of a specific screen instance.
+ * Subscriptions will only last as long as the screen itself, they'll disappear once the screen gets refreshed, closed or replaced.
+ * Use {@link ScreenEvents#BEFORE_INIT} to register the desired events every time it is necessary.
+ *
+ * <p>Events are fired in the following order:
+ * <pre>{@code AllowX -> BeforeX -> AfterX}</pre>
+ * If the result of the Allow event is false, then Before and After are not called.
+ *
+ * @see ScreenEvents
+ */
+@Environment(EnvType.CLIENT)
+public final class ScreenKeyboardEvents {
+	/**
+	 * An event that checks if a key press should be allowed.
+	 *
+	 * @return the event
+	 */
+	public static Event<AllowKeyPress> allowKeyPress(Screen screen) {
+		Objects.requireNonNull(screen, "Screen cannot be null");
+
+		return ScreenExtensions.getExtensions(screen).fabric_getAllowKeyPressEvent();
+	}
+
+	/**
+	 * An event that is called before a key press is processed for a screen.
+	 *
+	 * @return the event
+	 */
+	public static Event<BeforeKeyPress> beforeKeyPress(Screen screen) {
+		Objects.requireNonNull(screen, "Screen cannot be null");
+
+		return ScreenExtensions.getExtensions(screen).fabric_getBeforeKeyPressEvent();
+	}
+
+	/**
+	 * An event that is called after a key press is processed for a screen.
+	 *
+	 * @return the event
+	 */
+	public static Event<AfterKeyPress> afterKeyPress(Screen screen) {
+		Objects.requireNonNull(screen, "Screen cannot be null");
+
+		return ScreenExtensions.getExtensions(screen).fabric_getAfterKeyPressEvent();
+	}
+
+	/**
+	 * An event that checks if a pressed key should be allowed to release.
+	 *
+	 * @return the event
+	 */
+	public static Event<AllowKeyRelease> allowKeyRelease(Screen screen) {
+		Objects.requireNonNull(screen, "Screen cannot be null");
+
+		return ScreenExtensions.getExtensions(screen).fabric_getAllowKeyReleaseEvent();
+	}
+
+	/**
+	 * An event that is called after the release of a key is processed for a screen.
+	 *
+	 * @return the event
+	 */
+	public static Event<BeforeKeyRelease> beforeKeyRelease(Screen screen) {
+		Objects.requireNonNull(screen, "Screen cannot be null");
+
+		return ScreenExtensions.getExtensions(screen).fabric_getBeforeKeyReleaseEvent();
+	}
+
+	/**
+	 * An event that is called after the release a key is processed for a screen.
+	 *
+	 * @return the event
+	 */
+	public static Event<AfterKeyRelease> afterKeyRelease(Screen screen) {
+		Objects.requireNonNull(screen, "Screen cannot be null");
+
+		return ScreenExtensions.getExtensions(screen).fabric_getAfterKeyReleaseEvent();
+	}
+
+	private ScreenKeyboardEvents() {
+	}
+
+	@Environment(EnvType.CLIENT)
+	@FunctionalInterface
+	public interface AllowKeyPress {
+		/**
+		 * Checks if a key should be allowed to be pressed.
+		 *
+		 * @param key the named key code which can be identified by the constants in {@link org.lwjgl.glfw.GLFW GLFW}
+		 * @param scancode the unique/platform-specific scan code of the keyboard input
+		 * @param modifiers a GLFW bitfield describing the modifier keys that are held down
+		 * @return whether the key press should be processed
+		 * @see org.lwjgl.glfw.GLFW#GLFW_KEY_Q
+		 * @see <a href="https://www.glfw.org/docs/3.3/group__mods.html">Modifier key flags</a>
+		 */
+		boolean allowKeyPress(Screen screen, int key, int scancode, int modifiers);
+	}
+
+	@Environment(EnvType.CLIENT)
+	@FunctionalInterface
+	public interface BeforeKeyPress {
+		/**
+		 * Called before a key press is handled.
+		 *
+		 * @param key the named key code which can be identified by the constants in {@link org.lwjgl.glfw.GLFW GLFW}
+		 * @param scancode the unique/platform-specific scan code of the keyboard input
+		 * @param modifiers a GLFW bitfield describing the modifier keys that are held down
+		 * @see org.lwjgl.glfw.GLFW#GLFW_KEY_Q
+		 * @see <a href="https://www.glfw.org/docs/3.3/group__mods.html">Modifier key flags</a>
+		 */
+		void beforeKeyPress(Screen screen, int key, int scancode, int modifiers);
+	}
+
+	@Environment(EnvType.CLIENT)
+	@FunctionalInterface
+	public interface AfterKeyPress {
+		/**
+		 * Called after a key press is handled.
+		 *
+		 * @param key the named key code which can be identified by the constants in {@link org.lwjgl.glfw.GLFW GLFW}
+		 * @param scancode the unique/platform-specific scan code of the keyboard input
+		 * @param modifiers a GLFW bitfield describing the modifier keys that are held down
+		 * @see org.lwjgl.glfw.GLFW#GLFW_KEY_Q
+		 * @see <a href="https://www.glfw.org/docs/3.3/group__mods.html">Modifier key flags</a>
+		 */
+		void afterKeyPress(Screen screen, int key, int scancode, int modifiers);
+	}
+
+	@Environment(EnvType.CLIENT)
+	@FunctionalInterface
+	public interface AllowKeyRelease {
+		/**
+		 * Checks if a pressed key should be allowed to be released.
+		 *
+		 * @param key the named key code which can be identified by the constants in {@link org.lwjgl.glfw.GLFW GLFW}
+		 * @param scancode the unique/platform-specific scan code of the keyboard input
+		 * @param modifiers a GLFW bitfield describing the modifier keys that are held down
+		 * @return whether the key press should be released
+		 * @see org.lwjgl.glfw.GLFW#GLFW_KEY_Q
+		 * @see <a href="https://www.glfw.org/docs/3.3/group__mods.html">Modifier key flags</a>
+		 */
+		boolean allowKeyRelease(Screen screen, int key, int scancode, int modifiers);
+	}
+
+	@Environment(EnvType.CLIENT)
+	@FunctionalInterface
+	public interface BeforeKeyRelease {
+		/**
+		 * Called before a pressed key has been released.
+		 *
+		 * @param key the named key code which can be identified by the constants in {@link org.lwjgl.glfw.GLFW GLFW}
+		 * @param scancode the unique/platform-specific scan code of the keyboard input
+		 * @param modifiers a GLFW bitfield describing the modifier keys that are held down
+		 * @see org.lwjgl.glfw.GLFW#GLFW_KEY_Q
+		 * @see <a href="https://www.glfw.org/docs/3.3/group__mods.html">Modifier key flags</a>
+		 */
+		void beforeKeyRelease(Screen screen, int key, int scancode, int modifiers);
+	}
+
+	@Environment(EnvType.CLIENT)
+	@FunctionalInterface
+	public interface AfterKeyRelease {
+		/**
+		 * Called after a pressed key has been released.
+		 *
+		 * @param key the named key code which can be identified by the constants in {@link org.lwjgl.glfw.GLFW GLFW}
+		 * @param scancode the unique/platform-specific scan code of the keyboard input
+		 * @param modifiers a GLFW bitfield describing the modifier keys that are held down
+		 * @see org.lwjgl.glfw.GLFW#GLFW_KEY_Q
+		 * @see <a href="https://www.glfw.org/docs/3.3/group__mods.html">Modifier key flags</a>
+		 */
+		void afterKeyRelease(Screen screen, int key, int scancode, int modifiers);
+	}
+}
diff --git a/fabric-screen-api-v1/src/main/java/net/fabricmc/fabric/api/client/screen/v1/ScreenMouseEvents.java b/fabric-screen-api-v1/src/main/java/net/fabricmc/fabric/api/client/screen/v1/ScreenMouseEvents.java
new file mode 100644
index 000000000..81cb4258c
--- /dev/null
+++ b/fabric-screen-api-v1/src/main/java/net/fabricmc/fabric/api/client/screen/v1/ScreenMouseEvents.java
@@ -0,0 +1,271 @@
+/*
+ * Copyright (c) 2016, 2017, 2018, 2019 FabricMC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.fabricmc.fabric.api.client.screen.v1;
+
+import java.util.Objects;
+
+import net.minecraft.client.gui.screen.Screen;
+
+import net.fabricmc.api.EnvType;
+import net.fabricmc.api.Environment;
+import net.fabricmc.fabric.api.event.Event;
+import net.fabricmc.fabric.impl.client.screen.ScreenExtensions;
+
+/**
+ * Events related to use of the mouse in a {@link Screen}.
+ *
+ * <p>All of these events work on top of a specific screen instance.
+ * Subscriptions will only last as long as the screen itself, they'll disappear once the screen gets refreshed, closed or replaced.
+ * Use {@link ScreenEvents#BEFORE_INIT} to register the desired events every time it is necessary.
+ *
+ * <p>Events are fired in the following order:
+ * <pre>{@code AllowX -> BeforeX -> AfterX}</pre>
+ * If the result of the Allow event is false, then Before and After are not called.
+ *
+ * @see ScreenEvents
+ */
+@Environment(EnvType.CLIENT)
+public final class ScreenMouseEvents {
+	/**
+	 * An event that checks if the mouse click should be allowed.
+	 *
+	 * @return the event
+	 */
+	public static Event<AllowMouseClick> allowMouseClick(Screen screen) {
+		Objects.requireNonNull(screen, "Screen cannot be null");
+
+		return ScreenExtensions.getExtensions(screen).fabric_getAllowMouseClickEvent();
+	}
+
+	/**
+	 * An event that is called before a mouse click is processed for a screen.
+	 *
+	 * @return the event
+	 */
+	public static Event<BeforeMouseClick> beforeMouseClick(Screen screen) {
+		Objects.requireNonNull(screen, "Screen cannot be null");
+
+		return ScreenExtensions.getExtensions(screen).fabric_getBeforeMouseClickEvent();
+	}
+
+	/**
+	 * An event that is called after a mouse click is processed for a screen.
+	 *
+	 * @return the event
+	 */
+	public static Event<AfterMouseClick> afterMouseClick(Screen screen) {
+		Objects.requireNonNull(screen, "Screen cannot be null");
+
+		return ScreenExtensions.getExtensions(screen).fabric_getAfterMouseClickEvent();
+	}
+
+	/**
+	 * An event that checks if the mouse click should be allowed to release in a screen.
+	 *
+	 * @return the event
+	 */
+	public static Event<AllowMouseRelease> allowMouseRelease(Screen screen) {
+		Objects.requireNonNull(screen, "Screen cannot be null");
+
+		return ScreenExtensions.getExtensions(screen).fabric_getAllowMouseReleaseEvent();
+	}
+
+	/**
+	 * An event that is called before the release of a mouse click is processed for a screen.
+	 *
+	 * @return the event
+	 */
+	public static Event<BeforeMouseRelease> beforeMouseRelease(Screen screen) {
+		Objects.requireNonNull(screen, "Screen cannot be null");
+
+		return ScreenExtensions.getExtensions(screen).fabric_getBeforeMouseReleaseEvent();
+	}
+
+	/**
+	 * An event that is called after the release of a mouse click is processed for a screen.
+	 *
+	 * @return the event
+	 */
+	public static Event<AfterMouseRelease> afterMouseRelease(Screen screen) {
+		Objects.requireNonNull(screen, "Screen cannot be null");
+
+		return ScreenExtensions.getExtensions(screen).fabric_getAfterMouseReleaseEvent();
+	}
+
+	/**
+	 * An event that is checks if the mouse should be allowed to scroll in a screen.
+	 *
+	 * <p>This event tracks amount of vertical and horizontal scroll.
+	 *
+	 * @return the event
+	 */
+	public static Event<AllowMouseScroll> allowMouseScroll(Screen screen) {
+		Objects.requireNonNull(screen, "Screen cannot be null");
+
+		return ScreenExtensions.getExtensions(screen).fabric_getAllowMouseScrollEvent();
+	}
+
+	/**
+	 * An event that is called after mouse scrolling is processed for a screen.
+	 *
+	 * <p>This event tracks amount of vertical and horizontal scroll.
+	 *
+	 * @return the event
+	 */
+	public static Event<BeforeMouseScroll> beforeMouseScroll(Screen screen) {
+		Objects.requireNonNull(screen, "Screen cannot be null");
+
+		return ScreenExtensions.getExtensions(screen).fabric_getBeforeMouseScrollEvent();
+	}
+
+	/**
+	 * An event that is called after mouse scrolling is processed for a screen.
+	 *
+	 * <p>This event tracks amount a mouse was scrolled both vertically and horizontally.
+	 *
+	 * @return the event
+	 */
+	public static Event<AfterMouseScroll> afterMouseScroll(Screen screen) {
+		Objects.requireNonNull(screen, "Screen cannot be null");
+
+		return ScreenExtensions.getExtensions(screen).fabric_getAfterMouseScrollEvent();
+	}
+
+	private ScreenMouseEvents() {
+	}
+
+	@Environment(EnvType.CLIENT)
+	@FunctionalInterface
+	public interface AllowMouseClick {
+		/**
+		 * @param mouseX the x position of the mouse
+		 * @param mouseY the y position of the mouse
+		 * @param button the button number, which can be identified by the constants in {@link org.lwjgl.glfw.GLFW GLFW}.
+		 * @see org.lwjgl.glfw.GLFW#GLFW_MOUSE_BUTTON_1
+		 */
+		boolean allowMouseClick(Screen screen, double mouseX, double mouseY, int button);
+	}
+
+	@Environment(EnvType.CLIENT)
+	@FunctionalInterface
+	public interface BeforeMouseClick {
+		/**
+		 * @param mouseX the x position of the mouse
+		 * @param mouseY the y position of the mouse
+		 * @param button the button number, which can be identified by the constants in {@link org.lwjgl.glfw.GLFW GLFW}.
+		 * @see org.lwjgl.glfw.GLFW#GLFW_MOUSE_BUTTON_1
+		 */
+		void beforeMouseClick(Screen screen, double mouseX, double mouseY, int button);
+	}
+
+	@Environment(EnvType.CLIENT)
+	@FunctionalInterface
+	public interface AfterMouseClick {
+		/**
+		 * @param mouseX the x position of the mouse
+		 * @param mouseY the y position of the mouse
+		 * @param button the button number, which can be identified by the constants in {@link org.lwjgl.glfw.GLFW GLFW}.
+		 * @see org.lwjgl.glfw.GLFW#GLFW_MOUSE_BUTTON_1
+		 */
+		void afterMouseClick(Screen screen, double mouseX, double mouseY, int button);
+	}
+
+	@Environment(EnvType.CLIENT)
+	@FunctionalInterface
+	public interface AllowMouseRelease {
+		/**
+		 * Checks if the mouse click should be allowed to release in a screen.
+		 *
+		 * @param mouseX the x position of the mouse
+		 * @param mouseY the y position of the mouse
+		 * @param button the button number, which can be identified by the constants in {@link org.lwjgl.glfw.GLFW GLFW}.
+		 * @see org.lwjgl.glfw.GLFW#GLFW_MOUSE_BUTTON_1
+		 */
+		boolean allowMouseRelease(Screen screen, double mouseX, double mouseY, int button);
+	}
+
+	@Environment(EnvType.CLIENT)
+	@FunctionalInterface
+	public interface BeforeMouseRelease {
+		/**
+		 * Called before a mouse click has released in a screen.
+		 *
+		 * @param mouseX the x position of the mouse
+		 * @param mouseY the y position of the mouse
+		 * @param button the button number, which can be identified by the constants in {@link org.lwjgl.glfw.GLFW GLFW}.
+		 * @see org.lwjgl.glfw.GLFW#GLFW_MOUSE_BUTTON_1
+		 */
+		void beforeMouseRelease(Screen screen, double mouseX, double mouseY, int button);
+	}
+
+	@Environment(EnvType.CLIENT)
+	@FunctionalInterface
+	public interface AfterMouseRelease {
+		/**
+		 * Called after a mouse click has released in a screen.
+		 *
+		 * @param mouseX the x position of the mouse
+		 * @param mouseY the y position of the mouse
+		 * @param button the button number, which can be identified by the constants in {@link org.lwjgl.glfw.GLFW GLFW}.
+		 * @see org.lwjgl.glfw.GLFW#GLFW_MOUSE_BUTTON_1
+		 */
+		void afterMouseRelease(Screen screen, double mouseX, double mouseY, int button);
+	}
+
+	@Environment(EnvType.CLIENT)
+	@FunctionalInterface
+	public interface AllowMouseScroll {
+		/**
+		 * Checks if the mouse should be allowed to scroll in a screen.
+		 *
+		 * @param mouseX the x position of the mouse
+		 * @param mouseY the y position of the mouse
+		 * @param horizontalAmount the horizontal scroll amount
+		 * @param verticalAmount the vertical scroll amount
+		 * @return whether the mouse should be allowed to scroll
+		 */
+		boolean allowMouseScroll(Screen screen, double mouseX, double mouseY, double horizontalAmount, double verticalAmount);
+	}
+
+	@Environment(EnvType.CLIENT)
+	@FunctionalInterface
+	public interface BeforeMouseScroll {
+		/**
+		 * Called before a mouse has scrolled on screen.
+		 *
+		 * @param mouseX the x position of the mouse
+		 * @param mouseY the y position of the mouse
+		 * @param horizontalAmount the horizontal scroll amount
+		 * @param verticalAmount the vertical scroll amount
+		 */
+		void beforeMouseScroll(Screen screen, double mouseX, double mouseY, double horizontalAmount, double verticalAmount);
+	}
+
+	@Environment(EnvType.CLIENT)
+	@FunctionalInterface
+	public interface AfterMouseScroll {
+		/**
+		 * Called after a mouse has scrolled on screen.
+		 *
+		 * @param mouseX the x position of the mouse
+		 * @param mouseY the y position of the mouse
+		 * @param horizontalAmount the horizontal scroll amount
+		 * @param verticalAmount the vertical scroll amount
+		 */
+		void afterMouseScroll(Screen screen, double mouseX, double mouseY, double horizontalAmount, double verticalAmount);
+	}
+}
diff --git a/fabric-screen-api-v1/src/main/java/net/fabricmc/fabric/api/client/screen/v1/Screens.java b/fabric-screen-api-v1/src/main/java/net/fabricmc/fabric/api/client/screen/v1/Screens.java
new file mode 100644
index 000000000..ab5870ac2
--- /dev/null
+++ b/fabric-screen-api-v1/src/main/java/net/fabricmc/fabric/api/client/screen/v1/Screens.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2016, 2017, 2018, 2019 FabricMC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.fabricmc.fabric.api.client.screen.v1;
+
+import java.util.List;
+import java.util.Objects;
+
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.font.TextRenderer;
+import net.minecraft.client.gui.screen.Screen;
+import net.minecraft.client.gui.widget.AbstractButtonWidget;
+import net.minecraft.client.render.item.ItemRenderer;
+
+import net.fabricmc.api.EnvType;
+import net.fabricmc.api.Environment;
+import net.fabricmc.fabric.impl.client.screen.ScreenExtensions;
+import net.fabricmc.fabric.mixin.screen.ScreenAccessor;
+
+/**
+ * Utility methods related to screens.
+ *
+ * @see ScreenEvents
+ */
+@Environment(EnvType.CLIENT)
+public final class Screens {
+	/**
+	 * Gets all of a screen's button widgets.
+	 * The provided list allows for addition and removal of buttons from the screen.
+	 * This method should be preferred over adding buttons directly to a screen's {@link Screen#children() child elements}.
+	 *
+	 * @return a list of all of a screen's buttons
+	 */
+	public static List<AbstractButtonWidget> getButtons(Screen screen) {
+		Objects.requireNonNull(screen, "Screen cannot be null");
+
+		return ScreenExtensions.getExtensions(screen).fabric_getButtons();
+	}
+
+	/**
+	 * Gets a screen's item renderer.
+	 *
+	 * @return the screen's item renderer
+	 */
+	public static ItemRenderer getItemRenderer(Screen screen) {
+		Objects.requireNonNull(screen, "Screen cannot be null");
+
+		return ((ScreenAccessor) screen).getItemRenderer();
+	}
+
+	/**
+	 * Gets a screen's text renderer.
+	 *
+	 * @return the screen's text renderer.
+	 */
+	public static TextRenderer getTextRenderer(Screen screen) {
+		Objects.requireNonNull(screen, "Screen cannot be null");
+
+		return ((ScreenAccessor) screen).getTextRenderer();
+	}
+
+	public static MinecraftClient getClient(Screen screen) {
+		Objects.requireNonNull(screen, "Screen cannot be null");
+
+		return ((ScreenAccessor) screen).getClient();
+	}
+
+	private Screens() {
+	}
+}
diff --git a/fabric-screen-api-v1/src/main/java/net/fabricmc/fabric/api/client/screen/v1/package-info.java b/fabric-screen-api-v1/src/main/java/net/fabricmc/fabric/api/client/screen/v1/package-info.java
new file mode 100644
index 000000000..6d7391ec5
--- /dev/null
+++ b/fabric-screen-api-v1/src/main/java/net/fabricmc/fabric/api/client/screen/v1/package-info.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2016, 2017, 2018, 2019 FabricMC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Fabric Screen API v1.
+ *
+ * <p>The screen api provides events and utilities in this package are related to the lifecycle of a {@link net.minecraft.client.gui.screen.Screen}.
+ *
+ * <p>For general screen events see {@link net.fabricmc.fabric.api.client.screen.v1.ScreenEvents}.
+ *
+ * <p>For screen events related to the use of a mouse, see {@link net.fabricmc.fabric.api.client.screen.v1.ScreenMouseEvents}.
+ *
+ * <p>For screen events related to the use of a keyboard, see {@link net.fabricmc.fabric.api.client.screen.v1.ScreenKeyboardEvents}.
+ *
+ * @see net.fabricmc.fabric.api.client.screen.v1.Screens
+ */
+package net.fabricmc.fabric.api.client.screen.v1;
diff --git a/fabric-screen-api-v1/src/main/java/net/fabricmc/fabric/impl/client/screen/ButtonList.java b/fabric-screen-api-v1/src/main/java/net/fabricmc/fabric/impl/client/screen/ButtonList.java
new file mode 100644
index 000000000..ff2b0458d
--- /dev/null
+++ b/fabric-screen-api-v1/src/main/java/net/fabricmc/fabric/impl/client/screen/ButtonList.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2016, 2017, 2018, 2019 FabricMC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.fabricmc.fabric.impl.client.screen;
+
+import java.util.AbstractList;
+import java.util.List;
+
+import org.jetbrains.annotations.ApiStatus;
+
+import net.minecraft.client.gui.Element;
+import net.minecraft.client.gui.screen.Screen;
+import net.minecraft.client.gui.widget.AbstractButtonWidget;
+
+import net.fabricmc.api.EnvType;
+import net.fabricmc.api.Environment;
+
+// TODO: When events for listening to addition of child elements are added, fire events from this list.
+@ApiStatus.Internal
+@Environment(EnvType.CLIENT)
+public final class ButtonList<T extends AbstractButtonWidget> extends AbstractList<T> {
+	private final Screen screen;
+	private final List<T> buttons;
+	private final List<Element> children;
+
+	public ButtonList(Screen screen, List<T> buttons, List<Element> children) {
+		this.screen = screen;
+		this.buttons = buttons;
+		this.children = children;
+	}
+
+	@Override
+	public T get(int index) {
+		return this.buttons.get(index);
+	}
+
+	@Override
+	public T set(int index, T element) {
+		this.remove(element); // verify / ensure no duplicates
+
+		final T existingButton = this.buttons.get(index);
+		int elementIndex = this.children.indexOf(existingButton);
+
+		if (elementIndex > -1) {
+			this.children.set(elementIndex, element);
+		}
+
+		return this.buttons.set(index, element);
+	}
+
+	@Override
+	public void add(int index, T element) {
+		this.rangeCheckForAdd(index); // verify index bounds
+		this.remove(element); // ensure no duplicates
+
+		this.buttons.add(index, element);
+		this.children.add(Math.min(this.children.size(), index), element);
+	}
+
+	@Override
+	public T remove(int index) {
+		this.rangeCheck(index); // verify index bounds
+
+		final T removedButton = this.buttons.remove(index);
+		this.children.remove(removedButton);
+
+		return removedButton;
+	}
+
+	@Override
+	public int size() {
+		return this.buttons.size();
+	}
+
+	private void rangeCheck(int index) {
+		if (index >= this.size()) {
+			throw createOutOfBoundsException(index);
+		}
+	}
+
+	private void rangeCheckForAdd(int index) {
+		if (index > this.size() || index < 0) {
+			throw createOutOfBoundsException(index);
+		}
+	}
+
+	private IndexOutOfBoundsException createOutOfBoundsException(int index) {
+		return new IndexOutOfBoundsException("Index: " + index + ", Size: "+ this.size());
+	}
+}
+
diff --git a/fabric-screen-api-v1/src/main/java/net/fabricmc/fabric/impl/client/screen/ScreenEventFactory.java b/fabric-screen-api-v1/src/main/java/net/fabricmc/fabric/impl/client/screen/ScreenEventFactory.java
new file mode 100644
index 000000000..3476be54e
--- /dev/null
+++ b/fabric-screen-api-v1/src/main/java/net/fabricmc/fabric/impl/client/screen/ScreenEventFactory.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright (c) 2016, 2017, 2018, 2019 FabricMC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.fabricmc.fabric.impl.client.screen;
+
+import org.jetbrains.annotations.ApiStatus;
+
+import net.fabricmc.api.EnvType;
+import net.fabricmc.api.Environment;
+import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents;
+import net.fabricmc.fabric.api.client.screen.v1.ScreenKeyboardEvents;
+import net.fabricmc.fabric.api.client.screen.v1.ScreenMouseEvents;
+import net.fabricmc.fabric.api.event.Event;
+import net.fabricmc.fabric.api.event.EventFactory;
+
+/**
+ * Factory methods for creating event instances used in {@link ScreenExtensions}.
+ */
+@ApiStatus.Internal
+@Environment(EnvType.CLIENT)
+public final class ScreenEventFactory {
+	public static Event<ScreenEvents.Remove> createRemoveEvent() {
+		return EventFactory.createArrayBacked(ScreenEvents.Remove.class, callbacks -> screen -> {
+			for (ScreenEvents.Remove callback : callbacks) {
+				callback.onRemove(screen);
+			}
+		});
+	}
+
+	public static Event<ScreenEvents.BeforeRender> createBeforeRenderEvent() {
+		return EventFactory.createArrayBacked(ScreenEvents.BeforeRender.class, callbacks -> (screen, matrices, mouseX, mouseY, tickDelta) -> {
+			for (ScreenEvents.BeforeRender callback : callbacks) {
+				callback.beforeRender(screen, matrices, mouseX, mouseY, tickDelta);
+			}
+		});
+	}
+
+	public static Event<ScreenEvents.AfterRender> createAfterRenderEvent() {
+		return EventFactory.createArrayBacked(ScreenEvents.AfterRender.class, callbacks -> (screen, matrices, mouseX, mouseY, tickDelta) -> {
+			for (ScreenEvents.AfterRender callback : callbacks) {
+				callback.afterRender(screen, matrices, mouseX, mouseY, tickDelta);
+			}
+		});
+	}
+
+	public static Event<ScreenEvents.BeforeTick> createBeforeTickEvent() {
+		return EventFactory.createArrayBacked(ScreenEvents.BeforeTick.class, callbacks -> screen -> {
+			for (ScreenEvents.BeforeTick callback : callbacks) {
+				callback.beforeTick(screen);
+			}
+		});
+	}
+
+	public static Event<ScreenEvents.AfterTick> createAfterTickEvent() {
+		return EventFactory.createArrayBacked(ScreenEvents.AfterTick.class, callbacks -> screen -> {
+			for (ScreenEvents.AfterTick callback : callbacks) {
+				callback.afterTick(screen);
+			}
+		});
+	}
+
+	// Keyboard events
+
+	public static Event<ScreenKeyboardEvents.AllowKeyPress> createAllowKeyPressEvent() {
+		return EventFactory.createArrayBacked(ScreenKeyboardEvents.AllowKeyPress.class, callbacks -> (screen, key, scancode, modifiers) -> {
+			for (ScreenKeyboardEvents.AllowKeyPress callback : callbacks) {
+				if (!callback.allowKeyPress(screen, key, scancode, modifiers)) {
+					return false;
+				}
+			}
+
+			return true;
+		});
+	}
+
+	public static Event<ScreenKeyboardEvents.BeforeKeyPress> createBeforeKeyPressEvent() {
+		return EventFactory.createArrayBacked(ScreenKeyboardEvents.BeforeKeyPress.class, callbacks -> (screen, key, scancode, modifiers) -> {
+			for (ScreenKeyboardEvents.BeforeKeyPress callback : callbacks) {
+				callback.beforeKeyPress(screen, key, scancode, modifiers);
+			}
+		});
+	}
+
+	public static Event<ScreenKeyboardEvents.AfterKeyPress> createAfterKeyPressEvent() {
+		return EventFactory.createArrayBacked(ScreenKeyboardEvents.AfterKeyPress.class, callbacks -> (screen, key, scancode, modifiers) -> {
+			for (ScreenKeyboardEvents.AfterKeyPress callback : callbacks) {
+				callback.afterKeyPress(screen, key, scancode, modifiers);
+			}
+		});
+	}
+
+	public static Event<ScreenKeyboardEvents.AllowKeyRelease> createAllowKeyReleaseEvent() {
+		return EventFactory.createArrayBacked(ScreenKeyboardEvents.AllowKeyRelease.class, callbacks -> (screen, key, scancode, modifiers) -> {
+			for (ScreenKeyboardEvents.AllowKeyRelease callback : callbacks) {
+				if (!callback.allowKeyRelease(screen, key, scancode, modifiers)) {
+					return false;
+				}
+			}
+
+			return true;
+		});
+	}
+
+	public static Event<ScreenKeyboardEvents.BeforeKeyRelease> createBeforeKeyReleaseEvent() {
+		return EventFactory.createArrayBacked(ScreenKeyboardEvents.BeforeKeyRelease.class, callbacks -> (screen, key, scancode, modifiers) -> {
+			for (ScreenKeyboardEvents.BeforeKeyRelease callback : callbacks) {
+				callback.beforeKeyRelease(screen, key, scancode, modifiers);
+			}
+		});
+	}
+
+	public static Event<ScreenKeyboardEvents.AfterKeyRelease> createAfterKeyReleaseEvent() {
+		return EventFactory.createArrayBacked(ScreenKeyboardEvents.AfterKeyRelease.class, callbacks -> (screen, key, scancode, modifiers) -> {
+			for (ScreenKeyboardEvents.AfterKeyRelease callback : callbacks) {
+				callback.afterKeyRelease(screen, key, scancode, modifiers);
+			}
+		});
+	}
+
+	// Mouse Events
+
+	public static Event<ScreenMouseEvents.AllowMouseClick> createAllowMouseClickEvent() {
+		return EventFactory.createArrayBacked(ScreenMouseEvents.AllowMouseClick.class, callbacks -> (screen, mouseX, mouseY, button) -> {
+			for (ScreenMouseEvents.AllowMouseClick callback : callbacks) {
+				if (!callback.allowMouseClick(screen, mouseX, mouseY, button)) {
+					return false;
+				}
+			}
+
+			return true;
+		});
+	}
+
+	public static Event<ScreenMouseEvents.BeforeMouseClick> createBeforeMouseClickEvent() {
+		return EventFactory.createArrayBacked(ScreenMouseEvents.BeforeMouseClick.class, callbacks -> (screen, mouseX, mouseY, button) -> {
+			for (ScreenMouseEvents.BeforeMouseClick callback : callbacks) {
+				callback.beforeMouseClick(screen, mouseX, mouseY, button);
+			}
+		});
+	}
+
+	public static Event<ScreenMouseEvents.AfterMouseClick> createAfterMouseClickEvent() {
+		return EventFactory.createArrayBacked(ScreenMouseEvents.AfterMouseClick.class, callbacks -> (screen, mouseX, mouseY, button) -> {
+			for (ScreenMouseEvents.AfterMouseClick callback : callbacks) {
+				callback.afterMouseClick(screen, mouseX, mouseY, button);
+			}
+		});
+	}
+
+	public static Event<ScreenMouseEvents.AllowMouseRelease> createAllowMouseReleaseEvent() {
+		return EventFactory.createArrayBacked(ScreenMouseEvents.AllowMouseRelease.class, callbacks -> (screen, mouseX, mouseY, button) -> {
+			for (ScreenMouseEvents.AllowMouseRelease callback : callbacks) {
+				if (!callback.allowMouseRelease(screen, mouseX, mouseY, button)) {
+					return false;
+				}
+			}
+
+			return true;
+		});
+	}
+
+	public static Event<ScreenMouseEvents.BeforeMouseRelease> createBeforeMouseReleaseEvent() {
+		return EventFactory.createArrayBacked(ScreenMouseEvents.BeforeMouseRelease.class, callbacks -> (screen, mouseX, mouseY, button) -> {
+			for (ScreenMouseEvents.BeforeMouseRelease callback : callbacks) {
+				callback.beforeMouseRelease(screen, mouseX, mouseY, button);
+			}
+		});
+	}
+
+	public static Event<ScreenMouseEvents.AfterMouseRelease> createAfterMouseReleaseEvent() {
+		return EventFactory.createArrayBacked(ScreenMouseEvents.AfterMouseRelease.class, callbacks -> (screen, mouseX, mouseY, button) -> {
+			for (ScreenMouseEvents.AfterMouseRelease callback : callbacks) {
+				callback.afterMouseRelease(screen, mouseX, mouseY, button);
+			}
+		});
+	}
+
+	public static Event<ScreenMouseEvents.AllowMouseScroll> createAllowMouseScrollEvent() {
+		return EventFactory.createArrayBacked(ScreenMouseEvents.AllowMouseScroll.class, callbacks -> (screen, mouseX, mouseY, horizontalAmount, verticalAmount) -> {
+			for (ScreenMouseEvents.AllowMouseScroll callback : callbacks) {
+				if (!callback.allowMouseScroll(screen, mouseX, mouseY, horizontalAmount, verticalAmount)) {
+					return false;
+				}
+			}
+
+			return true;
+		});
+	}
+
+	public static Event<ScreenMouseEvents.BeforeMouseScroll> createBeforeMouseScrollEvent() {
+		return EventFactory.createArrayBacked(ScreenMouseEvents.BeforeMouseScroll.class, callbacks -> (screen, mouseX, mouseY, horizontalAmount, verticalAmount) -> {
+			for (ScreenMouseEvents.BeforeMouseScroll callback : callbacks) {
+				callback.beforeMouseScroll(screen, mouseX, mouseY, horizontalAmount, verticalAmount);
+			}
+		});
+	}
+
+	public static Event<ScreenMouseEvents.AfterMouseScroll> createAfterMouseScrollEvent() {
+		return EventFactory.createArrayBacked(ScreenMouseEvents.AfterMouseScroll.class, callbacks -> (screen, mouseX, mouseY, horizontalAmount, verticalAmount) -> {
+			for (ScreenMouseEvents.AfterMouseScroll callback : callbacks) {
+				callback.afterMouseScroll(screen, mouseX, mouseY, horizontalAmount, verticalAmount);
+			}
+		});
+	}
+
+	private ScreenEventFactory() {
+	}
+}
diff --git a/fabric-screen-api-v1/src/main/java/net/fabricmc/fabric/impl/client/screen/ScreenExtensions.java b/fabric-screen-api-v1/src/main/java/net/fabricmc/fabric/impl/client/screen/ScreenExtensions.java
new file mode 100644
index 000000000..3f480c0de
--- /dev/null
+++ b/fabric-screen-api-v1/src/main/java/net/fabricmc/fabric/impl/client/screen/ScreenExtensions.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2016, 2017, 2018, 2019 FabricMC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.fabricmc.fabric.impl.client.screen;
+
+import java.util.List;
+
+import org.jetbrains.annotations.ApiStatus;
+
+import net.minecraft.client.gui.screen.Screen;
+import net.minecraft.client.gui.widget.AbstractButtonWidget;
+
+import net.fabricmc.api.EnvType;
+import net.fabricmc.api.Environment;
+import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents;
+import net.fabricmc.fabric.api.client.screen.v1.ScreenKeyboardEvents;
+import net.fabricmc.fabric.api.client.screen.v1.ScreenMouseEvents;
+import net.fabricmc.fabric.api.event.Event;
+
+@ApiStatus.Internal
+@Environment(EnvType.CLIENT)
+public interface ScreenExtensions {
+	static ScreenExtensions getExtensions(Screen screen) {
+		return (ScreenExtensions) screen;
+	}
+
+	List<AbstractButtonWidget> fabric_getButtons();
+
+	Event<ScreenEvents.Remove> fabric_getRemoveEvent();
+
+	Event<ScreenEvents.BeforeTick> fabric_getBeforeTickEvent();
+
+	Event<ScreenEvents.AfterTick> fabric_getAfterTickEvent();
+
+	Event<ScreenEvents.BeforeRender> fabric_getBeforeRenderEvent();
+
+	Event<ScreenEvents.AfterRender> fabric_getAfterRenderEvent();
+
+	// Keyboard
+
+	Event<ScreenKeyboardEvents.AllowKeyPress> fabric_getAllowKeyPressEvent();
+
+	Event<ScreenKeyboardEvents.BeforeKeyPress> fabric_getBeforeKeyPressEvent();
+
+	Event<ScreenKeyboardEvents.AfterKeyPress> fabric_getAfterKeyPressEvent();
+
+	Event<ScreenKeyboardEvents.AllowKeyRelease> fabric_getAllowKeyReleaseEvent();
+
+	Event<ScreenKeyboardEvents.BeforeKeyRelease> fabric_getBeforeKeyReleaseEvent();
+
+	Event<ScreenKeyboardEvents.AfterKeyRelease> fabric_getAfterKeyReleaseEvent();
+
+	// Mouse
+
+	Event<ScreenMouseEvents.AllowMouseClick> fabric_getAllowMouseClickEvent();
+
+	Event<ScreenMouseEvents.BeforeMouseClick> fabric_getBeforeMouseClickEvent();
+
+	Event<ScreenMouseEvents.AfterMouseClick> fabric_getAfterMouseClickEvent();
+
+	Event<ScreenMouseEvents.AllowMouseRelease> fabric_getAllowMouseReleaseEvent();
+
+	Event<ScreenMouseEvents.BeforeMouseRelease> fabric_getBeforeMouseReleaseEvent();
+
+	Event<ScreenMouseEvents.AfterMouseRelease> fabric_getAfterMouseReleaseEvent();
+
+	Event<ScreenMouseEvents.AllowMouseScroll> fabric_getAllowMouseScrollEvent();
+
+	Event<ScreenMouseEvents.BeforeMouseScroll> fabric_getBeforeMouseScrollEvent();
+
+	Event<ScreenMouseEvents.AfterMouseScroll> fabric_getAfterMouseScrollEvent();
+}
+
diff --git a/fabric-screen-api-v1/src/main/java/net/fabricmc/fabric/mixin/screen/GameRendererMixin.java b/fabric-screen-api-v1/src/main/java/net/fabricmc/fabric/mixin/screen/GameRendererMixin.java
new file mode 100644
index 000000000..072bec19d
--- /dev/null
+++ b/fabric-screen-api-v1/src/main/java/net/fabricmc/fabric/mixin/screen/GameRendererMixin.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2016, 2017, 2018, 2019 FabricMC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.fabricmc.fabric.mixin.screen;
+
+import org.spongepowered.asm.mixin.Final;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
+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;
+import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
+
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gui.screen.Screen;
+import net.minecraft.client.render.GameRenderer;
+import net.minecraft.client.util.math.MatrixStack;
+
+import net.fabricmc.api.EnvType;
+import net.fabricmc.api.Environment;
+import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents;
+
+@Environment(EnvType.CLIENT)
+@Mixin(GameRenderer.class)
+abstract class GameRendererMixin {
+	@Shadow
+	@Final
+	private MinecraftClient client;
+
+	@Unique
+	private Screen renderingScreen;
+
+	@Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/Screen;render(Lnet/minecraft/client/util/math/MatrixStack;IIF)V"), locals = LocalCapture.CAPTURE_FAILEXCEPTION)
+	private void onBeforeRenderScreen(float tickDelta, long startTime, boolean tick, CallbackInfo ci, int mouseX, int mouseY, MatrixStack matrices) {
+		// Store the screen in a variable in case someone tries to change the screen during this before render event.
+		// If someone changes the screen, the after render event will likely have class cast exceptions or an NPE.
+		this.renderingScreen = this.client.currentScreen;
+		ScreenEvents.beforeRender(this.renderingScreen).invoker().beforeRender(this.renderingScreen, matrices, mouseX, mouseY, tickDelta);
+	}
+
+	// This injection should end up in the try block so exceptions are caught
+	@Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/Screen;render(Lnet/minecraft/client/util/math/MatrixStack;IIF)V", shift = At.Shift.AFTER), locals = LocalCapture.CAPTURE_FAILEXCEPTION)
+	private void onAfterRenderScreen(float tickDelta, long startTime, boolean tick, CallbackInfo ci, int mouseX, int mouseY, MatrixStack matrices) {
+		ScreenEvents.afterRender(this.renderingScreen).invoker().afterRender(this.renderingScreen, matrices, mouseX, mouseY, tickDelta);
+		// Finally set the currently rendering screen to null
+		this.renderingScreen = null;
+	}
+}
diff --git a/fabric-screen-api-v1/src/main/java/net/fabricmc/fabric/mixin/screen/KeyboardMixin.java b/fabric-screen-api-v1/src/main/java/net/fabricmc/fabric/mixin/screen/KeyboardMixin.java
new file mode 100644
index 000000000..66be6c211
--- /dev/null
+++ b/fabric-screen-api-v1/src/main/java/net/fabricmc/fabric/mixin/screen/KeyboardMixin.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2016, 2017, 2018, 2019 FabricMC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.fabricmc.fabric.mixin.screen;
+
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+
+import net.minecraft.client.Keyboard;
+import net.minecraft.client.gui.ParentElement;
+import net.minecraft.client.gui.screen.Screen;
+
+import net.fabricmc.fabric.api.client.screen.v1.ScreenKeyboardEvents;
+
+@Mixin(Keyboard.class)
+abstract class KeyboardMixin {
+	// private synthetic method_1454(I[ZLnet/minecraft/client/gui/ParentElement;III)V
+	@Inject(method = "method_1454(I[ZLnet/minecraft/client/gui/ParentElement;III)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/ParentElement;keyPressed(III)Z"), cancellable = true)
+	private void beforeKeyPressedEvent(int code, boolean[] resultHack, ParentElement parentElement, int key, int scancode, int modifiers, CallbackInfo ci) {
+		final Screen screen = (Screen) parentElement;
+
+		if (!ScreenKeyboardEvents.allowKeyPress(screen).invoker().allowKeyPress(screen, key, scancode, modifiers)) {
+			resultHack[0] = true; // Set this press action as handled.
+			ci.cancel(); // Exit the lambda
+			return;
+		}
+
+		ScreenKeyboardEvents.beforeKeyPress(screen).invoker().beforeKeyPress(screen, key, scancode, modifiers);
+	}
+
+	// private synthetic method_1454(I[ZLnet/minecraft/client/gui/ParentElement;III)V
+	@Inject(method = "method_1454(I[ZLnet/minecraft/client/gui/ParentElement;III)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/ParentElement;keyPressed(III)Z", shift = At.Shift.AFTER))
+	private void afterKeyPressedEvent(int code, boolean[] resultHack, ParentElement parentElement, int key, int scancode, int modifiers, CallbackInfo ci) {
+		final Screen screen = (Screen) parentElement;
+		ScreenKeyboardEvents.afterKeyPress(screen).invoker().afterKeyPress(screen, key, scancode, modifiers);
+	}
+
+	// private synthetic method_1454(I[ZLnet/minecraft/client/gui/ParentElement;III)V
+	@Inject(method = "method_1454(I[ZLnet/minecraft/client/gui/ParentElement;III)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/ParentElement;keyReleased(III)Z"), cancellable = true)
+	private void beforeKeyReleasedEvent(int code, boolean[] resultHack, ParentElement parentElement, int key, int scancode, int modifiers, CallbackInfo ci) {
+		final Screen screen = (Screen) parentElement;
+
+		if (!ScreenKeyboardEvents.allowKeyRelease(screen).invoker().allowKeyRelease(screen, key, scancode, modifiers)) {
+			resultHack[0] = true; // Set this press action as handled.
+			ci.cancel(); // Exit the lambda
+			return;
+		}
+
+		ScreenKeyboardEvents.beforeKeyRelease(screen).invoker().beforeKeyRelease(screen, key, scancode, modifiers);
+	}
+
+	// private synthetic method_1454(I[ZLnet/minecraft/client/gui/ParentElement;III)V
+	@Inject(method = "method_1454(I[ZLnet/minecraft/client/gui/ParentElement;III)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/ParentElement;keyReleased(III)Z", shift = At.Shift.AFTER))
+	private void afterKeyReleasedEvent(int code, boolean[] resultHack, ParentElement parentElement, int key, int scancode, int modifiers, CallbackInfo ci) {
+		final Screen screen = (Screen) parentElement;
+		ScreenKeyboardEvents.afterKeyRelease(screen).invoker().afterKeyRelease(screen, key, scancode, modifiers);
+	}
+}
diff --git a/fabric-screen-api-v1/src/main/java/net/fabricmc/fabric/mixin/screen/MinecraftClientMixin.java b/fabric-screen-api-v1/src/main/java/net/fabricmc/fabric/mixin/screen/MinecraftClientMixin.java
new file mode 100644
index 000000000..e4ade04e9
--- /dev/null
+++ b/fabric-screen-api-v1/src/main/java/net/fabricmc/fabric/mixin/screen/MinecraftClientMixin.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2016, 2017, 2018, 2019 FabricMC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.fabricmc.fabric.mixin.screen;
+
+import org.jetbrains.annotations.Nullable;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
+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;
+
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gui.screen.Screen;
+
+import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents;
+
+@Mixin(MinecraftClient.class)
+abstract class MinecraftClientMixin {
+	@Shadow
+	public Screen currentScreen;
+
+	@Unique
+	private Screen tickingScreen;
+
+	@Inject(method = "openScreen", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/Screen;removed()V", shift = At.Shift.AFTER))
+	private void onScreenRemove(@Nullable Screen screen, CallbackInfo ci) {
+		ScreenEvents.remove(this.currentScreen).invoker().onRemove(this.currentScreen);
+	}
+
+	@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);
+	}
+
+	// Synthetic method in `tick`
+	// These two injections should be caught by "Screen#wrapScreenError" if anything fails in an event and then rethrown in the crash report
+	@Inject(method = "method_1572", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/Screen;tick()V"))
+	private void beforeScreenTick(CallbackInfo ci) {
+		// Store the screen in a variable in case someone tries to change the screen during this before tick event.
+		// If someone changes the screen, the after tick event will likely have class cast exceptions or an NPE.
+		this.tickingScreen = this.currentScreen;
+		ScreenEvents.beforeTick(this.tickingScreen).invoker().beforeTick(this.tickingScreen);
+	}
+
+	// Synthetic method in `tick`
+	@Inject(method = "method_1572", at = @At("TAIL"))
+	private void afterScreenTick(CallbackInfo ci) {
+		ScreenEvents.afterTick(this.tickingScreen).invoker().afterTick(this.tickingScreen);
+		// Finally set the currently ticking screen to null
+		this.tickingScreen = null;
+	}
+
+	// The LevelLoadingScreen is the odd screen that isn't ticked by the main tick loop, so we fire events for this screen.
+	// We Coerce the package-private inner class representing the world load action so we don't need an access widener.
+	@Inject(method = "startIntegratedServer(Ljava/lang/String;Lnet/minecraft/util/registry/DynamicRegistryManager$Impl;Ljava/util/function/Function;Lcom/mojang/datafixers/util/Function4;ZLnet/minecraft/client/MinecraftClient$WorldLoadAction;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/LevelLoadingScreen;tick()V"))
+	private void beforeLoadingScreenTick(CallbackInfo ci) {
+		// Store the screen in a variable in case someone tries to change the screen during this before tick event.
+		// If someone changes the screen, the after tick event will likely have class cast exceptions or throw a NPE.
+		this.tickingScreen = this.currentScreen;
+		ScreenEvents.beforeTick(this.tickingScreen).invoker().beforeTick(this.tickingScreen);
+	}
+
+	@Inject(method = "startIntegratedServer(Ljava/lang/String;Lnet/minecraft/util/registry/DynamicRegistryManager$Impl;Ljava/util/function/Function;Lcom/mojang/datafixers/util/Function4;ZLnet/minecraft/client/MinecraftClient$WorldLoadAction;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/MinecraftClient;render(Z)V"))
+	private void afterLoadingScreenTick(CallbackInfo ci) {
+		ScreenEvents.afterTick(this.tickingScreen).invoker().afterTick(this.tickingScreen);
+		// Finally set the currently ticking screen to null
+		this.tickingScreen = null;
+	}
+}
diff --git a/fabric-screen-api-v1/src/main/java/net/fabricmc/fabric/mixin/screen/MouseMixin.java b/fabric-screen-api-v1/src/main/java/net/fabricmc/fabric/mixin/screen/MouseMixin.java
new file mode 100644
index 000000000..66b2a5940
--- /dev/null
+++ b/fabric-screen-api-v1/src/main/java/net/fabricmc/fabric/mixin/screen/MouseMixin.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (c) 2016, 2017, 2018, 2019 FabricMC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.fabricmc.fabric.mixin.screen;
+
+import org.spongepowered.asm.mixin.Final;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
+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;
+import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
+
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.Mouse;
+import net.minecraft.client.gui.screen.Screen;
+
+import net.fabricmc.fabric.api.client.screen.v1.ScreenMouseEvents;
+
+@Mixin(Mouse.class)
+abstract class MouseMixin {
+	@Shadow
+	@Final
+	private MinecraftClient client;
+	@Unique
+	private Screen currentScreen;
+	@Unique
+	private Double horizontalScrollAmount;
+
+	// private synthetic method_1611([ZDDI)V
+	@Inject(method = "method_1611([ZDDI)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/Screen;mouseClicked(DDI)Z"), cancellable = true)
+	private void beforeMouseClickedEvent(boolean[] resultHack, double mouseX, double mouseY, int button, CallbackInfo ci) {
+		// Store the screen in a variable in case someone tries to change the screen during this before event.
+		// If someone changes the screen, the after event will likely have class cast exceptions or throw a NPE.
+		this.currentScreen = this.client.currentScreen;
+
+		if (this.currentScreen == null) {
+			return;
+		}
+
+		if (!ScreenMouseEvents.allowMouseClick(this.currentScreen).invoker().allowMouseClick(this.currentScreen, mouseX, mouseY, button)) {
+			resultHack[0] = true; // Set this press action as handled.
+			this.currentScreen = null;
+			ci.cancel(); // Exit the lambda
+			return;
+		}
+
+		ScreenMouseEvents.beforeMouseClick(this.currentScreen).invoker().beforeMouseClick(this.currentScreen, mouseX, mouseY, button);
+	}
+
+	// private synthetic method_1611([ZDDI)V
+	@Inject(method = "method_1611([ZDDI)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/Screen;mouseClicked(DDI)Z", shift = At.Shift.AFTER))
+	private void afterMouseClickedEvent(boolean[] resultHack, double mouseX, double mouseY, int button, CallbackInfo ci) {
+		if (this.currentScreen == null) {
+			return;
+		}
+
+		ScreenMouseEvents.afterMouseClick(this.currentScreen).invoker().afterMouseClick(this.currentScreen, mouseX, mouseY, button);
+		this.currentScreen = null;
+	}
+
+	// private synthetic method_1605([ZDDI)V
+	@Inject(method = "method_1605([ZDDI)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/Screen;mouseReleased(DDI)Z"), cancellable = true)
+	private void beforeMouseReleasedEvent(boolean[] resultHack, double mouseX, double mouseY, int button, CallbackInfo ci) {
+		// Store the screen in a variable in case someone tries to change the screen during this before event.
+		// If someone changes the screen, the after event will likely have class cast exceptions or throw a NPE.
+		this.currentScreen = this.client.currentScreen;
+
+		if (this.currentScreen == null) {
+			return;
+		}
+
+		if (!ScreenMouseEvents.allowMouseRelease(this.currentScreen).invoker().allowMouseRelease(this.currentScreen, mouseX, mouseY, button)) {
+			resultHack[0] = true; // Set this press action as handled.
+			this.currentScreen = null;
+			ci.cancel(); // Exit the lambda
+			return;
+		}
+
+		ScreenMouseEvents.beforeMouseRelease(this.currentScreen).invoker().beforeMouseRelease(this.currentScreen, mouseX, mouseY, button);
+	}
+
+	// private synthetic method_1605([ZDDI)V
+	@Inject(method = "method_1605([ZDDI)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/Screen;mouseReleased(DDI)Z", shift = At.Shift.AFTER))
+	private void afterMouseReleasedEvent(boolean[] resultHack, double mouseX, double mouseY, int button, CallbackInfo ci) {
+		if (this.currentScreen == null) {
+			return;
+		}
+
+		ScreenMouseEvents.afterMouseRelease(this.currentScreen).invoker().afterMouseRelease(this.currentScreen, mouseX, mouseY, button);
+		this.currentScreen = null;
+	}
+
+	@Inject(method = "onMouseScroll", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/Screen;mouseScrolled(DDD)Z"), locals = LocalCapture.CAPTURE_FAILEXCEPTION, cancellable = true)
+	private void beforeMouseScrollEvent(long window, double horizontal, double vertical, CallbackInfo ci, double verticalAmount, double mouseX, double mouseY) {
+		// Store the screen in a variable in case someone tries to change the screen during this before event.
+		// If someone changes the screen, the after event will likely have class cast exceptions or throw a NPE.
+		this.currentScreen = this.client.currentScreen;
+
+		if (this.currentScreen == null) {
+			return;
+		}
+
+		// Apply same calculations to horizontal scroll as vertical scroll amount has
+		this.horizontalScrollAmount = this.client.options.discreteMouseScroll ? Math.signum(horizontal) : horizontal * this.client.options.mouseWheelSensitivity;
+
+		if (!ScreenMouseEvents.allowMouseScroll(this.currentScreen).invoker().allowMouseScroll(this.currentScreen, mouseX, mouseY, this.horizontalScrollAmount, verticalAmount)) {
+			this.currentScreen = null;
+			this.horizontalScrollAmount = null;
+			ci.cancel();
+			return;
+		}
+
+		ScreenMouseEvents.beforeMouseScroll(this.currentScreen).invoker().beforeMouseScroll(this.currentScreen, mouseX, mouseY, this.horizontalScrollAmount, verticalAmount);
+	}
+
+	@Inject(method = "onMouseScroll", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/Screen;mouseScrolled(DDD)Z", shift = At.Shift.AFTER), locals = LocalCapture.CAPTURE_FAILEXCEPTION)
+	private void afterMouseScrollEvent(long window, double horizontal, double vertical, CallbackInfo ci, double verticalAmount, double mouseX, double mouseY) {
+		if (this.currentScreen == null) {
+			return;
+		}
+
+		ScreenMouseEvents.afterMouseScroll(this.currentScreen).invoker().afterMouseScroll(this.currentScreen, mouseX, mouseY, this.horizontalScrollAmount, verticalAmount);
+		this.currentScreen = null;
+		this.horizontalScrollAmount = null;
+	}
+}
diff --git a/fabric-screen-api-v1/src/main/java/net/fabricmc/fabric/mixin/screen/ScreenAccessor.java b/fabric-screen-api-v1/src/main/java/net/fabricmc/fabric/mixin/screen/ScreenAccessor.java
new file mode 100644
index 000000000..6f4de1e9f
--- /dev/null
+++ b/fabric-screen-api-v1/src/main/java/net/fabricmc/fabric/mixin/screen/ScreenAccessor.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2016, 2017, 2018, 2019 FabricMC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.fabricmc.fabric.mixin.screen;
+
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.gen.Accessor;
+
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.font.TextRenderer;
+import net.minecraft.client.gui.screen.Screen;
+import net.minecraft.client.render.item.ItemRenderer;
+
+@Mixin(Screen.class)
+public interface ScreenAccessor {
+	@Accessor
+	ItemRenderer getItemRenderer();
+
+	@Accessor
+	TextRenderer getTextRenderer();
+
+	@Accessor
+	MinecraftClient getClient();
+}
diff --git a/fabric-screen-api-v1/src/main/java/net/fabricmc/fabric/mixin/screen/ScreenMixin.java b/fabric-screen-api-v1/src/main/java/net/fabricmc/fabric/mixin/screen/ScreenMixin.java
new file mode 100644
index 000000000..e1b0d1fc1
--- /dev/null
+++ b/fabric-screen-api-v1/src/main/java/net/fabricmc/fabric/mixin/screen/ScreenMixin.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright (c) 2016, 2017, 2018, 2019 FabricMC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.fabricmc.fabric.mixin.screen;
+
+import java.util.List;
+
+import org.spongepowered.asm.mixin.Final;
+import org.spongepowered.asm.mixin.Mixin;
+import org.spongepowered.asm.mixin.Shadow;
+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;
+
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gui.Element;
+import net.minecraft.client.gui.screen.Screen;
+import net.minecraft.client.gui.widget.AbstractButtonWidget;
+
+import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents;
+import net.fabricmc.fabric.impl.client.screen.ScreenExtensions;
+import net.fabricmc.fabric.api.client.screen.v1.ScreenKeyboardEvents;
+import net.fabricmc.fabric.api.client.screen.v1.ScreenMouseEvents;
+import net.fabricmc.fabric.api.event.Event;
+import net.fabricmc.fabric.impl.client.screen.ButtonList;
+import net.fabricmc.fabric.impl.client.screen.ScreenEventFactory;
+
+@Mixin(Screen.class)
+abstract class ScreenMixin implements ScreenExtensions {
+	@Shadow
+	@Final
+	protected List<AbstractButtonWidget> buttons;
+	@Shadow
+	@Final
+	protected List<Element> children;
+
+	@Unique
+	private ButtonList<AbstractButtonWidget> fabricButtons;
+	@Unique
+	private Event<ScreenEvents.Remove> removeEvent;
+	@Unique
+	private Event<ScreenEvents.BeforeTick> beforeTickEvent;
+	@Unique
+	private Event<ScreenEvents.AfterTick> afterTickEvent;
+	@Unique
+	private Event<ScreenEvents.BeforeRender> beforeRenderEvent;
+	@Unique
+	private Event<ScreenEvents.AfterRender> afterRenderEvent;
+
+	// Keyboard
+	@Unique
+	private Event<ScreenKeyboardEvents.AllowKeyPress> allowKeyPressEvent;
+	@Unique
+	private Event<ScreenKeyboardEvents.BeforeKeyPress> beforeKeyPressEvent;
+	@Unique
+	private Event<ScreenKeyboardEvents.AfterKeyPress> afterKeyPressEvent;
+	@Unique
+	private Event<ScreenKeyboardEvents.AllowKeyRelease> allowKeyReleaseEvent;
+	@Unique
+	private Event<ScreenKeyboardEvents.BeforeKeyRelease> beforeKeyReleaseEvent;
+	@Unique
+	private Event<ScreenKeyboardEvents.AfterKeyRelease> afterKeyReleaseEvent;
+
+	// Mouse
+	@Unique
+	private Event<ScreenMouseEvents.AllowMouseClick> allowMouseClickEvent;
+	@Unique
+	private Event<ScreenMouseEvents.BeforeMouseClick> beforeMouseClickEvent;
+	@Unique
+	private Event<ScreenMouseEvents.AfterMouseClick> afterMouseClickEvent;
+	@Unique
+	private Event<ScreenMouseEvents.AllowMouseRelease> allowMouseReleaseEvent;
+	@Unique
+	private Event<ScreenMouseEvents.BeforeMouseRelease> beforeMouseReleaseEvent;
+	@Unique
+	private Event<ScreenMouseEvents.AfterMouseRelease> afterMouseReleaseEvent;
+	@Unique
+	private Event<ScreenMouseEvents.AllowMouseScroll> allowMouseScrollEvent;
+	@Unique
+	private Event<ScreenMouseEvents.BeforeMouseScroll> beforeMouseScrollEvent;
+	@Unique
+	private Event<ScreenMouseEvents.AfterMouseScroll> afterMouseScrollEvent;
+
+	@Inject(method = "init(Lnet/minecraft/client/MinecraftClient;II)V", at = @At("HEAD"))
+	private void beforeInitScreen(MinecraftClient client, int width, int height, CallbackInfo ci) {
+		// All elements are repopulated on the screen, so we need to reinitialize all events
+		this.fabricButtons = null;
+		this.removeEvent = ScreenEventFactory.createRemoveEvent();
+		this.beforeRenderEvent = ScreenEventFactory.createBeforeRenderEvent();
+		this.afterRenderEvent = ScreenEventFactory.createAfterRenderEvent();
+		this.beforeTickEvent = ScreenEventFactory.createBeforeTickEvent();
+		this.afterTickEvent = ScreenEventFactory.createAfterTickEvent();
+
+		// Keyboard
+		this.allowKeyPressEvent = ScreenEventFactory.createAllowKeyPressEvent();
+		this.beforeKeyPressEvent = ScreenEventFactory.createBeforeKeyPressEvent();
+		this.afterKeyPressEvent = ScreenEventFactory.createAfterKeyPressEvent();
+		this.allowKeyReleaseEvent = ScreenEventFactory.createAllowKeyReleaseEvent();
+		this.beforeKeyReleaseEvent = ScreenEventFactory.createBeforeKeyReleaseEvent();
+		this.afterKeyReleaseEvent = ScreenEventFactory.createAfterKeyReleaseEvent();
+
+		// Mouse
+		this.allowMouseClickEvent = ScreenEventFactory.createAllowMouseClickEvent();
+		this.beforeMouseClickEvent = ScreenEventFactory.createBeforeMouseClickEvent();
+		this.afterMouseClickEvent = ScreenEventFactory.createAfterMouseClickEvent();
+		this.allowMouseReleaseEvent = ScreenEventFactory.createAllowMouseReleaseEvent();
+		this.beforeMouseReleaseEvent = ScreenEventFactory.createBeforeMouseReleaseEvent();
+		this.afterMouseReleaseEvent = ScreenEventFactory.createAfterMouseReleaseEvent();
+		this.allowMouseScrollEvent = ScreenEventFactory.createAllowMouseScrollEvent();
+		this.beforeMouseScrollEvent = ScreenEventFactory.createBeforeMouseScrollEvent();
+		this.afterMouseScrollEvent = ScreenEventFactory.createAfterMouseScrollEvent();
+
+		ScreenEvents.BEFORE_INIT.invoker().beforeInit(client, (Screen) (Object) this, width, height);
+	}
+
+	@Inject(method = "init(Lnet/minecraft/client/MinecraftClient;II)V", at = @At("TAIL"))
+	private void afterInitScreen(MinecraftClient client, int width, int height, CallbackInfo ci) {
+		ScreenEvents.AFTER_INIT.invoker().afterInit(client, (Screen) (Object) this, width, height);
+	}
+
+	@Override
+	public List<AbstractButtonWidget> fabric_getButtons() {
+		// Lazy init to make the list access safe after Screen#init
+		if (this.fabricButtons == null) {
+			this.fabricButtons = new ButtonList<>((Screen) (Object) this, this.buttons, this.children);
+		}
+
+		return this.fabricButtons;
+	}
+
+	@Override
+	public Event<ScreenEvents.Remove> fabric_getRemoveEvent() {
+		return this.removeEvent;
+	}
+
+	@Override
+	public Event<ScreenEvents.BeforeTick> fabric_getBeforeTickEvent() {
+		return this.beforeTickEvent;
+	}
+
+	@Override
+	public Event<ScreenEvents.AfterTick> fabric_getAfterTickEvent() {
+		return this.afterTickEvent;
+	}
+
+	@Override
+	public Event<ScreenEvents.BeforeRender> fabric_getBeforeRenderEvent() {
+		return this.beforeRenderEvent;
+	}
+
+	@Override
+	public Event<ScreenEvents.AfterRender> fabric_getAfterRenderEvent() {
+		return this.afterRenderEvent;
+	}
+
+	// Keyboard
+
+	@Override
+	public Event<ScreenKeyboardEvents.AllowKeyPress> fabric_getAllowKeyPressEvent() {
+		return this.allowKeyPressEvent;
+	}
+
+	@Override
+	public Event<ScreenKeyboardEvents.BeforeKeyPress> fabric_getBeforeKeyPressEvent() {
+		return this.beforeKeyPressEvent;
+	}
+
+	@Override
+	public Event<ScreenKeyboardEvents.AfterKeyPress> fabric_getAfterKeyPressEvent() {
+		return this.afterKeyPressEvent;
+	}
+
+	@Override
+	public Event<ScreenKeyboardEvents.AllowKeyRelease> fabric_getAllowKeyReleaseEvent() {
+		return this.allowKeyReleaseEvent;
+	}
+
+	@Override
+	public Event<ScreenKeyboardEvents.BeforeKeyRelease> fabric_getBeforeKeyReleaseEvent() {
+		return this.beforeKeyReleaseEvent;
+	}
+
+	@Override
+	public Event<ScreenKeyboardEvents.AfterKeyRelease> fabric_getAfterKeyReleaseEvent() {
+		return this.afterKeyReleaseEvent;
+	}
+
+	// Mouse
+
+	@Override
+	public Event<ScreenMouseEvents.AllowMouseClick> fabric_getAllowMouseClickEvent() {
+		return this.allowMouseClickEvent;
+	}
+
+	@Override
+	public Event<ScreenMouseEvents.BeforeMouseClick> fabric_getBeforeMouseClickEvent() {
+		return this.beforeMouseClickEvent;
+	}
+
+	@Override
+	public Event<ScreenMouseEvents.AfterMouseClick> fabric_getAfterMouseClickEvent() {
+		return this.afterMouseClickEvent;
+	}
+
+	@Override
+	public Event<ScreenMouseEvents.AllowMouseRelease> fabric_getAllowMouseReleaseEvent() {
+		return this.allowMouseReleaseEvent;
+	}
+
+	@Override
+	public Event<ScreenMouseEvents.BeforeMouseRelease> fabric_getBeforeMouseReleaseEvent() {
+		return this.beforeMouseReleaseEvent;
+	}
+
+	@Override
+	public Event<ScreenMouseEvents.AfterMouseRelease> fabric_getAfterMouseReleaseEvent() {
+		return this.afterMouseReleaseEvent;
+	}
+
+	@Override
+	public Event<ScreenMouseEvents.AllowMouseScroll> fabric_getAllowMouseScrollEvent() {
+		return this.allowMouseScrollEvent;
+	}
+
+	@Override
+	public Event<ScreenMouseEvents.BeforeMouseScroll> fabric_getBeforeMouseScrollEvent() {
+		return this.beforeMouseScrollEvent;
+	}
+
+	@Override
+	public Event<ScreenMouseEvents.AfterMouseScroll> fabric_getAfterMouseScrollEvent() {
+		return this.afterMouseScrollEvent;
+	}
+}
diff --git a/fabric-screen-api-v1/src/main/resources/assets/fabric-screen-api-v1/icon.png b/fabric-screen-api-v1/src/main/resources/assets/fabric-screen-api-v1/icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..2931efbf610873c0084debb8690902b0103d27fe
GIT binary patch
literal 1579
zcmbVMTWB0r7@iGm)TAXsYw<=rnU=;v=W=GRbL=!tc4Brl6GO7t2vVJ$IlDV#XU;e?
z+r2ymsZdMQqAyaFLLUo;RumtE8Z@?uf_*4nP^4;o6fOFoSkN<j8Kcx&2hN<!|DEr@
zod5ryo}Zi9I&|j{!!TP5d3Ax#ARIRj(!bws|3s%--25Xx!whc=M~pe~^6d;0zuPJ<
z1xwm2GKkIVCMjXQX}c87F#9H&u5K*j0F-dWauoLax87!fWh(5!lqPC!4p*&w+rx|P
zxuVftHe{2X*bnwK5hbv3po6Aeb9~fP*bXnE>+o1$K?f2nE9_*b5G-l)AV)k5Qhb^-
zU{V4ZnTKgnmXdpcB*Kg!W(1hvM2N&RO30x1u~eI9meGQGe@_?PDQq<eTqFpMMuTr8
zc;ZzAD9f@SN`fSD6v6o|C(xUm<L~S<sMt3=%MC2zfRIryk$Rx8)Y7gCwj0qpelJWk
zU_w)O1;~q`OC6wTL^!wZ)jG;eL%=m`V<+$_7DiaNN&@0n$=^^%;y)>%q1CiV$8~M7
z?MQ_mOdqCh^a65Sv|ntwSXjV5se1;VK1|Kr8G7TQoQL&*ctt{L{fC<Gn+$Tf@L{sx
z-h-B7l7_!$(;ckp0an;h&OwQT=^{*{R1(1qCuWf-cA*+Et#T`Z%AA<tq-;@w6o-;o
zy8Q!6v&Pf|{a?YRfy%_Qb?UQa>lG}xPK5<gBvFpkh({Gu>k^yK3%T69N6J=>3jBqc
zDNvZsrJ<Zoa}p0b=4u)$IDViz1}>-yOXI^^mWf1cmY^XST)CVzIGjvEPENowmy}ax
zvJ8_(Cf#+H-dBlH53`_u-~6BVAMz|(g?jCVdBWNZ(+A}(pFV7>S3QgPiQcMaflkIC
z-3Ti|VT~{au*vq0ts9O&m$p&Gl=L6+q6_m$IcVq}o~+Pl{g>1esQp4%wp~|*zk1n`
zZ7T6Toc4`y88s}riCP|ZXrJ?FLz@^KTcyqLje<H^TzdE0PhK5b5x2)LE6;s)`N3ZX
z2Cv?+vFqVy1|k2_cl$rw7=Ck$@N2wL$?9VEp7Gi35A3;$)g3FR_g;G8x7eAVQRC>y
zu95Yz%F&S{<0~f)Iomek?+hQ%MhCu%T^zsg>C_L`1`Br`xNY&))k9yTQb$JC>)w_f
zpU<A$%?N8BFlS$R^rq&-d+#&n&pq`bxOC;qliz$4TRVRI>(^tu^Q)y%W~lVz`jz;_
jF?g&s@Y=Qe&c#kW|JbvqK0Y=Rw)4XDoVqsk_>;c_`@;F@

literal 0
HcmV?d00001

diff --git a/fabric-screen-api-v1/src/main/resources/fabric-screen-api-v1.mixins.json b/fabric-screen-api-v1/src/main/resources/fabric-screen-api-v1.mixins.json
new file mode 100644
index 000000000..5a5f30ca8
--- /dev/null
+++ b/fabric-screen-api-v1/src/main/resources/fabric-screen-api-v1.mixins.json
@@ -0,0 +1,18 @@
+{
+  "required": true,
+  "package": "net.fabricmc.fabric.mixin.screen",
+  "compatibilityLevel": "JAVA_8",
+  "client": [
+    "GameRendererMixin",
+    "MinecraftClientMixin",
+    "ScreenMixin"
+  ],
+  "injectors": {
+    "defaultRequire": 1
+  },
+  "mixins": [
+    "KeyboardMixin",
+    "MouseMixin",
+    "ScreenAccessor"
+  ]
+}
diff --git a/fabric-screen-api-v1/src/main/resources/fabric.mod.json b/fabric-screen-api-v1/src/main/resources/fabric.mod.json
new file mode 100644
index 000000000..c22ea4cfd
--- /dev/null
+++ b/fabric-screen-api-v1/src/main/resources/fabric.mod.json
@@ -0,0 +1,29 @@
+{
+  "schemaVersion": 1,
+  "id": "fabric-screen-api-v1",
+  "name": "Fabric Screen API (v1)",
+  "version": "${version}",
+  "environment": "client",
+  "license": "Apache-2.0",
+  "icon": "assets/fabric-screen-api-v1/icon.png",
+  "contact": {
+    "homepage": "https://fabricmc.net",
+    "irc": "irc://irc.esper.net:6667/fabric",
+    "issues": "https://github.com/FabricMC/fabric/issues",
+    "sources": "https://github.com/FabricMC/fabric"
+  },
+  "authors": [
+    "FabricMC"
+  ],
+  "depends": {
+    "fabricloader": ">=0.8.2",
+    "fabric-api-base": "*"
+  },
+  "description": "Adds screen related hooks.",
+  "mixins": [
+    "fabric-screen-api-v1.mixins.json"
+  ],
+  "custom": {
+    "fabric-api:module-lifecycle": "stable"
+  }
+}
diff --git a/fabric-screen-api-v1/src/testmod/java/net/fabricmc/fabric/test/screen/ScreenTests.java b/fabric-screen-api-v1/src/testmod/java/net/fabricmc/fabric/test/screen/ScreenTests.java
new file mode 100644
index 000000000..c8e2afe21
--- /dev/null
+++ b/fabric-screen-api-v1/src/testmod/java/net/fabricmc/fabric/test/screen/ScreenTests.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2016, 2017, 2018, 2019 FabricMC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.fabricmc.fabric.test.screen;
+
+import java.util.List;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gui.DrawableHelper;
+import net.minecraft.client.gui.hud.InGameHud;
+import net.minecraft.client.gui.screen.Screen;
+import net.minecraft.client.gui.screen.TitleScreen;
+import net.minecraft.client.gui.widget.AbstractButtonWidget;
+
+import net.fabricmc.api.ClientModInitializer;
+import net.fabricmc.api.EnvType;
+import net.fabricmc.api.Environment;
+import net.fabricmc.fabric.api.client.screen.v1.ScreenEvents;
+import net.fabricmc.fabric.api.client.screen.v1.ScreenKeyboardEvents;
+import net.fabricmc.fabric.api.client.screen.v1.Screens;
+
+@Environment(EnvType.CLIENT)
+public final class ScreenTests implements ClientModInitializer {
+	private static final Logger LOGGER = LogManager.getLogger("FabricScreenApiTests");
+
+	@Override
+	public void onInitializeClient() {
+		LOGGER.info("Started Screen Testmod");
+		ScreenEvents.BEFORE_INIT.register((client, screen, width, height) -> {
+			// TODO: Write tests listening to addition of child elements
+		});
+
+		ScreenEvents.AFTER_INIT.register(this::afterInitScreen);
+	}
+
+	private void afterInitScreen(MinecraftClient client, Screen screen, int windowWidth, int windowHeight) {
+		LOGGER.info("Initializing {}", screen.getClass().getName());
+
+		if (screen instanceof TitleScreen) {
+			final List<AbstractButtonWidget> buttons = Screens.getButtons(screen);
+
+			// Shrink the realms button, should be the third button on the list
+			final AbstractButtonWidget optionsButton = buttons.get(2);
+			optionsButton.setWidth(98);
+
+			// Add a new button
+			buttons.add(new SoundButton((screen.width / 2) + 2, ((screen.height / 4) + 96), 72, 20));
+			// And another button
+			buttons.add(new StopSoundButton(screen, (screen.width / 2) + 80, ((screen.height / 4) + 95), 20, 20));
+
+			// Testing:
+			// Some automatic validation that the screen list works, make sure the buttons we added are on the list of child elements
+			screen.children().stream()
+					.filter(element -> element instanceof SoundButton)
+					.findAny()
+					.orElseThrow(() -> new AssertionError("Failed to find the \"Sound\" button in the screen's elements"));
+
+			screen.children().stream()
+					.filter(element -> element instanceof StopSoundButton)
+					.findAny()
+					.orElseThrow(() -> new AssertionError("Failed to find the \"Stop Sound\" button in the screen's elements"));
+
+			// Register render event to draw an icon on the screen
+			ScreenEvents.afterRender(screen).register((_screen, matrices, mouseX, mouseY, tickDelta) -> {
+				// Render an armor icon to test
+				client.getTextureManager().bindTexture(InGameHud.GUI_ICONS_TEXTURE);
+				DrawableHelper.drawTexture(matrices, (screen.width / 2) - 124, (screen.height / 4) + 96, 20, 20, 34, 9, 9, 9, 256, 256);
+			});
+
+			ScreenKeyboardEvents.allowKeyPress(screen).register((_screen, key, scancode, modifiers) -> {
+				LOGGER.info("After Pressed, Code: {}, Scancode: {}, Modifiers: {}", key, scancode, modifiers);
+				return false; // Let actions continue
+			});
+
+			ScreenKeyboardEvents.afterKeyPress(screen).register((_screen, key, scancode, modifiers) -> {
+				LOGGER.warn("Pressed, Code: {}, Scancode: {}, Modifiers: {}", key, scancode, modifiers);
+			});
+		}
+	}
+}
diff --git a/fabric-screen-api-v1/src/testmod/java/net/fabricmc/fabric/test/screen/SoundButton.java b/fabric-screen-api-v1/src/testmod/java/net/fabricmc/fabric/test/screen/SoundButton.java
new file mode 100644
index 000000000..3c9461229
--- /dev/null
+++ b/fabric-screen-api-v1/src/testmod/java/net/fabricmc/fabric/test/screen/SoundButton.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2016, 2017, 2018, 2019 FabricMC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.fabricmc.fabric.test.screen;
+
+import java.util.Random;
+
+import org.jetbrains.annotations.Nullable;
+
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gui.widget.AbstractPressableButtonWidget;
+import net.minecraft.client.sound.PositionedSoundInstance;
+import net.minecraft.sound.SoundEvent;
+import net.minecraft.sound.SoundEvents;
+import net.minecraft.text.Text;
+import net.minecraft.util.registry.Registry;
+import net.minecraft.util.registry.SimpleRegistry;
+
+class SoundButton extends AbstractPressableButtonWidget {
+	private static final Random RANDOM = new Random();
+
+	SoundButton(int x, int y, int width, int height) {
+		super(x, y, width, height, Text.of("Sound Button"));
+	}
+
+	@Override
+	public void onPress() {
+		// Upcast on registry is fine
+		@Nullable
+		final SoundEvent event = ((SimpleRegistry<SoundEvent>) Registry.SOUND_EVENT).getRandom(RANDOM);
+
+		MinecraftClient.getInstance().getSoundManager().play(PositionedSoundInstance.master(event != null ? event : SoundEvents.ENTITY_GENERIC_EXPLODE, 1.0F, 1.0F));
+	}
+}
diff --git a/fabric-screen-api-v1/src/testmod/java/net/fabricmc/fabric/test/screen/StopSoundButton.java b/fabric-screen-api-v1/src/testmod/java/net/fabricmc/fabric/test/screen/StopSoundButton.java
new file mode 100644
index 000000000..8d5303c8a
--- /dev/null
+++ b/fabric-screen-api-v1/src/testmod/java/net/fabricmc/fabric/test/screen/StopSoundButton.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2016, 2017, 2018, 2019 FabricMC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.fabricmc.fabric.test.screen;
+
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gui.DrawableHelper;
+import net.minecraft.client.gui.hud.InGameHud;
+import net.minecraft.client.gui.screen.Screen;
+import net.minecraft.client.gui.widget.AbstractPressableButtonWidget;
+import net.minecraft.client.util.math.MatrixStack;
+import net.minecraft.text.LiteralText;
+import net.minecraft.text.Text;
+
+class StopSoundButton extends AbstractPressableButtonWidget {
+	private final Screen screen;
+
+	StopSoundButton(Screen screen, int x, int y, int width, int height) {
+		super(x, y, width, height, Text.of(""));
+		this.screen = screen;
+	}
+
+	@Override
+	public void render(MatrixStack matrices, int mouseX, int mouseY, float tickDelta) {
+		MinecraftClient client = MinecraftClient.getInstance();
+		// Render the armor icon to test
+		client.getTextureManager().bindTexture(InGameHud.GUI_ICONS_TEXTURE);
+		DrawableHelper.drawTexture(matrices, this.x, this.y, this.width, this.height, 43, 27, 9, 9, 256, 256);
+
+		if (this.isMouseOver(mouseX, mouseY)) {
+			this.screen.renderTooltip(matrices, new LiteralText("Click to stop all sounds"), this.x, this.y);
+		}
+	}
+
+	@Override
+	public void onPress() {
+		MinecraftClient.getInstance().getSoundManager().stopAll();
+	}
+}
diff --git a/fabric-screen-api-v1/src/testmod/resources/fabric.mod.json b/fabric-screen-api-v1/src/testmod/resources/fabric.mod.json
new file mode 100644
index 000000000..755329a1c
--- /dev/null
+++ b/fabric-screen-api-v1/src/testmod/resources/fabric.mod.json
@@ -0,0 +1,16 @@
+{
+  "schemaVersion": 1,
+  "id": "fabric-screen-api-v1-testmod",
+  "name": "Fabric Screen API (v1) Test Mod",
+  "version": "1.0.0",
+  "environment": "client",
+  "license": "Apache-2.0",
+  "depends": {
+    "fabric-screen-api-v1": "*"
+  },
+  "entrypoints": {
+    "client": [
+      "net.fabricmc.fabric.test.screen.ScreenTests"
+    ]
+  }
+}
diff --git a/settings.gradle b/settings.gradle
index 8787b84e1..df79d2869 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -49,6 +49,7 @@ include 'fabric-rendering-v1'
 include 'fabric-rendering-data-attachment-v1'
 include 'fabric-rendering-fluids-v1'
 include 'fabric-resource-loader-v0'
+include 'fabric-screen-api-v1'
 include 'fabric-screen-handler-api-v1'
 include 'fabric-structure-api-v1'
 include 'fabric-tag-extensions-v0'