diff --git a/src/main/java/io/wispforest/owo/mixin/ui/MinecraftClientMixin.java b/src/main/java/io/wispforest/owo/mixin/ui/MinecraftClientMixin.java index 56a65757..701bb951 100644 --- a/src/main/java/io/wispforest/owo/mixin/ui/MinecraftClientMixin.java +++ b/src/main/java/io/wispforest/owo/mixin/ui/MinecraftClientMixin.java @@ -1,5 +1,6 @@ package io.wispforest.owo.mixin.ui; +import io.wispforest.owo.ui.event.ClientRenderCallback; import io.wispforest.owo.ui.event.WindowResizeCallback; import io.wispforest.owo.ui.util.DisposableScreen; import net.minecraft.client.MinecraftClient; @@ -38,6 +39,16 @@ private void captureResize(CallbackInfo ci) { WindowResizeCallback.EVENT.invoker().onResized((MinecraftClient) (Object) this, this.window); } + @Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/util/Window;setPhase(Ljava/lang/String;)V", ordinal = 1)) + private void beforeRender(boolean tick, CallbackInfo ci) { + ClientRenderCallback.BEFORE.invoker().onRender((MinecraftClient) (Object) this); + } + + @Inject(method = "render", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/util/Window;swapBuffers()V", shift = At.Shift.AFTER)) + private void afterRender(boolean tick, CallbackInfo ci) { + ClientRenderCallback.AFTER.invoker().onRender((MinecraftClient) (Object) this); + } + @Inject(method = "setScreen", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/screen/Screen;removed()V")) private void captureSetScreen(Screen screen, CallbackInfo ci) { if (screen != null && this.currentScreen instanceof DisposableScreen disposable) { @@ -64,5 +75,4 @@ private void captureSetScreen(Screen screen, CallbackInfo ci) { this.screensToDispose.clear(); } } - } diff --git a/src/main/java/io/wispforest/owo/ui/event/ClientRenderCallback.java b/src/main/java/io/wispforest/owo/ui/event/ClientRenderCallback.java new file mode 100644 index 00000000..158cdf42 --- /dev/null +++ b/src/main/java/io/wispforest/owo/ui/event/ClientRenderCallback.java @@ -0,0 +1,30 @@ +package io.wispforest.owo.ui.event; + +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.EventFactory; +import net.minecraft.client.MinecraftClient; + +public interface ClientRenderCallback { + + /** + * Invoked just before the client's window enters the 'Render' phase, after the client + * has ticked and cleared the render task queue + */ + Event BEFORE = EventFactory.createArrayBacked(ClientRenderCallback.class, callbacks -> (client) -> { + for (var callback : callbacks) { + callback.onRender(client); + } + }); + + /** + * Called just after the client has finished rendering and drawing the + * current frame and swapped buffers + */ + Event AFTER = EventFactory.createArrayBacked(ClientRenderCallback.class, callbacks -> (client) -> { + for (var callback : callbacks) { + callback.onRender(client); + } + }); + + void onRender(MinecraftClient client); +} diff --git a/src/main/java/io/wispforest/owo/ui/hud/Hud.java b/src/main/java/io/wispforest/owo/ui/hud/Hud.java index 908e530b..8fb54bd5 100644 --- a/src/main/java/io/wispforest/owo/ui/hud/Hud.java +++ b/src/main/java/io/wispforest/owo/ui/hud/Hud.java @@ -3,16 +3,16 @@ import io.wispforest.owo.ui.container.FlowLayout; import io.wispforest.owo.ui.core.Component; import io.wispforest.owo.ui.core.OwoUIAdapter; +import io.wispforest.owo.ui.event.ClientRenderCallback; import io.wispforest.owo.ui.event.WindowResizeCallback; import net.fabricmc.fabric.api.client.rendering.v1.HudRenderCallback; +import net.fabricmc.fabric.api.resource.ResourceManagerHelper; import net.minecraft.client.MinecraftClient; import net.minecraft.util.Identifier; import org.jetbrains.annotations.Nullable; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; +import java.util.*; +import java.util.function.Consumer; import java.util.function.Supplier; /** @@ -25,8 +25,7 @@ public class Hud { static boolean suppress = false; private static final Map activeComponents = new HashMap<>(); - private static final Map> pendingComponents = new HashMap<>(); - private static final Set pendingRemovals = new HashSet<>(); + private static final List> pendingActions = new ArrayList<>(); /** * Add a new component to be rendered on the in-game HUD. @@ -40,7 +39,12 @@ public class Hud { * when the HUD is first rendered */ public static void add(Identifier id, Supplier component) { - pendingComponents.put(id, component); + pendingActions.add(flowLayout -> { + var instance = component.get(); + + flowLayout.child(instance); + activeComponents.put(id, instance); + }); } /** @@ -49,7 +53,13 @@ public static void add(Identifier id, Supplier component) { * @param id The ID of the HUD component to remove */ public static void remove(Identifier id) { - pendingRemovals.add(id); + pendingActions.add(flowLayout -> { + var component = activeComponents.get(id); + if (component == null) return; + + flowLayout.removeChild(component); + activeComponents.remove(id); + }); } /** @@ -84,33 +94,18 @@ private static void initializeAdapter() { adapter.moveAndResize(0, 0, window.getScaledWidth(), window.getScaledHeight()); }); - HudRenderCallback.EVENT.register((context, tickDelta) -> { - if (suppress) return; - - if (!pendingRemovals.isEmpty() && adapter != null) { - pendingRemovals.forEach(identifier -> { - var component = activeComponents.get(identifier); - if (component == null) return; - - adapter.rootComponent.removeChild(component); - activeComponents.remove(identifier); - }); - pendingRemovals.clear(); - } - - if (!pendingComponents.isEmpty()) { + ClientRenderCallback.BEFORE.register(client -> { + if (client.world == null) return; + if (!pendingActions.isEmpty()) { if (adapter == null) initializeAdapter(); - pendingComponents.forEach((identifier, componentSupplier) -> { - var component = componentSupplier.get(); - - adapter.rootComponent.child(component); - activeComponents.put(identifier, component); - }); - pendingComponents.clear(); + pendingActions.forEach(action -> action.accept(adapter.rootComponent)); + pendingActions.clear(); } + }); - if (adapter == null) return; + HudRenderCallback.EVENT.register((context, tickDelta) -> { + if (adapter == null || suppress) return; context.push().translate(0, 0, 100); adapter.render(context, -69, -69, tickDelta);