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 + * @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'